frontengine 1.0.32__tar.gz → 1.0.34__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 (187) hide show
  1. {frontengine-1.0.32 → frontengine-1.0.34}/PKG-INFO +45 -1
  2. {frontengine-1.0.32 → frontengine-1.0.34}/README.md +44 -0
  3. frontengine-1.0.34/frontengine/show/focus_shield/focus_shield_widget.py +137 -0
  4. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/pet/desktop_pet.py +24 -94
  5. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/text/draw_text.py +12 -1
  6. frontengine-1.0.34/frontengine/show/wallpaper/wallpaper_widget.py +155 -0
  7. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/main_ui.py +25 -4
  8. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/menu/preset_menu.py +85 -0
  9. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/menu/settings_menu.py +61 -0
  10. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/control_center/control_center_ui.py +27 -2
  11. frontengine-1.0.34/frontengine/ui/page/focus/focus_setting_ui.py +211 -0
  12. frontengine-1.0.34/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +247 -0
  13. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/user_setting/user_setting_file.py +6 -0
  14. frontengine-1.0.34/frontengine/utils/autostart/autostart_service.py +172 -0
  15. frontengine-1.0.34/frontengine/utils/focus_shield/focus_shield.py +93 -0
  16. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/multi_language/english.py +39 -0
  17. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/multi_language/traditional_chinese.py +39 -0
  18. frontengine-1.0.34/frontengine/utils/platform_info/platform_info.py +303 -0
  19. frontengine-1.0.34/frontengine/utils/playlist/playlist.py +135 -0
  20. frontengine-1.0.34/frontengine/utils/plugins/__init__.py +0 -0
  21. frontengine-1.0.34/frontengine/utils/plugins/plugin_loader.py +138 -0
  22. frontengine-1.0.34/frontengine/utils/power_mode/__init__.py +0 -0
  23. frontengine-1.0.34/frontengine/utils/power_mode/power_mode.py +29 -0
  24. frontengine-1.0.34/frontengine/utils/preset_schedule/__init__.py +0 -0
  25. frontengine-1.0.34/frontengine/utils/redirect_manager/__init__.py +0 -0
  26. frontengine-1.0.34/frontengine/utils/smart_pause/__init__.py +0 -0
  27. frontengine-1.0.34/frontengine/utils/system_stats/__init__.py +0 -0
  28. frontengine-1.0.34/frontengine/utils/text_source/__init__.py +0 -0
  29. frontengine-1.0.34/frontengine/utils/theme_schedule/__init__.py +0 -0
  30. frontengine-1.0.34/frontengine/utils/weather/__init__.py +0 -0
  31. frontengine-1.0.34/frontengine/utils/workshop/__init__.py +0 -0
  32. frontengine-1.0.34/frontengine/utils/workshop/workshop_content.py +149 -0
  33. frontengine-1.0.34/frontengine/worker/__init__.py +0 -0
  34. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine.egg-info/PKG-INFO +45 -1
  35. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine.egg-info/SOURCES.txt +22 -0
  36. {frontengine-1.0.32 → frontengine-1.0.34}/pyproject.toml +1 -1
  37. {frontengine-1.0.32 → frontengine-1.0.34}/LICENSE +0 -0
  38. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/__init__.py +0 -0
  39. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/__main__.py +0 -0
  40. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/__init__.py +0 -0
  41. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/base_widget.py +0 -0
  42. {frontengine-1.0.32/frontengine/show/gif → frontengine-1.0.34/frontengine/show/focus_shield}/__init__.py +0 -0
  43. {frontengine-1.0.32/frontengine/show/image → frontengine-1.0.34/frontengine/show/gif}/__init__.py +0 -0
  44. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/gif/paint_gif.py +0 -0
  45. {frontengine-1.0.32/frontengine/show/load → frontengine-1.0.34/frontengine/show/image}/__init__.py +0 -0
  46. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/image/paint_image.py +0 -0
  47. {frontengine-1.0.32/frontengine/show/particle → frontengine-1.0.34/frontengine/show/load}/__init__.py +0 -0
  48. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/load/load_someone_make_ui.py +0 -0
  49. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/overlay_factory.py +0 -0
  50. {frontengine-1.0.32/frontengine/show/pet → frontengine-1.0.34/frontengine/show/particle}/__init__.py +0 -0
  51. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/particle/particle_ui.py +0 -0
  52. {frontengine-1.0.32/frontengine/show/presentation → frontengine-1.0.34/frontengine/show/pet}/__init__.py +0 -0
  53. {frontengine-1.0.32/frontengine/show/scene → frontengine-1.0.34/frontengine/show/presentation}/__init__.py +0 -0
  54. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/presentation/annotation_overlay.py +0 -0
  55. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/presentation/cursor_effects.py +0 -0
  56. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/presentation/keystroke_display.py +0 -0
  57. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/presentation/magnifier.py +0 -0
  58. {frontengine-1.0.32/frontengine/show/screen_care → frontengine-1.0.34/frontengine/show/scene}/__init__.py +0 -0
  59. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/scene/extend_graphic_scene.py +0 -0
  60. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/scene/extend_graphic_view.py +0 -0
  61. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/scene/scene.py +0 -0
  62. {frontengine-1.0.32/frontengine/show/sound_player → frontengine-1.0.34/frontengine/show/screen_care}/__init__.py +0 -0
  63. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/screen_care/screen_filter.py +0 -0
  64. {frontengine-1.0.32/frontengine/show/text → frontengine-1.0.34/frontengine/show/sound_player}/__init__.py +0 -0
  65. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/sound_player/sound_effect.py +0 -0
  66. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/sound_player/sound_player.py +0 -0
  67. {frontengine-1.0.32/frontengine/show/video → frontengine-1.0.34/frontengine/show/text}/__init__.py +0 -0
  68. {frontengine-1.0.32/frontengine/show/web → frontengine-1.0.34/frontengine/show/video}/__init__.py +0 -0
  69. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/video/video_player.py +0 -0
  70. {frontengine-1.0.32/frontengine/system_tray → frontengine-1.0.34/frontengine/show/wallpaper}/__init__.py +0 -0
  71. {frontengine-1.0.32/frontengine/ui → frontengine-1.0.34/frontengine/show/web}/__init__.py +0 -0
  72. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/web/webview.py +0 -0
  73. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/show/window_helpers.py +0 -0
  74. {frontengine-1.0.32/frontengine/ui/color → frontengine-1.0.34/frontengine/system_tray}/__init__.py +0 -0
  75. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/system_tray/extend_system_tray.py +0 -0
  76. {frontengine-1.0.32/frontengine/ui/dialog → frontengine-1.0.34/frontengine/ui}/__init__.py +0 -0
  77. {frontengine-1.0.32/frontengine/ui/menu → frontengine-1.0.34/frontengine/ui/color}/__init__.py +0 -0
  78. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/color/global_color.py +0 -0
  79. {frontengine-1.0.32/frontengine/ui/page → frontengine-1.0.34/frontengine/ui/dialog}/__init__.py +0 -0
  80. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
  81. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
  82. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/dialog/save_file_dialog.py +0 -0
  83. {frontengine-1.0.32/frontengine/ui/page/control_center → frontengine-1.0.34/frontengine/ui/menu}/__init__.py +0 -0
  84. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/menu/help_menu.py +0 -0
  85. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/menu/how_to_menu.py +0 -0
  86. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/menu/language_menu.py +0 -0
  87. {frontengine-1.0.32/frontengine/ui/page/gif → frontengine-1.0.34/frontengine/ui/page}/__init__.py +0 -0
  88. {frontengine-1.0.32/frontengine/ui/page/image → frontengine-1.0.34/frontengine/ui/page/control_center}/__init__.py +0 -0
  89. {frontengine-1.0.32/frontengine/ui/page/particle → frontengine-1.0.34/frontengine/ui/page/focus}/__init__.py +0 -0
  90. {frontengine-1.0.32/frontengine/ui/page/pet → frontengine-1.0.34/frontengine/ui/page/gif}/__init__.py +0 -0
  91. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
  92. {frontengine-1.0.32/frontengine/ui/page/presentation → frontengine-1.0.34/frontengine/ui/page/image}/__init__.py +0 -0
  93. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/image/image_setting_ui.py +0 -0
  94. {frontengine-1.0.32/frontengine/ui/page/scene_setting → frontengine-1.0.34/frontengine/ui/page/particle}/__init__.py +0 -0
  95. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
  96. {frontengine-1.0.32/frontengine/ui/page/scene_setting/scene_page → frontengine-1.0.34/frontengine/ui/page/pet}/__init__.py +0 -0
  97. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
  98. {frontengine-1.0.32/frontengine/ui/page/screen_care → frontengine-1.0.34/frontengine/ui/page/presentation}/__init__.py +0 -0
  99. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/presentation/presentation_setting_ui.py +0 -0
  100. {frontengine-1.0.32/frontengine/ui/page/sound_player → frontengine-1.0.34/frontengine/ui/page/scene_setting}/__init__.py +0 -0
  101. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
  102. {frontengine-1.0.32/frontengine/ui/page/text → frontengine-1.0.34/frontengine/ui/page/scene_setting/scene_page}/__init__.py +0 -0
  103. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
  104. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
  105. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
  106. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
  107. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
  108. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
  109. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
  110. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
  111. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
  112. {frontengine-1.0.32/frontengine/ui/page/video → frontengine-1.0.34/frontengine/ui/page/screen_care}/__init__.py +0 -0
  113. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +0 -0
  114. {frontengine-1.0.32/frontengine/ui/page/web → frontengine-1.0.34/frontengine/ui/page/sound_player}/__init__.py +0 -0
  115. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
  116. {frontengine-1.0.32/frontengine/user_setting → frontengine-1.0.34/frontengine/ui/page/text}/__init__.py +0 -0
  117. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/text/text_setting_ui.py +0 -0
  118. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/utils.py +0 -0
  119. {frontengine-1.0.32/frontengine/utils → frontengine-1.0.34/frontengine/ui/page/video}/__init__.py +0 -0
  120. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/video/video_setting_ui.py +0 -0
  121. {frontengine-1.0.32/frontengine/utils/audio_meter → frontengine-1.0.34/frontengine/ui/page/wallpaper}/__init__.py +0 -0
  122. {frontengine-1.0.32/frontengine/utils/break_reminder → frontengine-1.0.34/frontengine/ui/page/web}/__init__.py +0 -0
  123. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/ui/page/web/web_setting_ui.py +0 -0
  124. {frontengine-1.0.32/frontengine/utils/browser → frontengine-1.0.34/frontengine/user_setting}/__init__.py +0 -0
  125. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/user_setting/preset_repository.py +0 -0
  126. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/user_setting/scene_setting.py +0 -0
  127. {frontengine-1.0.32/frontengine/utils/critical_exit → frontengine-1.0.34/frontengine/utils}/__init__.py +0 -0
  128. {frontengine-1.0.32/frontengine/utils/file → frontengine-1.0.34/frontengine/utils/audio_meter}/__init__.py +0 -0
  129. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
  130. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/audio_meter/screen_audio.py +0 -0
  131. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/audio_meter/system_audio_meter.py +0 -0
  132. {frontengine-1.0.32/frontengine/utils/focus_timer → frontengine-1.0.34/frontengine/utils/autostart}/__init__.py +0 -0
  133. {frontengine-1.0.32/frontengine/utils/hotkey → frontengine-1.0.34/frontengine/utils/break_reminder}/__init__.py +0 -0
  134. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/break_reminder/break_reminder.py +0 -0
  135. {frontengine-1.0.32/frontengine/utils/input_watch → frontengine-1.0.34/frontengine/utils/browser}/__init__.py +0 -0
  136. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/browser/browser.py +0 -0
  137. {frontengine-1.0.32/frontengine/utils/json → frontengine-1.0.34/frontengine/utils/critical_exit}/__init__.py +0 -0
  138. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
  139. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/critical_exit/critical_exit.py +0 -0
  140. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/critical_exit/win32_vk.py +0 -0
  141. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/exception/__init__.py +0 -0
  142. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/exception/exception_tags.py +0 -0
  143. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/exception/exceptions.py +0 -0
  144. {frontengine-1.0.32/frontengine/utils/logging → frontengine-1.0.34/frontengine/utils/file}/__init__.py +0 -0
  145. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/file/open/__init__.py +0 -0
  146. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/file/open/open_file.py +0 -0
  147. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/file/save/__init__.py +0 -0
  148. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/file/save/save_file.py +0 -0
  149. {frontengine-1.0.32/frontengine/utils/multi_language → frontengine-1.0.34/frontengine/utils/focus_shield}/__init__.py +0 -0
  150. {frontengine-1.0.32/frontengine/utils/pet_chat → frontengine-1.0.34/frontengine/utils/focus_timer}/__init__.py +0 -0
  151. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/focus_timer/focus_timer.py +0 -0
  152. {frontengine-1.0.32/frontengine/utils/preset_schedule → frontengine-1.0.34/frontengine/utils/hotkey}/__init__.py +0 -0
  153. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/hotkey/hotkey_service.py +0 -0
  154. {frontengine-1.0.32/frontengine/utils/redirect_manager → frontengine-1.0.34/frontengine/utils/input_watch}/__init__.py +0 -0
  155. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/input_watch/input_watch_service.py +0 -0
  156. {frontengine-1.0.32/frontengine/utils/smart_pause → frontengine-1.0.34/frontengine/utils/json}/__init__.py +0 -0
  157. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/json/json_file.py +0 -0
  158. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/json/json_repository.py +0 -0
  159. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/json_format/__init__.py +0 -0
  160. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/json_format/json_process.py +0 -0
  161. {frontengine-1.0.32/frontengine/utils/system_stats → frontengine-1.0.34/frontengine/utils/logging}/__init__.py +0 -0
  162. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/logging/loggin_instance.py +0 -0
  163. {frontengine-1.0.32/frontengine/utils/text_source → frontengine-1.0.34/frontengine/utils/multi_language}/__init__.py +0 -0
  164. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/multi_language/france.py +0 -0
  165. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/multi_language/germany.py +0 -0
  166. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/multi_language/italy.py +0 -0
  167. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/multi_language/language_wrapper.py +0 -0
  168. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/multi_language/russian.py +0 -0
  169. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
  170. {frontengine-1.0.32/frontengine/utils/theme_schedule → frontengine-1.0.34/frontengine/utils/pet_chat}/__init__.py +0 -0
  171. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
  172. {frontengine-1.0.32/frontengine/utils/weather → frontengine-1.0.34/frontengine/utils/platform_info}/__init__.py +0 -0
  173. {frontengine-1.0.32/frontengine/worker → frontengine-1.0.34/frontengine/utils/playlist}/__init__.py +0 -0
  174. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
  175. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
  176. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
  177. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/system_stats/system_stats.py +0 -0
  178. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/text_source/text_source.py +0 -0
  179. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
  180. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/weather/weather_service.py +0 -0
  181. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/utils/web_url.py +0 -0
  182. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine/worker/qthread_worker.py +0 -0
  183. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine.egg-info/dependency_links.txt +0 -0
  184. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine.egg-info/entry_points.txt +0 -0
  185. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine.egg-info/requires.txt +0 -0
  186. {frontengine-1.0.32 → frontengine-1.0.34}/frontengine.egg-info/top_level.txt +0 -0
  187. {frontengine-1.0.32 → frontengine-1.0.34}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.32
3
+ Version: 1.0.34
4
4
  Summary: FrontEngine is use-define frontview or only use like screen saver
5
5
  Author-email: JE-Chen <jechenmailman@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/Intergration-Automation-Testing/FrontEngine
@@ -85,6 +85,50 @@ Audio features are Windows-only and degrade to "no pulse" elsewhere.
85
85
 
86
86
  ---
87
87
 
88
+ ## Focus
89
+
90
+ Two overlays for when the screen is competing with your work, both on the
91
+ **Focus** tab:
92
+
93
+ - **Dim background windows** — everything except the window you are working in
94
+ is shaded, at an adjustable strength. Off Windows, where the active window
95
+ cannot be identified without a native binding, it shades the whole screen.
96
+ - **Cover a distraction** — mask a strip of the screen: the taskbar, a
97
+ notification corner, an edge, or all of it.
98
+
99
+ Both pass clicks through, so what they cover still works — it just stops pulling
100
+ your eye.
101
+
102
+ ---
103
+
104
+ ## Wallpaper
105
+
106
+ Play a folder of images and animations *beneath* every window, from the
107
+ **Wallpaper** tab:
108
+
109
+ - **A playlist per monitor** — each screen points at its own folder, changes on
110
+ its own timer, and can be shuffled or read recursively.
111
+ - **React to audio** — the wallpaper can pulse with your speakers' output level,
112
+ using the same meter-only reading as the pet (no audio is captured).
113
+
114
+ ---
115
+
116
+ ## Community content
117
+
118
+ - **Steam Workshop** — subscribed items are picked up from Steam's own
119
+ `steamapps/workshop/content` folder: presets are imported and pet packs are
120
+ listed with their paths, from **Presets → Import Workshop content**.
121
+ Publishing to the Workshop needs the Steamworks SDK and is not built in.
122
+ - **Plugins** — a `plugins/` folder can add its own tabs, either through a
123
+ `FRONTENGINE_TABS` mapping or a `register(registry)` hook.
124
+
125
+ > A plugin is Python and runs with the same privileges as FrontEngine — it
126
+ > cannot be sandboxed. Loading is **off by default** (Settings → Load plugins),
127
+ > every load is logged, and one broken plugin is skipped rather than stopping
128
+ > the app. Only install plugins you trust.
129
+
130
+ ---
131
+
88
132
  ## Install
89
133
 
90
134
  - **System Requirements**
@@ -61,6 +61,50 @@ Audio features are Windows-only and degrade to "no pulse" elsewhere.
61
61
 
62
62
  ---
63
63
 
64
+ ## Focus
65
+
66
+ Two overlays for when the screen is competing with your work, both on the
67
+ **Focus** tab:
68
+
69
+ - **Dim background windows** — everything except the window you are working in
70
+ is shaded, at an adjustable strength. Off Windows, where the active window
71
+ cannot be identified without a native binding, it shades the whole screen.
72
+ - **Cover a distraction** — mask a strip of the screen: the taskbar, a
73
+ notification corner, an edge, or all of it.
74
+
75
+ Both pass clicks through, so what they cover still works — it just stops pulling
76
+ your eye.
77
+
78
+ ---
79
+
80
+ ## Wallpaper
81
+
82
+ Play a folder of images and animations *beneath* every window, from the
83
+ **Wallpaper** tab:
84
+
85
+ - **A playlist per monitor** — each screen points at its own folder, changes on
86
+ its own timer, and can be shuffled or read recursively.
87
+ - **React to audio** — the wallpaper can pulse with your speakers' output level,
88
+ using the same meter-only reading as the pet (no audio is captured).
89
+
90
+ ---
91
+
92
+ ## Community content
93
+
94
+ - **Steam Workshop** — subscribed items are picked up from Steam's own
95
+ `steamapps/workshop/content` folder: presets are imported and pet packs are
96
+ listed with their paths, from **Presets → Import Workshop content**.
97
+ Publishing to the Workshop needs the Steamworks SDK and is not built in.
98
+ - **Plugins** — a `plugins/` folder can add its own tabs, either through a
99
+ `FRONTENGINE_TABS` mapping or a `register(registry)` hook.
100
+
101
+ > A plugin is Python and runs with the same privileges as FrontEngine — it
102
+ > cannot be sandboxed. Loading is **off by default** (Settings → Load plugins),
103
+ > every load is logged, and one broken plugin is skipped rather than stopping
104
+ > the app. Only install plugins you trust.
105
+
106
+ ---
107
+
64
108
  ## Install
65
109
 
66
110
  - **System Requirements**
@@ -0,0 +1,137 @@
1
+ """
2
+ 專注用的兩種遮罩:
3
+ - DimBackgroundWidget:把作用中視窗以外的地方壓暗,視線自然留在手上的工作
4
+ - DistractionMaskWidget:蓋住指定的螢幕區域(工作列、通知角落)
5
+ 兩者都是點擊穿透的,所以被遮住的地方仍然可以正常點擊。
6
+
7
+ Two focus masks: one dims everything except the active window so your eye stays
8
+ on the work, the other covers a chosen strip of the screen (the taskbar, a
9
+ notification corner). Both pass clicks through, so what is covered still works.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import sys
14
+ from typing import List, Optional, Tuple
15
+
16
+ from PySide6.QtCore import QRect, QTimer
17
+ from PySide6.QtGui import QColor, QPainter
18
+
19
+ from frontengine.show.base_widget import BaseWidget
20
+ from frontengine.utils.focus_shield.focus_shield import (
21
+ REGION_BOTTOM, clamp_percent, mask_rect, surrounding_rects,
22
+ )
23
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
24
+
25
+ DEFAULT_DIM = 45
26
+
27
+
28
+ def active_window_rect() -> Optional[Tuple[int, int, int, int]]:
29
+ """
30
+ 回傳目前作用中視窗的 (x, y, w, h);取不到或非 Windows 回傳 None
31
+ (其他平台會退化成「整片壓暗」,仍然可用)。
32
+ The active window's rectangle, or None off Windows — where the dimmer simply
33
+ shades the whole screen instead.
34
+ """
35
+ if sys.platform != "win32":
36
+ return None
37
+ try:
38
+ import ctypes
39
+ from ctypes import wintypes
40
+
41
+ user32 = ctypes.windll.user32
42
+ hwnd = user32.GetForegroundWindow()
43
+ if not hwnd:
44
+ return None
45
+ rect = wintypes.RECT()
46
+ if not user32.GetWindowRect(hwnd, ctypes.byref(rect)):
47
+ return None
48
+ return (rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top)
49
+ except Exception as error: # pragma: no cover - Win32 boundary
50
+ front_engine_logger.debug(f"[focus_shield] active window unavailable: {error!r}")
51
+ return None
52
+
53
+
54
+ class DimBackgroundWidget(BaseWidget):
55
+ """
56
+ 背景變暗:只有作用中視窗保持原樣,其餘壓暗;視窗換了會自動跟上。
57
+ """
58
+
59
+ REFRESH_MS = 250
60
+
61
+ def __init__(self, dim: int = DEFAULT_DIM) -> None:
62
+ front_engine_logger.info(f"[DimBackgroundWidget] Init | dim={dim}")
63
+ super().__init__()
64
+ self.opacity = 1.0
65
+ self.dim = clamp_percent(dim, DEFAULT_DIM)
66
+ self.shaded: List[Tuple[int, int, int, int]] = []
67
+ self._window_provider = active_window_rect
68
+ self._timer = QTimer(self)
69
+ self._timer.timeout.connect(self.refresh)
70
+
71
+ def set_window_provider(self, provider) -> None:
72
+ """注入作用中視窗來源(測試用)。"""
73
+ self._window_provider = provider
74
+
75
+ def set_dim(self, dim: int) -> None:
76
+ self.dim = clamp_percent(dim, DEFAULT_DIM)
77
+ self.update()
78
+
79
+ def start_following(self, interval_ms: int = REFRESH_MS) -> None:
80
+ self.refresh()
81
+ self._timer.start(max(50, int(interval_ms)))
82
+
83
+ def screen_rect(self) -> Tuple[int, int, int, int]:
84
+ geometry = self.geometry()
85
+ return (geometry.x(), geometry.y(), geometry.width(), geometry.height())
86
+
87
+ def refresh(self) -> None:
88
+ """重新計算要壓暗的區塊。"""
89
+ try:
90
+ active = self._window_provider()
91
+ except Exception: # pragma: no cover - defensive around providers
92
+ active = None
93
+ rects = surrounding_rects(self.screen_rect(), active)
94
+ origin_x, origin_y = self.x(), self.y()
95
+ # 換算成本視窗的座標系 / Convert to this widget's own coordinates
96
+ self.shaded = [(x - origin_x, y - origin_y, w, h) for x, y, w, h in rects]
97
+ self.update()
98
+
99
+ def draw_content(self, painter: QPainter) -> None:
100
+ shade = QColor(0, 0, 0, int(255 * self.dim / 100.0))
101
+ for x, y, width, height in self.shaded:
102
+ painter.fillRect(QRect(x, y, width, height), shade)
103
+
104
+ def closeEvent(self, event) -> None:
105
+ if self._timer.isActive():
106
+ self._timer.stop()
107
+ super().closeEvent(event)
108
+
109
+
110
+ class DistractionMaskWidget(BaseWidget):
111
+ """
112
+ 干擾遮罩:把螢幕的某個區域(預設底部,正好蓋住工作列)以不透明色蓋掉。
113
+ """
114
+
115
+ def __init__(self, region: str = REGION_BOTTOM, percent: int = 6,
116
+ color: str = "#101010") -> None:
117
+ front_engine_logger.info(
118
+ f"[DistractionMaskWidget] Init | region={region}, percent={percent}")
119
+ super().__init__()
120
+ self.opacity = 1.0
121
+ self.region = region
122
+ self.percent = clamp_percent(percent)
123
+ self.mask_color = QColor(color) if QColor(color).isValid() else QColor("#101010")
124
+
125
+ def geometry_for(self, screen_rect: Tuple[int, int, int, int]) -> QRect:
126
+ """算出這個遮罩該放在哪裡、多大。"""
127
+ x, y, width, height = mask_rect(screen_rect, self.region, self.percent)
128
+ return QRect(x, y, width, height)
129
+
130
+ def set_region(self, region: Optional[str] = None, percent: Optional[int] = None) -> None:
131
+ if region is not None:
132
+ self.region = region
133
+ if percent is not None:
134
+ self.percent = clamp_percent(percent)
135
+
136
+ def draw_content(self, painter: QPainter) -> None:
137
+ painter.fillRect(self.rect(), self.mask_color)
@@ -1,6 +1,5 @@
1
1
  import json
2
2
  import random as _random_module
3
- import sys
4
3
  from datetime import datetime, timedelta
5
4
  from pathlib import Path
6
5
  from urllib.parse import urlparse
@@ -16,6 +15,12 @@ from frontengine.show.window_helpers import apply_overlay_window_flags
16
15
  from frontengine.user_setting.user_setting_file import user_setting_dict
17
16
  from frontengine.utils.audio_meter.audio_envelope import AudioEnvelope
18
17
  from frontengine.utils.logging.loggin_instance import front_engine_logger
18
+ from frontengine.utils.power_mode.power_mode import scaled_interval
19
+ from frontengine.utils.platform_info.platform_info import (
20
+ idle_seconds as platform_idle_seconds,
21
+ read_battery as platform_read_battery,
22
+ standable_windows,
23
+ )
19
24
  from frontengine.utils.multi_language.language_wrapper import language_wrapper
20
25
 
21
26
  # 行為模式 / Behaviour modes
@@ -212,75 +217,17 @@ def derive_visual_state(dragging: bool, airborne: bool, surface: str, motion_sta
212
217
  return STATE_WALK
213
218
 
214
219
 
215
- # 列舉可站立視窗時要略過的外殼視窗類別
216
- # Shell window classes skipped when enumerating standable windows.
217
- _PLATFORM_SKIP_CLASSES = {"WorkerW", "Progman", "Shell_TrayWnd", "Button"}
218
-
219
-
220
220
  def windows_platforms(exclude_hwnds=()) -> list:
221
221
  """
222
- 回傳目前可見頂層視窗的上緣做為可站立平台 (left, right, top_y)。僅 Windows
223
- 有效,其餘平台或發生錯誤時回傳空清單。
224
- Return the top edges of visible top-level windows as standable platforms
225
- (left, right, top_y). Windows only; returns [] elsewhere or on error.
222
+ 回傳目前可站立的視窗上緣 (left, right, top_y);各平台實作見 platform_info。
223
+ Standable window top edges; per-platform implementations live in platform_info.
226
224
  """
227
- if sys.platform != "win32":
228
- return []
229
- try:
230
- import ctypes
231
- from ctypes import wintypes
232
-
233
- user32 = ctypes.windll.user32
234
- exclude = {int(h) for h in exclude_hwnds}
235
- platforms: list = []
236
-
237
- @ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
238
- def _collect(hwnd, _lparam):
239
- if int(hwnd) in exclude or not user32.IsWindowVisible(hwnd):
240
- return True
241
- rect = wintypes.RECT()
242
- if not user32.GetWindowRect(hwnd, ctypes.byref(rect)):
243
- return True
244
- width = rect.right - rect.left
245
- height = rect.bottom - rect.top
246
- if width < 80 or height < 40:
247
- return True
248
- class_buffer = ctypes.create_unicode_buffer(64)
249
- user32.GetClassNameW(hwnd, class_buffer, 64)
250
- if class_buffer.value in _PLATFORM_SKIP_CLASSES:
251
- return True
252
- platforms.append((rect.left, rect.right, rect.top))
253
- return True
254
-
255
- user32.EnumWindows(_collect, 0)
256
- return platforms
257
- except Exception as error: # pragma: no cover - Win32 boundary
258
- front_engine_logger.warning(f"[windows_platforms] error: {error!r}")
259
- return []
225
+ return standable_windows(exclude_hwnds)
260
226
 
261
227
 
262
228
  def idle_seconds():
263
- """
264
- 回傳使用者未操作的秒數;無法取得時回傳 None。僅 Windows 實作。
265
- Seconds since the last user input, or None when unavailable (Windows only).
266
- """
267
- if sys.platform != "win32":
268
- return None
269
- try:
270
- import ctypes
271
-
272
- class _LASTINPUTINFO(ctypes.Structure):
273
- _fields_ = [("cbSize", ctypes.c_uint), ("dwTime", ctypes.c_uint)]
274
-
275
- info = _LASTINPUTINFO()
276
- info.cbSize = ctypes.sizeof(info)
277
- if not ctypes.windll.user32.GetLastInputInfo(ctypes.byref(info)):
278
- return None
279
- elapsed_ms = ctypes.windll.kernel32.GetTickCount() - info.dwTime
280
- return max(0.0, elapsed_ms / 1000.0)
281
- except Exception as error: # pragma: no cover - Win32 boundary
282
- front_engine_logger.warning(f"[idle_seconds] error: {error!r}")
283
- return None
229
+ """使用者未操作的秒數;取不到回傳 None(跨平台,見 platform_info)。"""
230
+ return platform_idle_seconds()
284
231
 
285
232
 
286
233
  class PetTimeline:
@@ -335,35 +282,8 @@ def pop_due_reminders(now, reminders):
335
282
 
336
283
 
337
284
  def read_battery():
338
- """
339
- 回傳 (電量百分比, 是否充電中);無法取得時回傳 None。僅 Windows 實作。
340
- Return (percent, charging) or None when unavailable. Windows only.
341
- """
342
- if sys.platform != "win32":
343
- return None
344
- try:
345
- import ctypes
346
-
347
- class _SPS(ctypes.Structure):
348
- _fields_ = [
349
- ("ACLineStatus", ctypes.c_byte),
350
- ("BatteryFlag", ctypes.c_byte),
351
- ("BatteryLifePercent", ctypes.c_byte),
352
- ("SystemStatusFlag", ctypes.c_byte),
353
- ("BatteryLifeTime", ctypes.c_ulong),
354
- ("BatteryFullLifeTime", ctypes.c_ulong),
355
- ]
356
-
357
- status = _SPS()
358
- if not ctypes.windll.kernel32.GetSystemPowerStatus(ctypes.byref(status)):
359
- return None
360
- percent = status.BatteryLifePercent & 0xFF
361
- if percent == 255: # unknown / no battery
362
- return None
363
- return (int(percent), status.ACLineStatus == 1)
364
- except Exception as error: # pragma: no cover - Win32 boundary
365
- front_engine_logger.warning(f"[read_battery] error: {error!r}")
366
- return None
285
+ """回傳 (電量百分比, 是否充電中);沒有電池或取不到回傳 None(跨平台)。"""
286
+ return platform_read_battery()
367
287
 
368
288
 
369
289
  def nearest_peer(center, peers):
@@ -1147,6 +1067,9 @@ class DesktopPetWidget(BaseWidget):
1147
1067
 
1148
1068
  self._timer = QTimer(self)
1149
1069
  self._timer.timeout.connect(self._on_tick)
1070
+ # 省電模式與原始更新間隔 / Low-power state and the base tick interval
1071
+ self._low_power = False
1072
+ self._tick_interval_ms = 33
1150
1073
 
1151
1074
  # Speech bubbles / chatter / mood
1152
1075
  self._talk = bool(talk)
@@ -1417,6 +1340,12 @@ class DesktopPetWidget(BaseWidget):
1417
1340
  painter.drawPixmap(
1418
1341
  QRect((self.width() - width) // 2, self.height() - height, width, height), pixmap)
1419
1342
 
1343
+ def set_low_power(self, enabled: bool) -> None:
1344
+ """省電模式:把移動更新拉慢(畫面較不順,但省電)。"""
1345
+ self._low_power = bool(enabled)
1346
+ if self._timer.isActive():
1347
+ self._timer.start(scaled_interval(self._tick_interval_ms, self._low_power))
1348
+
1420
1349
  def start_moving(self, bounds: Tuple[int, int, int, int], interval_ms: int = 33) -> None:
1421
1350
  front_engine_logger.info(f"[DesktopPetWidget] start_moving | bounds={bounds}")
1422
1351
  left, top, right, bottom = bounds
@@ -1424,7 +1353,8 @@ class DesktopPetWidget(BaseWidget):
1424
1353
  self.motion.x = float(left)
1425
1354
  self.motion.y = float(bottom - self.pet_size)
1426
1355
  self.move(int(self.motion.x), int(self.motion.y))
1427
- self._timer.start(max(10, int(interval_ms)))
1356
+ self._tick_interval_ms = max(10, int(interval_ms))
1357
+ self._timer.start(scaled_interval(self._tick_interval_ms, self._low_power))
1428
1358
  self._schedule_chatter()
1429
1359
  self._reminder_timer.start(10000)
1430
1360
  if self._timeline_start is None:
@@ -3,6 +3,7 @@ from PySide6.QtGui import QPainter, QFont, QColor, QFontMetrics
3
3
 
4
4
  from frontengine.show.base_widget import BaseWidget
5
5
  from frontengine.utils.logging.loggin_instance import front_engine_logger
6
+ from frontengine.utils.power_mode.power_mode import scaled_interval
6
7
 
7
8
 
8
9
  _ALIGNMENT_MAP = {
@@ -35,6 +36,7 @@ class TextWidget(BaseWidget):
35
36
  self.draw_font = QFont(self.font().family(), self.font_size)
36
37
 
37
38
  # Marquee (horizontal scroll) state
39
+ self.low_power: bool = False
38
40
  self.marquee: bool = False
39
41
  self.marquee_speed: int = 4
40
42
  self.marquee_offset: int = 0
@@ -83,7 +85,7 @@ class TextWidget(BaseWidget):
83
85
  self.marquee_speed = 4
84
86
  if self.marquee:
85
87
  self.marquee_offset = self.width()
86
- self._marquee_timer.start(30)
88
+ self._marquee_timer.start(scaled_interval(30, self.low_power))
87
89
  else:
88
90
  self._marquee_timer.stop()
89
91
 
@@ -116,6 +118,15 @@ class TextWidget(BaseWidget):
116
118
  self.text = text
117
119
  self.update()
118
120
 
121
+ def set_low_power(self, enabled: bool) -> None:
122
+ """省電模式:拉慢文字來源與跑馬燈的更新,降低重繪次數。"""
123
+ self.low_power = bool(enabled)
124
+ if self._source_timer.isActive() and self.text_source is not None:
125
+ self._source_timer.start(scaled_interval(
126
+ getattr(self.text_source, "refresh_interval_ms", 1000), self.low_power))
127
+ if self._marquee_timer.isActive():
128
+ self._marquee_timer.start(scaled_interval(30, self.low_power))
129
+
119
130
  def _tick_marquee(self) -> None:
120
131
  text_width = QFontMetrics(self.draw_font).horizontalAdvance(self.text)
121
132
  self.marquee_offset -= self.marquee_speed
@@ -0,0 +1,155 @@
1
+ """
2
+ 桌布覆蓋層:在所有視窗底下播放圖片或動圖,可依系統音量脈動(音訊反應桌布),
3
+ 並由播放清單定時換張。音量來源沿用寵物已經在用的系統峰值電表,只讀電表、
4
+ 不擷取音訊內容。
5
+
6
+ A wallpaper layer: plays an image or animation beneath every window, optionally
7
+ pulsing with the system output level, with a playlist swapping it over time.
8
+ The level comes from the same meter the pet uses — a meter reading only, never
9
+ captured audio.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+ from PySide6.QtCore import QRect, QTimer
17
+ from PySide6.QtGui import QMovie, QPainter, QPixmap
18
+
19
+ from frontengine.show.base_widget import BaseWidget
20
+ from frontengine.utils.audio_meter.audio_envelope import AudioEnvelope
21
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
22
+
23
+ _ANIMATED_SUFFIXES = (".gif", ".webp")
24
+ MIN_REACT_SCALE = 1.0
25
+ MAX_REACT_SCALE = 1.25
26
+
27
+
28
+ def react_scale(level, strength: int = 100) -> float:
29
+ """
30
+ 把音量 (0~1) 換成桌布的縮放比例:安靜時 1.0,最大聲時最多放大 25%。
31
+ strength 是效果強度百分比,0 代表完全不反應。
32
+ Map an audio level to a wallpaper scale: 1.0 when quiet, up to +25% loud.
33
+ """
34
+ try:
35
+ clamped = max(0.0, min(1.0, float(level)))
36
+ except (TypeError, ValueError):
37
+ return MIN_REACT_SCALE
38
+ try:
39
+ share = max(0, min(100, int(strength))) / 100.0
40
+ except (TypeError, ValueError):
41
+ share = 1.0
42
+ return MIN_REACT_SCALE + (MAX_REACT_SCALE - MIN_REACT_SCALE) * clamped * share
43
+
44
+
45
+ class WallpaperWidget(BaseWidget):
46
+ """
47
+ 桌布:顯示一張圖或一段動圖,位於所有視窗底下;可選擇隨系統音量脈動。
48
+ """
49
+
50
+ REACT_INTERVAL_MS = 33
51
+
52
+ def __init__(self, media_path: str = "", audio_react: bool = False,
53
+ react_strength: int = 100) -> None:
54
+ front_engine_logger.info(
55
+ f"[WallpaperWidget] Init | path={media_path}, audio_react={audio_react}")
56
+ super().__init__()
57
+ self.opacity = 1.0
58
+ self.media_path: Optional[Path] = None
59
+ self._movie: Optional[QMovie] = None
60
+ self._pixmap: Optional[QPixmap] = None
61
+ self.audio_react = bool(audio_react)
62
+ self.react_strength = max(0, min(100, int(react_strength)))
63
+ self.scale = MIN_REACT_SCALE
64
+ self._level_provider = None
65
+ self._envelope = AudioEnvelope()
66
+ self._react_timer = QTimer(self)
67
+ self._react_timer.timeout.connect(self._update_scale)
68
+ if media_path:
69
+ self.set_media(media_path)
70
+
71
+ def set_media(self, media_path: str) -> bool:
72
+ """換一張桌布;載不起來時保留原本的並回傳 False。"""
73
+ path = Path(media_path)
74
+ if not path.is_file():
75
+ front_engine_logger.warning(f"[WallpaperWidget] missing media: {path}")
76
+ return False
77
+ if path.suffix.lower() in _ANIMATED_SUFFIXES:
78
+ movie = QMovie(str(path))
79
+ if not movie.isValid():
80
+ return False
81
+ self._stop_movie()
82
+ self._movie = movie
83
+ self._pixmap = None
84
+ movie.frameChanged.connect(self.update)
85
+ movie.start()
86
+ else:
87
+ pixmap = QPixmap(str(path))
88
+ if pixmap.isNull():
89
+ return False
90
+ self._stop_movie()
91
+ self._pixmap = pixmap
92
+ self._movie = None
93
+ self.media_path = path
94
+ self.update()
95
+ return True
96
+
97
+ def set_audio_level_provider(self, provider) -> None:
98
+ """設定音量來源(回傳 0~1 或 None)。"""
99
+ self._level_provider = provider
100
+ self._envelope.reset()
101
+
102
+ def set_audio_react(self, enabled: bool, strength: Optional[int] = None) -> None:
103
+ """開關音訊反應並視需要調整強度。"""
104
+ self.audio_react = bool(enabled)
105
+ if strength is not None:
106
+ self.react_strength = max(0, min(100, int(strength)))
107
+ if self.audio_react:
108
+ self._react_timer.start(self.REACT_INTERVAL_MS)
109
+ else:
110
+ self._react_timer.stop()
111
+ self.scale = MIN_REACT_SCALE
112
+ self.update()
113
+
114
+ def _update_scale(self) -> None:
115
+ """取一次音量、平滑後換成縮放比例。"""
116
+ if not self.audio_react or self._level_provider is None:
117
+ self.scale = MIN_REACT_SCALE
118
+ return
119
+ try:
120
+ level = self._level_provider()
121
+ except Exception: # pragma: no cover - defensive around providers
122
+ level = None
123
+ if level is None:
124
+ self.scale = MIN_REACT_SCALE
125
+ self._envelope.reset()
126
+ else:
127
+ self.scale = react_scale(self._envelope.push(level), self.react_strength)
128
+ self.update()
129
+
130
+ def _stop_movie(self) -> None:
131
+ if self._movie is not None:
132
+ self._movie.stop()
133
+ self._movie = None
134
+
135
+ def current_pixmap(self) -> Optional[QPixmap]:
136
+ if self._movie is not None:
137
+ return self._movie.currentPixmap()
138
+ return self._pixmap
139
+
140
+ def draw_content(self, painter: QPainter) -> None:
141
+ pixmap = self.current_pixmap()
142
+ if pixmap is None or pixmap.isNull():
143
+ return
144
+ width = int(self.width() * self.scale)
145
+ height = int(self.height() * self.scale)
146
+ # 放大時往外溢出,維持置中,畫面才不會出現黑邊
147
+ # Overflow outward and stay centred so no bars appear while pulsing.
148
+ painter.drawPixmap(
149
+ QRect((self.width() - width) // 2, (self.height() - height) // 2, width, height), pixmap)
150
+
151
+ def closeEvent(self, event) -> None:
152
+ if self._react_timer.isActive():
153
+ self._react_timer.stop()
154
+ self._stop_movie()
155
+ super().closeEvent(event)