Skip to content

Commit 2fc57f7

Browse files
committed
refactor(wallpapers): centralize engine state handling in WallpaperManager
- Move engine enable/disable logic from WallpaperEngine into WallpaperManager.set_wallpaper() - Add finished signal to WallpaperEngine instead of calling WallpaperManager directly - Remove engine param from ImageGallery, callers now use unified manager API - Consolidate set_wallpaper and _commit_wallpaper into single function - Fix duplicate _run_after_thread call in change_background
1 parent 4f1b310 commit 2fc57f7

4 files changed

Lines changed: 56 additions & 45 deletions

File tree

src/core/widgets/services/wallpapers/wallpaper_engine.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import ctypes
6+
import logging
67
import math
78
import os
89
import winreg
@@ -22,8 +23,7 @@
2223
WS_POPUP,
2324
)
2425

25-
from core.widgets.services.wallpapers.wallpaper_manager import WallpaperManager
26-
26+
logger = logging.getLogger("wallpaper_engine")
2727
user32 = ctypes.WinDLL("user32", use_last_error=True)
2828

2929
HWND = wintypes.HWND
@@ -153,6 +153,9 @@ def _read_background_color() -> QColor:
153153
def _locate_workerw() -> int:
154154
"""Find the WorkerW window that sits behind desktop icons."""
155155
progman = user32.FindWindowW("Progman", None)
156+
if not progman:
157+
logger.warning("Could not locate Progman. Wallpaper animation skipped.")
158+
return 0
156159
user32.SendMessageTimeoutW(progman, WM_SPAWN_WORKER, 0, 0, 0, 1000, ctypes.byref(ULONG_PTR()))
157160
worker = HWND()
158161

@@ -187,14 +190,17 @@ def _enum_proc(hwnd, _):
187190
user32.EnumWindows(_enum_proc, 0)
188191

189192
if not worker:
190-
raise RuntimeError("Could not locate WorkerW")
193+
logger.warning("Could not locate WorkerW. Wallpaper animation skipped.")
194+
return 0
191195
user32.ShowWindow(worker, 5)
192196
return worker
193197

194198

195-
def _attach_to_workerw(widget: QWidget) -> None:
199+
def _attach_to_workerw(widget: QWidget) -> bool:
196200
"""Parent *widget* to WorkerW and compute per-monitor screen areas."""
197201
worker = _locate_workerw()
202+
if not worker:
203+
return False
198204
hwnd = HWND(int(widget.winId()))
199205

200206
exstyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE)
@@ -216,6 +222,7 @@ def _attach_to_workerw(widget: QWidget) -> None:
216222
widget.set_screen_areas(areas)
217223

218224
user32.SetWindowPos(hwnd, HWND_TOP, 0, 0, ww, wh, SWP_NOACTIVATE | SWP_FRAMECHANGED)
225+
return True
219226

220227

221228
class _ImageLoader(QThread):
@@ -238,6 +245,8 @@ class WallpaperEngine(QWidget):
238245
_ANIMATION_MS = 1200
239246
_FRAME_MS = 16
240247

248+
finished = pyqtSignal()
249+
241250
def __init__(self, image_path: str, animation: str = "circle") -> None:
242251
super().__init__()
243252
self._image_path = image_path
@@ -277,17 +286,18 @@ def _on_images_loaded(self, new_img: QImage, old_img: QImage) -> None:
277286
self._pixmap_new = QPixmap.fromImage(new_img)
278287
self._pixmap_old = QPixmap.fromImage(old_img)
279288

280-
# If either image fails to load, skip animation and set wallpaper immediately
289+
# If either image fails to load, skip animation and let caller commit immediately
281290
if self._pixmap_new.isNull() or self._pixmap_old.isNull():
282-
try:
283-
WallpaperManager().set_wallpaper(self._image_path)
284-
except Exception:
285-
pass
291+
self.finished.emit()
286292
self.deleteLater()
287293
return
288294

289295
self.setWindowOpacity(0.0)
290-
_attach_to_workerw(self)
296+
if not _attach_to_workerw(self):
297+
self.finished.emit()
298+
self.deleteLater()
299+
return
300+
291301
self.show()
292302
QTimer.singleShot(0, self._start_fade_in)
293303

@@ -487,10 +497,7 @@ def _on_finished(self) -> None:
487497
self.update()
488498
if not self._committed:
489499
self._committed = True
490-
try:
491-
WallpaperManager().set_wallpaper(self._image_path)
492-
except Exception:
493-
pass
500+
self.finished.emit()
494501

495502
def _clip_path_for_monitor(self, dx: int, dy: int, dw: int, dh: int, t: float) -> QPainterPath:
496503
"""Reveal shape for the new wallpaper."""

src/core/widgets/services/wallpapers/wallpaper_manager.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from core.events.service import EventService
1313
from core.utils.win32.bindings.shell32 import IDesktopWallpaper
14+
from core.widgets.services.wallpapers.wallpaper_engine import WallpaperEngine
1415

1516

1617
class WallpaperManager(QObject):
@@ -37,6 +38,8 @@ def __init__(self):
3738
self._is_running = False
3839
self._last_image = None
3940
self._timer_running = False
41+
self._engine_config = None
42+
self._engine = None
4043

4144
self._event_service = EventService()
4245

@@ -45,7 +48,12 @@ def __init__(self):
4548
self._event_service.register_event("set_wallpaper_signal", self._set_wallpaper_signal)
4649

4750
def configure(
48-
self, image_path: str | list[str], update_interval: int, change_automatically: bool, run_after: list[str]
51+
self,
52+
image_path: str | list[str],
53+
update_interval: int,
54+
change_automatically: bool,
55+
run_after: list[str],
56+
engine=None,
4957
):
5058
"""
5159
Configure the manager.
@@ -56,6 +64,7 @@ def configure(
5664
self._image_paths = image_path
5765

5866
self._run_after = run_after
67+
self._engine_config = engine
5968

6069
if change_automatically and not self._timer_running:
6170
if update_interval and update_interval > 0:
@@ -68,23 +77,27 @@ def configure(
6877
def _timer_callback(self):
6978
self.change_background()
7079

71-
def set_wallpaper(self, image_path: str, monitor_id: str | None = None):
72-
"""
73-
Set the desktop wallpaper using the IDesktopWallpaper COM interface.
74-
Args:
75-
image_path: Absolute path to the wallpaper image file
76-
monitor_id: Monitor device path from GetMonitorDevicePathAt, or None for all monitors
77-
"""
80+
def set_wallpaper(self, image_path: str, monitor_id: str | None = None, animate: bool = True):
81+
"""Set the desktop wallpaper, using the YASB engine animation when enabled."""
82+
eng = self._engine_config
83+
if animate and monitor_id is None and eng and eng.enabled:
84+
try:
85+
self._engine = WallpaperEngine(image_path, eng.animation)
86+
self._engine.finished.connect(lambda: self.set_wallpaper(image_path, animate=False))
87+
self._engine.start()
88+
return
89+
except Exception as e:
90+
logging.error("Wallpaper engine failed, falling back: %s", e)
91+
7892
pythoncom.CoInitialize()
7993
try:
80-
desktop_wallpaper_clsid = GUID("{C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD}")
81-
desktop_wallpaper = comtypes.client.CreateObject(desktop_wallpaper_clsid, interface=IDesktopWallpaper)
82-
abs_path = os.path.abspath(image_path)
83-
desktop_wallpaper.SetWallpaper(monitor_id, abs_path)
84-
94+
clsid = GUID("{C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD}")
95+
dwp = comtypes.client.CreateObject(clsid, interface=IDesktopWallpaper)
96+
dwp.SetWallpaper(monitor_id, os.path.abspath(image_path))
8597
except Exception as e:
86-
logging.error("Failed to set wallpaper using IDesktopWallpaper: %s", e)
87-
raise
98+
logging.error("Failed to set wallpaper: %s", e)
99+
self._is_running = False
100+
return
88101
self._run_after_thread(image_path)
89102

90103
def get_monitor_ids(self) -> list[str]:
@@ -139,7 +152,7 @@ def change_background(self, image_path: str = None):
139152
self._last_image = new_wallpaper
140153
except Exception as e:
141154
logging.error("Error setting wallpaper %s: %s", new_wallpaper, e)
142-
self._run_after_thread(new_wallpaper)
155+
self._is_running = False
143156

144157
def _run_after_thread(self, image_path: str):
145158
if self._run_after:

src/core/widgets/services/wallpapers/wallpapers_gallery.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
from core.utils.win32.backdrop import enable_blur
3535
from core.utils.win32.utils import apply_qmenu_style
3636
from core.utils.win32.window_actions import force_foreground_focus
37-
from core.widgets.services.wallpapers.wallpaper_engine import WallpaperEngine
3837
from core.widgets.services.wallpapers.wallpaper_manager import WallpaperManager
3938

4039

@@ -159,10 +158,9 @@ def run(self):
159158
class ImageGallery(QMainWindow):
160159
"""ImageGallery displays a gallery of images with navigation and lazy loading features."""
161160

162-
def __init__(self, image_paths, gallery, engine=None):
161+
def __init__(self, image_paths, gallery):
163162
super().__init__()
164163
self.gallery = gallery
165-
self.engine = engine # EngineConfig | None
166164

167165
if isinstance(image_paths, str):
168166
self.image_paths = [image_paths]
@@ -684,20 +682,13 @@ def show_context_menu_for_image(self, index: int, pos):
684682
def _apply_wallpaper_on_monitor(self, image_path: str, monitor_id: str) -> None:
685683
"""Set wallpaper on a specific monitor (no animation)."""
686684
self.fade_out_and_close_gallery()
687-
WallpaperManager().set_wallpaper(image_path, monitor_id=monitor_id)
685+
WallpaperManager().set_wallpaper(image_path, monitor_id=monitor_id, animate=False)
688686

689687
def _apply_wallpaper(self, image_path: str, monitor_id: str | None) -> None:
690-
"""Apply wallpaper with engine animation (all screens) or direct per-monitor."""
688+
"""Apply wallpaper. The manager handles engine animation and fallbacks."""
691689
if monitor_id is None:
692-
animation = self.engine.animation if self.engine else "circle"
693-
if self.engine and self.engine.enabled:
694-
self.fade_out_and_close_gallery()
695-
self._engine = WallpaperEngine(image_path, animation)
696-
self._engine.destroyed.connect(lambda: setattr(self, "_engine", None))
697-
self._engine.start()
698-
else:
699-
self.fade_out_and_close_gallery()
700-
WallpaperManager().set_wallpaper(image_path)
690+
self.fade_out_and_close_gallery()
691+
WallpaperManager().set_wallpaper(image_path)
701692
else:
702693
self._apply_wallpaper_on_monitor(image_path, monitor_id)
703694

src/core/widgets/yasb/wallpapers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def __init__(self, config: WallpapersConfig):
2020
self.config.update_interval,
2121
self.config.change_automatically,
2222
self.config.run_after,
23+
self.config.engine,
2324
)
2425

2526
# Connect signals
@@ -56,6 +57,5 @@ def _toggle_widget(self):
5657
self._image_gallery = ImageGallery(
5758
self.config.image_path,
5859
self.config.gallery.model_dump(),
59-
self.config.engine,
6060
)
6161
self._image_gallery.fade_in_gallery(parent=self)

0 commit comments

Comments
 (0)