Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js
Original file line number Diff line number Diff line change
Expand Up @@ -1717,17 +1717,15 @@ class CinnamonMenuApplet extends Applet.TextIconApplet {
}
}

/* check for a keybinding and quit early, otherwise we get a double hit
of the keybinding callback */
let action = global.display.get_keybinding_action(keyCode, modifierState);

if (action == Meta.KeyBindingAction.CUSTOM) {
return Clutter.EVENT_STOP;
}

if (this.searchEntryText.has_preedit()) {
// There is an uncommitted text in the search box, let the input method to handle this.
return Clutter.EVENT_PROPAGATE;
/* Handle keyboard layout switching (Alt+Shift, etc.)
* This uses a helper function from util.js that respects:
* - Input methods (has_preedit check)
* - Custom keybindings (switch-input-source)
* - XKB-based layout switching (modifier-only keys)
*/
let layoutResult = Util.handleKeyboardLayoutSwitchingInTextEntry(this.searchEntryText, event);
if (layoutResult !== null) {
return layoutResult;
}

let ctrlKey = modifierState & Clutter.ModifierType.CONTROL_MASK;
Expand Down
68 changes: 68 additions & 0 deletions js/misc/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -769,3 +769,71 @@ function wiggle(actor, params) {
}
});
}

/**
* Handles keyboard layout switching in text entry fields.
*
* This function should be called early in key-press-event handlers
* of text entries to enable keyboard layout switching (e.g., Alt+Shift)
* while typing.
*
* It respects:
* - Input methods (has_preedit check) for Japanese, Chinese, Korean input
* - Custom keybindings (switch-input-source)
* - XKB-based layout switching (modifier-only keys)
*
* Background:
* - Commit 6a5105420 moved switch-input-source from global.display to KeybindingManager
* - Commit 05e68dd2c blocked keybindings in text entries to prevent duplicate events
* - This function allows layout switching while preventing duplicate events
*
* @param {Clutter.Text} entryText - The text entry widget (e.g., St.Entry.clutter_text)
* @param {Clutter.KeyEvent} event - The key press event
* @returns {Clutter.EventResult|null} - EVENT_STOP/PROPAGATE if handled, null otherwise
*/
function handleKeyboardLayoutSwitchingInTextEntry(entryText, event) {
const Meta = imports.gi.Meta;

// CRITICAL: Don't interfere with input methods (Japanese, Chinese, Korean)
// If there's uncommitted text (preedit), let the input method handle it
// This respects the pattern from commit 640dad661
if (entryText.has_preedit && entryText.has_preedit()) {
return Clutter.EVENT_PROPAGATE;
}

let keyCode = event.get_key_code();
let modifierState = event.get_state();
let symbol = event.get_key_symbol();

// Check if this is a registered keybinding
let action = global.display.get_keybinding_action(keyCode, modifierState);

// Handle custom keybindings (e.g., switch-input-source)
// These were moved to KeybindingManager in commit 6a5105420
if (action == Meta.KeyBindingAction.CUSTOM) {
Main.keybindingManager.invoke_keybinding_action_by_id(action);
return Clutter.EVENT_STOP;
}

// Allow modifier-only keys to propagate for XKB-based switching
// This handles cases where no custom keybinding is registered
if (action == Meta.KeyBindingAction.NONE) {
// Use Set for O(1) lookup instead of multiple OR comparisons
const MODIFIER_KEYS = new Set([
Clutter.KEY_Shift_L, Clutter.KEY_Shift_R,
Clutter.KEY_Alt_L, Clutter.KEY_Alt_R,
Clutter.KEY_Control_L, Clutter.KEY_Control_R,
Clutter.KEY_Super_L, Clutter.KEY_Super_R,
Clutter.KEY_ISO_Level3_Shift,
Clutter.KEY_Hyper_L, Clutter.KEY_Hyper_R,
Clutter.KEY_Meta_L, Clutter.KEY_Meta_R
]);

if (MODIFIER_KEYS.has(symbol)) {
return Clutter.EVENT_PROPAGATE;
}
}

// Not a layout switching key, let caller handle it
return null;
}
7 changes: 7 additions & 0 deletions js/ui/expoThumbnail.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const PointerTracker = imports.misc.pointerTracker;
const SignalManager = imports.misc.signalManager;
const GridNavigator = imports.misc.gridNavigator;
const WindowUtils = imports.misc.windowUtils;
const Util = imports.misc.util;

// The maximum size of a thumbnail is 1/8 the width and height of the screen
let MAX_THUMBNAIL_SCALE = 0.9;
Expand Down Expand Up @@ -499,6 +500,12 @@ ExpoWorkspaceThumbnail.prototype = {
},

onTitleKeyPressEvent: function(actor, event) {
// Handle keyboard layout switching (Alt+Shift, etc.) before other key handling
let layoutResult = Util.handleKeyboardLayoutSwitchingInTextEntry(actor, event);
if (layoutResult !== null) {
return layoutResult;
}

this.undoTitleEdit = false;
let symbol = event.get_key_symbol();
if (symbol === Clutter.KEY_Return ||
Expand Down
16 changes: 16 additions & 0 deletions js/ui/keyringPrompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ class KeyringDialog extends ModalDialog.ModalDialog {
});
CinnamonEntry.addContextMenu(this._passwordEntry);
this._passwordEntry.clutter_text.connect('activate', this._onPasswordActivate.bind(this));
// Handle keyboard layout switching (Alt+Shift, etc.)
this._passwordEntry.clutter_text.connect('key-press-event', (actor, event) => {
let layoutResult = Util.handleKeyboardLayoutSwitchingInTextEntry(actor, event);
if (layoutResult !== null) {
return layoutResult;
}
return Clutter.EVENT_PROPAGATE;
});
this.prompt.bind_property('password-visible',
this._passwordEntry, 'visible', GObject.BindingFlags.SYNC_CREATE);
passwordBox.add_child(this._passwordEntry);
Expand All @@ -52,6 +60,14 @@ class KeyringDialog extends ModalDialog.ModalDialog {
});
CinnamonEntry.addContextMenu(this._confirmEntry);
this._confirmEntry.clutter_text.connect('activate', this._onConfirmActivate.bind(this));
// Handle keyboard layout switching (Alt+Shift, etc.)
this._confirmEntry.clutter_text.connect('key-press-event', (actor, event) => {
let layoutResult = Util.handleKeyboardLayoutSwitchingInTextEntry(actor, event);
if (layoutResult !== null) {
return layoutResult;
}
return Clutter.EVENT_PROPAGATE;
});
this.prompt.bind_property('confirm-visible',
this._confirmEntry, 'visible', GObject.BindingFlags.SYNC_CREATE);
passwordBox.add_child(this._confirmEntry);
Expand Down
9 changes: 9 additions & 0 deletions js/ui/networkAgent.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const ModalDialog = imports.ui.modalDialog;
const CinnamonEntry = imports.ui.cinnamonEntry;
const Util = imports.misc.util;

const VPN_UI_GROUP = 'VPN Plugin UI';

Expand Down Expand Up @@ -71,6 +72,14 @@ class NetworkSecretDialog extends ModalDialog.ModalDialog {
}

secret.entry.clutter_text.connect('activate', this._onOk.bind(this));
// Handle keyboard layout switching (Alt+Shift, etc.)
secret.entry.clutter_text.connect('key-press-event', (actor, event) => {
let layoutResult = Util.handleKeyboardLayoutSwitchingInTextEntry(actor, event);
if (layoutResult !== null) {
return layoutResult;
}
return Clutter.EVENT_PROPAGATE;
});
secret.entry.clutter_text.connect('text-changed', () => {
secret.value = secret.entry.get_text();
if (secret.validate)
Expand Down
8 changes: 8 additions & 0 deletions js/ui/polkitAuthenticationAgent.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,14 @@ var AuthenticationDialog = GObject.registerClass({
});
CinnamonEntry.addContextMenu(this._passwordEntry);
this._passwordEntry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
// Handle keyboard layout switching (Alt+Shift, etc.)
this._passwordEntry.clutter_text.connect('key-press-event', (actor, event) => {
let layoutResult = Util.handleKeyboardLayoutSwitchingInTextEntry(actor, event);
if (layoutResult !== null) {
return layoutResult;
}
return Clutter.EVENT_PROPAGATE;
});
this._passwordEntry.bind_property('reactive',
this._passwordEntry.clutter_text, 'editable',
GObject.BindingFlags.SYNC_CREATE);
Expand Down
6 changes: 6 additions & 0 deletions js/ui/runDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@ class RunDialog extends ModalDialog.ModalDialog {
}

_onKeyPress(o, e) {
// Handle keyboard layout switching (Alt+Shift, etc.) before other key handling
let layoutResult = Util.handleKeyboardLayoutSwitchingInTextEntry(o, e);
if (layoutResult !== null) {
return layoutResult;
}

let symbol = e.get_key_symbol();
if (symbol === Clutter.KEY_Return || symbol === Clutter.KEY_KP_Enter) {
if (o.get_text().trim() == "") {
Expand Down
Loading