diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 645f203..281e6e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,12 @@ jobs: /tmp/npd-packaging-venv/bin/now-playing --help /tmp/npd-packaging-venv/bin/python -m now_playing_desktops --help + - name: PyInstaller Linux release binary + run: | + pip install -e ".[linux]" + pyinstaller --noconfirm packaging/now-playing-linux.spec + dist/now-playing-linux --help + - name: Tests run: pytest --cov=now_playing_desktops --cov-report=term-missing -q diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 249ec61..4ea25e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: inputs: dry_run: - description: "Build Windows .exe as artifact only (no GitHub Release or PyPI)" + description: "Build Windows/macOS/Linux binaries as artifacts only (no GitHub Release or PyPI)" type: boolean default: true @@ -51,6 +51,74 @@ jobs: files: dist/now-playing-windows.exe generate_release_notes: true + macos-app: + name: macOS executable + runs-on: macos-latest + steps: + - uses: actions/checkout@v7 + + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: "3.12" + cache: pip + + - name: Install package and PyInstaller + run: | + python -m pip install --upgrade pip + pip install -e ".[macos]" + pip install pyinstaller + + - name: Build now-playing-macos + run: pyinstaller --noconfirm packaging/now-playing-macos.spec + + - name: Upload dry-run artifact + if: github.event_name == 'workflow_dispatch' + uses: actions/upload-artifact@v4 + with: + name: now-playing-macos + path: dist/now-playing-macos + + - name: Attach to GitHub Release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + with: + files: dist/now-playing-macos + + linux-app: + name: Linux executable + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: "3.12" + cache: pip + + - name: Install package and PyInstaller + run: | + python -m pip install --upgrade pip + pip install -e ".[linux]" + pip install pyinstaller + + - name: Build now-playing-linux + run: pyinstaller --noconfirm packaging/now-playing-linux.spec + + - name: Upload dry-run artifact + if: github.event_name == 'workflow_dispatch' + uses: actions/upload-artifact@v4 + with: + name: now-playing-linux + path: dist/now-playing-linux + + - name: Attach to GitHub Release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + with: + files: dist/now-playing-linux + publish-pypi: name: Publish to PyPI runs-on: ubuntu-latest diff --git a/README.md b/README.md index eb301f2..281a0d7 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,21 @@

Glass album-art tile updating as the track changes, then the original wallpaper restoring on pause

+## Download (pre-built) + +GitHub [Releases](https://github.com/hippi345/nowPlayingDesktops/releases) attach one-file PyInstaller builds per OS (also available as workflow artifacts from **Actions → Release → Run workflow** with dry run enabled): + +| OS | Download | Run | +|----|----------|-----| +| **Windows** | `now-playing-windows.exe` | Double-click or run from a terminal | +| **macOS** | `now-playing-macos` | `chmod +x now-playing-macos && ./now-playing-macos run --source local` | +| **Linux** | `now-playing-linux` | `chmod +x now-playing-linux && ./now-playing-linux run --source local` | + +Or install with [pipx](https://pipx.pypa.io/): `pipx install now-playing-desktops` + ## Quick start (Windows) -1. **Get the app** — download `now-playing-windows.exe` from [Releases](https://github.com/hippi345/nowPlayingDesktops/releases), or install with [pipx](https://pipx.pypa.io/): `pipx install now-playing-desktops` +1. **Get the app** — download `now-playing-windows.exe` from [Releases](https://github.com/hippi345/nowPlayingDesktops/releases), or use pipx (above) 2. **Run it** — double-click the `.exe`, or run `now-playing run --source local` (pipx puts `now-playing` on your PATH) 3. **Done** — play something in the Spotify desktop app; pause or exit to restore your wallpaper @@ -59,7 +71,10 @@ now-playing run --source local ### Linux (local) -**Prerequisites** +1. **Get the app** — download `now-playing-linux` from [Releases](https://github.com/hippi345/nowPlayingDesktops/releases), `chmod +x` it, and run `./now-playing-linux run --source local`, or install from source below. +2. **Player** — Spotify desktop or another **MPRIS** player; pause or exit restores your wallpaper. + +**Prerequisites (source install)** - **Python 3.12+** - **Install:** `pip install -e ".[linux]"` (`dbus-next` for MPRIS). @@ -78,7 +93,10 @@ now-playing run --source local ### macOS (local) -**Prerequisites** +1. **Get the app** — download `now-playing-macos` from [Releases](https://github.com/hippi345/nowPlayingDesktops/releases), `chmod +x` it, and run `./now-playing-macos run --source local`, or install from source below. +2. **Player** — **Spotify.app** on this Mac; pause or exit restores your wallpaper. + +**Prerequisites (source install)** - **Python 3.12+** - **Install:** `pip install -e ".[macos]"` (`appscript` for Spotify AppleScript control). diff --git a/packaging/now-playing-linux.spec b/packaging/now-playing-linux.spec new file mode 100644 index 0000000..a7fbc97 --- /dev/null +++ b/packaging/now-playing-linux.spec @@ -0,0 +1,48 @@ +# -*- mode: python ; coding: utf-8 -*- +"""PyInstaller spec for now-playing-linux (run on ubuntu-latest).""" + +from PyInstaller.utils.hooks import collect_all + +block_cipher = None + +pkg_datas, pkg_binaries, pkg_hidden = collect_all("now_playing_desktops") +dbus_datas, dbus_binaries, dbus_hidden = collect_all("dbus_next") +pkg_datas += dbus_datas +pkg_binaries += dbus_binaries +hiddenimports = list(pkg_hidden) + list(dbus_hidden) + +a = Analysis( + ["pyinstaller_linux_entry.py"], + pathex=["../src"], + binaries=pkg_binaries, + datas=pkg_datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name="now-playing-linux", + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/packaging/now-playing-macos.spec b/packaging/now-playing-macos.spec new file mode 100644 index 0000000..5ccf4a1 --- /dev/null +++ b/packaging/now-playing-macos.spec @@ -0,0 +1,48 @@ +# -*- mode: python ; coding: utf-8 -*- +"""PyInstaller spec for now-playing-macos (run on macos-latest).""" + +from PyInstaller.utils.hooks import collect_all + +block_cipher = None + +pkg_datas, pkg_binaries, pkg_hidden = collect_all("now_playing_desktops") +app_datas, app_binaries, app_hidden = collect_all("appscript") +pkg_datas += app_datas +pkg_binaries += app_binaries +hiddenimports = list(pkg_hidden) + list(app_hidden) + +a = Analysis( + ["pyinstaller_macos_entry.py"], + pathex=["../src"], + binaries=pkg_binaries, + datas=pkg_datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name="now-playing-macos", + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/packaging/pyinstaller_linux_entry.py b/packaging/pyinstaller_linux_entry.py new file mode 100644 index 0000000..d3885ff --- /dev/null +++ b/packaging/pyinstaller_linux_entry.py @@ -0,0 +1,6 @@ +"""PyInstaller entry point for the Linux console build.""" + +from now_playing_desktops.cli import main + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/packaging/pyinstaller_macos_entry.py b/packaging/pyinstaller_macos_entry.py new file mode 100644 index 0000000..9d31afc --- /dev/null +++ b/packaging/pyinstaller_macos_entry.py @@ -0,0 +1,6 @@ +"""PyInstaller entry point for the macOS console build.""" + +from now_playing_desktops.cli import main_macos + +if __name__ == "__main__": + main_macos()