Skip to content

csd-media-keys: touchpad toggle hotkey only disables, never re-enables, when no external mouse is present #451

@akosmaroy

Description

@akosmaroy

Summary

do_touchpad_action() in plugins/media-keys/csd-media-keys-manager.c computes an incorrect state when no external mouse is connected, causing every press of the touchpad-toggle key to write SEND_EVENTS_DISABLED rather than toggling. With an external mouse connected, the same code path works correctly.

Environment

  • Linux Mint 22.3 "Zena" (Cinnamon 6.6.7, on Ubuntu 24.04 noble)
  • Kernel 6.17.0-113020-tuxedo
  • X11 session
  • Laptop with touchpad + separate XF86-ish function key (Fn+F11 emitting KEY_F21) that triggers csd-media-keys' touchpad-toggle action

Reproduction

  1. Ensure no external USB/Bluetooth mouse is connected.
  2. gsettings get org.cinnamon.desktop.peripherals.touchpad send-events'enabled'
  3. Press the touchpad-toggle hotkey (or dbus-send the media-key action).
  4. gsettings get ...'disabled' ✅ (toggle worked)
  5. Press the hotkey again.
  6. gsettings get ...'disabled' ❌ (should be 'enabled')
  7. Press again. Still 'disabled'. Indefinitely.

With an external mouse connected during the same sequence, the toggle alternates correctly.

Manually setting send-events back to 'enabled' via the Mouse and Touchpad UI works once, but the next hotkey press immediately flips it back to 'disabled', so the toggle never actually toggles without a mouse present.

Root cause

In do_touchpad_action() (plugins/media-keys/csd-media-keys-manager.c):

state = g_settings_get_enum (settings, TOUCHPAD_SEND_EVENTS_KEY) ==
    C_DESKTOP_DEVICE_SEND_EVENTS_ENABLED ||
    (C_DESKTOP_DEVICE_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE && !mouse_is_present ());

The second operand of the || is (C_DESKTOP_DEVICE_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE && !mouse_is_present()). C_DESKTOP_DEVICE_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE is an enum constant (non-zero), so that sub-expression reduces to simply !mouse_is_present().

So the line effectively evaluates to:

state = (send_events == ENABLED) || !mouse_is_present();

When no mouse is present, state is always true, so the subsequent g_settings_set_enum(..., state ? DISABLED : ENABLED) always writes DISABLED. Every press.

This was almost certainly intended to be a comparison, mirroring the "is the touchpad effectively on right now?" check. Probably something like:

CsdDeviceSendEvents current = g_settings_get_enum (settings, TOUCHPAD_SEND_EVENTS_KEY);
state = (current == C_DESKTOP_DEVICE_SEND_EVENTS_ENABLED) ||
        (current == C_DESKTOP_DEVICE_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE && !mouse_is_present ());

i.e. the touchpad is considered effectively enabled if send-events is either enabled, or disabled-on-external-mouse while no external mouse is present. As written, the comparison was lost and the bare enum constant was left as a boolean operand.

For reference, the GNOME upstream equivalent (gnome-settings-daemon) does it correctly:

state = (g_settings_get_enum (settings, TOUCHPAD_ENABLED_KEY) ==
         G_DESKTOP_DEVICE_SEND_EVENTS_ENABLED);

Suggested fix

Replace the broken line with an explicit comparison as shown above. Happy to submit a PR if useful.

Related

Issue #256 ("disable touchpad resets itself") may be a different manifestation of similar state-tracking issues in this handler, though the symptom there was the opposite (auto re-enable after time rather than stuck disabled).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions