Proof Portal
Omarchy
ProbeLabs65 findings · 95 requirementsA proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.
Synergy remote client keyboard and mouse not working
Reported by chittams · filed Sep 11, 2026 · open at source
At a glance
- 1 · What is claimed?
- Only the report text below — no validator has restated it as checkable claims yet.
- 2 · What does it affect?
- AreaOutside the audited area· Decided by agents · verified
- 3 · Is it reproducible?
- No validation has been run yet.
- 4 · What remains unclear?
- Not validated yet: no claims, no reproducer.
- 5 · What happens next?
- Decided by agents and verified by a second, independent agent — see the decision.
Original report
System details
i7-3770k nvidia 5060ti
What's wrong?
Problem
With Synergy 3 (Deskflow) configured as an EIS remote input backend for hypr-remote-desktop, keyboard and mouse work correctly, but modifier key bindings never fire — specifically the Super/Win key, which means Super+Space (Omarchy menu) and all other Super+<key> keybinds are dead.
Root cause
Hyprland's virtual keyboard implementation (zwp_virtual_keyboard_v1) only learns modifier state from the client-sent modifiers request. It does not derive modifiers from key events on virtual keyboards — this is by design, confirmed in the Hyprland 0.56.2 source:
// src/devices/VirtualKeyboard.cpp (line 43)
m_listeners.key = keeb_->m_events.key.listen(this {
updatePressed(event.keycode, ...); // ← only tracks pressed keys, not xkb state
m_keyboardEvents.key.emit(event);
});
When a real keyboard receives a key event, InputManager calls updateXkbStateWithKey() on the device, which updates its xkb state and modifier bits. For virtual keyboards, this never happens — the client must call zwp_virtual_keyboard_v1_modifiers() itself after every change.
The portal (hypr-remote-desktop) never sent that request, so modifier state was always zero. Every modifier key press (Super, Ctrl, Alt) was lost for binding purposes.
Environment
- Compositor: Hyprland 0.56.2
- Remote input: Synergy 3.1.28, via EIS (libei/eis-1.0)
- Portal: xdg-desktop-portal-hypr-remote (this project)
- Keycodes on the wire: raw Linux evdev scancodes (KEY_A=30, KEY_SPACE=57, KEY_LEFTMETA=125). Hyprland adds the evdev→xkb offset (+8) internally on all virtual keyboard events, so the portal must pass them through unchanged.
Fix
Two files changed:
src/wayland_virtual_keyboard.h
Added xkb state tracking fields to the class:
struct xkb_context;
struct xkb_keymap;
struct xkb_state;
// ...
struct xkb_context* xkb_ctx;
struct xkb_keymap* xkb_keymap_obj;
struct xkb_state* xkb_state_obj;
uint32_t last_depressed;
uint32_t last_latched;
uint32_t last_locked;
uint32_t last_group;
bool mods_sent;
src/wayland_virtual_keyboard.cpp
In set_keymap(): compile the xkb keymap string locally to create a mirror xkb_state that the portal can query for modifier bits.
In send_key(): before emitting the key event, update the xkb state with the keycode (keycode + 8, since evdev→xkb requires +8), serialize the modifier state, and if it changed, call zwp_virtual_keyboard_v1_modifiers(). Then send the key event.
void WaylandVirtualKeyboard::send_key(uint32_t time, uint32_t keycode, bool is_press) {
// ...
const uint32_t xkb_code = keycode + 8;
xkb_state_update_key(xkb_state_obj, xkb_code,
is_press ? XKB_KEY_DOWN : XKB_KEY_UP);
const uint32_t depressed = xkb_state_serialize_mods(
xkb_state_obj, XKB_STATE_MODS_DEPRESSED);
const uint32_t latched = xkb_state_serialize_mods(
xkb_state_obj, XKB_STATE_MODS_LATCHED);
const uint32_t locked = xkb_state_serialize_mods(
xkb_state_obj, XKB_STATE_MODS_LOCKED);
const uint32_t group = xkb_state_serialize_layout(
xkb_state_obj, XKB_STATE_LAYOUT_EFFECTIVE);
if (!mods_sent || depressed != last_depressed ||
latched != last_latched || locked != last_locked ||
group != last_group) {
zwp_virtual_keyboard_v1_modifiers(
virtual_keyboard, depressed, latched, locked, group);
mods_sent = true;
last_depressed = depressed;
last_latched = latched;
last_locked = locked;
last_group = group;
}
// Send key event (raw evdev — Hyprland applies +8 internally)
zwp_virtual_keyboard_v1_key(virtual_keyboard, time, keycode, state);
flush();
}
Verification
After the fix:
- Super+Space opens the Omarchy menu (modmask 64 = SUPER confirmed in hyprctl binds)
- All other Super+<key> binds work (window close, terminal, etc.)
- Modifier state correctly propagated to guest applications via wl_keyboard.modifiers
- Normal typing unaffected (evdev passthrough, no offset applied)
Known limitation: Clipboard
Cut/copy/paste does not work between Synergy clients over EIS. The Synergy EIS screen does not implement clipboard sharing from client→server — only server→client transfers work (on screen enter). This appears to be an upstream Synergy/Deskflow limitation: zero "grabbed clipboard" or "sending clipboard" events appear in the Synergy log regardless of content size. This should be filed as a separate upstream issue.
Notes
This issue is specific to the zwp_virtual_keyboard_v1 protocol path. The same problem applies to any virtual keyboard client (on-screen keyboards, automation tools) that fails to send the modifiers request — it's easy to miss because the protocol's key event still succeeds (the compositor processes it), but all modifier-dependent bindings silently break.
View raw text
### System details
i7-3770k nvidia 5060ti
### What's wrong?
**Problem**
With Synergy 3 (Deskflow) configured as an EIS remote input backend for hypr-remote-desktop, keyboard and mouse work correctly, but modifier key bindings never fire — specifically the Super/Win key, which means Super+Space (Omarchy menu) and all other Super+<key> keybinds are dead.
**Root cause**
Hyprland's virtual keyboard implementation (zwp_virtual_keyboard_v1) only learns modifier state from the client-sent modifiers request. It does not derive modifiers from key events on virtual keyboards — this is by design, confirmed in the Hyprland 0.56.2 source:
// src/devices/VirtualKeyboard.cpp (line 43)
m_listeners.key = keeb_->m_events.key.listen([this](const auto& event) {
updatePressed(event.keycode, ...); // ← only tracks pressed keys, not xkb state
m_keyboardEvents.key.emit(event);
});
When a real keyboard receives a key event, InputManager calls updateXkbStateWithKey() on the device, which updates its xkb state and modifier bits. For virtual keyboards, this never happens — the client must call zwp_virtual_keyboard_v1_modifiers() itself after every change.
The portal (hypr-remote-desktop) never sent that request, so modifier state was always zero. Every modifier key press (Super, Ctrl, Alt) was lost for binding purposes.
**Environment**
- Compositor: Hyprland 0.56.2
- Remote input: Synergy 3.1.28, via EIS (libei/eis-1.0)
- Portal: xdg-desktop-portal-hypr-remote (this project)
- Keycodes on the wire: raw Linux evdev scancodes (KEY_A=30, KEY_SPACE=57, KEY_LEFTMETA=125). Hyprland adds the evdev→xkb offset (+8) internally on all virtual keyboard events, so the portal must pass them through unchanged.
**Fix**
Two files changed:
src/wayland_virtual_keyboard.h
Added xkb state tracking fields to the class:
struct xkb_context;
struct xkb_keymap;
struct xkb_state;
// ...
struct xkb_context* xkb_ctx;
struct xkb_keymap* xkb_keymap_obj;
struct xkb_state* xkb_state_obj;
uint32_t last_depressed;
uint32_t last_latched;
uint32_t last_locked;
uint32_t last_group;
bool mods_sent;
src/wayland_virtual_keyboard.cpp
In set_keymap(): compile the xkb keymap string locally to create a mirror xkb_state that the portal can query for modifier bits.
In send_key(): before emitting the key event, update the xkb state with the keycode (keycode + 8, since evdev→xkb requires +8), serialize the modifier state, and if it changed, call zwp_virtual_keyboard_v1_modifiers(). Then send the key event.
void WaylandVirtualKeyboard::send_key(uint32_t time, uint32_t keycode, bool is_press) {
// ...
const uint32_t xkb_code = keycode + 8;
xkb_state_update_key(xkb_state_obj, xkb_code,
is_press ? XKB_KEY_DOWN : XKB_KEY_UP);
const uint32_t depressed = xkb_state_serialize_mods(
xkb_state_obj, XKB_STATE_MODS_DEPRESSED);
const uint32_t latched = xkb_state_serialize_mods(
xkb_state_obj, XKB_STATE_MODS_LATCHED);
const uint32_t locked = xkb_state_serialize_mods(
xkb_state_obj, XKB_STATE_MODS_LOCKED);
const uint32_t group = xkb_state_serialize_layout(
xkb_state_obj, XKB_STATE_LAYOUT_EFFECTIVE);
if (!mods_sent || depressed != last_depressed ||
latched != last_latched || locked != last_locked ||
group != last_group) {
zwp_virtual_keyboard_v1_modifiers(
virtual_keyboard, depressed, latched, locked, group);
mods_sent = true;
last_depressed = depressed;
last_latched = latched;
last_locked = locked;
last_group = group;
}
// Send key event (raw evdev — Hyprland applies +8 internally)
zwp_virtual_keyboard_v1_key(virtual_keyboard, time, keycode, state);
flush();
}
**Verification**
After the fix:
- Super+Space opens the Omarchy menu (modmask 64 = SUPER confirmed in hyprctl binds)
- All other Super+<key> binds work (window close, terminal, etc.)
- Modifier state correctly propagated to guest applications via wl_keyboard.modifiers
- Normal typing unaffected (evdev passthrough, no offset applied)
**Known limitation: Clipboard**
Cut/copy/paste does not work between Synergy clients over EIS. The Synergy EIS screen does not implement clipboard sharing from client→server — only server→client transfers work (on screen enter). This appears to be an upstream Synergy/Deskflow limitation: zero "grabbed clipboard" or "sending clipboard" events appear in the Synergy log regardless of content size. This should be filed as a separate upstream issue.
**Notes**
This issue is specific to the zwp_virtual_keyboard_v1 protocol path. The same problem applies to any virtual keyboard client (on-screen keyboards, automation tools) that fails to send the modifiers request — it's easy to miss because the protocol's key event still succeeds (the compositor processes it), but all modifier-dependent bindings silently break.Validation
Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.
Linked
Not linked to any known issue, other report or change yet.
Decision
Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.
- Out of scopeDecided by agents · verified · 2026-10-09 11:52 UTC