Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .github/workflows/packaging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ jobs:
set -euo pipefail
bash -n \
packaging/linux/install.sh \
packaging/linux/install-from-source.sh \
packaging/linux/uninstall.sh \
packaging/linux/resources/launcher.sh \
packaging/arch/axidev-osk.install \
packaging/deb/debian/rules \
packaging/deb/debian/postinst \
packaging/deb/debian/postrm
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
packaging/linux/install-from-source.sh \
packaging/linux/uninstall.sh \
packaging/linux/resources/launcher.sh \
packaging/arch/axidev-osk.install \
packaging/deb/debian/rules \
packaging/deb/debian/postinst \
packaging/deb/debian/postrm
Expand Down
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ sudo pacman -S --needed qt6-wayland layer-shell-qt pyside6 \
curl -L https://raw.githubusercontent.com/axide-dev/axidev-osk/main/packaging/linux/install.sh | sudo bash
```

After installing, log out and back in once so the new `input` group membership takes effect, then run `axidev-osk`.
After installing, log out and back in once so the new `uinput` group membership takes effect, then run `axidev-osk`.

To uninstall:

Expand Down Expand Up @@ -66,6 +66,32 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& ([scriptblock]::Cr

A production-signed MSI remains planned for public distribution.

## Command Line

Running `axidev-osk` without arguments starts the keyboard exactly as before. Passing any argument selects the headless command line instead, without importing Qt or starting the application runtime.

Linux permission commands default to the invoking user. Administrators can prepare another local account with `--user NAME`.

```bash
axidev-osk linux setup-permissions
axidev-osk linux status-permissions
axidev-osk linux remove-permissions
```

Permission setup creates a dedicated `uinput` group, installs the udev rule, and adds the selected user. Log out and back in after a new membership is added. Permission removal disables the Axidev OSK rule but preserves the shared group and its memberships.

Linux autostart uses the desktop-session XDG autostart standard. It starts the current visible keyboard after the selected user logs into a graphical desktop.

```bash
axidev-osk linux setup-autostart
axidev-osk linux status-autostart
axidev-osk linux remove-autostart
```

These autostart commands support regular user desktop sessions. Login-screen greeters run under display-manager-specific accounts and environments, so GDM, SDDM, and other greeters require separate adapters that are not implemented yet.

On Windows, replacing `osk.exe` or another protected system binary is not supported. Future Windows integration should use supported accessibility registration and startup mechanisms instead of modifying Windows system files.

## Wayland Notes

The overlay works best on compositors that support the layer-shell protocol, such as:
Expand Down
6 changes: 3 additions & 3 deletions packaging/MANUAL_INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ PYTHONPATH=src python -m axidev_osk

### One-time uinput permissions

Linux requires permission to emit keystrokes through `/dev/uinput`. Run the helper script bundled with the vendored input backend:
Linux requires permission to emit keystrokes through `/dev/uinput`. Run the installed Axidev OSK setup command:

```bash
bash ./vendor/axidev-io-python/src/axidev_io/vendor/axidev-io/scripts/setup_uinput_permissions.sh
axidev-osk linux setup-permissions
```

This installs a udev rule, ensures the `input` group exists, and adds your user to it. Log out and back in for the new group membership to take effect.
This installs a udev rule, ensures the dedicated `uinput` group exists, and adds your user to it. Log out and back in for the new group membership to take effect.

## Windows

Expand Down
8 changes: 4 additions & 4 deletions packaging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ The install path is intentionally simple:

- **Everything the application owns lives under `/opt/axidev-osk/`.** Wiping that directory removes the program. There is no per-user data managed by the installer at this stage.
- **The launcher in `/usr/local/bin/`** is a one-line shim that exec's `/opt/axidev-osk/.venv/bin/python -m axidev_osk`. The application performs its own environment discovery (Wayland vs X11, layer-shell plugin location, etc.); the shim does not pass extra environment variables.
- **The udev rule** allows non-root processes to write to `/dev/uinput` when they belong to the `input` group. The installer adds the invoking user to the `input` group when it is missing.
- **The udev rule** allows non-root processes to write to `/dev/uinput` when they belong to the dedicated `uinput` group. The installer adds the invoking user to that group when it is missing.

PySide6, Qt6, layer-shell-qt, libinput, libudev, and libxkbcommon are **not** bundled with the install. They are loaded from the system at runtime so that the Qt and layer-shell-qt versions match (a mismatch causes hard-to-diagnose ABI segfaults). The Linux dependency list lives in `linux/README.md` and in the top-level `README.md` install commands.

Expand All @@ -66,14 +66,14 @@ If anything fails before the swap, the previous install remains untouched and `a
Re-running `install.sh` on a system that already has the correct configuration touches only what needs to change:

- The `/opt/axidev-osk/` swap always happens (the new release replaces the old).
- The `input` group is created only if missing.
- The `uinput` group is created only if missing.
- The udev rule file is rewritten only if its contents differ from the expected value.
- `udevadm` is reloaded only when the rule actually changed.
- The user is added to the `input` group only if not already a member.
- The user is added to the `uinput` group only if not already a member.

### Uninstall

`/opt/axidev-osk/packaging/linux/uninstall.sh` removes `/opt/axidev-osk/`, `/usr/local/bin/axidev-osk`, and the udev rule. It does **not** touch the user's `input` group membership, since that group is shared with other parts of the system.
`/opt/axidev-osk/packaging/linux/uninstall.sh` removes `/opt/axidev-osk/`, `/usr/local/bin/axidev-osk`, and the udev rule. It does **not** touch the user's `uinput` group membership, since that group can be shared with other software.

## Windows

Expand Down
2 changes: 1 addition & 1 deletion packaging/arch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ The package depends on system `pyside6`, `qt6-wayland`, and `layer-shell-qt`. Th

## uinput Permissions

The install hook reloads udev and reminds the user to join the `input` group. It does not add a user automatically because package hooks do not reliably know which desktop user should receive input-device permissions.
The install hook reloads udev and tells the user to run `axidev-osk linux setup-permissions`. It does not add a user automatically because package hooks do not reliably know which desktop user should receive input-device permissions.
9 changes: 6 additions & 3 deletions packaging/arch/axidev-osk.install
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
post_install() {
getent group input >/dev/null || groupadd -r input
getent group uinput >/dev/null || groupadd -r uinput
udevadm control --reload-rules >/dev/null 2>&1 || true
udevadm trigger /dev/uinput >/dev/null 2>&1 || true

cat <<'EOF'
Axidev OSK installed.

Add your user to the input group and log out/back in before using key emission:
sudo usermod -aG input "$USER"
Configure your user, then log out and back in before using key emission:
axidev-osk linux setup-permissions
EOF
}

Expand All @@ -16,6 +16,9 @@ post_upgrade() {
}

post_remove() {
if [ "$(readlink /etc/udev/rules.d/70-axidev-io-uinput.rules 2>/dev/null || true)" = "/dev/null" ]; then
rm -f /etc/udev/rules.d/70-axidev-io-uinput.rules
fi
udevadm control --reload-rules >/dev/null 2>&1 || true
udevadm trigger /dev/uinput >/dev/null 2>&1 || true
}
2 changes: 1 addition & 1 deletion packaging/deb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ The package depends on system `python3-pyside6`, `qt6-wayland`, and `layer-shell

## uinput Permissions

The post-install script ensures the `input` group exists, reloads udev, and reminds the user to join the `input` group. It does not choose a desktop user automatically because package maintainer scripts run as root without reliable user context.
The post-install script ensures the `uinput` group exists, reloads udev, and tells the user to run `axidev-osk linux setup-permissions`. It does not choose a desktop user automatically because package maintainer scripts run as root without reliable user context.
6 changes: 3 additions & 3 deletions packaging/deb/debian/postinst
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
set -e

if [ "$1" = "configure" ]; then
getent group input >/dev/null || groupadd -r input
getent group uinput >/dev/null || groupadd -r uinput
udevadm control --reload-rules >/dev/null 2>&1 || true
udevadm trigger /dev/uinput >/dev/null 2>&1 || true

cat <<'EOF'
Axidev OSK installed.

Add your user to the input group and log out/back in before using key emission:
sudo usermod -aG input "$USER"
Configure your user, then log out and back in before using key emission:
axidev-osk linux setup-permissions
EOF
fi

Expand Down
3 changes: 3 additions & 0 deletions packaging/deb/debian/postrm
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
set -e

if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then
if [ "$(readlink /etc/udev/rules.d/70-axidev-io-uinput.rules 2>/dev/null || true)" = "/dev/null" ]; then
rm -f /etc/udev/rules.d/70-axidev-io-uinput.rules
fi
udevadm control --reload-rules >/dev/null 2>&1 || true
udevadm trigger /dev/uinput >/dev/null 2>&1 || true
fi
Expand Down
13 changes: 7 additions & 6 deletions packaging/linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ For an architecture overview, see [`../README.md`](../README.md).
/opt/axidev-osk.new -> /opt/axidev-osk, then
rm -rf /opt/axidev-osk.old.
8. Install /usr/local/bin/axidev-osk from the bundled launcher.
9. Ensure 'input' group exists.
9. Ensure 'uinput' group exists.
10. Ensure /etc/udev/rules.d/70-axidev-io-uinput.rules has the expected
contents; reload udev only if the rule actually changed.
11. Verify /dev/uinput is owned by group 'input' with mode 0660; modprobe
11. Verify /dev/uinput is owned by group 'uinput' with mode 0660; modprobe
if needed; warn if a reboot may be required.
12. Add the invoking user to the 'input' group if not already a member.
12. Add the invoking user to the 'uinput' group if not already a member.
```

## Required system packages
Expand Down Expand Up @@ -82,8 +82,9 @@ The installer does not create or modify:
**`/dev/uinput` still wrong after install.** The kernel module may not be loaded (`modprobe uinput`), or the udev rule may need a reboot to take effect on some setups. The launcher will fail with a permission error in that case. Confirm with:

```bash
stat -c '%a %G' /dev/uinput # expected: 660 input
groups # should include 'input' after logout/login
stat -c '%a %G' /dev/uinput # expected: 660 uinput
groups # should include 'uinput' after logout/login
axidev-osk linux status-permissions
```

## What `uninstall.sh` does
Expand All @@ -96,7 +97,7 @@ groups # should include 'input' after logout/login
5. udevadm control --reload-rules
```

The script does not remove the user from the `input` group. That group exists on most systems independently of this application and may be used by other software.
The script does not remove the user from the `uinput` group. Other virtual-input software may use the same least-privilege group.

## Re-running `install.sh`

Expand Down
32 changes: 18 additions & 14 deletions packaging/linux/install-from-source.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ readonly STAGING_DIR="/opt/axidev-osk.new"
readonly BACKUP_DIR="/opt/axidev-osk.old"
readonly LAUNCHER_PATH="/usr/local/bin/axidev-osk"
readonly UDEV_RULE_PATH="/etc/udev/rules.d/70-axidev-io-uinput.rules"
readonly UDEV_RULE_CONTENTS='KERNEL=="uinput", MODE="0660", GROUP="input", OPTIONS+="static_node=uinput"'
readonly UDEV_RULE_CONTENTS='KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput"'

log() { printf '[install-from-source] %s\n' "$*"; }
warn() { printf '[install-from-source] WARNING: %s\n' "$*" >&2; }
Expand Down Expand Up @@ -114,16 +114,20 @@ install_launcher() {
log "Installed launcher at ${LAUNCHER_PATH}"
}

ensure_input_group() {
if getent group input >/dev/null 2>&1; then
ensure_uinput_group() {
if getent group uinput >/dev/null 2>&1; then
return 0
fi
log "Creating 'input' group..."
groupadd input
log "Creating 'uinput' group..."
groupadd --system uinput
}

ensure_udev_rule() {
local current=""
if [ -L "${UDEV_RULE_PATH}" ]; then
log "Removing existing udev rule mask..."
rm -f "${UDEV_RULE_PATH}"
fi
if [ -f "${UDEV_RULE_PATH}" ]; then
current="$(cat "${UDEV_RULE_PATH}")"
fi
Expand All @@ -149,7 +153,7 @@ uinput_state_correct() {
[ -e /dev/uinput ] || return 1
local state
state="$(stat -c '%a %G' /dev/uinput 2>/dev/null || true)"
[ "${state}" = "660 input" ]
[ "${state}" = "660 uinput" ]
}

ensure_uinput_node() {
Expand All @@ -167,25 +171,25 @@ ensure_uinput_node() {
return 0
fi

warn "/dev/uinput is not yet owned by group 'input' with mode 0660."
warn "/dev/uinput is not yet owned by group 'uinput' with mode 0660."
warn "A reboot may be required for the new udev rule to take effect."
}

ensure_user_in_input_group() {
local target_user="$1"
if [ -z "${target_user}" ]; then
warn "Cannot determine the target user; skipping 'input' group membership step."
warn "Add yourself to the 'input' group manually with: sudo usermod -aG input <username>"
warn "Cannot determine the target user; skipping 'uinput' group membership step."
warn "Run: axidev-osk linux setup-permissions --user <username>"
return 0
fi

if id -nG "${target_user}" 2>/dev/null | tr ' ' '\n' | grep -qx input; then
log "User '${target_user}' is already in the 'input' group."
if id -nG "${target_user}" 2>/dev/null | tr ' ' '\n' | grep -qx uinput; then
log "User '${target_user}' is already in the 'uinput' group."
return 0
fi

log "Adding user '${target_user}' to the 'input' group..."
usermod -aG input "${target_user}"
log "Adding user '${target_user}' to the 'uinput' group..."
usermod -aG uinput "${target_user}"
log "Log out and back in for the new group membership to take effect."
}

Expand All @@ -209,7 +213,7 @@ main() {
install_launcher


ensure_input_group
ensure_uinput_group
if ensure_udev_rule; then
reload_udev
fi
Expand Down
34 changes: 19 additions & 15 deletions packaging/linux/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ readonly STAGING_DIR="/opt/axidev-osk.new"
readonly BACKUP_DIR="/opt/axidev-osk.old"
readonly LAUNCHER_PATH="/usr/local/bin/axidev-osk"
readonly UDEV_RULE_PATH="/etc/udev/rules.d/70-axidev-io-uinput.rules"
readonly UDEV_RULE_CONTENTS='KERNEL=="uinput", MODE="0660", GROUP="input", OPTIONS+="static_node=uinput"'
readonly UDEV_RULE_CONTENTS='KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput"'

readonly RELEASE_BASE_URL="https://github.com/axide-dev/axidev-osk/releases/latest/download"
readonly BUNDLE_NAME="axidev-osk-linux-x86_64.tar.gz"
Expand Down Expand Up @@ -74,7 +74,7 @@ require_commands() {

resolve_target_user() {
# When invoked through sudo, $SUDO_USER is the original user we want
# to add to the input group. When invoked directly as root we have no
# to add to the uinput group. When invoked directly as root we have no
# safe way to guess, so the group-add step is skipped with a warning.
if [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then
printf '%s' "${SUDO_USER}"
Expand Down Expand Up @@ -159,16 +159,20 @@ install_launcher() {
# touch nothing in this section.
# ---------------------------------------------------------------------------

ensure_input_group() {
if getent group input >/dev/null 2>&1; then
ensure_uinput_group() {
if getent group uinput >/dev/null 2>&1; then
return 0
fi
log "Creating 'input' group..."
groupadd input
log "Creating 'uinput' group..."
groupadd --system uinput
}

ensure_udev_rule() {
local current=""
if [ -L "${UDEV_RULE_PATH}" ]; then
log "Removing existing udev rule mask..."
rm -f "${UDEV_RULE_PATH}"
fi
if [ -f "${UDEV_RULE_PATH}" ]; then
current="$(cat "${UDEV_RULE_PATH}")"
fi
Expand All @@ -194,7 +198,7 @@ uinput_state_correct() {
[ -e /dev/uinput ] || return 1
local state
state="$(stat -c '%a %G' /dev/uinput 2>/dev/null || true)"
[ "${state}" = "660 input" ]
[ "${state}" = "660 uinput" ]
}

ensure_uinput_node() {
Expand All @@ -212,25 +216,25 @@ ensure_uinput_node() {
return 0
fi

warn "/dev/uinput is not yet owned by group 'input' with mode 0660."
warn "/dev/uinput is not yet owned by group 'uinput' with mode 0660."
warn "A reboot may be required for the new udev rule to take effect."
}

ensure_user_in_input_group() {
local target_user="$1"
if [ -z "${target_user}" ]; then
warn "Cannot determine the target user; skipping 'input' group membership step."
warn "Add yourself to the 'input' group manually with: sudo usermod -aG input <username>"
warn "Cannot determine the target user; skipping 'uinput' group membership step."
warn "Run: axidev-osk linux setup-permissions --user <username>"
return 0
fi

if id -nG "${target_user}" 2>/dev/null | tr ' ' '\n' | grep -qx input; then
log "User '${target_user}' is already in the 'input' group."
if id -nG "${target_user}" 2>/dev/null | tr ' ' '\n' | grep -qx uinput; then
log "User '${target_user}' is already in the 'uinput' group."
return 0
fi

log "Adding user '${target_user}' to the 'input' group..."
usermod -aG input "${target_user}"
log "Adding user '${target_user}' to the 'uinput' group..."
usermod -aG uinput "${target_user}"
log "Log out and back in for the new group membership to take effect."
}

Expand Down Expand Up @@ -263,7 +267,7 @@ main() {

install_launcher

ensure_input_group
ensure_uinput_group
if ensure_udev_rule; then
reload_udev
fi
Expand Down
2 changes: 1 addition & 1 deletion packaging/linux/resources/70-axidev-io-uinput.rules
Original file line number Diff line number Diff line change
@@ -1 +1 @@
KERNEL=="uinput", MODE="0660", GROUP="input", OPTIONS+="static_node=uinput"
KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput"
Loading