Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f36d30b
feat(hook): add Wayland frontmost-window backends (wlr + GNOME Shell)
recchia Jun 6, 2026
93e69c4
style: fix rustfmt on wayland_candidates vec literal
recchia Jun 9, 2026
6c857a3
fix(hook): reconnect wlr backend after Finished; fix stale doc comments
recchia Jun 9, 2026
4a078ec
fix(hook): bound wlr frontmost dispatch with deadlines (review)
recchia Jun 9, 2026
14f632c
fix(deps): restore master Cargo.lock to keep gpui at the working rev
recchia Jun 11, 2026
8a7760f
fix(hook): collapse nested if into if-let-and guard (clippy)
recchia Jun 11, 2026
28cdca3
fix(hook): share a single INIT_TIMEOUT deadline across both round-trips
recchia Jun 11, 2026
362d798
Merge remote-tracking branch 'origin/master' into feat/frontmost-wayl…
recchia Jun 12, 2026
54176e2
Merge branch 'master' into feat/frontmost-wayland-backends
recchia Jun 14, 2026
4900500
fix(gui): set Wayland app_id so OpenLogi's own window is identifiable
recchia Jun 14, 2026
d01debe
Merge branch 'master' into feat/frontmost-wayland-backends
recchia Jun 15, 2026
b45e191
refactor(brand): hoist the app id into brand::APP_ID
recchia Jun 16, 2026
e92d200
Merge remote-tracking branch 'origin/master' into feat/frontmost-wayl…
recchia Jun 16, 2026
4f0eba8
Merge remote-tracking branch 'origin/master' into feat/frontmost-wayl…
recchia Jun 24, 2026
cfc9030
Merge remote-tracking branch 'origin/master' into pr191-merge-test
recchia Jul 3, 2026
43ed322
fix(hook): reconnect wlr-foreign-toplevel backend on connection error
recchia Jul 3, 2026
1c30d64
style(hook): apply cargo fmt to drain_events reconnect fix
recchia Jul 3, 2026
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions crates/openlogi-core/src/brand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ pub const HELP_URL: &str = "https://github.com/AprilNEA/OpenLogi#readme";
/// The "latest release" page.
pub const RELEASES_URL: &str = "https://github.com/AprilNEA/OpenLogi/releases/latest";

/// The application identifier: the Wayland xdg-toplevel `app_id` (and X11
/// `WM_CLASS`) the GUI advertises, the root of the macOS bundle-id family
/// (`org.openlogi.agent`, `org.openlogi.openlogi.dev`), and the value the Linux
/// `.desktop` file pins as `StartupWMClass`. Defined once here so the window the
/// compositor sees, the launcher that groups it, and the frontmost backend that
/// self-identifies OpenLogi can never disagree. The `.desktop` file carries its
/// own literal copy (it can't reference Rust) — keep the two in sync.
pub const APP_ID: &str = "org.openlogi.openlogi";

/// The release page for a specific version tag (e.g. the running build).
#[must_use]
pub fn release_tag_url(version: &str) -> String {
Expand Down
10 changes: 8 additions & 2 deletions crates/openlogi-gui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ use gpui::{
WindowBounds, WindowOptions, px,
};
use gpui_component::{ActiveTheme, Root};
use openlogi_core::brand::DeeplinkCommand;
use openlogi_core::brand::{APP_ID, DeeplinkCommand};
use openlogi_core::config::Config;
use openlogi_core::device::{DeviceInventory, DeviceModelInfo};
use tracing::{info, warn};
Expand Down Expand Up @@ -554,11 +554,17 @@ fn main_window_options(cx: &mut gpui::App) -> WindowOptions {
let bounds = Bounds::centered(None, Size::new(px(1100.), px(750.)), cx);
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
// Advertise a Wayland xdg-toplevel app_id (and X11 WM_CLASS). Without it
// the window ships no app_id, so GNOME's `get_wm_class()` returns empty
// and our own `gnome_shell` frontmost backend reports OpenLogi as `None`
// (and the dash can't group the window under its launcher icon). The id
// is the shared `brand::APP_ID`, matching the desktop file's
// `StartupWMClass` and the macOS bundle-id family.
app_id: Some(APP_ID.into()),
// Min height keeps the buttons tab's mouse model above its scale floor
// (`MODEL_MIN_H` + the chrome/padding reserve) so its side labels never
// overlap; below this the model can't shrink further without crowding.
window_min_size: Some(Size::new(px(720.), px(680.))),
app_id: Some("openlogi".to_string()),
titlebar: Some(TitlebarOptions {
title: Some(SharedString::from("OpenLogi")),
appears_transparent: false,
Expand Down
3 changes: 2 additions & 1 deletion crates/openlogi-gui/src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use gpui::{
Subscription, TitlebarOptions, WindowBounds, WindowHandle, WindowOptions,
};
use gpui_component::{ActiveTheme as _, Root};
use openlogi_core::brand::APP_ID;
use tracing::warn;

/// One live handle per auxiliary window, stored as a GPUI global so the menu
Expand Down Expand Up @@ -72,7 +73,7 @@ pub fn open_or_focus<V: AuxWindow + 'static>(
let bounds = Bounds::centered(None, size, cx);
let options = WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
app_id: Some("openlogi".to_string()),
app_id: Some(APP_ID.into()),
titlebar: Some(TitlebarOptions {
title: Some(title.clone()),
appears_transparent: false,
Expand Down
3 changes: 3 additions & 0 deletions crates/openlogi-hook/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ tracing = { workspace = true }
[target.'cfg(target_os = "linux")'.dependencies]
evdev = "0.13"
libc = "0.2"
wayland-client = "0.31"
wayland-protocols-wlr = { version = "0.3", features = ["client"] }
x11rb = "0.13"
zbus = "5"

[target.'cfg(target_os = "linux")'.dev-dependencies]
ctrlc = "3"
Expand Down
59 changes: 59 additions & 0 deletions crates/openlogi-hook/gnome-shell-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# OpenLogi Frontmost Window — GNOME Shell extension

GNOME (Mutter) does not let ordinary clients see which window is focused on
Wayland, and it implements neither `wlr-foreign-toplevel` nor a focused-window
portal. This minimal extension bridges that gap: it exports the WM_CLASS of the
focused window over D-Bus so OpenLogi's `gnome-shell` frontmost backend can
drive per-app mouse-profile switching.

It reads only `global.display.focus_window.get_wm_class()`. No titles, no window
contents, no input, no UI.

## D-Bus surface

- name: `org.openlogi.Frontmost`
- path: `/org/openlogi/Frontmost`
- method: `GetFocusedWmClass() -> s` (empty string when nothing is focused)

## Install

```sh
UUID=openlogi-frontmost@openlogi.dev
DEST="$HOME/.local/share/gnome-shell/extensions/$UUID"
mkdir -p "$DEST"
cp metadata.json extension.js "$DEST"/
```

On Wayland the shell cannot be reloaded in place, so **log out and back in** to
let GNOME pick up the newly added extension, then enable it:

```sh
gnome-extensions enable "$UUID"
gnome-extensions info "$UUID" # State should be ACTIVE
```

## Verify

```sh
# Introspect the service:
busctl --user introspect org.openlogi.Frontmost /org/openlogi/Frontmost

# Focus a window, then query it:
gdbus call --session \
-d org.openlogi.Frontmost \
-o /org/openlogi/Frontmost \
-m org.openlogi.Frontmost.GetFocusedWmClass
```

If `gdbus call` prints the focused window's WM_CLASS, OpenLogi's GNOME backend
will pick it up automatically the next time the hook starts.

## Notes

- The `shell-version` list in `metadata.json` covers GNOME 45–50. Newer GNOME
releases may need an added entry; the API used here (`Gio.DBusExportedObject`,
`global.display.focus_window`, `Meta.Window.get_wm_class`) has been stable
across these versions.
- The extension name/UUID and the D-Bus name (`org.openlogi.*`) are placeholders
that should track the project's namespace; if they change, update the matching
constants in `crates/openlogi-hook/src/linux/gnome_shell.rs`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// OpenLogi Frontmost Window — GNOME Shell extension.
//
// Exports a tiny D-Bus service that returns the WM_CLASS of the currently
// focused window. OpenLogi's `gnome_shell` frontmost backend polls this to
// drive per-app mouse-profile switching on GNOME Wayland, where the focused
// window is otherwise not visible to ordinary clients.
//
// It reads only `global.display.focus_window.get_wm_class()` — no titles, no
// window contents, no input. ESM module style; targets GNOME Shell 45+.

import Gio from 'gi://Gio';
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';

const DBUS_NAME = 'org.openlogi.Frontmost';
const DBUS_PATH = '/org/openlogi/Frontmost';
const DBUS_INTERFACE = `
<node>
<interface name="org.openlogi.Frontmost">
<method name="GetFocusedWmClass">
<arg type="s" direction="out" name="wmClass"/>
</method>
</interface>
</node>`;

export default class OpenLogiFrontmostExtension extends Extension {
enable() {
this._dbus = Gio.DBusExportedObject.wrapJSObject(DBUS_INTERFACE, this);
this._dbus.export(Gio.DBus.session, DBUS_PATH);
this._nameId = Gio.bus_own_name_on_connection(
Gio.DBus.session,
DBUS_NAME,
Gio.BusNameOwnerFlags.NONE,
null,
null);
}

disable() {
if (this._nameId) {
Gio.bus_unown_name(this._nameId);
this._nameId = 0;
}
if (this._dbus) {
this._dbus.unexport();
this._dbus = null;
}
}

// D-Bus method org.openlogi.Frontmost.GetFocusedWmClass.
GetFocusedWmClass() {
const win = global.display.focus_window;
if (!win)
return '';
return win.get_wm_class() || '';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"uuid": "openlogi-frontmost@openlogi.dev",
"name": "OpenLogi Frontmost Window",
"description": "Exposes the focused window's WM_CLASS over D-Bus so OpenLogi can switch per-app mouse profiles on GNOME Wayland. Read-only; no UI, no window contents.",
"shell-version": ["45", "46", "47", "48", "49", "50"],
"url": "https://github.com/AprilNEA/OpenLogi",
"version": 1
}
Loading
Loading