diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa178d4..713413d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,8 @@ name: Release # the GitHub Release for that tag: # - cyclone2-linux@vdemonchy.github.io.shell-extension.zip (GNOME extension) # - cyclone2-applet--x86_64-linux.tar.gz (COSMIC applet + daemon) -# - cyclone2--x86_64-linux (daemon, shared by both) +# - cyclone2-plasmoid-.plasmoid (KDE Plasma 6 plasmoid) +# - cyclone2--x86_64-linux (daemon, shared by all) # # GitHub can't filter tag pushes by branch, so the `gate` job checks that the # tagged commit is contained in `main`; the `release` job runs only if so. A @@ -108,6 +109,15 @@ jobs: tar -C dist -czf "dist/${stage}.tar.gz" "${stage}" rm -rf "dist/${stage}" + # ---- KDE Plasma plasmoid ------------------------------------------- + # The plasmoid is plain QML, so "building" it is zipping the package + # contents with metadata.json at the archive root — the layout + # `kpackagetool6 --type Plasma/Applet --install ` expects. + - name: Package KDE plasmoid + run: | + ( cd plasmoid/package \ + && zip -r -X "${GITHUB_WORKSPACE}/dist/cyclone2-plasmoid-${VERSION}.plasmoid" . ) + # ---- GNOME extension ---------------------------------------------- # Compile the gschema and zip the extension contents at the archive root, # producing a package installable with `gnome-extensions install `. @@ -127,6 +137,7 @@ jobs: files=( "dist/${EXT_UUID}.shell-extension.zip" "dist/cyclone2-applet-${VERSION}-x86_64-linux.tar.gz" + "dist/cyclone2-plasmoid-${VERSION}.plasmoid" "dist/cyclone2-${VERSION}-x86_64-linux" ) if gh release view "${VERSION}" >/dev/null 2>&1; then diff --git a/INSTALL.md b/INSTALL.md index a3cb171..96929bf 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -122,16 +122,29 @@ frontend-specific targets.) If you can't (or don't want to) build from source, every [GitHub Release](https://github.com/vdemonchy/cyclone2-linux/releases) attaches pre-built -**x86_64 Linux** artefacts you can install by hand. On another architecture, -build from source with `make install` above. +**x86_64 Linux** artefacts. On another architecture, build from source with +`make install` above. -Each release attaches three files (`` is the version, e.g. `v1.0.0`): +The fastest path is the install script, which downloads the latest release, +installs the core, detects your desktop, and installs the matching frontend +(`sudo` is prompted once, for the udev rule): + +```bash +curl -fsSL https://raw.githubusercontent.com/vdemonchy/cyclone2-linux/main/scripts/install.sh | sh +``` + +Pin a release with `VERSION=vX.Y.Z` and/or force a frontend with +`FRONTEND=gnome|cosmic|kde|none` (environment variables). The rest of this +section installs the same artefacts by hand. + +Each release attaches four files (`` is the version, e.g. `v1.0.0`): | Artefact | What it is | For | |---|---|---| -| `cyclone2--x86_64-linux` | the daemon binary (Go, static) | **core** — both desktops | +| `cyclone2--x86_64-linux` | the daemon binary (Go, static) | **core** — every desktop | | `cyclone2-linux@vdemonchy.github.io.shell-extension.zip` | the GNOME Shell extension | GNOME frontend | | `cyclone2-applet--x86_64-linux.tar.gz` | the COSMIC applet + a bundled daemon copy + `INSTALL.txt` | COSMIC frontend | +| `cyclone2-plasmoid-.plasmoid` | the KDE Plasma 6 plasmoid (kpackage zip) | KDE frontend | Set the version once so the commands copy/paste cleanly: @@ -210,10 +223,20 @@ update-desktop-database ~/.local/share/applications 2>/dev/null || true Then [finish the frontend](#cosmic) — add *Cyclone 2* to your panel. -### KDE Plasma (manual) +### KDE Plasma frontend (release .plasmoid) + +The `.plasmoid` artefact is a kpackage zip that `kpackagetool6` installs +directly: + +```bash +curl -L -o /tmp/cyclone2.plasmoid \ + "https://github.com/vdemonchy/cyclone2-linux/releases/download/${VERSION}/cyclone2-plasmoid-${VERSION}.plasmoid" +kpackagetool6 --type Plasma/Applet --upgrade /tmp/cyclone2.plasmoid \ + || kpackagetool6 --type Plasma/Applet --install /tmp/cyclone2.plasmoid +``` -The plasmoid is plain QML — no release artefact needed; install it straight from -the repo: +The plasmoid is plain QML, so from a clone it also installs straight from the +repo — no artefact needed: ```bash kpackagetool6 --type Plasma/Applet --upgrade plasmoid/package diff --git a/README.md b/README.md index 98fa5b7..cb7fe9d 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,33 @@ DS4 battery level (the dongle has no usable source). It's cosmetic; ignore it. ## Install +### Quick install (pre-built, no toolchain) + +One command downloads the latest [release](https://github.com/vdemonchy/cyclone2-linux/releases) +artefacts and installs everything — the core (daemon + udev rule + systemd +`--user` service, x86_64 only) plus the frontend matching your desktop +(`$XDG_CURRENT_DESKTOP`: GNOME extension, COSMIC applet, or KDE Plasma plasmoid): + +```bash +curl -fsSL https://raw.githubusercontent.com/vdemonchy/cyclone2-linux/main/scripts/install.sh | sh +``` + +`sudo` is prompted once, for the udev rule; everything else lands in your user +prefix (`~/.local`, `~/.config`). Pin a version or force a frontend with +environment variables: + +```bash +VERSION=v1.2.0 FRONTEND=kde \ + sh -c "$(curl -fsSL https://raw.githubusercontent.com/vdemonchy/cyclone2-linux/main/scripts/install.sh)" +``` + +(`FRONTEND=gnome|cosmic|kde|none` — `none` installs the core only.) Prefer to +read before you pipe? The script is +[`scripts/install.sh`](scripts/install.sh); the same artefacts also install by +hand — see [INSTALL.md](INSTALL.md). + +### From source (`make install`) + One command does it all — from a clone, `make install` builds the daemon, installs the **core** (udev rule + systemd `--user` service), **detects your desktop**, and installs the matching **frontend** (GNOME extension, COSMIC @@ -95,9 +122,11 @@ separated under the hood — `install-gnome` never touches the others, and vice-versa. Needs **Go 1.24+** always, plus **Rust stable ≥ 1.93** + libcosmic deps for the -COSMIC applet. No toolchain? Pre-built **release artefacts** install by hand — -see **[INSTALL.md](INSTALL.md)**, which also covers verification, uninstall, and -troubleshooting. `make help` lists every target. +COSMIC applet. No toolchain? Use the +[quick install script](#quick-install-pre-built-no-toolchain) above, or install +the pre-built **release artefacts** by hand — see **[INSTALL.md](INSTALL.md)**, +which also covers verification, uninstall, and troubleshooting. `make help` +lists every target. Each frontend then needs one manual step the desktop can't do for you: diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..ef06a6d --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,187 @@ +#!/bin/sh +# install.sh — install cyclone2-linux from the pre-built GitHub release artefacts. +# +# No build toolchain needed: downloads the latest release (or VERSION=vX.Y.Z), +# installs the core (daemon + udev rule + systemd --user service), detects your +# desktop from $XDG_CURRENT_DESKTOP, and installs the matching frontend +# (GNOME extension, COSMIC applet, or KDE Plasma plasmoid). +# +# Usage: +# curl -fsSL https://raw.githubusercontent.com/vdemonchy/cyclone2-linux/main/scripts/install.sh | sh +# +# Environment overrides: +# VERSION=v1.2.0 install a specific release tag (default: latest) +# FRONTEND=gnome force the frontend: gnome | cosmic | kde | none +# +# The udev rule is the only step that needs root (sudo); everything else goes +# to your user prefix (~/.local, ~/.config). Mirrors `make install` from a clone. +set -eu + +REPO="vdemonchy/cyclone2-linux" +EXT_UUID="cyclone2-linux@vdemonchy.github.io" +PLASMOID_ID="io.github.vdemonchy.cyclone2" + +VERSION="${VERSION:-}" +FRONTEND="${FRONTEND:-}" + +BINDIR="$HOME/.local/bin" +DESKTOPDIR="$HOME/.local/share/applications" +SYSTEMD_USER="$HOME/.config/systemd/user" +UDEV_RULE="60-gamesir-cyclone2.rules" +SERVICE="cyclone2-linux.service" + +info() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33mwarning:\033[0m %s\n' "$*" >&2; } +die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } + +command -v curl >/dev/null 2>&1 || die "curl is required" + +tmpdir=$(mktemp -d) +trap 'rm -rf "$tmpdir"' EXIT INT TERM + +fetch() { # fetch + curl -fsSL --retry 3 -o "$2" "$1" || die "download failed: $1" +} + +# ---- resolve the release tag ------------------------------------------------ +if [ -z "$VERSION" ]; then + info "Resolving the latest release..." + # The API redirect target of releases/latest carries the tag; parse the + # JSON tag_name with sed so the script doesn't depend on jq. + VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \ + | sed -n 's/^[[:space:]]*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1) + [ -n "$VERSION" ] || die "could not determine the latest release tag (rate-limited?). Set VERSION=vX.Y.Z and retry." +fi +info "Installing cyclone2-linux ${VERSION}" + +RELEASE_URL="https://github.com/${REPO}/releases/download/${VERSION}" +RAW_URL="https://raw.githubusercontent.com/${REPO}/${VERSION}" + +# ---- arch check (pre-built binaries are x86_64 only) ------------------------ +arch=$(uname -m) +[ "$arch" = "x86_64" ] || die "pre-built artefacts are x86_64 only (this machine: ${arch}). Build from source instead: git clone https://github.com/${REPO} && cd cyclone2-linux && make install" + +# ---- pick the frontend ------------------------------------------------------- +if [ -z "$FRONTEND" ]; then + de=$(printf '%s' "${XDG_CURRENT_DESKTOP:-}" | tr '[:upper:]' '[:lower:]') + case ":${de}:" in + *cosmic*) FRONTEND=cosmic ;; + *gnome*) FRONTEND=gnome ;; + *kde*|*plasma*) FRONTEND=kde ;; + *) FRONTEND=unknown ;; + esac +fi +case "$FRONTEND" in + gnome|cosmic|kde|none|unknown) ;; + *) die "unknown FRONTEND='${FRONTEND}' (expected gnome | cosmic | kde | none)" ;; +esac + +# ---- core: daemon binary ----------------------------------------------------- +info "Installing the daemon to ${BINDIR}/cyclone2" +fetch "${RELEASE_URL}/cyclone2-${VERSION}-x86_64-linux" "$tmpdir/cyclone2" +mkdir -p "$BINDIR" +install -m0755 "$tmpdir/cyclone2" "$BINDIR/cyclone2" +case ":${PATH}:" in + *:"$BINDIR":*) ;; + *) warn "${BINDIR} is not on your PATH — add it to use the 'cyclone2' CLI" ;; +esac + +# ---- core: udev rule (the one sudo step) ------------------------------------- +info "Installing the udev rule (needs sudo) for root-free HID access" +fetch "${RAW_URL}/packaging/udev/${UDEV_RULE}" "$tmpdir/${UDEV_RULE}" +sudo install -m0644 "$tmpdir/${UDEV_RULE}" "/etc/udev/rules.d/${UDEV_RULE}" +sudo udevadm control --reload-rules +sudo udevadm trigger --subsystem-match=hidraw + +# ---- core: systemd --user service -------------------------------------------- +info "Enabling the systemd --user service" +fetch "${RAW_URL}/packaging/systemd/${SERVICE}" "$tmpdir/${SERVICE}" +mkdir -p "$SYSTEMD_USER" +install -m0644 "$tmpdir/${SERVICE}" "$SYSTEMD_USER/${SERVICE}" +systemctl --user daemon-reload +systemctl --user enable --now "$SERVICE" + +# ---- frontend ----------------------------------------------------------------- +install_gnome() { + info "Installing the GNOME Shell extension" + command -v gnome-extensions >/dev/null 2>&1 \ + || die "gnome-extensions CLI not found (is this really GNOME?)" + fetch "${RELEASE_URL}/${EXT_UUID}.shell-extension.zip" "$tmpdir/extension.zip" + gnome-extensions install --force "$tmpdir/extension.zip" + cat </dev/null || true + cat <<'EOF' + +COSMIC applet installed. Add it to your panel: + Settings -> Desktop -> Panel (or Dock) -> Configure applets -> add "Cyclone 2" +EOF +} + +install_kde() { + info "Installing the KDE Plasma 6 plasmoid" + plasmoid="$tmpdir/cyclone2.plasmoid" + # Releases older than the plasmoid artefact don't attach it — fall back to + # the source tarball at the same tag, which has the identical package. + if ! curl -fsSL --retry 3 -o "$plasmoid" \ + "${RELEASE_URL}/cyclone2-plasmoid-${VERSION}.plasmoid" 2>/dev/null; then + warn "release ${VERSION} has no plasmoid artefact; using the source tree at that tag" + fetch "https://github.com/${REPO}/archive/refs/tags/${VERSION}.tar.gz" "$tmpdir/src.tar.gz" + tar -xzf "$tmpdir/src.tar.gz" -C "$tmpdir" + pkg="$tmpdir/cyclone2-linux-${VERSION#v}/plasmoid/package" + [ -d "$pkg" ] || die "release ${VERSION} predates the KDE plasmoid — install a newer release (or the core with FRONTEND=none)" + fi + if command -v kpackagetool6 >/dev/null 2>&1; then + src="${pkg:-$plasmoid}" + kpackagetool6 --type Plasma/Applet --upgrade "$src" \ + || kpackagetool6 --type Plasma/Applet --install "$src" + else + warn "kpackagetool6 not found — copying the package manually" + dest="$HOME/.local/share/plasma/plasmoids/${PLASMOID_ID}" + mkdir -p "$dest" + if [ -n "${pkg:-}" ]; then + cp -r "$pkg/." "$dest/" + else + command -v unzip >/dev/null 2>&1 || die "need unzip (or kpackagetool6) to install the plasmoid" + unzip -oq "$plasmoid" -d "$dest" + fi + fi + cat <<'EOF' + +KDE plasmoid installed. Add it: right-click your panel -> Add Widgets -> Cyclone 2 +(If it doesn't appear, log out and back in so Plasma rescans widgets.) +EOF +} + +case "$FRONTEND" in + gnome) install_gnome ;; + cosmic) install_cosmic ;; + kde) install_kde ;; + none) + info "FRONTEND=none — core installed, no frontend" ;; + unknown) + warn "no supported desktop detected (XDG_CURRENT_DESKTOP='${XDG_CURRENT_DESKTOP:-}')" + cat <<'EOF' +Core installed. Pick a frontend explicitly by re-running with FRONTEND=: + FRONTEND=gnome sh install.sh # GNOME Shell + FRONTEND=cosmic sh install.sh # COSMIC desktop + FRONTEND=kde sh install.sh # KDE Plasma +EOF + ;; +esac + +info "Done. Verify with: cyclone2 status (controller in XInput/Switch mode)"