From bcbd081521726d4239b9dcd06098d448651258a7 Mon Sep 17 00:00:00 2001 From: H09-0 Date: Thu, 28 May 2026 15:59:29 +0300 Subject: [PATCH 1/3] Fix menu misalignment on first open after login - Add _calculatePosition() + set_position() in onComplete of the animation path to ensure the menu snaps to the correct position immediately after the visual transition finishes. Without this, _allocationChanged is blocked by this.animating during the animation, so any deferred layout changes (e.g. app buttons shown via Mainloop.idle_add) cannot correct the position until after the animation ends and the next allocation change fires. - Replace separate this.actor.x / this.actor.y assignments with this.actor.set_position() in the non-animation path to avoid a race where setting x triggers _allocationChanged synchronously, which calls set_position(), and then the subsequent y assignment overwrites the corrected Y coordinate with the stale value. Closes https://github.com/linuxmint/cinnamon/issues/13780 --- js/ui/popupMenu.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 34f2cc130f..6ee9d08066 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -1858,6 +1858,8 @@ var PopupMenu = class PopupMenu extends PopupMenuBase { opacity: 255, onComplete: () => { this.animating = false; + let [xPos, yPos] = this._calculatePosition(); + this.actor.set_position(xPos, yPos); } } @@ -1891,8 +1893,7 @@ var PopupMenu = class PopupMenu extends PopupMenuBase { this.animating = false; let [xPos, yPos] = this._calculatePosition(); // should this be conditional on this._slidePosition being -1? - this.actor.x = xPos; - this.actor.y = yPos; + this.actor.set_position(xPos, yPos); this.actor.show(); } From abd1b2179bf9bce1af80fcecb017b5ae240e3ff6 Mon Sep 17 00:00:00 2001 From: H09-0 Date: Fri, 29 May 2026 09:01:04 +0300 Subject: [PATCH 2/3] Fix three keyboard-related issues Issue 1: Ctrl+Shift order-dependence (layout switching) - In _stageEventHandler, when a modifier key press doesn't match a binding and a previous modifier was tracked, try the reverse order: use the previous modifier's keycode with the current key's modifier mask. This handles bindings like Shift where pressing Shift first then Ctrl would otherwise fail to match. - Added _lastModifierKeyCode tracking and _modifierMaskForKeySymbol helper. Issue 2: Super+Space closes Start Menu - Clear _modifierOnlyAction when an unmatched modifier key is pressed. Previously, pressing Super (overlay) set _modifierOnlyAction, and if the user then pressed another modifier (e.g. Ctrl) that didn't match, _modifierOnlyAction was never cleared, causing the overlay to fire on Super release even though the key was part of a modifier combo. Issue 3: Ctrl+A does not select all text in search field - Added Ctrl+A handling in _onMenuKeyPress in the menu applet. ClutterText does not implement select-all via Ctrl+A by default, so we explicitly set the selection from 0 to text length. --- .../applets/menu@cinnamon.org/applet.js | 8 +++ js/ui/main.js | 57 +++++++++++++++++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js index d19e5f88c7..5c25f5c3fc 100644 --- a/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js +++ b/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js @@ -1937,6 +1937,14 @@ class CinnamonMenuApplet extends Applet.TextIconApplet { return Clutter.EVENT_STOP; } break; + case Clutter.KEY_A: + case Clutter.KEY_a: + if (ctrlKey) { + let textLen = this.searchEntryText.get_text().length; + this.searchEntryText.set_selection(0, textLen); + return Clutter.EVENT_STOP; + } + break; case Clutter.KEY_Menu: if (this._activeContainer === this.applicationsBox) { this.toggleContextMenu(active._delegate); diff --git a/js/ui/main.js b/js/ui/main.js index 1ba0868689..fe86820933 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -1306,6 +1306,7 @@ function _shouldFilterKeybinding(entry) { * to give the multi-key binding a chance to activate on key-press. */ let _modifierOnlyAction = 0; +let _lastModifierKeyCode = 0; function _isModifierKeyval(symbol) { return symbol === Clutter.KEY_Super_L || symbol === Clutter.KEY_Super_R || @@ -1314,6 +1315,25 @@ function _isModifierKeyval(symbol) { symbol === Clutter.KEY_Shift_L || symbol === Clutter.KEY_Shift_R; } +function _modifierMaskForKeySymbol(symbol) { + switch (symbol) { + case Clutter.KEY_Control_L: + case Clutter.KEY_Control_R: + return Clutter.ModifierType.CONTROL_MASK; + case Clutter.KEY_Shift_L: + case Clutter.KEY_Shift_R: + return Clutter.ModifierType.SHIFT_MASK; + case Clutter.KEY_Alt_L: + case Clutter.KEY_Alt_R: + return Clutter.ModifierType.MOD1_MASK; + case Clutter.KEY_Super_L: + case Clutter.KEY_Super_R: + return Clutter.ModifierType.MOD4_MASK; + default: + return 0; + } +} + function _stageEventHandler(actor, event) { if (modalCount == 0) return false; @@ -1345,10 +1365,34 @@ function _stageEventHandler(actor, event) { return true; } } + + // Try reverse order for multi-modifier keybindings (e.g., Shift+Ctrl + // when the binding is stored as Ctrl+Shift). If a previous modifier + // key was pressed without matching, try its keycode with this key's + // modifier mask. + if (action <= 0 && _lastModifierKeyCode > 0) { + let currentMask = _modifierMaskForKeySymbol(event.get_key_symbol()); + action = global.display.get_keybinding_action(_lastModifierKeyCode, modifierState | currentMask); + if (action > 0) { + let entry = keybindingManager.getBindingById(action); + if (!_shouldFilterKeybinding(entry)) { + _modifierOnlyAction = action; + _lastModifierKeyCode = 0; + return true; + } + } + } + + // Any modifier key press that doesn't match cancels a pending + // single-modifier action from a previous modifier press. + _modifierOnlyAction = 0; + _lastModifierKeyCode = keyCode; return false; } + // Non-modifier key press clears pending modifier-only action. _modifierOnlyAction = 0; + _lastModifierKeyCode = 0; // During modal, muffin's process_iso_next_group doesn't run, handle xkb 'grp' // here. @@ -1370,11 +1414,14 @@ function _stageEventHandler(actor, event) { } // Release event - activate the single-key modifier keybinding if one was stored. - if (_isModifierKeyval(event.get_key_symbol()) && _modifierOnlyAction > 0) { - let action = _modifierOnlyAction; - _modifierOnlyAction = 0; - keybindingManager.invoke_keybinding_action_by_id(action); - return true; + if (_isModifierKeyval(event.get_key_symbol())) { + _lastModifierKeyCode = 0; + if (_modifierOnlyAction > 0) { + let action = _modifierOnlyAction; + _modifierOnlyAction = 0; + keybindingManager.invoke_keybinding_action_by_id(action); + return true; + } } return false; From 12717ad63f709e2e1760eeab2547cc105f9ec45f Mon Sep 17 00:00:00 2001 From: H09-0 Date: Fri, 29 May 2026 09:12:05 +0300 Subject: [PATCH 3/3] Fix reverse-match modifier state and clear stale lastModifierKeyCode in completeModalSetup --- js/ui/main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/ui/main.js b/js/ui/main.js index fe86820933..8c7962b810 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -1372,7 +1372,7 @@ function _stageEventHandler(actor, event) { // modifier mask. if (action <= 0 && _lastModifierKeyCode > 0) { let currentMask = _modifierMaskForKeySymbol(event.get_key_symbol()); - action = global.display.get_keybinding_action(_lastModifierKeyCode, modifierState | currentMask); + action = global.display.get_keybinding_action(_lastModifierKeyCode, currentMask); if (action > 0) { let entry = keybindingManager.getBindingById(action); if (!_shouldFilterKeybinding(entry)) { @@ -1437,6 +1437,7 @@ function _findModal(actor) { function _completeModalSetup(actor, mode, onDismiss) { _modifierOnlyAction = 0; + _lastModifierKeyCode = 0; if (modalCount == 0) Meta.disable_unredirect_for_display(global.display);