now-playing-desktops 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. now_playing_desktops-1.0.0/.editorconfig +15 -0
  2. now_playing_desktops-1.0.0/.env.example +20 -0
  3. now_playing_desktops-1.0.0/.github/dependabot.yml +11 -0
  4. now_playing_desktops-1.0.0/.github/workflows/ci.yml +105 -0
  5. now_playing_desktops-1.0.0/.github/workflows/release.yml +73 -0
  6. now_playing_desktops-1.0.0/.gitignore +48 -0
  7. now_playing_desktops-1.0.0/LICENSE +21 -0
  8. now_playing_desktops-1.0.0/PKG-INFO +362 -0
  9. now_playing_desktops-1.0.0/README.md +315 -0
  10. now_playing_desktops-1.0.0/SECURITY.md +15 -0
  11. now_playing_desktops-1.0.0/docs/demo.gif +0 -0
  12. now_playing_desktops-1.0.0/docs/screenshot-ariana-grande.png +0 -0
  13. now_playing_desktops-1.0.0/docs/screenshot-don-toliver.png +0 -0
  14. now_playing_desktops-1.0.0/docs/social-preview.png +0 -0
  15. now_playing_desktops-1.0.0/now_playing_desktops/__init__.py +3 -0
  16. now_playing_desktops-1.0.0/now_playing_desktops/__main__.py +13 -0
  17. now_playing_desktops-1.0.0/now_playing_desktops/apply_timing.py +68 -0
  18. now_playing_desktops-1.0.0/now_playing_desktops/art_background_upgrade.py +130 -0
  19. now_playing_desktops-1.0.0/now_playing_desktops/art_cache.py +83 -0
  20. now_playing_desktops-1.0.0/now_playing_desktops/art_resolution.py +338 -0
  21. now_playing_desktops-1.0.0/now_playing_desktops/auth.py +283 -0
  22. now_playing_desktops-1.0.0/now_playing_desktops/cli.py +420 -0
  23. now_playing_desktops-1.0.0/now_playing_desktops/compose_verify.py +99 -0
  24. now_playing_desktops-1.0.0/now_playing_desktops/composer.py +932 -0
  25. now_playing_desktops-1.0.0/now_playing_desktops/config.py +67 -0
  26. now_playing_desktops-1.0.0/now_playing_desktops/cover_art.py +55 -0
  27. now_playing_desktops-1.0.0/now_playing_desktops/env_loader.py +102 -0
  28. now_playing_desktops-1.0.0/now_playing_desktops/fonts/Inter-Bold.ttf +0 -0
  29. now_playing_desktops-1.0.0/now_playing_desktops/fonts/Inter-Medium.ttf +0 -0
  30. now_playing_desktops-1.0.0/now_playing_desktops/fonts/Inter-Regular.ttf +0 -0
  31. now_playing_desktops-1.0.0/now_playing_desktops/fonts/Inter-SemiBold.ttf +0 -0
  32. now_playing_desktops-1.0.0/now_playing_desktops/fonts/OFL.txt +92 -0
  33. now_playing_desktops-1.0.0/now_playing_desktops/fonts/__init__.py +1 -0
  34. now_playing_desktops-1.0.0/now_playing_desktops/logging_setup.py +101 -0
  35. now_playing_desktops-1.0.0/now_playing_desktops/platforms/__init__.py +44 -0
  36. now_playing_desktops-1.0.0/now_playing_desktops/platforms/autostart.py +310 -0
  37. now_playing_desktops-1.0.0/now_playing_desktops/platforms/base.py +60 -0
  38. now_playing_desktops-1.0.0/now_playing_desktops/platforms/linux.py +132 -0
  39. now_playing_desktops-1.0.0/now_playing_desktops/platforms/linux_backends.py +289 -0
  40. now_playing_desktops-1.0.0/now_playing_desktops/platforms/linux_de.py +85 -0
  41. now_playing_desktops-1.0.0/now_playing_desktops/platforms/linux_screen.py +142 -0
  42. now_playing_desktops-1.0.0/now_playing_desktops/platforms/macos.py +214 -0
  43. now_playing_desktops-1.0.0/now_playing_desktops/platforms/windows.py +300 -0
  44. now_playing_desktops-1.0.0/now_playing_desktops/platforms/windows_com.py +152 -0
  45. now_playing_desktops-1.0.0/now_playing_desktops/platforms/windows_com_ctypes.py +134 -0
  46. now_playing_desktops-1.0.0/now_playing_desktops/platforms/windows_console.py +42 -0
  47. now_playing_desktops-1.0.0/now_playing_desktops/platforms/windows_diag.py +238 -0
  48. now_playing_desktops-1.0.0/now_playing_desktops/platforms/windows_dpi.py +363 -0
  49. now_playing_desktops-1.0.0/now_playing_desktops/platforms/windows_monitors.py +431 -0
  50. now_playing_desktops-1.0.0/now_playing_desktops/platforms/windows_restore.py +473 -0
  51. now_playing_desktops-1.0.0/now_playing_desktops/platforms/windows_virtual_compose.py +115 -0
  52. now_playing_desktops-1.0.0/now_playing_desktops/playback_diag.py +99 -0
  53. now_playing_desktops-1.0.0/now_playing_desktops/playback_factory.py +46 -0
  54. now_playing_desktops-1.0.0/now_playing_desktops/playback_source.py +48 -0
  55. now_playing_desktops-1.0.0/now_playing_desktops/playback_types.py +35 -0
  56. now_playing_desktops-1.0.0/now_playing_desktops/poll_watchdog.py +65 -0
  57. now_playing_desktops-1.0.0/now_playing_desktops/runner.py +591 -0
  58. now_playing_desktops-1.0.0/now_playing_desktops/single_instance.py +172 -0
  59. now_playing_desktops-1.0.0/now_playing_desktops/sources/__init__.py +1 -0
  60. now_playing_desktops-1.0.0/now_playing_desktops/sources/base.py +21 -0
  61. now_playing_desktops-1.0.0/now_playing_desktops/sources/local/__init__.py +25 -0
  62. now_playing_desktops-1.0.0/now_playing_desktops/sources/local/base.py +22 -0
  63. now_playing_desktops-1.0.0/now_playing_desktops/sources/local/linux_mpris.py +79 -0
  64. now_playing_desktops-1.0.0/now_playing_desktops/sources/local/macos_spotify.py +65 -0
  65. now_playing_desktops-1.0.0/now_playing_desktops/sources/local/unsupported.py +15 -0
  66. now_playing_desktops-1.0.0/now_playing_desktops/sources/local/windows_smtc.py +42 -0
  67. now_playing_desktops-1.0.0/now_playing_desktops/sources/local/windows_smtc_async.py +110 -0
  68. now_playing_desktops-1.0.0/now_playing_desktops/sources/local/windows_smtc_thumbnail.py +76 -0
  69. now_playing_desktops-1.0.0/now_playing_desktops/sources/local/windows_smtc_winrt.py +29 -0
  70. now_playing_desktops-1.0.0/now_playing_desktops/sources/local/windows_smtc_worker.py +168 -0
  71. now_playing_desktops-1.0.0/now_playing_desktops/sources/spotify_provider.py +49 -0
  72. now_playing_desktops-1.0.0/now_playing_desktops/spotify_art.py +159 -0
  73. now_playing_desktops-1.0.0/now_playing_desktops/verification/__init__.py +1 -0
  74. now_playing_desktops-1.0.0/now_playing_desktops/verification/wallpaper_analysis.py +404 -0
  75. now_playing_desktops-1.0.0/now_playing_desktops/verification/windows_display.py +89 -0
  76. now_playing_desktops-1.0.0/now_playing_desktops/wallpaper_snapshot.py +155 -0
  77. now_playing_desktops-1.0.0/now_playing_desktops/wallpaper_state.py +72 -0
  78. now_playing_desktops-1.0.0/packaging/now-playing-smoke.spec +44 -0
  79. now_playing_desktops-1.0.0/packaging/now-playing-windows.spec +45 -0
  80. now_playing_desktops-1.0.0/packaging/pyinstaller_win_entry.py +6 -0
  81. now_playing_desktops-1.0.0/pyproject.toml +91 -0
  82. now_playing_desktops-1.0.0/requirements-lock.txt +18 -0
  83. now_playing_desktops-1.0.0/scripts/profile_compose.py +50 -0
  84. now_playing_desktops-1.0.0/tests/__init__.py +1 -0
  85. now_playing_desktops-1.0.0/tests/conftest.py +19 -0
  86. now_playing_desktops-1.0.0/tests/helpers.py +191 -0
  87. now_playing_desktops-1.0.0/tests/linux_desktop_capture.py +241 -0
  88. now_playing_desktops-1.0.0/tests/test_art_cache.py +22 -0
  89. now_playing_desktops-1.0.0/tests/test_art_resolution.py +228 -0
  90. now_playing_desktops-1.0.0/tests/test_auth.py +264 -0
  91. now_playing_desktops-1.0.0/tests/test_autostart.py +66 -0
  92. now_playing_desktops-1.0.0/tests/test_autostart_compose_regression.py +97 -0
  93. now_playing_desktops-1.0.0/tests/test_autostart_env.py +159 -0
  94. now_playing_desktops-1.0.0/tests/test_cli.py +187 -0
  95. now_playing_desktops-1.0.0/tests/test_cli_autostart.py +95 -0
  96. now_playing_desktops-1.0.0/tests/test_compose_performance.py +127 -0
  97. now_playing_desktops-1.0.0/tests/test_composer.py +362 -0
  98. now_playing_desktops-1.0.0/tests/test_composer_glass_contrast.py +83 -0
  99. now_playing_desktops-1.0.0/tests/test_composer_layout_fonts.py +163 -0
  100. now_playing_desktops-1.0.0/tests/test_env_loader.py +100 -0
  101. now_playing_desktops-1.0.0/tests/test_linux_de_detection.py +33 -0
  102. now_playing_desktops-1.0.0/tests/test_linux_desktop_real_capture.py +173 -0
  103. now_playing_desktops-1.0.0/tests/test_linux_desktop_wallpaper.py +103 -0
  104. now_playing_desktops-1.0.0/tests/test_online_art.py +132 -0
  105. now_playing_desktops-1.0.0/tests/test_packaging.py +54 -0
  106. now_playing_desktops-1.0.0/tests/test_platforms_linux.py +244 -0
  107. now_playing_desktops-1.0.0/tests/test_platforms_macos.py +80 -0
  108. now_playing_desktops-1.0.0/tests/test_platforms_windows.py +35 -0
  109. now_playing_desktops-1.0.0/tests/test_platforms_windows_monitors.py +319 -0
  110. now_playing_desktops-1.0.0/tests/test_playback_sources.py +286 -0
  111. now_playing_desktops-1.0.0/tests/test_runner.py +378 -0
  112. now_playing_desktops-1.0.0/tests/test_runner_art_upgrade.py +212 -0
  113. now_playing_desktops-1.0.0/tests/test_runner_compose_quality.py +122 -0
  114. now_playing_desktops-1.0.0/tests/test_runner_linux_restore.py +33 -0
  115. now_playing_desktops-1.0.0/tests/test_runner_upgrade_steady_play.py +167 -0
  116. now_playing_desktops-1.0.0/tests/test_single_instance.py +75 -0
  117. now_playing_desktops-1.0.0/tests/test_spotify_art.py +136 -0
  118. now_playing_desktops-1.0.0/tests/test_spotify_errors.py +43 -0
  119. now_playing_desktops-1.0.0/tests/test_wallpaper_analysis.py +137 -0
  120. now_playing_desktops-1.0.0/tests/test_wallpaper_snapshot.py +197 -0
  121. now_playing_desktops-1.0.0/tests/test_wallpaper_state.py +42 -0
  122. now_playing_desktops-1.0.0/tests/test_windows_com_probe.py +35 -0
  123. now_playing_desktops-1.0.0/tests/test_windows_console.py +37 -0
  124. now_playing_desktops-1.0.0/tests/test_windows_desktop_wallpaper.py +238 -0
  125. now_playing_desktops-1.0.0/tests/test_windows_diag.py +210 -0
  126. now_playing_desktops-1.0.0/tests/test_windows_display_simulation.py +136 -0
  127. now_playing_desktops-1.0.0/tests/test_windows_dm_pels_compose.py +214 -0
  128. now_playing_desktops-1.0.0/tests/test_windows_dpi.py +69 -0
  129. now_playing_desktops-1.0.0/tests/test_windows_entry_point_dpi.py +71 -0
  130. now_playing_desktops-1.0.0/tests/test_windows_multi_monitor.py +222 -0
  131. now_playing_desktops-1.0.0/tests/test_windows_restore.py +232 -0
  132. now_playing_desktops-1.0.0/tests/test_windows_smtc_thumbnail_fastpath.py +40 -0
  133. now_playing_desktops-1.0.0/tests/test_windows_smtc_winrt.py +18 -0
  134. now_playing_desktops-1.0.0/tests/test_windows_smtc_worker.py +115 -0
  135. now_playing_desktops-1.0.0/tests/test_windows_wallpaper_geometry.py +154 -0
  136. now_playing_desktops-1.0.0/tests/test_windows_winrt_deps.py +61 -0
@@ -0,0 +1,15 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.{yml,yaml,json,toml}]
12
+ indent_size = 2
13
+
14
+ [*.md]
15
+ trim_trailing_whitespace = false
@@ -0,0 +1,20 @@
1
+ # Spotify OAuth — create an app at https://developer.spotify.com/dashboard
2
+ # Copy to .env and fill in values (never commit .env).
3
+ #
4
+ # Minimal setup (PKCE): SPOTIPY_CLIENT_ID only — run `now-playing login` once.
5
+ # Legacy setup: also set SPOTIPY_CLIENT_SECRET (browser sign-in can happen on first `run`).
6
+ #
7
+ # Interactive runs: repo .env or shell exports are fine.
8
+ # Login autostart: copy this file to the per-user config directory, e.g.
9
+ # Windows: %APPDATA%\now-playing-desktops\.env
10
+ # macOS: ~/Library/Application Support/now-playing-desktops/.env
11
+ # Linux: ~/.config/now-playing-desktops/.env
12
+ SPOTIPY_CLIENT_ID=your_client_id_here
13
+ # Optional (legacy client-secret OAuth flow):
14
+ # SPOTIPY_CLIENT_SECRET=your_client_secret_here
15
+ # Optional; must match a redirect URI in your Spotify app settings.
16
+ SPOTIPY_REDIRECT_URI=http://127.0.0.1:8897/callback
17
+ # Optional but recommended for autostart (OAuth token cache username).
18
+ SPOTIPY_CLIENT_USERNAME=your_spotify_username
19
+ # Optional: set to 0 to disable internet album-art lookups (iTunes Search / Spotify art URLs).
20
+ # NOW_PLAYING_ONLINE_ART=0
@@ -0,0 +1,11 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: pip
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
7
+ open-pull-requests-limit: 10
8
+ - package-ecosystem: github-actions
9
+ directory: "/"
10
+ schedule:
11
+ interval: weekly
@@ -0,0 +1,105 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master, main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ test-linux:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ matrix:
16
+ python-version: ["3.12", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v7
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v7
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+ cache: pip
25
+
26
+ - name: Install dependencies
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ pip install -e ".[dev]"
30
+ sudo apt-get update -qq
31
+ sudo apt-get install -y --no-install-recommends \
32
+ dbus-x11 xvfb feh openbox imagemagick x11-apps \
33
+ gsettings-desktop-schemas dconf-cli
34
+
35
+ - name: Ruff
36
+ run: ruff check src tests
37
+
38
+ - name: Ruff format
39
+ run: ruff format --check src tests
40
+
41
+ - name: Build package
42
+ run: pip install build && python -m build
43
+
44
+ - name: Verify editable install (clean venv, outside repo)
45
+ run: |
46
+ python -m venv /tmp/npd-packaging-venv
47
+ /tmp/npd-packaging-venv/bin/pip install -e .
48
+ cd /tmp
49
+ /tmp/npd-packaging-venv/bin/python -c "import now_playing_desktops"
50
+ /tmp/npd-packaging-venv/bin/now-playing --help
51
+ /tmp/npd-packaging-venv/bin/python -m now_playing_desktops --help
52
+
53
+ - name: Tests
54
+ run: pytest --cov=now_playing_desktops --cov-report=term-missing -q
55
+
56
+ - name: Linux desktop wallpaper centering (Xvfb + feh + mss)
57
+ run: pytest tests/test_linux_desktop_wallpaper.py -q
58
+
59
+ test-windows:
60
+ runs-on: windows-latest
61
+ steps:
62
+ - uses: actions/checkout@v7
63
+
64
+ - name: Set up Python
65
+ uses: actions/setup-python@v7
66
+ with:
67
+ python-version: "3.12"
68
+ cache: pip
69
+
70
+ - name: Install dependencies
71
+ run: |
72
+ python -m pip install --upgrade pip
73
+ pip install -e ".[dev,windows]"
74
+
75
+ - name: Ruff
76
+ run: ruff check src tests
77
+
78
+ - name: Ruff format
79
+ run: ruff format --check src tests
80
+
81
+ - name: Tests
82
+ run: pytest --cov=now_playing_desktops --cov-report=term-missing -q
83
+
84
+ - name: Windows winrt optional dependency imports
85
+ run: pytest tests/test_windows_winrt_deps.py -q
86
+
87
+ - name: Windows display enumeration smoke test
88
+ run: pytest tests/test_platforms_windows_monitors.py::test_real_monitor_enumeration_and_compose -q
89
+
90
+ - name: Windows wallpaper restore smoke test
91
+ run: pytest tests/test_windows_restore.py::test_windows_wallpaper_smoke_snapshot_apply_restore -q
92
+
93
+ - name: Windows desktop wallpaper capture verification
94
+ env:
95
+ NP_ARTIFACTS_DIR: ${{ runner.temp }}/np-artifacts
96
+ run: pytest tests/test_windows_desktop_wallpaper.py -q
97
+
98
+ - name: Upload desktop verification captures
99
+ if: always()
100
+ uses: actions/upload-artifact@v4
101
+ with:
102
+ name: windows-desktop-captures
103
+ path: |
104
+ ${{ runner.temp }}/np-artifacts/win-desktop-capture-after-fix.png
105
+ if-no-files-found: warn
@@ -0,0 +1,73 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+ inputs:
9
+ dry_run:
10
+ description: "Build Windows .exe as artifact only (no GitHub Release or PyPI)"
11
+ type: boolean
12
+ default: true
13
+
14
+ permissions:
15
+ contents: write
16
+ id-token: write
17
+
18
+ jobs:
19
+ windows-exe:
20
+ name: Windows executable
21
+ runs-on: windows-latest
22
+ steps:
23
+ - uses: actions/checkout@v7
24
+
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v7
27
+ with:
28
+ python-version: "3.12"
29
+ cache: pip
30
+
31
+ - name: Install package and PyInstaller
32
+ run: |
33
+ python -m pip install --upgrade pip
34
+ pip install -e ".[windows]"
35
+ pip install pyinstaller
36
+
37
+ - name: Build now-playing-windows.exe
38
+ run: pyinstaller --noconfirm packaging/now-playing-windows.spec
39
+
40
+ - name: Upload dry-run artifact
41
+ if: github.event_name == 'workflow_dispatch'
42
+ uses: actions/upload-artifact@v4
43
+ with:
44
+ name: now-playing-windows-exe
45
+ path: dist/now-playing-windows.exe
46
+
47
+ - name: Attach to GitHub Release
48
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
49
+ uses: softprops/action-gh-release@v2
50
+ with:
51
+ files: dist/now-playing-windows.exe
52
+ generate_release_notes: true
53
+
54
+ publish-pypi:
55
+ name: Publish to PyPI
56
+ runs-on: ubuntu-latest
57
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
58
+ environment: pypi
59
+ steps:
60
+ - uses: actions/checkout@v7
61
+
62
+ - name: Set up Python
63
+ uses: actions/setup-python@v7
64
+ with:
65
+ python-version: "3.12"
66
+
67
+ - name: Build sdist and wheel
68
+ run: |
69
+ python -m pip install --upgrade pip build
70
+ python -m build
71
+
72
+ - name: Publish to PyPI (trusted publishing)
73
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,48 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ .venv/
23
+ venv/
24
+ ENV/
25
+
26
+ # Testing / tooling
27
+ .pytest_cache/
28
+ .ruff_cache/
29
+ .coverage
30
+ htmlcov/
31
+ .mypy_cache/
32
+
33
+ # Spotify OAuth cache (spotipy)
34
+ .cache-*
35
+
36
+ # Local artwork
37
+ current_artwork*.jpg
38
+
39
+ # IDE
40
+ .idea/
41
+ .vscode/
42
+ *.swp
43
+ .DS_Store
44
+
45
+ # Env secrets
46
+ .env
47
+ .env.*
48
+ !.env.example
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joel Shearon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,362 @@
1
+ Metadata-Version: 2.5
2
+ Name: now-playing-desktops
3
+ Version: 1.0.0
4
+ Summary: Glass-style album-art tile on your desktop wallpaper for the Spotify track (or local player) you are playing now.
5
+ Project-URL: Homepage, https://github.com/hippi345/nowPlayingDesktops
6
+ Project-URL: Repository, https://github.com/hippi345/nowPlayingDesktops
7
+ Project-URL: Issues, https://github.com/hippi345/nowPlayingDesktops/issues
8
+ Project-URL: Documentation, https://github.com/hippi345/nowPlayingDesktops#readme
9
+ Author: Joel Shearon
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: album-art,desktop,mpris,now-playing,spotify,wallpaper
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: MacOS X
15
+ Classifier: Environment :: Win32 (MS Windows)
16
+ Classifier: Environment :: X11 Applications
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Desktop Environment
22
+ Classifier: Topic :: Multimedia :: Sound/Audio
23
+ Requires-Python: >=3.12
24
+ Requires-Dist: pillow<12,>=11.0.0
25
+ Requires-Dist: python-dotenv<2,>=1.0.0
26
+ Requires-Dist: requests<3,>=2.32.0
27
+ Requires-Dist: spotipy<3,>=2.24.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: build<2,>=1.2.0; extra == 'dev'
30
+ Requires-Dist: mss<11,>=9.0.0; extra == 'dev'
31
+ Requires-Dist: pyinstaller<7,>=6.10.0; extra == 'dev'
32
+ Requires-Dist: pytest-cov<8,>=7.1.0; extra == 'dev'
33
+ Requires-Dist: pytest<10,>=9.1.0; extra == 'dev'
34
+ Requires-Dist: ruff<1,>=0.9.0; extra == 'dev'
35
+ Requires-Dist: twine<8,>=7.0.0; extra == 'dev'
36
+ Provides-Extra: linux
37
+ Requires-Dist: dbus-next<1,>=0.2.3; extra == 'linux'
38
+ Provides-Extra: macos
39
+ Requires-Dist: appscript<2,>=1.2.5; extra == 'macos'
40
+ Provides-Extra: windows
41
+ Requires-Dist: winrt-runtime~=3.2.1; (sys_platform == 'win32') and extra == 'windows'
42
+ Requires-Dist: winrt-windows-foundation-collections~=3.2.1; (sys_platform == 'win32') and extra == 'windows'
43
+ Requires-Dist: winrt-windows-foundation~=3.2.1; (sys_platform == 'win32') and extra == 'windows'
44
+ Requires-Dist: winrt-windows-media-control~=3.2.1; (sys_platform == 'win32') and extra == 'windows'
45
+ Requires-Dist: winrt-windows-storage-streams~=3.2.1; (sys_platform == 'win32') and extra == 'windows'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # nowPlayingDesktops
49
+
50
+ [![CI](https://github.com/hippi345/nowPlayingDesktops/actions/workflows/ci.yml/badge.svg)](https://github.com/hippi345/nowPlayingDesktops/actions/workflows/ci.yml)
51
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
52
+
53
+ **A glass-style album-art tile on your desktop wallpaper** for whatever you are playing on Spotify (or another supported player in **local** mode).
54
+
55
+ ## Screenshots
56
+
57
+ <p>
58
+ <img src="docs/screenshot-don-toliver.png" width="49%" alt="Now-playing wallpaper: Don Toliver, No Pole" />
59
+ <img src="docs/screenshot-ariana-grande.png" width="49%" alt="Now-playing wallpaper: Ariana Grande, Let Me Love You" />
60
+ </p>
61
+
62
+ <p align="center"><img src="docs/demo.gif" alt="Glass album-art tile updating as the track changes, then the original wallpaper restoring on pause" width="720"></p>
63
+
64
+ ## Quick start (Windows)
65
+
66
+ 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`
67
+ 2. **Run it** — double-click the `.exe`, or run `now-playing run --source local` (pipx puts `now-playing` on your PATH)
68
+ 3. **Done** — play something in the Spotify desktop app; pause or exit to restore your wallpaper
69
+
70
+ For macOS, Linux, Spotify Web API mode, or hacking on the code, see the sections below.
71
+
72
+ ## Install from source
73
+
74
+ ```bash
75
+ git clone https://github.com/hippi345/nowPlayingDesktops.git
76
+ cd nowPlayingDesktops
77
+ python3.12 -m venv .venv
78
+ source .venv/bin/activate # Windows: .\.venv\Scripts\Activate.ps1
79
+ pip install -e ".[dev]" # add windows, linux, or macos extras as needed
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Quick Start: Local mode
85
+
86
+ Local mode reads playback from the OS media session on **this** machine. Pick your OS:
87
+
88
+ ### Windows (local)
89
+
90
+ **Prerequisites**
91
+
92
+ - **Python 3.12+** (`requires-python` in `pyproject.toml`; CI tests 3.12 and 3.13).
93
+ - **Install:** `pip install -e ".[windows]"` (WinRT optional deps for SMTC).
94
+ - **Player:** Spotify **desktop** app playing on this PC (System Media Transport Controls).
95
+
96
+ **Run**
97
+
98
+ ```powershell
99
+ python -m venv .venv
100
+ .\.venv\Scripts\Activate.ps1
101
+ pip install -e ".[windows]"
102
+ now-playing run --source local
103
+ ```
104
+
105
+ **Nice extra:** When the lock screen is set to use the **same picture as the desktop wallpaper**, the composed tile can appear on the lock screen while a track is playing; it is restored when you pause (same restore path as the desktop).
106
+
107
+ ### Linux (local)
108
+
109
+ **Prerequisites**
110
+
111
+ - **Python 3.12+**
112
+ - **Install:** `pip install -e ".[linux]"` (`dbus-next` for MPRIS).
113
+ - **D-Bus session:** `echo $DBUS_SESSION_BUS_ADDRESS` should be set in your desktop session.
114
+ - **Player:** Spotify desktop or another app with **MPRIS** (`org.mpris.MediaPlayer2.*`). `playerctl` is not required by this app.
115
+ - **Wallpaper backend** (one of): GNOME family — `gsettings`; KDE — `plasma-apply-wallpaperimage` and/or `qdbus6` / `qdbus`; other WMs — `feh`, `swaybg`, or `nitrogen` (plus `xrandr` or `wlr-randr` for screen size).
116
+
117
+ **Run**
118
+
119
+ ```bash
120
+ python3.12 -m venv .venv
121
+ source .venv/bin/activate
122
+ pip install -e ".[linux]"
123
+ now-playing run --source local
124
+ ```
125
+
126
+ ### macOS (local)
127
+
128
+ **Prerequisites**
129
+
130
+ - **Python 3.12+**
131
+ - **Install:** `pip install -e ".[macos]"` (`appscript` for Spotify AppleScript control).
132
+ - **Player:** **Spotify.app** playing on this Mac.
133
+ - **Automation:** macOS may prompt for **Automation** permission for your terminal or Python to control Spotify (System Settings → Privacy & Security → Automation).
134
+
135
+ **Run**
136
+
137
+ ```bash
138
+ python3.12 -m venv .venv
139
+ source .venv/bin/activate
140
+ pip install -e ".[macos]"
141
+ now-playing run --source local
142
+ ```
143
+
144
+ Optional PyObjC (`pyobjc-framework-Cocoa`) improves per-display wallpaper control; without it, `osascript` sets all screens.
145
+
146
+ ### Common `run` flags (local)
147
+
148
+ - `-v` / `--verbose` — debug logging
149
+ - `--once` — single poll/apply cycle
150
+ - `--env-file PATH` — load settings from a `.env` file
151
+ - `--no-online-art` — no internet art lookups (see [Album art](#album-art-in-local-mode))
152
+ - `--poll-interval SECONDS` — default `2.5`
153
+
154
+ `YOUR_SPOTIFY_USERNAME` on the command line is **optional** for local mode (only required for `--source spotify`).
155
+
156
+ ### Login autostart (local)
157
+
158
+ ```bash
159
+ now-playing autostart enable --source local
160
+ ```
161
+
162
+ `enable` forwards `--source`, `--env-file`, `--no-online-art`, and an optional username into the registered autostart command (Windows Run key, XDG autostart `.desktop`, or macOS LaunchAgent). Check status with `now-playing autostart status`; remove with `now-playing autostart disable`.
163
+
164
+ ---
165
+
166
+ ## Album art in local mode
167
+
168
+ When **online art is enabled** (default), cover resolution is:
169
+
170
+ 1. **System media thumbnail** when available (SMTC on Windows, MPRIS art on Linux, AppleScript artwork URL on macOS — URL download only if online art is enabled).
171
+ 2. **[Apple iTunes Search API](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/iTuneSearchAPI/)** (public HTTPS; no iTunes app or Apple account) when the thumbnail is missing or small.
172
+ 3. **Generated placeholder** (gradient + track title/artist) if nothing else is available.
173
+
174
+ If the iTunes lookup fails (network error, no match, timeout), the app logs a warning and **continues** with the system thumbnail or placeholder; the **wallpaper still applies** for that poll. A background thread may **upgrade** wallpaper art after the first apply when iTunes returns a larger image for the same playing track (see logs: `Upgraded art Apply took …`).
175
+
176
+ ### Disable online art (offline / privacy)
177
+
178
+ ```bash
179
+ NOW_PLAYING_ONLINE_ART=0
180
+ ```
181
+
182
+ Or on the CLI:
183
+
184
+ ```bash
185
+ now-playing run --source local --no-online-art
186
+ ```
187
+
188
+ When disabled: local thumbnails only, then placeholder; **no** iTunes requests, **no** Spotify art URL downloads, **no** background iTunes upgrader. (Spotify **Web API** mode still uses the network for playback and API album art unless you also pass `--no-online-art`, which skips art URL downloads and uses placeholders.)
189
+
190
+ The app loads `.env` from (first match wins, without overriding variables already set in the shell):
191
+
192
+ 1. `--env-file` / `NOW_PLAYING_ENV_FILE`
193
+ 2. Per-user config: `%APPDATA%\now-playing-desktops\.env` (Windows), `~/Library/Application Support/now-playing-desktops/.env` (macOS), `$XDG_CONFIG_HOME/now-playing-desktops/.env` (Linux, default `~/.config/now-playing-desktops/.env`)
194
+ 3. Current working directory `.env`
195
+
196
+ ---
197
+
198
+ ## Quick Start: Spotify mode
199
+
200
+ Spotify mode uses the Web API (any device). Wallpaper setup is the same on every OS; playback detection does not need the `[windows]` / `[linux]` / `[macos]` extras.
201
+
202
+ ### Windows (Spotify)
203
+
204
+ **Prerequisites**
205
+
206
+ - **Python 3.12+**
207
+ - **Install:** `pip install -e .` (base package; wallpaper uses `ctypes` / WinRT desktop APIs already in the main install path).
208
+ - **Spotify Developer** app with **Client ID** in `.env` (see below).
209
+
210
+ ### Linux (Spotify)
211
+
212
+ **Prerequisites**
213
+
214
+ - **Python 3.12+**
215
+ - **Install:** `pip install -e .`
216
+ - **Wallpaper backend** (same as local): `gsettings` (GNOME), `plasma-apply-wallpaperimage` / `qdbus6` / `qdbus` (KDE), or `feh` / `swaybg` / `nitrogen` on lightweight WMs.
217
+
218
+ ### macOS (Spotify)
219
+
220
+ **Prerequisites**
221
+
222
+ - **Python 3.12+**
223
+ - **Install:** `pip install -e .` (wallpaper via AppKit if PyObjC is installed, otherwise `osascript`).
224
+
225
+ ### 1. Create a Spotify app
226
+
227
+ 1. Open the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard) and create an app.
228
+ 2. Copy the **Client ID** (PKCE does **not** need a Client Secret).
229
+ 3. Under **Settings** → **Redirect URIs**, add this exact URI (unless you override it):
230
+
231
+ `http://127.0.0.1:8897/callback`
232
+
233
+ This is the default from `SPOTIPY_REDIRECT_URI` / `DEFAULT_REDIRECT_URI` in `auth.py`. If port `8897` is in use, pick another loopback URL (for example `http://127.0.0.1:8898/callback`), set `SPOTIPY_REDIRECT_URI` to match, and register the same URI in the dashboard.
234
+
235
+ ### 2. Configure credentials
236
+
237
+ Copy `.env.example` to `.env` or to the per-user config path (recommended for autostart):
238
+
239
+ | OS | Config directory |
240
+ |----|------------------|
241
+ | **Windows** | `%APPDATA%\now-playing-desktops\.env` |
242
+ | **macOS** | `~/Library/Application Support/now-playing-desktops/.env` |
243
+ | **Linux** | `~/.config/now-playing-desktops/.env` |
244
+
245
+ Minimum for PKCE:
246
+
247
+ ```env
248
+ SPOTIPY_CLIENT_ID=your_client_id_here
249
+ SPOTIPY_CLIENT_USERNAME=your_spotify_username
250
+ ```
251
+
252
+ Optional: `NOW_PLAYING_SOURCE=spotify|local|auto` (default `auto`).
253
+
254
+ ### 3. Allowlist users (Development mode)
255
+
256
+ Apps in [Development mode](https://developer.spotify.com/documentation/web-api/concepts/quota-modes) only work for users listed under the app’s **Users Management** (currently up to **five** users plus the owner). Add every Spotify account that will use the app.
257
+
258
+ ### 4. Sign in once
259
+
260
+ ```bash
261
+ now-playing login YOUR_SPOTIFY_USERNAME
262
+ ```
263
+
264
+ Optional: `--env-file PATH` if your `.env` is not in the default config directory.
265
+
266
+ Token cache file: `spotify-token-<username>.cache` under the same config directory (`config.spotify_token_cache_path`).
267
+
268
+ ### 5. Run
269
+
270
+ ```bash
271
+ now-playing run YOUR_SPOTIFY_USERNAME --source spotify
272
+ ```
273
+
274
+ Autostart:
275
+
276
+ ```bash
277
+ now-playing autostart enable YOUR_SPOTIFY_USERNAME --source spotify
278
+ ```
279
+
280
+ (`enable` requires Spotify credentials in the resolved `.env` when source is `spotify` or `auto` with Client ID present.)
281
+
282
+ ---
283
+
284
+ ## Command reference
285
+
286
+ | Command | Purpose |
287
+ |---------|---------|
288
+ | `now-playing run [USER] [--source spotify\|local\|auto] [--once] [--env-file PATH] [--no-online-art] [-v]` | Poll playback and update wallpaper |
289
+ | `now-playing login [USER] [--env-file PATH] [-v]` | Interactive Spotify OAuth (PKCE or legacy secret flow) |
290
+ | `now-playing restore [--state-file PATH] [-v]` | Restore wallpaper saved at session start |
291
+ | `now-playing diag [-v]` | **Windows only:** playback source diagnostics, Spotify auth summary, monitor/DPI/wallpaper report |
292
+ | `now-playing autostart enable [USER] [--source …] [--env-file PATH] [--no-online-art] [-v]` | Register login autostart |
293
+ | `now-playing autostart disable` | Remove autostart |
294
+ | `now-playing autostart status` | Print `enabled` or `disabled` |
295
+
296
+ Stop a foreground `run` with `Ctrl+C`; the original wallpaper is restored automatically.
297
+
298
+ ---
299
+
300
+ ## Wallpaper behavior
301
+
302
+ - Composes a blurred backdrop, glass panel, cover art, and track labels; restores your original wallpaper on pause or exit.
303
+ - **Windows** — multi-monitor aware; optional per-monitor wallpaper when `IDesktopWallpaper` is available.
304
+ - **macOS** — sets wallpaper on every display (AppKit / `osascript`).
305
+ - **Linux** — GNOME (`gsettings`), KDE (`plasma-apply-wallpaperimage` / `qdbus`), or fallbacks (`feh`, `swaybg`, `nitrogen`).
306
+
307
+ Session state: `wallpaper-state.json` in the config directory. If a previous run exited without restoring (`session_active`), the next startup restores immediately.
308
+
309
+ ---
310
+
311
+ ## Which mode should I pick?
312
+
313
+ | Mode | Command | What it needs | Players / devices | Account / app setup | Pros | Cons |
314
+ |------|---------|---------------|-------------------|----------------------|------|------|
315
+ | **Local** | `--source local` | Python 3.12+, OS extras below, media app **playing on this computer** | Spotify desktop (Windows SMTC, Linux MPRIS, macOS AppleScript); other MPRIS players on Linux may work if they expose metadata | None | No Spotify Developer app; no OAuth; playback detection works offline | Art is often a small system thumbnail unless [online art](#album-art-in-local-mode) is enabled; desktop app must be on this machine |
316
+ | **Spotify** | `--source spotify` | Same Python install + `SPOTIPY_CLIENT_ID` in `.env` | Any device where **your** Spotify account is playing (phone, web, desktop, etc.) | [Spotify Developer](https://developer.spotify.com/dashboard) app; PKCE `login` once; Development mode **user allowlist** (up to 5 users) | High-resolution album art from the Web API | Requires Spotify credentials and allowlisted users in dev mode |
317
+ | **Auto** | omit `--source`, or `--source auto`, or `NOW_PLAYING_SOURCE=auto` (default) | Resolves to Spotify or local using the rules below | Same as the **effective** mode | Same as the effective mode | One command whether or not you use the Web API | Surprising if you expected local but have `SPOTIPY_CLIENT_ID` set |
318
+
319
+ **How `auto` resolves** (see `resolve_effective_playback_source` in `playback_source.py`):
320
+
321
+ 1. `--source spotify` or `NOW_PLAYING_SOURCE=spotify` → **Spotify** Web API.
322
+ 2. `--source local` or `NOW_PLAYING_SOURCE=local` → **local** OS media session.
323
+ 3. Otherwise (`auto`): if `SPOTIPY_CLIENT_ID` is set and non-empty in the environment (after `.env` load), effective mode is **Spotify**; if not, **local**.
324
+
325
+ A resolved Spotify run still needs a username (`SPOTIPY_CLIENT_USERNAME` or a `run`/`login` argument) and a usable token cache from `now-playing login` (or legacy client-secret flow). **Online art** (iTunes Search lookups and Spotify art URL downloads) applies to **local** mode by default; see [Album art in local mode](#album-art-in-local-mode).
326
+
327
+ Legacy console scripts `now-playing-macos` and `now-playing-windows` behave like `now-playing run …` when you omit the `run` subcommand.
328
+
329
+ ---
330
+
331
+ ## Troubleshooting
332
+
333
+ | Topic | What to do |
334
+ |-------|------------|
335
+ | **`now-playing diag`** | Windows only. Shows effective playback source, what local/Spotify sources see, auth mode, and display/wallpaper geometry. |
336
+ | **Log file** | During `run`, logs go to a rotating file under the config directory: **Windows** `%APPDATA%\now-playing-desktops\logs\now-playing.log`; **macOS** `~/Library/Application Support/now-playing-desktops/logs/now-playing.log`; **Linux** `~/.config/now-playing-desktops/logs/now-playing.log` (`logging_setup.log_file_path()`). |
337
+ | **Pause / resume does not restore or re-apply** | Run with `-v` and inspect the log above. On pause or no session, the runner calls `restore_original_wallpaper()`; on resume it should log `Updated wallpaper for …`. If nothing changes, check whether a **second** `now-playing run` is holding the single-instance lock (below) or whether restore failed (`Failed to apply wallpaper restore snapshot` in the log). |
338
+ | **Single instance** | Only one `now-playing run` at a time. A second instance logs `Another now-playing run instance is already active (...); exiting` and exits **0** without changing the wallpaper. **Stop only the other instance:** Windows — mutex `Local\now-playing-desktops-run` (see `now-playing diag` run-lock lines) or find the process whose command line contains `now_playing_desktops` / `now-playing run`; Linux/macOS — read PID from `run.lock` in the config dir (`~/.config/now-playing-desktops/run.lock` on Linux) and `kill` that PID, or end the terminal session running `run`. Do not delete `run.lock` while the holder is still running. |
339
+ | **Online art lookup failed** | Warning in logs (`iTunes artwork lookup failed` / `Failed to download iTunes artwork`); wallpaper still uses SMTC/MPRIS thumbnail or placeholder. Opt out entirely: `NOW_PLAYING_ONLINE_ART=0` or `--no-online-art`. |
340
+ | **Verbose** | `-v` / `--verbose` on any subcommand that accepts shared flags. |
341
+ | **Compose quality checks** | Set `NOW_PLAYING_COMPOSE_VERIFY=1` to run optional post-apply checks in a background thread (never blocks wallpaper set). |
342
+ | **Apply timing** | Look for `Apply took X.XXs (fetch=…, compose=…, save=…, set=…, cache=hit\|miss)` in logs; background upgrades log `Upgraded art Apply took …`. |
343
+ | **Linux unsupported platform** | Install `feh`, `swaybg`, or `nitrogen`, or use GNOME/KDE with their wallpaper tools. |
344
+ | **Local mode: no track** | Start the desktop player and press play; on Windows install `pip install -e ".[windows]"`. |
345
+ | **OAuth port in use** | Change `SPOTIPY_REDIRECT_URI` and register the new URI in the Spotify dashboard. |
346
+
347
+ ---
348
+
349
+ ## Development
350
+
351
+ ```bash
352
+ pip install -e ".[dev]"
353
+ ruff check src tests
354
+ ruff format src tests
355
+ pytest -q
356
+ ```
357
+
358
+ Profile compose at laptop resolution: `python scripts/profile_compose.py`.
359
+
360
+ ## License
361
+
362
+ MIT — see [LICENSE](LICENSE). Copyright (c) 2026 Joel Shearon.