Skip to content
Open
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
11 changes: 8 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,10 @@ jobs:
mkdir -p dist
ref_name="${GITHUB_REF_NAME:-dev}"
ref_name="${ref_name//\//-}"
for pkg in target/release/*.tar.gz; do
[ -f "$pkg" ] || continue
cp "$pkg" "dist/openlogi-${ref_name}-linux-${PKG_ARCH}.tar.gz"
done
for pkg in target/release/*.deb target/release/*.rpm; do
[ -f "$pkg" ] || continue
ext="${pkg##*.}"
Expand Down Expand Up @@ -671,7 +675,7 @@ jobs:
# nothing instead of erroring on a literal pattern, so the DMGs are
# hashed alone.
shopt -s nullglob
sha256sum *.dmg *.exe *.msi *.deb *.rpm > SHA256SUMS
sha256sum *.dmg *.exe *.msi *.tar.gz *.deb *.rpm > SHA256SUMS

# softprops' `files` can't list a glob that matches nothing without
# tripping fail_on_unmatched_files, and the exe/msi sets vary per arch
Expand Down Expand Up @@ -747,11 +751,11 @@ jobs:
# treatment as the DMGs: manual verification today, and the future
# auto-updaters need the detached signatures to exist for every
# shipped version.
# nullglob: the exe/msi/deb/rpm sets are best-effort per arch leg, so
# nullglob: the exe/msi/tar/deb/rpm sets are best-effort per arch leg, so
# the globs may match nothing; the DMGs are guaranteed by the publish
# gate.
shopt -s nullglob
for artifact in dist/*.dmg dist/*.exe dist/*.msi dist/*.deb dist/*.rpm; do
for artifact in dist/*.dmg dist/*.exe dist/*.msi dist/*.tar.gz dist/*.deb dist/*.rpm; do
minisign -S -m "$artifact" -s "$key_file" -x "$artifact.minisig" -W
minisign -V -m "$artifact" -P "$OPENLOGI_UPDATE_MINISIGN_PUBLIC_KEY" -x "$artifact.minisig"
done
Expand Down Expand Up @@ -814,6 +818,7 @@ jobs:
body_path: .release/RELEASE_NOTES.md
files: |
dist/*.dmg
dist/*.tar.gz
dist/*.deb
dist/*.rpm
dist/*.minisig
Expand Down
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Things OpenLogi does that Options+ won't:
| Per-application profile overlays (auto-switch on app focus) | ✅ macOS, 🟡 Linux (X11 only) |
| Settings window: launch-at-login, update check, menu-bar, permissions, language | ✅ macOS + Linux |
| Interface localization (20 languages: da, de, el, en, es, fi, fr, it, ja, ko, nb, nl, pl, pt-BR, pt-PT, ru, sv, zh-CN, zh-HK, zh-TW) | ✅ |
| Linux packaging: udev rules, systemd unit, `.deb` / `.rpm` | ✅ Linux |
| Linux packaging: udev rules, systemd unit, `.deb` / `.rpm` / `.tar.gz` | ✅ Linux |
| Gesture-button per-direction bindings | 🟡 configurable; hardware capture pending |
| Middle / mode-shift / thumbwheel button capture | 🟡 configurable; hook owns side buttons only |
| Windows (agent, GUI, event hook) | 🟡 untested preview — signed `.exe` / `.msi` ship per release |
Expand Down Expand Up @@ -113,7 +113,8 @@ before the official cask autobump lands. Install either `openlogi` or

### Linux

Download the `.deb` or `.rpm` from the [latest release](https://github.com/AprilNEA/OpenLogi/releases/latest):
Download the Linux package for your distro from the
[latest release](https://github.com/AprilNEA/OpenLogi/releases/latest):

```sh
# Debian / Ubuntu
Expand All @@ -123,18 +124,24 @@ sudo dpkg -i openlogi_*.deb
sudo rpm -i openlogi-*.rpm
```

Packages are published for both `x86_64`/`amd64` and `arm64`/`aarch64`.
For other Linux distributions, use the portable release tarball:

The package installs udev rules that grant your user access to
`/dev/hidraw*` and `/dev/uinput` without `sudo`. After installation,
enable the background agent for your user:
```sh
tar -xzf openlogi-*-linux-*.tar.gz
cd openlogi-*-linux-*
sudo packaging/linux/install.sh --prefix=/usr
```

The packages and install script install udev rules that grant your user access
to `/dev/hidraw*` and `/dev/uinput` without `sudo`. After installation, enable
the background agent for your user:

```sh
systemctl --user enable --now openlogi-agent.service
```

See [docs/INSTALL-linux.md](docs/INSTALL-linux.md) for manual / source installs
and distros without systemd.
See [docs/INSTALL-linux.md](docs/INSTALL-linux.md) for source installs,
packaging details, and distros without systemd.

### Windows (preview)

Expand Down
36 changes: 36 additions & 0 deletions crates/openlogi-agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ use tracing_subscriber::EnvFilter;
use crate::server::AgentServer;

fn main() {
if handle_probe_args() {
return;
}

init_tracing();

// Single-instance guard: the agent owns all device I/O, the CGEventTap, and
Expand Down Expand Up @@ -102,6 +106,38 @@ fn main() {
runtime.block_on(run(config));
}

fn handle_probe_args() -> bool {
let mut args = std::env::args().skip(1);
let Some(arg) = args.next() else {
return false;
};
if arg != "--check-uinput" {
return false;
}
if args.next().is_some() {
eprintln!("usage: openlogi-agent --check-uinput");
std::process::exit(2);
}
#[cfg(target_os = "linux")]
{
match std::fs::OpenOptions::new().write(true).open("/dev/uinput") {
Ok(_) => {
println!("uinput OK");
true
}
Err(e) => {
eprintln!("uinput not writable: {e}");
std::process::exit(1);
}
}
}
#[cfg(not(target_os = "linux"))]
{
eprintln!("--check-uinput is only supported on Linux");
std::process::exit(2);
}
}

async fn run(config: Config) {
// Reconcile the agent's launch-at-login autostart and clear the legacy GUI
// LaunchAgent, before `config` moves into the orchestrator.
Expand Down
36 changes: 27 additions & 9 deletions crates/openlogi-cli/src/cmd/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,7 @@ pub async fn run(_args: ListArgs) -> Result<()> {
println!("No Logitech HID++ devices found.");
println!();
println!("Notes:");
println!(" - On macOS, quit Logi Options+ first — both apps fight over HID++ access.");
println!(
" - A Bluetooth-direct mouse (e.g. Lift, Signature) needs Input Monitoring \
permission: System Settings → Privacy & Security → Input Monitoring."
);
println!(
" - hidpp 0.2 only recognises Logi Bolt receivers (PID 0xC548); other \
receivers (Unifying) aren't surfaced yet."
);
print_no_devices_notes();
std::process::exit(2);
}

Expand All @@ -36,6 +28,32 @@ pub async fn run(_args: ListArgs) -> Result<()> {
Ok(())
}

#[cfg(target_os = "linux")]
fn print_no_devices_notes() {
println!(
" - Plug in a Logi Bolt or Unifying receiver, or pair/connect a Bluetooth Logitech device."
);
println!(
" - Install the Linux udev rules from packaging/linux/udev/70-openlogi.rules \
so your user can access /dev/hidraw* and /dev/uinput."
);
println!(" - Quit Solaar or any other Logitech manager before starting OpenLogi.");
}

#[cfg(target_os = "macos")]
fn print_no_devices_notes() {
println!(" - Quit Logi Options+ first — both apps fight over HID++ access.");
println!(
" - A Bluetooth-direct mouse (e.g. Lift, Signature) needs Input Monitoring \
permission: System Settings → Privacy & Security → Input Monitoring."
);
}

#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn print_no_devices_notes() {
println!(" - Connect a supported Logitech HID++ receiver or direct device.");
}

fn print_inventory(inv: &DeviceInventory) {
let uid = inv.receiver.unique_id.as_deref().unwrap_or("—");
println!(
Expand Down
Loading