Skip to content
Draft
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
79 changes: 75 additions & 4 deletions doc/user/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.|


Expand All @@ -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)`
Expand Down Expand Up @@ -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].
Expand Down
28 changes: 15 additions & 13 deletions src/lib/deskflow/IKeyState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<std::string> &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<std::string> &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;
Expand All @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/lib/deskflow/IKeyState.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> &destinations);
static KeyInfo *alloc(
KeyID, KeyModifierMask, KeyButton, int32_t count, const std::set<std::string> &destinations,
bool activeScreenOnly
);
static KeyInfo *alloc(const KeyInfo &);

static bool isDefault(const char *screens);
Expand All @@ -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];
};
Expand Down
4 changes: 2 additions & 2 deletions src/lib/deskflow/IPrimaryScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
/*!
Expand Down
4 changes: 2 additions & 2 deletions src/lib/deskflow/PlatformScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions src/lib/deskflow/Screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions src/lib/deskflow/Screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
/*!
Expand Down
7 changes: 7 additions & 0 deletions src/lib/gui/Action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
12 changes: 11 additions & 1 deletion src/lib/gui/Action.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -106,6 +107,10 @@ class Action
{
return m_restartServer;
}
bool activeScreenOnly() const
{
return m_activeScreenOnly;
}

bool operator==(const Action &a) const = default;

Expand Down Expand Up @@ -150,6 +155,10 @@ class Action
{
m_restartServer = b;
}
void setActiveScreenOnly(bool b)
{
m_activeScreenOnly = b;
}

private:
KeySequence m_keySequence;
Expand All @@ -160,7 +169,8 @@ class Action
int m_lockCursorMode = static_cast<int>(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{
Expand Down
19 changes: 16 additions & 3 deletions src/lib/gui/Hotkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -30,6 +38,8 @@ void Hotkey::loadSettings(QSettings &settings)
}

settings.endArray();

m_disableGlobalHotkeyRegister = settings.value(kDisableGlobalHotkeyRegister, false).toBool();
}

void Hotkey::saveSettings(QSettings &settings) const
Expand All @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions src/lib/gui/Hotkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class Hotkey
{
return m_actions;
}
bool disableGlobalHotkeyRegister() const
{
return m_disableGlobalHotkeyRegister;
}

void loadSettings(QSettings &settings);
void saveSettings(QSettings &settings) const;
Expand All @@ -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)");
};
Expand Down
Loading
Loading