diff --git a/doc/user/configuration.md b/doc/user/configuration.md index ebbe0896e488..5ed5fe81c39f 100644 --- a/doc/user/configuration.md +++ b/doc/user/configuration.md @@ -403,7 +403,7 @@ end |clipboardSharing| `true` or `false`|If set to ''true'' then clipboard sharing will be enabled and the ''clipboardSharingSize'' setting will be used. If set to false, then clipboard sharing will be disabled and the the ''clipboardSharingSize'' setting will be ignored.| |clipboardSharingSize| integer (N)| Deskflow will send a maximum of `N` kilobytes of clipboard data to another computer when the mouse transitions to that computer.| |win32KeepForeground | `true` or `false`| If set to ''true'' (the default), Deskflow will grab the foreground focus on a Windows server (thereby putting all other windows in the background) upon switching to a client. If set to ''false'', it will leave the currently foreground window in the foreground. Deskflow grabs the focus to avoid issues with other apps interfering with Deskflow's ability to read the hardware inputs. | -|keystroke(key) | actions | Binds the ''key'' combination key to the given ''actions''. ''key'' is an optional list of modifiers (''shift'', ''control'', ''alt'', ''meta'' or ''super'') optionally followed by a character or a key name, all separated by + (plus signs). You must have either modifiers or a character/key name or both. See below for `valid key names` and `actions`. Keyboard hot keys are handled while the cursor is on the primary screen and secondary screens. Separate actions can be assigned to press and release.| +|keystroke(key[,options]) | actions | Binds the ''key'' combination key to the given ''actions''. ''key'' is an optional list of modifiers (''shift'', ''control'', ''alt'', ''meta'' or ''super'') optionally followed by a character or a key name, all separated by + (plus signs). You must have either modifiers or a character/key name or both. See below for `valid key names` and `actions`. Keyboard hot keys are handled while the cursor is on the primary screen and secondary screens. Separate actions can be assigned to press and release. The optional ''options'' parameter can be ''disableGlobalHotkeyRegister'' to allow apps on the server to respond to the original keystroke without OS blocking.| |mousebutton(button) | actions| Binds the modifier and mouse button combination ''button'' to the given ''actions''. ''button'' is an optional list of modifiers (''shift'', ''control'', ''alt'', ''meta'' or ''super'') followed by a button number. The primary button (the left button for right handed users) is button 1, the middle button is 2, etc. Actions can be found below. Mouse button actions are not handled while the cursor is on the primary screen. You cannot use these to perform an action while on the primary screen. Separate actions can be assigned to press and release.| @@ -413,16 +413,17 @@ You can use both the ''switchDelay'' and ''switchDoubleTap'' options at the same Actions are two lists of individual actions separated by commas. The two lists are separated by a '';'' (semicolon). Either list can be empty and if the second list is empty then the semicolon is optional. The first list lists actions to take when the condition becomes true (e.g. the hot key or mouse button is pressed) and the second lists actions to take when the condition becomes false (e.g. the hot key or button is released). The condition becoming true is called activation and becoming false is called deactivation. Allowed individual actions are: -* `keystroke(key[,screens])` +* `keystroke(key[,screens[,options]])` -* `keyDown(key[,screens])` +* `keyDown(key[,screens[,options]])` -* `keyUp(key[,screens])` +* `keyUp(key[,screens[,options]])` : Synthesizes the modifiers and key given in ''key'' which has the same form as described in the ''keystroke'' option. If given, ''screens'' lists the screen or screens to direct the event to, regardless of the active screen. If not given then the event is directed to the active screen only. : ''keyDown'' synthesizes a key press and ''keyUp'' synthesizes a key release. ''keystroke'' synthesizes a key press on activation and a release on deactivation and is equivalent to a ''keyDown'' on activation and ''keyUp'' on deactivation. : ''screens'' is either ''*'' (asterisk) to indicate all screens or a '':'' (colon) separated list of screen names. (Note that the screen name must have already been encountered in the configuration file so you'll probably want to put ''actions'' at the bottom of the file.) +: The optional ''options'' parameter can be ''activeScreenOnly'' to perform the action only when the specified screen is currently active. This is useful for creating screen-specific hotkeys that only work when that screen is in focus. * `mousebutton(button)` * `mouseDown(button)` @@ -682,6 +683,76 @@ section: options end ``` +### Active Screen Only Hotkeys + +The following example shows how to use the `activeScreenOnly` option to create screen-specific hotkeys. This is useful when you want different hotkey behaviors on different screens. + +**Important limitation**: When using `activeScreenOnly` with the primary/server screen as a target, the action may not work if it uses the same keystroke as the condition. This is because the primary client registers the original keystroke with the OS as a hotkey, which blocks Deskflow from creating fake events for them. To work around this, ensure that actions targeting the server screen use different keystrokes than the condition, or only target client screens. + +``` +# Physical monitor arrangement +# +----------+----------+ +# | Server | Mac-Mini | +# | | | +# +----------+----------+ + +section: screens + Server: + Mac-Mini: +end + +section: links + Server: + right = Mac-Mini + Mac-Mini: + left = Server +end + +section: options + # Map Control key combinations to Super (Command) key on Mac, only when Mac is active + # Using disableGlobalHotkeyRegister allows the server to still use Control shortcuts normally + keystroke(Control+Left,disableGlobalHotkeyRegister) = keystroke(Super+Left,Mac-Mini,activeScreenOnly) + keystroke(Control+Right,disableGlobalHotkeyRegister) = keystroke(Super+Right,Mac-Mini,activeScreenOnly) + keystroke(Control+Up,disableGlobalHotkeyRegister) = keystroke(Super+Up,Mac-Mini,activeScreenOnly) + keystroke(Control+c,disableGlobalHotkeyRegister) = keystroke(Super+c,Mac-Mini,activeScreenOnly) + keystroke(Control+v,disableGlobalHotkeyRegister) = keystroke(Super+v,Mac-Mini,activeScreenOnly) +end +``` + +For multiple clients, you can use a colon-separated list of screen names or chain actions with commas: + +``` +# Physical monitor arrangement +# +----------+----------+----------+ +# | Server | Mac-Mini | Macbook | +# | | | | +# +----------+----------+----------+ + +section: screens + Server: + Mac-Mini: + Macbook: +end + +section: links + Server: + right = Mac-Mini + Mac-Mini: + left = Server + right = Macbook + Macbook: + left = Mac-Mini +end + +section: options + # Option 1: Use colon-separated screen list (applies same action to multiple screens) + keystroke(Control+c,disableGlobalHotkeyRegister) = keystroke(Super+c,Mac-Mini:Macbook,activeScreenOnly) + + # Option 2: Chain multiple actions with commas (different actions per screen) + keystroke(Control+Left,disableGlobalHotkeyRegister) = keystroke(Super+Left,Mac-Mini,activeScreenOnly),keystroke(Super+Left,Macbook,activeScreenOnly) +end +``` + ### AltGr key The following screen config allows the mapping for ''Alt'' to ''AltGr''. Although this may not work, see [https://github.com/deskflow/deskflow-core/issues/4411 bug #4411]. diff --git a/src/lib/deskflow/IKeyState.cpp b/src/lib/deskflow/IKeyState.cpp index 553c1980941b..b83d007dda86 100644 --- a/src/lib/deskflow/IKeyState.cpp +++ b/src/lib/deskflow/IKeyState.cpp @@ -25,11 +25,12 @@ IKeyState::IKeyState(const IEventQueue *) IKeyState::KeyInfo *IKeyState::KeyInfo::alloc(KeyID id, KeyModifierMask mask, KeyButton button, int32_t count) { - auto *info = new KeyInfo(); + auto *info = (KeyInfo *)malloc(sizeof(KeyInfo)); info->m_key = id; info->m_mask = mask; info->m_button = button; info->m_count = count; + info->m_activeScreenOnly = false; info->m_screens = nullptr; info->m_screensBuffer[0] = '\0'; return info; @@ -38,22 +39,26 @@ IKeyState::KeyInfo *IKeyState::KeyInfo::alloc(KeyID id, KeyModifierMask mask, Ke IKeyState::KeyInfo *IKeyState::KeyInfo::alloc( KeyID id, KeyModifierMask mask, KeyButton button, int32_t count, const std::set &destinations ) +{ + return alloc(id, mask, button, count, destinations, false); +} + +IKeyState::KeyInfo *IKeyState::KeyInfo::alloc( + KeyID id, KeyModifierMask mask, KeyButton button, int32_t count, const std::set &destinations, + bool activeScreenOnly +) { std::string screens = join(destinations); const char *buffer = screens.c_str(); - // build structure -#if SYSAPI_WIN32 - // On windows we use malloc to avoid random test failures + // build structure - allocate extra space for the screens string auto *info = (KeyInfo *)malloc(sizeof(KeyInfo) + screens.size()); -#else - auto *info = new KeyInfo(); -#endif info->m_key = id; info->m_mask = mask; info->m_button = button; info->m_count = count; + info->m_activeScreenOnly = activeScreenOnly; info->m_screens = info->m_screensBuffer; std::copy(buffer, buffer + screens.size() + 1, info->m_screensBuffer); return info; @@ -63,17 +68,14 @@ IKeyState::KeyInfo *IKeyState::KeyInfo::alloc(const KeyInfo &x) { auto bufferLen = strnlen(x.m_screensBuffer, SIZE_MAX); -#if SYSAPI_WIN32 - // On windows we use malloc to avoid random test failures - auto info = (KeyInfo *)malloc(sizeof(KeyInfo) + bufferLen); -#else - auto *info = new KeyInfo(); -#endif + // allocate extra space for the screens string + auto *info = (KeyInfo *)malloc(sizeof(KeyInfo) + bufferLen); info->m_key = x.m_key; info->m_mask = x.m_mask; info->m_button = x.m_button; info->m_count = x.m_count; + info->m_activeScreenOnly = x.m_activeScreenOnly; info->m_screens = x.m_screens ? info->m_screensBuffer : nullptr; memcpy(info->m_screensBuffer, x.m_screensBuffer, bufferLen + 1); return info; diff --git a/src/lib/deskflow/IKeyState.h b/src/lib/deskflow/IKeyState.h index cad575a64f39..e8e2cb14fca0 100644 --- a/src/lib/deskflow/IKeyState.h +++ b/src/lib/deskflow/IKeyState.h @@ -32,6 +32,10 @@ class IKeyState public: static KeyInfo *alloc(KeyID, KeyModifierMask, KeyButton, int32_t count); static KeyInfo *alloc(KeyID, KeyModifierMask, KeyButton, int32_t count, const std::set &destinations); + static KeyInfo *alloc( + KeyID, KeyModifierMask, KeyButton, int32_t count, const std::set &destinations, + bool activeScreenOnly + ); static KeyInfo *alloc(const KeyInfo &); static bool isDefault(const char *screens); @@ -45,6 +49,7 @@ class IKeyState KeyModifierMask m_mask; KeyButton m_button; int32_t m_count; + bool m_activeScreenOnly; char *m_screens; char m_screensBuffer[1]; }; diff --git a/src/lib/deskflow/IPrimaryScreen.h b/src/lib/deskflow/IPrimaryScreen.h index 406deaeffab6..76216fa78fda 100644 --- a/src/lib/deskflow/IPrimaryScreen.h +++ b/src/lib/deskflow/IPrimaryScreen.h @@ -123,13 +123,13 @@ class IPrimaryScreen the modifiers in any order or to require the user to press the given key last. */ - virtual uint32_t registerHotKey(KeyID key, KeyModifierMask mask) = 0; + virtual uint32_t registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) = 0; //! Unregister a system hotkey /*! Unregisters a previously registered hot key. */ - virtual void unregisterHotKey(uint32_t id) = 0; + virtual void unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey) = 0; //! Prepare to synthesize input on primary screen /*! diff --git a/src/lib/deskflow/PlatformScreen.h b/src/lib/deskflow/PlatformScreen.h index 512682f7175f..a6b435956273 100644 --- a/src/lib/deskflow/PlatformScreen.h +++ b/src/lib/deskflow/PlatformScreen.h @@ -31,8 +31,8 @@ class PlatformScreen : public IPlatformScreen void reconfigure(uint32_t activeSides) override = 0; uint32_t activeSides() override = 0; void warpCursor(int32_t x, int32_t y) override = 0; - uint32_t registerHotKey(KeyID key, KeyModifierMask mask) override = 0; - void unregisterHotKey(uint32_t id) override = 0; + uint32_t registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) override = 0; + void unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey) override = 0; void fakeInputBegin() override = 0; void fakeInputEnd() override = 0; int32_t getJumpZoneSize() const override = 0; diff --git a/src/lib/deskflow/Screen.cpp b/src/lib/deskflow/Screen.cpp index 3258acbb3475..da47ad537be4 100644 --- a/src/lib/deskflow/Screen.cpp +++ b/src/lib/deskflow/Screen.cpp @@ -277,14 +277,14 @@ void Screen::setSequenceNumber(uint32_t seqNum) m_screen->setSequenceNumber(seqNum); } -uint32_t Screen::registerHotKey(KeyID key, KeyModifierMask mask) +uint32_t Screen::registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) { - return m_screen->registerHotKey(key, mask); + return m_screen->registerHotKey(key, mask, registerGlobalHotkey); } -void Screen::unregisterHotKey(uint32_t id) +void Screen::unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey) { - m_screen->unregisterHotKey(id); + m_screen->unregisterHotKey(id, unregisterGlobalHotkey); } void Screen::fakeInputBegin() diff --git a/src/lib/deskflow/Screen.h b/src/lib/deskflow/Screen.h index 8859e851ccef..978e3d25ee36 100644 --- a/src/lib/deskflow/Screen.h +++ b/src/lib/deskflow/Screen.h @@ -188,13 +188,13 @@ class Screen : public IScreen Registers a system-wide hotkey for key \p key with modifiers \p mask. Returns an id used to unregister the hotkey. */ - uint32_t registerHotKey(KeyID key, KeyModifierMask mask); + uint32_t registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey); //! Unregister a system hotkey /*! Unregisters a previously registered hot key. */ - void unregisterHotKey(uint32_t id); + void unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey); //! Prepare to synthesize input on primary screen /*! diff --git a/src/lib/gui/Action.cpp b/src/lib/gui/Action.cpp index 65717c96e8e8..b53435f76e7d 100644 --- a/src/lib/gui/Action.cpp +++ b/src/lib/gui/Action.cpp @@ -34,6 +34,11 @@ QString Action::text() const commandArgs.append(QStringLiteral(",%1").arg(screenList)); } else commandArgs.append(QStringLiteral(",*")); + + // Add activeScreenOnly option if set + if (activeScreenOnly()) { + commandArgs.append(QStringLiteral(",activeScreenOnly")); + } } text.append(m_commandTemplate.arg(commandArgs)); } break; @@ -76,6 +81,7 @@ void Action::loadSettings(QSettings &settings) setActiveOnRelease(settings.value(SettingsKeys::ActiveOnRelease, false).toBool()); setHaveScreens(settings.value(SettingsKeys::HasScreens, false).toBool()); setRestartServer(settings.value(SettingsKeys::RestartServer, false).toBool()); + setActiveScreenOnly(settings.value(SettingsKeys::ActiveScreenOnly, false).toBool()); } void Action::saveSettings(QSettings &settings) const @@ -96,6 +102,7 @@ void Action::saveSettings(QSettings &settings) const settings.setValue(SettingsKeys::ActiveOnRelease, activeOnRelease()); settings.setValue(SettingsKeys::HasScreens, haveScreens()); settings.setValue(SettingsKeys::RestartServer, restartServer()); + settings.setValue(SettingsKeys::ActiveScreenOnly, activeScreenOnly()); } QTextStream &operator<<(QTextStream &outStream, const Action &action) diff --git a/src/lib/gui/Action.h b/src/lib/gui/Action.h index 279d3059a972..b5cba2d45c0e 100644 --- a/src/lib/gui/Action.h +++ b/src/lib/gui/Action.h @@ -28,6 +28,7 @@ inline static const QString LockToScreen = QStringLiteral("lockCursorToScreen"); inline static const QString ActiveOnRelease = QStringLiteral("activeOnRelease"); inline static const QString HasScreens = QStringLiteral("hasScreens"); inline static const QString RestartServer = QStringLiteral("restartServer"); +inline static const QString ActiveScreenOnly = QStringLiteral("activeScreenOnly"); } // namespace SettingsKeys class Action @@ -106,6 +107,10 @@ class Action { return m_restartServer; } + bool activeScreenOnly() const + { + return m_activeScreenOnly; + } bool operator==(const Action &a) const = default; @@ -150,6 +155,10 @@ class Action { m_restartServer = b; } + void setActiveScreenOnly(bool b) + { + m_activeScreenOnly = b; + } private: KeySequence m_keySequence; @@ -160,7 +169,8 @@ class Action int m_lockCursorMode = static_cast(LockCursorMode::toggle); bool m_activeOnRelease = false; bool m_hasScreens = false; - bool m_restartServer; + bool m_restartServer = false; + bool m_activeScreenOnly = false; inline static const QString m_commandTemplate = QStringLiteral("(%1)"); inline static const QStringList m_actionTypeNames{ diff --git a/src/lib/gui/Hotkey.cpp b/src/lib/gui/Hotkey.cpp index 3ec948b11314..4cd1d8e73c69 100644 --- a/src/lib/gui/Hotkey.cpp +++ b/src/lib/gui/Hotkey.cpp @@ -12,8 +12,16 @@ QString Hotkey::text() const { - return m_keySequence.isMouseButton() ? kMousebutton.arg(m_keySequence.toString()) - : kKeystroke.arg(m_keySequence.toString()); + QString hotkeyText = m_keySequence.isMouseButton() ? kMousebutton.arg(m_keySequence.toString()) + : kKeystroke.arg(m_keySequence.toString()); + + // Add disableGlobalHotkeyRegister option if set (only for keystroke, not mousebutton) + if (!m_keySequence.isMouseButton() && m_disableGlobalHotkeyRegister) { + // Insert the option before the closing parenthesis + hotkeyText.insert(hotkeyText.length() - 1, QStringLiteral(",disableGlobalHotkeyRegister")); + } + + return hotkeyText; } void Hotkey::loadSettings(QSettings &settings) @@ -30,6 +38,8 @@ void Hotkey::loadSettings(QSettings &settings) } settings.endArray(); + + m_disableGlobalHotkeyRegister = settings.value(kDisableGlobalHotkeyRegister, false).toBool(); } void Hotkey::saveSettings(QSettings &settings) const @@ -42,11 +52,14 @@ void Hotkey::saveSettings(QSettings &settings) const m_actions.at(i).saveSettings(settings); } settings.endArray(); + + settings.setValue(kDisableGlobalHotkeyRegister, m_disableGlobalHotkeyRegister); } bool Hotkey::operator==(const Hotkey &hk) const { - return m_keySequence == hk.keySequence() && m_actions == hk.actions(); + return m_keySequence == hk.keySequence() && m_actions == hk.actions() && + m_disableGlobalHotkeyRegister == hk.disableGlobalHotkeyRegister(); } QTextStream &operator<<(QTextStream &outStream, const Hotkey &hotkey) diff --git a/src/lib/gui/Hotkey.h b/src/lib/gui/Hotkey.h index f06c892cd651..58965cf4324d 100644 --- a/src/lib/gui/Hotkey.h +++ b/src/lib/gui/Hotkey.h @@ -37,6 +37,10 @@ class Hotkey { return m_actions; } + bool disableGlobalHotkeyRegister() const + { + return m_disableGlobalHotkeyRegister; + } void loadSettings(QSettings &settings); void saveSettings(QSettings &settings) const; @@ -56,11 +60,17 @@ class Hotkey { return m_actions; } + void setDisableGlobalHotkeyRegister(bool b) + { + m_disableGlobalHotkeyRegister = b; + } private: KeySequence m_keySequence = {}; ActionList m_actions = {}; + bool m_disableGlobalHotkeyRegister = false; inline static const QString kSectionActions = QStringLiteral("actions"); + inline static const QString kDisableGlobalHotkeyRegister = QStringLiteral("disableGlobalHotkeyRegister"); inline static const QString kMousebutton = QStringLiteral("mousebutton(%1)"); inline static const QString kKeystroke = QStringLiteral("keystroke(%1)"); }; diff --git a/src/lib/gui/dialogs/ActionDialog.cpp b/src/lib/gui/dialogs/ActionDialog.cpp index ccbfc46d0ea6..38ffc3550bec 100644 --- a/src/lib/gui/dialogs/ActionDialog.cpp +++ b/src/lib/gui/dialogs/ActionDialog.cpp @@ -62,6 +62,8 @@ ActionDialog::ActionDialog(QWidget *parent, const ServerConfig &config, Hotkey & ui->comboSwitchToScreen->setVisible(false); ui->comboSwitchInDirection->setVisible(false); ui->comboLockCursorToScreen->setVisible(false); + ui->m_pCheckBoxActiveScreenOnly->setVisible(false); + ui->m_pCheckBoxActiveScreenOnly->setChecked(m_action.activeScreenOnly()); actionTypeChanged(ui->comboActionType->currentIndex()); } @@ -97,6 +99,7 @@ void ActionDialog::accept() m_action.setLockCursorMode(ui->comboLockCursorToScreen->currentIndex()); m_action.setActiveOnRelease(ui->comboTriggerOn->currentIndex()); m_action.setRestartServer(ui->comboActionType->currentIndex() == ActionTypes::RestartServer); + m_action.setActiveScreenOnly(ui->m_pCheckBoxActiveScreenOnly->isChecked()); QDialog::accept(); } @@ -112,6 +115,7 @@ void ActionDialog::updateSize() void ActionDialog::keySequenceChanged() { ui->listScreens->setEnabled(!ui->keySequenceWidget->keySequence().isMouseButton()); + ui->m_pCheckBoxActiveScreenOnly->setVisible(!ui->keySequenceWidget->keySequence().isMouseButton()); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(canSave()); } @@ -129,6 +133,9 @@ void ActionDialog::actionTypeChanged(int index) ui->comboSwitchToScreen->setVisible(index == ActionTypes::SwitchTo); ui->comboSwitchInDirection->setVisible(index == ActionTypes::SwitchInDirection); ui->comboLockCursorToScreen->setVisible(index == ActionTypes::ModifyCursorLock); + ui->m_pCheckBoxActiveScreenOnly->setVisible( + isKeyAction(index) && !ui->keySequenceWidget->keySequence().isMouseButton() + ); QTimer::singleShot(1, this, &ActionDialog::updateSize); } diff --git a/src/lib/gui/dialogs/ActionDialog.ui b/src/lib/gui/dialogs/ActionDialog.ui index 59ebbd5292c7..37fb9cbe79c0 100644 --- a/src/lib/gui/dialogs/ActionDialog.ui +++ b/src/lib/gui/dialogs/ActionDialog.ui @@ -7,7 +7,7 @@ 0 0 706 - 256 + 290 @@ -167,6 +167,16 @@ + + + + Only fire when target screen is active (activeScreenOnly) + + + When enabled, this action will only fire when the specified screen(s) are currently active + + + diff --git a/src/lib/gui/dialogs/HotkeyDialog.cpp b/src/lib/gui/dialogs/HotkeyDialog.cpp index 1fb11b7c9544..4bd657d78c2d 100644 --- a/src/lib/gui/dialogs/HotkeyDialog.cpp +++ b/src/lib/gui/dialogs/HotkeyDialog.cpp @@ -16,6 +16,7 @@ HotkeyDialog::HotkeyDialog(QWidget *parent, Hotkey &hotkey) ui->setupUi(this); ui->m_pKeySequenceWidgetHotkey->setText(m_Hotkey.text()); + ui->m_pCheckBoxDisableGlobalHotkeyRegister->setChecked(m_Hotkey.disableGlobalHotkeyRegister()); } HotkeyDialog::~HotkeyDialog() = default; @@ -26,6 +27,7 @@ void HotkeyDialog::accept() return; hotkey().setKeySequence(sequenceWidget()->keySequence()); + hotkey().setDisableGlobalHotkeyRegister(ui->m_pCheckBoxDisableGlobalHotkeyRegister->isChecked()); QDialog::accept(); } diff --git a/src/lib/gui/dialogs/HotkeyDialog.ui b/src/lib/gui/dialogs/HotkeyDialog.ui index ed8e8927c9d2..3550c7688e38 100644 --- a/src/lib/gui/dialogs/HotkeyDialog.ui +++ b/src/lib/gui/dialogs/HotkeyDialog.ui @@ -7,7 +7,7 @@ 0 0 344 - 86 + 120 @@ -24,6 +24,16 @@ + + + + Disable global hotkey registration (allow server to use this key) + + + When enabled, the hotkey will not be registered as a global hotkey with the OS, allowing the server to handle the key normally when focused + + + diff --git a/src/lib/platform/EiScreen.cpp b/src/lib/platform/EiScreen.cpp index ce42a2f5e093..64425be57718 100644 --- a/src/lib/platform/EiScreen.cpp +++ b/src/lib/platform/EiScreen.cpp @@ -209,8 +209,9 @@ void EiScreen::warpCursor(int32_t x, int32_t y) m_cursorY = y; } -std::uint32_t EiScreen::registerHotKey(KeyID key, KeyModifierMask mask) +std::uint32_t EiScreen::registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) { + (void)registerGlobalHotkey; // Unused for Ei implementation static std::uint32_t next_id; std::uint32_t id = std::min(++next_id, 1u); @@ -226,8 +227,9 @@ std::uint32_t EiScreen::registerHotKey(KeyID key, KeyModifierMask mask) return id; } -void EiScreen::unregisterHotKey(uint32_t id) +void EiScreen::unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey) { + (void)unregisterGlobalHotkey; // Unused for Ei implementation for (auto &[key, set] : m_hotkeys) { (void)key; if (set.removeById(id)) { diff --git a/src/lib/platform/EiScreen.h b/src/lib/platform/EiScreen.h index e75108db0c81..95458e405cf8 100644 --- a/src/lib/platform/EiScreen.h +++ b/src/lib/platform/EiScreen.h @@ -47,8 +47,8 @@ class EiScreen : public PlatformScreen void reconfigure(std::uint32_t activeSides) override; std::uint32_t activeSides() override; void warpCursor(std::int32_t x, std::int32_t y) override; - std::uint32_t registerHotKey(KeyID key, KeyModifierMask mask) override; - void unregisterHotKey(std::uint32_t id) override; + std::uint32_t registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) override; + void unregisterHotKey(std::uint32_t id, bool unregisterGlobalHotkey) override; void fakeInputBegin() override; void fakeInputEnd() override; std::int32_t getJumpZoneSize() const override; diff --git a/src/lib/platform/MSWindowsScreen.cpp b/src/lib/platform/MSWindowsScreen.cpp index a606da7a9a7c..80a0630434e0 100644 --- a/src/lib/platform/MSWindowsScreen.cpp +++ b/src/lib/platform/MSWindowsScreen.cpp @@ -538,7 +538,7 @@ void MSWindowsScreen::saveMousePosition(int32_t x, int32_t y) LOG_DEBUG5("saved mouse position for next delta: %+d,%+d", x, y); } -uint32_t MSWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) +uint32_t MSWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) { // only allow certain modifiers if ((mask & ~(KeyModifierShift | KeyModifierControl | KeyModifierAlt | KeyModifierSuper)) != 0) { @@ -587,11 +587,11 @@ uint32_t MSWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) } // if this hot key has modifiers only then we'll handle it specially - bool err; + bool err = false; if (key == kKeyNone) { // check if already registered err = (m_hotKeyToIDMap.count(HotKeyItem(vk, modifiers)) > 0); - } else { + } else if (registerGlobalHotkey) { // register with OS err = (RegisterHotKey(nullptr, id, modifiers, vk) == 0); } @@ -615,7 +615,7 @@ uint32_t MSWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) return id; } -void MSWindowsScreen::unregisterHotKey(uint32_t id) +void MSWindowsScreen::unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey) { // look up hotkey HotKeyMap::iterator i = m_hotKeys.find(id); @@ -624,11 +624,9 @@ void MSWindowsScreen::unregisterHotKey(uint32_t id) } // unregister with OS - bool err; - if (i->second.getVirtualKey() != 0) { + bool err = false; + if (unregisterGlobalHotkey && i->second.getVirtualKey() != 0) { err = !UnregisterHotKey(nullptr, id); - } else { - err = false; } if (err) { LOG_WARN("failed to unregister hotkey id=%d", id); diff --git a/src/lib/platform/MSWindowsScreen.h b/src/lib/platform/MSWindowsScreen.h index 5430c3dba3f3..7e7a86051356 100644 --- a/src/lib/platform/MSWindowsScreen.h +++ b/src/lib/platform/MSWindowsScreen.h @@ -86,8 +86,8 @@ class MSWindowsScreen : public PlatformScreen void reconfigure(uint32_t activeSides) override; uint32_t activeSides() override; void warpCursor(int32_t x, int32_t y) override; - uint32_t registerHotKey(KeyID key, KeyModifierMask mask) override; - void unregisterHotKey(uint32_t id) override; + uint32_t registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) override; + void unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey) override; void fakeInputBegin() override; void fakeInputEnd() override; int32_t getJumpZoneSize() const override; diff --git a/src/lib/platform/OSXScreen.h b/src/lib/platform/OSXScreen.h index 8a24bcfdbdca..73f43f78d3fe 100644 --- a/src/lib/platform/OSXScreen.h +++ b/src/lib/platform/OSXScreen.h @@ -62,8 +62,8 @@ class OSXScreen : public PlatformScreen void reconfigure(uint32_t activeSides) override; uint32_t activeSides() override; void warpCursor(int32_t x, int32_t y) override; - uint32_t registerHotKey(KeyID key, KeyModifierMask mask) override; - void unregisterHotKey(uint32_t id) override; + uint32_t registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) override; + void unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey) override; void fakeInputBegin() override; void fakeInputEnd() override; int32_t getJumpZoneSize() const override; diff --git a/src/lib/platform/OSXScreen.mm b/src/lib/platform/OSXScreen.mm index 19afc25046b6..18aa30002d93 100644 --- a/src/lib/platform/OSXScreen.mm +++ b/src/lib/platform/OSXScreen.mm @@ -301,7 +301,7 @@ y = m_yCenter; } -uint32_t OSXScreen::registerHotKey(KeyID key, KeyModifierMask mask) +uint32_t OSXScreen::registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) { // get mac virtual key and modifier mask matching deskflow key and mask uint32_t macKey, macMask; @@ -321,7 +321,7 @@ // if this hot key has modifiers only then we'll handle it specially EventHotKeyRef ref = nullptr; - bool okay; + bool okay = true; if (key == kKeyNone) { if (m_modifierHotKeys.count(mask) > 0) { // already registered @@ -331,9 +331,11 @@ okay = true; } } else { - EventHotKeyID hkid = {'SNRG', (uint32_t)id}; - OSStatus status = RegisterEventHotKey(macKey, macMask, hkid, GetApplicationEventTarget(), 0, &ref); - okay = (status == noErr); + if (registerGlobalHotkey) { + EventHotKeyID hkid = {'SNRG', (uint32_t)id}; + OSStatus status = RegisterEventHotKey(macKey, macMask, hkid, GetApplicationEventTarget(), 0, &ref); + okay = (status == noErr); + } m_hotKeyToIDMap[HotKeyItem(macKey, macMask)] = id; } @@ -354,7 +356,7 @@ return id; } -void OSXScreen::unregisterHotKey(uint32_t id) +void OSXScreen::unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey) { // look up hotkey HotKeyMap::iterator i = m_hotKeys.find(id); @@ -363,9 +365,11 @@ } // unregister with OS - bool okay; + bool okay = true; if (i->second.getRef() != nullptr) { - okay = (UnregisterEventHotKey(i->second.getRef()) == noErr); + if (unregisterGlobalHotkey) { + okay = (UnregisterEventHotKey(i->second.getRef()) == noErr); + } } else { okay = false; // XXX -- this is inefficient diff --git a/src/lib/platform/XWindowsScreen.cpp b/src/lib/platform/XWindowsScreen.cpp index c57ebf8470e3..e22dd3a92c12 100644 --- a/src/lib/platform/XWindowsScreen.cpp +++ b/src/lib/platform/XWindowsScreen.cpp @@ -511,7 +511,7 @@ void XWindowsScreen::warpCursor(int32_t x, int32_t y) m_yCursor = y; } -uint32_t XWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) +uint32_t XWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) { // only allow certain modifiers if ((mask & ~(KeyModifierShift | KeyModifierControl | KeyModifierAlt | KeyModifierSuper)) != 0) { @@ -620,7 +620,9 @@ uint32_t XWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) for (int k = 0; k < modKeymap->max_keypermod && !err; ++k) { KeyCode code = modifiermap[k]; if (modifiermap[k] != 0) { - XGrabKey(m_display, code, modifiers2, m_root, False, GrabModeAsync, GrabModeAsync); + if (registerGlobalHotkey) { + XGrabKey(m_display, code, modifiers2, m_root, False, GrabModeAsync, GrabModeAsync); + } if (!err) { hotKeys.push_back(std::make_pair(code, modifiers2)); m_hotKeyToIDMap[HotKeyItem(code, modifiers2)] = id; @@ -664,7 +666,9 @@ uint32_t XWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) } // add grab - XGrabKey(m_display, *j, tmpModifiers, m_root, False, GrabModeAsync, GrabModeAsync); + if (registerGlobalHotkey) { + XGrabKey(m_display, *j, tmpModifiers, m_root, False, GrabModeAsync, GrabModeAsync); + } if (!err) { hotKeys.push_back(std::make_pair(*j, tmpModifiers)); m_hotKeyToIDMap[HotKeyItem(*j, tmpModifiers)] = id; @@ -677,7 +681,9 @@ uint32_t XWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) if (err) { // if any failed then unregister any we did get for (const auto &[keyCode, keyMask] : hotKeys) { - XUngrabKey(m_display, keyCode, keyMask, m_root); + if (registerGlobalHotkey) { + XUngrabKey(m_display, keyCode, keyMask, m_root); + } m_hotKeyToIDMap.erase(HotKeyItem(keyCode, keyMask)); } @@ -697,7 +703,7 @@ uint32_t XWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) return id; } -void XWindowsScreen::unregisterHotKey(uint32_t id) +void XWindowsScreen::unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey) { // look up hotkey HotKeyMap::iterator i = m_hotKeys.find(id); @@ -711,7 +717,9 @@ void XWindowsScreen::unregisterHotKey(uint32_t id) XWindowsUtil::ErrorLock lock(m_display, &err); const HotKeyList &hotKeys = i->second; for (const auto &[keyCode, keyMask] : hotKeys) { - XUngrabKey(m_display, keyCode, keyMask, m_root); + if (unregisterGlobalHotkey) { + XUngrabKey(m_display, keyCode, keyMask, m_root); + } m_hotKeyToIDMap.erase(HotKeyItem(keyCode, keyMask)); } } diff --git a/src/lib/platform/XWindowsScreen.h b/src/lib/platform/XWindowsScreen.h index 4c436028e640..d0f5dbf6bb78 100644 --- a/src/lib/platform/XWindowsScreen.h +++ b/src/lib/platform/XWindowsScreen.h @@ -48,8 +48,8 @@ class XWindowsScreen : public PlatformScreen void reconfigure(uint32_t activeSides) override; uint32_t activeSides() override; void warpCursor(int32_t x, int32_t y) override; - uint32_t registerHotKey(KeyID key, KeyModifierMask mask) override; - void unregisterHotKey(uint32_t id) override; + uint32_t registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) override; + void unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey) override; void fakeInputBegin() override; void fakeInputEnd() override; int32_t getJumpZoneSize() const override; diff --git a/src/lib/server/Config.cpp b/src/lib/server/Config.cpp index ed60e3159219..35bf416dc89c 100644 --- a/src/lib/server/Config.cpp +++ b/src/lib/server/Config.cpp @@ -924,13 +924,18 @@ InputFilter::Condition * Config::parseCondition(const ConfigReadContext &s, const std::string &name, const std::vector &args) { if (name == "keystroke") { - if (args.size() != 1) { - throw ServerConfigReadException(s, "syntax for condition: keystroke(modifiers+key)"); + if (args.size() < 1 || args.size() > 2) { + throw ServerConfigReadException(s, "syntax for condition: keystroke(modifiers+key[,options])"); } IPlatformScreen::KeyInfo *keyInfo = s.parseKeystroke(args[0]); + bool disableGlobalHotkeyRegister = false; + + if (args.size() > 1) { + parseKeystrokeConditionOptions(s, args[1], disableGlobalHotkeyRegister); + } - return new InputFilter::KeystrokeCondition(m_events, keyInfo); + return new InputFilter::KeystrokeCondition(m_events, keyInfo, disableGlobalHotkeyRegister); } if (name == "mousebutton") { @@ -969,8 +974,8 @@ void Config::parseAction( InputFilter::Action *action; if (name == "keystroke" || name == "keyDown" || name == "keyUp") { - if (args.size() < 1 || args.size() > 2) { - throw ServerConfigReadException(s, "syntax for action: keystroke(modifiers+key[,screens])"); + if (args.size() < 1 || args.size() > 3) { + throw ServerConfigReadException(s, "syntax for action: keystroke(modifiers+key[,screens[,options]])"); } IPlatformScreen::KeyInfo *keyInfo; @@ -979,7 +984,11 @@ void Config::parseAction( } else { std::set screens; parseScreens(s, args[1], screens); - keyInfo = s.parseKeystroke(args[0], screens); + bool activeScreenOnly = false; + if (args.size() > 2) { + parseKeystrokeActionOptions(s, args[2], activeScreenOnly); + } + keyInfo = s.parseKeystroke(args[0], screens, activeScreenOnly); } if (name == "keystroke") { @@ -1186,6 +1195,44 @@ void Config::parseScreens(const ConfigReadContext &c, const std::string_view &s, } } +void Config::parseKeystrokeConditionOptions( + const ConfigReadContext &c, const std::string &s, bool &disableGlobalHotkeyRegister +) const +{ + // Default to false when no option is provided + disableGlobalHotkeyRegister = false; + + if (s.empty()) { + return; + } + + if (s == "disableGlobalHotkeyRegister") { + disableGlobalHotkeyRegister = true; + return; + } + + // Unknown option string: fail fast to surface configuration errors + throw ServerConfigReadException(c, "unknown keystroke condition option \"%{1}\"", s); +} + +void Config::parseKeystrokeActionOptions(const ConfigReadContext &c, const std::string &s, bool &activeScreenOnly) const +{ + // Default to false when no option is provided + activeScreenOnly = false; + + if (s.empty()) { + return; + } + + if (s == "activeScreenOnly") { + activeScreenOnly = true; + return; + } + + // Unknown option string: fail fast to surface configuration errors + throw ServerConfigReadException(c, "unknown keystroke action option \"%{1}\"", s); +} + const char *Config::getOptionName(OptionID id) { if (id == kOptionHalfDuplexCapsLock) { @@ -2000,6 +2047,13 @@ IPlatformScreen::KeyInfo *ConfigReadContext::parseKeystroke(const std::string &k IPlatformScreen::KeyInfo * ConfigReadContext::parseKeystroke(const std::string &keystroke, const std::set &screens) const +{ + return parseKeystroke(keystroke, screens, false); +} + +IPlatformScreen::KeyInfo *ConfigReadContext::parseKeystroke( + const std::string &keystroke, const std::set &screens, bool activeScreenOnly +) const { std::string s = keystroke; @@ -2017,7 +2071,7 @@ ConfigReadContext::parseKeystroke(const std::string &keystroke, const std::set &screens) const; + void parseKeystrokeConditionOptions( + const ConfigReadContext &c, const std::string &s, bool &disableGlobalHotkeyRegister + ) const; + void parseKeystrokeActionOptions(const ConfigReadContext &c, const std::string &s, bool &activeScreenOnly) const; static const char *getOptionName(OptionID); static std::string getOptionValue(OptionID, OptionValue); @@ -506,6 +510,8 @@ class ConfigReadContext ) const; IPlatformScreen::KeyInfo *parseKeystroke(const std::string &keystroke) const; IPlatformScreen::KeyInfo *parseKeystroke(const std::string &keystroke, const std::set &screens) const; + IPlatformScreen::KeyInfo * + parseKeystroke(const std::string &keystroke, const std::set &screens, bool activeScreenOnly) const; IPlatformScreen::ButtonInfo parseMouse(const std::string &mouse) const; KeyModifierMask parseModifier(const std::string &modifiers) const; std::istream &getStream() const diff --git a/src/lib/server/InputFilter.cpp b/src/lib/server/InputFilter.cpp index e3e7e6da9a76..08be4307a0e4 100644 --- a/src/lib/server/InputFilter.cpp +++ b/src/lib/server/InputFilter.cpp @@ -34,7 +34,19 @@ void InputFilter::Condition::disablePrimary(PrimaryClient *) InputFilter::KeystrokeCondition::KeystrokeCondition(IEventQueue *events, IPlatformScreen::KeyInfo *info) : m_key(info->m_key), m_mask(info->m_mask), - m_events(events) + m_events(events), + m_disableGlobalHotkeyRegister(false) +{ + free(info); +} + +InputFilter::KeystrokeCondition::KeystrokeCondition( + IEventQueue *events, IPlatformScreen::KeyInfo *info, bool disableGlobalHotkeyRegister +) + : m_key(info->m_key), + m_mask(info->m_mask), + m_events(events), + m_disableGlobalHotkeyRegister(disableGlobalHotkeyRegister) { free(info); } @@ -42,7 +54,19 @@ InputFilter::KeystrokeCondition::KeystrokeCondition(IEventQueue *events, IPlatfo InputFilter::KeystrokeCondition::KeystrokeCondition(IEventQueue *events, KeyID key, KeyModifierMask mask) : m_key(key), m_mask(mask), - m_events(events) + m_events(events), + m_disableGlobalHotkeyRegister(false) +{ + // do nothing +} + +InputFilter::KeystrokeCondition::KeystrokeCondition( + IEventQueue *events, KeyID key, KeyModifierMask mask, bool disableGlobalHotkeyRegister +) + : m_key(key), + m_mask(mask), + m_events(events), + m_disableGlobalHotkeyRegister(disableGlobalHotkeyRegister) { // do nothing } @@ -59,7 +83,7 @@ KeyModifierMask InputFilter::KeystrokeCondition::getMask() const InputFilter::Condition *InputFilter::KeystrokeCondition::clone() const { - return new KeystrokeCondition(m_events, m_key, m_mask); + return new KeystrokeCondition(m_events, m_key, m_mask, m_disableGlobalHotkeyRegister); } std::string InputFilter::KeystrokeCondition::format() const @@ -91,12 +115,12 @@ InputFilter::FilterStatus InputFilter::KeystrokeCondition::match(const Event &ev void InputFilter::KeystrokeCondition::enablePrimary(PrimaryClient *primary) { - m_id = primary->registerHotKey(m_key, m_mask); + m_id = primary->registerHotKey(m_key, m_mask, !m_disableGlobalHotkeyRegister); } void InputFilter::KeystrokeCondition::disablePrimary(PrimaryClient *primary) { - primary->unregisterHotKey(m_id); + primary->unregisterHotKey(m_id, !m_disableGlobalHotkeyRegister); m_id = 0; } diff --git a/src/lib/server/InputFilter.h b/src/lib/server/InputFilter.h index 322f119a0ed5..13b695bf94fb 100644 --- a/src/lib/server/InputFilter.h +++ b/src/lib/server/InputFilter.h @@ -52,7 +52,9 @@ class InputFilter { public: KeystrokeCondition(IEventQueue *events, IPlatformScreen::KeyInfo *); + KeystrokeCondition(IEventQueue *events, IPlatformScreen::KeyInfo *, bool disableGlobalHotkeyRegister); KeystrokeCondition(IEventQueue *events, KeyID key, KeyModifierMask mask); + KeystrokeCondition(IEventQueue *events, KeyID key, KeyModifierMask mask, bool disableGlobalHotkeyRegister); ~KeystrokeCondition() override = default; KeyID getKey() const; @@ -70,6 +72,7 @@ class InputFilter KeyID m_key; KeyModifierMask m_mask; IEventQueue *m_events; + bool m_disableGlobalHotkeyRegister = false; }; // MouseButtonCondition diff --git a/src/lib/server/PrimaryClient.cpp b/src/lib/server/PrimaryClient.cpp index a98cd9bd0e01..b81cc982c6c6 100644 --- a/src/lib/server/PrimaryClient.cpp +++ b/src/lib/server/PrimaryClient.cpp @@ -25,14 +25,14 @@ void PrimaryClient::reconfigure(uint32_t activeSides) m_screen->reconfigure(activeSides); } -uint32_t PrimaryClient::registerHotKey(KeyID key, KeyModifierMask mask) +uint32_t PrimaryClient::registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey) { - return m_screen->registerHotKey(key, mask); + return m_screen->registerHotKey(key, mask, registerGlobalHotkey); } -void PrimaryClient::unregisterHotKey(uint32_t id) +void PrimaryClient::unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey) { - m_screen->unregisterHotKey(id); + m_screen->unregisterHotKey(id, unregisterGlobalHotkey); } void PrimaryClient::fakeInputBegin() @@ -139,14 +139,10 @@ void PrimaryClient::setClipboardDirty(ClipboardID id, bool dirty) m_clipboardDirty[id] = dirty; } -void PrimaryClient::keyDown(KeyID key, KeyModifierMask mask, KeyButton button, const std::string &) +void PrimaryClient::keyDown(KeyID key, KeyModifierMask mask, KeyButton button, const std::string &lang) { if (m_fakeInputCount > 0) { - // XXX -- don't forward keystrokes to primary screen for now - (void)key; - (void)mask; - (void)button; - // m_screen->keyDown(key, mask, button); + m_screen->keyDown(key, mask, button, lang); } } @@ -158,11 +154,7 @@ void PrimaryClient::keyRepeat(KeyID, KeyModifierMask, int32_t, KeyButton, const void PrimaryClient::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) { if (m_fakeInputCount > 0) { - // XXX -- don't forward keystrokes to primary screen for now - (void)key; - (void)mask; - (void)button; - // m_screen->keyUp(key, mask, button); + m_screen->keyUp(key, mask, button); } } diff --git a/src/lib/server/PrimaryClient.h b/src/lib/server/PrimaryClient.h index 68d1c61e8d0c..ffae824609e9 100644 --- a/src/lib/server/PrimaryClient.h +++ b/src/lib/server/PrimaryClient.h @@ -42,13 +42,13 @@ class PrimaryClient : public BaseClientProxy Registers a system-wide hotkey for key \p key with modifiers \p mask. Returns an id used to unregister the hotkey. */ - virtual uint32_t registerHotKey(KeyID key, KeyModifierMask mask); + virtual uint32_t registerHotKey(KeyID key, KeyModifierMask mask, bool registerGlobalHotkey); //! Unregister a system hotkey /*! Unregisters a previously registered hot key. */ - virtual void unregisterHotKey(uint32_t id); + virtual void unregisterHotKey(uint32_t id, bool unregisterGlobalHotkey); //! Prepare to synthesize input on primary screen /*! diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index ade4dd8054e2..a325c121d2bb 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -205,7 +205,7 @@ bool Server::setConfig(const ServerConfig &config) // ScrollLock as a hotkey. if (!m_disableLockToScreen && !m_config->hasLockToScreenAction()) { IPlatformScreen::KeyInfo *key = IPlatformScreen::KeyInfo::alloc(kKeyScrollLock, 0, 0, 0); - InputFilter::Rule rule(new InputFilter::KeystrokeCondition(m_events, key)); + InputFilter::Rule rule(new InputFilter::KeystrokeCondition(m_events, key, true)); rule.adoptAction(new InputFilter::LockCursorToScreenAction(m_events), true); m_inputFilter->addFilterRule(rule); } @@ -1228,13 +1228,13 @@ void Server::handleKeyDownEvent(const Event &event) { const auto *info = static_cast(event.getData()); auto lang = AppUtil::instance().getCurrentLanguageCode(); - onKeyDown(info->m_key, info->m_mask, info->m_button, lang, info->m_screens); + onKeyDown(info->m_key, info->m_mask, info->m_button, lang, info->m_screens, info->m_activeScreenOnly); } void Server::handleKeyUpEvent(const Event &event) { auto *info = static_cast(event.getData()); - onKeyUp(info->m_key, info->m_mask, info->m_button, info->m_screens); + onKeyUp(info->m_key, info->m_mask, info->m_button, info->m_screens, info->m_activeScreenOnly); } void Server::handleKeyRepeatEvent(const Event &event) @@ -1531,7 +1531,10 @@ void Server::onScreensaver(bool activated) } } -void Server::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button, const std::string &lang, const char *screens) +void Server::onKeyDown( + KeyID id, KeyModifierMask mask, KeyButton button, const std::string &lang, const char *screens, + bool activeScreenOnly +) { LOG_DEBUG1("onKeyDown id=%d mask=0x%04x button=0x%04x lang=%s", id, mask, button, lang.c_str()); assert(m_active != nullptr); @@ -1539,6 +1542,14 @@ void Server::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button, const s // relay if (!m_keyboardBroadcasting && IKeyState::KeyInfo::isDefault(screens)) { m_active->keyDown(id, mask, button, lang); + } else if (activeScreenOnly) { + auto activeName = m_active->getName(); + if (IKeyState::KeyInfo::contains(screens, activeName)) { + // This won't work on the primary client if the action has the same keystroke as the condition. + // Unlike other clients, the primary client registers the original keystroke with the OS as a hotkey to block + // other apps from handling them, which also stops Deskflow from being able to create a fake event for them. + m_active->keyDown(id, mask, button, lang); + } } else { if (!screens && m_keyboardBroadcasting) { screens = m_keyboardBroadcastingScreens.c_str(); @@ -1554,7 +1565,7 @@ void Server::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button, const s } } -void Server::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button, const char *screens) +void Server::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button, const char *screens, bool activeScreenOnly) { LOG_DEBUG1("onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button); assert(m_active != nullptr); @@ -1562,6 +1573,14 @@ void Server::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button, const cha // relay if (!m_keyboardBroadcasting && IKeyState::KeyInfo::isDefault(screens)) { m_active->keyUp(id, mask, button); + } else if (activeScreenOnly) { + auto activeName = m_active->getName(); + if (IKeyState::KeyInfo::contains(screens, activeName)) { + // This won't work on the primary client if the action has the same keystroke as the condition. + // Unlike other clients, the primary client registers the original keystroke with the OS as a hotkey to block + // other apps from handling them, which also stops Deskflow from being able to create a fake event for them. + m_active->keyUp(id, mask, button); + } } else { if (!screens && m_keyboardBroadcasting) { screens = m_keyboardBroadcastingScreens.c_str(); diff --git a/src/lib/server/Server.h b/src/lib/server/Server.h index d4e8fd1f608c..7bc6595524e3 100644 --- a/src/lib/server/Server.h +++ b/src/lib/server/Server.h @@ -325,8 +325,8 @@ class Server // event processing void onClipboardChanged(const BaseClientProxy *sender, ClipboardID id, uint32_t seqNum); void onScreensaver(bool activated); - void onKeyDown(KeyID, KeyModifierMask, KeyButton, const std::string &, const char *screens); - void onKeyUp(KeyID, KeyModifierMask, KeyButton, const char *screens); + void onKeyDown(KeyID, KeyModifierMask, KeyButton, const std::string &, const char *screens, bool activeScreenOnly); + void onKeyUp(KeyID, KeyModifierMask, KeyButton, const char *screens, bool activeScreenOnly); void onKeyRepeat(KeyID, KeyModifierMask, int32_t, KeyButton, const std::string &); void onMouseDown(ButtonID); void onMouseUp(ButtonID); diff --git a/src/unittests/server/CMakeLists.txt b/src/unittests/server/CMakeLists.txt index 864d1462f816..5f0eeb70a392 100644 --- a/src/unittests/server/CMakeLists.txt +++ b/src/unittests/server/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: (C) 2025 Deskflow Developers +# SPDX-FileCopyrightText: 2025 Deskflow Developers # SPDX-License-Identifier: MIT if(WIN32)