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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions