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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
Expand All @@ -23,7 +23,7 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test
183 changes: 183 additions & 0 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
name: Package

on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
workflow_dispatch:

env:
CARGO_TERM_COLOR: always

jobs:
rpm:
name: Build RPM
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2

- name: Install cargo-generate-rpm
run: cargo install cargo-generate-rpm --locked

- name: Compute version metadata
id: ver
# On tag pushes (refs/tags/v*) emit a clean release (`release = "1"`)
# and validate that the tag matches Cargo.toml; on all other triggers
# emit a dev-build release tag with run number + short SHA.
run: |
set -euo pipefail
version=$(grep -E '^version *= *' Cargo.toml | head -n1 | cut -d'"' -f2)
short_sha="${GITHUB_SHA::7}"

if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
tag_version="${GITHUB_REF_NAME#v}"
if [ "$tag_version" != "$version" ]; then
echo "::error::Tag ${GITHUB_REF_NAME} does not match Cargo.toml version ${version}" >&2
exit 1
fi
rpm_release="1"
artifact_name="presence-switch-rpm-${GITHUB_REF_NAME}"
else
rpm_release="0.dev.${GITHUB_RUN_NUMBER}.git${short_sha}"
artifact_name="presence-switch-rpm-${short_sha}"
fi

echo "version=$version" >> "$GITHUB_OUTPUT"
echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT"
echo "rpm_release=$rpm_release" >> "$GITHUB_OUTPUT"
echo "artifact_name=$artifact_name" >> "$GITHUB_OUTPUT"

- name: Compile release binary
run: cargo build --release --locked

- name: Generate RPM
run: |
mkdir -p target/generate-rpm
cargo generate-rpm \
--output "target/generate-rpm/presence-switch-${{ steps.ver.outputs.version }}-${{ steps.ver.outputs.rpm_release }}.x86_64.rpm" \
-s 'release = "${{ steps.ver.outputs.rpm_release }}"'

- name: Upload RPM artifact
uses: actions/upload-artifact@v7
with:
name: ${{ steps.ver.outputs.artifact_name }}
path: target/generate-rpm/*.rpm
if-no-files-found: error

msi:
name: Build MSI (Linux cross-compile)
runs-on: ubuntu-latest

env:
WIN_TARGET: x86_64-pc-windows-gnu

steps:
- uses: actions/checkout@v6

- name: Install mingw-w64 and wixl
# Debian/Ubuntu split wixl out of msitools — install both.
run: |
sudo apt-get update
sudo apt-get install -y gcc-mingw-w64-x86-64 msitools wixl

- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-gnu

- uses: Swatinem/rust-cache@v2
with:
key: windows-gnu

- name: Compute version metadata
id: ver
run: |
set -euo pipefail
version=$(grep -E '^version *= *' Cargo.toml | head -n1 | cut -d'"' -f2)
short_sha="${GITHUB_SHA::7}"

if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
tag_version="${GITHUB_REF_NAME#v}"
if [ "$tag_version" != "$version" ]; then
echo "::error::Tag ${GITHUB_REF_NAME} does not match Cargo.toml version ${version}" >&2
exit 1
fi
msi_filename="presence-switch-${version}.msi"
artifact_name="presence-switch-msi-${GITHUB_REF_NAME}"
else
msi_filename="presence-switch-${version}-${short_sha}.msi"
artifact_name="presence-switch-msi-${short_sha}"
fi

echo "version=$version" >> "$GITHUB_OUTPUT"
echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT"
echo "msi_filename=$msi_filename" >> "$GITHUB_OUTPUT"
echo "artifact_name=$artifact_name" >> "$GITHUB_OUTPUT"

- name: Cross-compile Windows binary
env:
# Rust auto-detects the mingw linker by triple, but be explicit so
# this doesn't silently regress if the apt package layout changes.
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc
run: cargo build --release --locked --target "${WIN_TARGET}"

- name: Build MSI with wixl
run: |
mkdir -p target/wix
wixl \
--arch x64 \
-D "Version=${{ steps.ver.outputs.version }}" \
-D "ExePath=target/${WIN_TARGET}/release/presence-switch.exe" \
--output "target/wix/${{ steps.ver.outputs.msi_filename }}" \
wix/main.wxs

- name: Upload MSI artifact
uses: actions/upload-artifact@v7
with:
name: ${{ steps.ver.outputs.artifact_name }}
path: target/wix/*.msi
if-no-files-found: error

release:
name: Publish GitHub Release
needs: [rpm, msi]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest

permissions:
contents: write

steps:
- uses: actions/checkout@v6

- name: Download RPM artifact
uses: actions/download-artifact@v8
with:
name: presence-switch-rpm-${{ github.ref_name }}
path: artifacts/

- name: Download MSI artifact
uses: actions/download-artifact@v8
with:
name: presence-switch-msi-${{ github.ref_name }}
path: artifacts/

- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
ls -la artifacts/
# --generate-notes auto-generates the body from commits since last
# release; --verify-tag fails fast if the tag isn't actually pushed.
gh release create "${GITHUB_REF_NAME}" \
--title "${GITHUB_REF_NAME}" \
--generate-notes \
--verify-tag \
artifacts/*.rpm artifacts/*.msi
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
/vendor
.claude/settings.local.json
16 changes: 16 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
name = "presence-switch"
version = "0.1.0"
edition = "2024"
license = "MIT"
description = "Discord Rich Presence IPC proxy that multiplexes RPC messages across multiple Discord instances"
repository = "https://github.com/kramerc/presence-switch"

[dependencies]
lazy_static = "1.5.0"
Expand All @@ -13,3 +16,16 @@ tokio-util = "0.7.18"
tracing = "0.1.44"
tracing-subscriber = "0.3.23"
windows-sys = "0.61.2"

# RPM packaging via cargo-generate-rpm.
# `name`, `version`, `license`, and `summary` are inherited from [package].
# Override `release` at build time for dev builds:
# cargo generate-rpm -s 'release = "0.dev.<N>.git<SHA>"'
[package.metadata.generate-rpm]
release = "1"
assets = [
{ source = "target/release/presence-switch", dest = "/usr/bin/presence-switch", mode = "755" },
{ source = "packaging/linux/systemd/presence-switch.service", dest = "/usr/lib/systemd/user/presence-switch.service", mode = "644" },
{ source = "LICENSE", dest = "/usr/share/licenses/presence-switch/LICENSE", mode = "644", doc = true },
{ source = "README.md", dest = "/usr/share/doc/presence-switch/README.md", mode = "644", doc = true },
]
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Kramer Campbell

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,35 @@ The IPC binary protocol uses a simple format: 4-byte LE opcode + 4-byte LE lengt
cargo build --release
```

## Installing

Tagged releases publish `.rpm` and `.msi` builds to the [Releases](https://github.com/kramerc/presence-switch/releases) page. For unreleased changes, the [`Package`](.github/workflows/package.yml) workflow also produces dev artifacts on every push to `main` and every PR — download them from the workflow run's Artifacts section.

To build packages locally:

```sh
scripts/package.sh rpm # → target/generate-rpm/presence-switch-*.rpm
scripts/package.sh msi # → target/wix/presence-switch-*.msi (cross-compiled from Linux)
scripts/package.sh all
```

See `scripts/package.sh --help` for the toolchain requirements.

### Linux (any RPM-based distro with systemd)

```sh
sudo dnf install ./presence-switch-*.rpm # Fedora, RHEL, CentOS, Rocky, Alma
sudo zypper install ./presence-switch-*.rpm # openSUSE
systemctl --user daemon-reload
systemctl --user enable --now presence-switch
```

The package installs a per-user systemd unit at `/usr/lib/systemd/user/presence-switch.service`. View logs with `journalctl --user -u presence-switch`. CI builds the RPM against Ubuntu 24.04's glibc (2.39+), so the target distro needs glibc ≥ 2.39 — that covers Fedora 41+, RHEL 10+, recent openSUSE Tumbleweed, and similar.

### Windows

Double-click the `.msi` to install per-user (no admin prompt). The installer adds an entry under `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` so presence-switch launches at every logon — inspect or disable it via *Task Manager → Startup apps*. Uninstall via *Settings → Apps & features*.

## Usage

1. Close Discord or ensure `discord-ipc-0` is not taken
Expand Down Expand Up @@ -76,3 +105,22 @@ src/
├── unix.rs # Unix domain socket connection
└── windows.rs # Named pipe connection
```

## Releasing

To cut a new release:

1. Bump `version` in `Cargo.toml` (and run `cargo update -w` so `Cargo.lock` matches).
2. Commit the bump and merge to `main`.
3. Tag the commit on `main` matching the new version, e.g.:
```sh
git tag v0.2.0
git push origin v0.2.0
```
4. The [`Package`](.github/workflows/package.yml) workflow runs on the tag, validates that the tag matches `Cargo.toml`, builds the `.rpm` and `.msi`, and publishes a GitHub Release with both attached and auto-generated notes from the commits since the previous release.

If the tag version doesn't match `Cargo.toml`'s `version` field, both build jobs fail loudly before doing any work.

## License

[MIT](LICENSE) © Kramer Campbell
15 changes: 15 additions & 0 deletions packaging/linux/systemd/presence-switch.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=Discord Rich Presence IPC proxy
Documentation=https://github.com/kramerc/presence-switch
After=graphical-session.target
PartOf=graphical-session.target

[Service]
Type=simple
ExecStart=/usr/bin/presence-switch
Restart=on-failure
RestartSec=3
NoNewPrivileges=true

[Install]
WantedBy=default.target
Loading