frontengine 1.0.33__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.33 → frontengine-1.0.34}/PKG-INFO +45 -1
  2. {frontengine-1.0.33 → 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.33 → frontengine-1.0.34}/frontengine/show/pet/desktop_pet.py +0 -1
  5. frontengine-1.0.34/frontengine/show/wallpaper/wallpaper_widget.py +155 -0
  6. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/main_ui.py +15 -1
  7. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/menu/preset_menu.py +64 -0
  8. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/menu/settings_menu.py +24 -0
  9. frontengine-1.0.34/frontengine/ui/page/focus/focus_setting_ui.py +211 -0
  10. frontengine-1.0.34/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +247 -0
  11. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/user_setting/user_setting_file.py +3 -0
  12. frontengine-1.0.34/frontengine/utils/focus_shield/focus_shield.py +93 -0
  13. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/english.py +34 -0
  14. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/traditional_chinese.py +34 -0
  15. frontengine-1.0.34/frontengine/utils/playlist/playlist.py +135 -0
  16. frontengine-1.0.34/frontengine/utils/plugins/plugin_loader.py +138 -0
  17. frontengine-1.0.34/frontengine/utils/redirect_manager/__init__.py +0 -0
  18. frontengine-1.0.34/frontengine/utils/smart_pause/__init__.py +0 -0
  19. frontengine-1.0.34/frontengine/utils/system_stats/__init__.py +0 -0
  20. frontengine-1.0.34/frontengine/utils/text_source/__init__.py +0 -0
  21. frontengine-1.0.34/frontengine/utils/theme_schedule/__init__.py +0 -0
  22. frontengine-1.0.34/frontengine/utils/weather/__init__.py +0 -0
  23. frontengine-1.0.34/frontengine/utils/workshop/__init__.py +0 -0
  24. frontengine-1.0.34/frontengine/utils/workshop/workshop_content.py +149 -0
  25. frontengine-1.0.34/frontengine/worker/__init__.py +0 -0
  26. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/PKG-INFO +45 -1
  27. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/SOURCES.txt +16 -0
  28. {frontengine-1.0.33 → frontengine-1.0.34}/pyproject.toml +1 -1
  29. {frontengine-1.0.33 → frontengine-1.0.34}/LICENSE +0 -0
  30. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/__init__.py +0 -0
  31. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/__main__.py +0 -0
  32. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/__init__.py +0 -0
  33. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/base_widget.py +0 -0
  34. {frontengine-1.0.33/frontengine/show/gif → frontengine-1.0.34/frontengine/show/focus_shield}/__init__.py +0 -0
  35. {frontengine-1.0.33/frontengine/show/image → frontengine-1.0.34/frontengine/show/gif}/__init__.py +0 -0
  36. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/gif/paint_gif.py +0 -0
  37. {frontengine-1.0.33/frontengine/show/load → frontengine-1.0.34/frontengine/show/image}/__init__.py +0 -0
  38. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/image/paint_image.py +0 -0
  39. {frontengine-1.0.33/frontengine/show/particle → frontengine-1.0.34/frontengine/show/load}/__init__.py +0 -0
  40. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/load/load_someone_make_ui.py +0 -0
  41. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/overlay_factory.py +0 -0
  42. {frontengine-1.0.33/frontengine/show/pet → frontengine-1.0.34/frontengine/show/particle}/__init__.py +0 -0
  43. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/particle/particle_ui.py +0 -0
  44. {frontengine-1.0.33/frontengine/show/presentation → frontengine-1.0.34/frontengine/show/pet}/__init__.py +0 -0
  45. {frontengine-1.0.33/frontengine/show/scene → frontengine-1.0.34/frontengine/show/presentation}/__init__.py +0 -0
  46. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/presentation/annotation_overlay.py +0 -0
  47. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/presentation/cursor_effects.py +0 -0
  48. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/presentation/keystroke_display.py +0 -0
  49. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/presentation/magnifier.py +0 -0
  50. {frontengine-1.0.33/frontengine/show/screen_care → frontengine-1.0.34/frontengine/show/scene}/__init__.py +0 -0
  51. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/scene/extend_graphic_scene.py +0 -0
  52. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/scene/extend_graphic_view.py +0 -0
  53. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/scene/scene.py +0 -0
  54. {frontengine-1.0.33/frontengine/show/sound_player → frontengine-1.0.34/frontengine/show/screen_care}/__init__.py +0 -0
  55. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/screen_care/screen_filter.py +0 -0
  56. {frontengine-1.0.33/frontengine/show/text → frontengine-1.0.34/frontengine/show/sound_player}/__init__.py +0 -0
  57. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/sound_player/sound_effect.py +0 -0
  58. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/sound_player/sound_player.py +0 -0
  59. {frontengine-1.0.33/frontengine/show/video → frontengine-1.0.34/frontengine/show/text}/__init__.py +0 -0
  60. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/text/draw_text.py +0 -0
  61. {frontengine-1.0.33/frontengine/show/web → frontengine-1.0.34/frontengine/show/video}/__init__.py +0 -0
  62. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/video/video_player.py +0 -0
  63. {frontengine-1.0.33/frontengine/system_tray → frontengine-1.0.34/frontengine/show/wallpaper}/__init__.py +0 -0
  64. {frontengine-1.0.33/frontengine/ui → frontengine-1.0.34/frontengine/show/web}/__init__.py +0 -0
  65. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/web/webview.py +0 -0
  66. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/window_helpers.py +0 -0
  67. {frontengine-1.0.33/frontengine/ui/color → frontengine-1.0.34/frontengine/system_tray}/__init__.py +0 -0
  68. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/system_tray/extend_system_tray.py +0 -0
  69. {frontengine-1.0.33/frontengine/ui/dialog → frontengine-1.0.34/frontengine/ui}/__init__.py +0 -0
  70. {frontengine-1.0.33/frontengine/ui/menu → frontengine-1.0.34/frontengine/ui/color}/__init__.py +0 -0
  71. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/color/global_color.py +0 -0
  72. {frontengine-1.0.33/frontengine/ui/page → frontengine-1.0.34/frontengine/ui/dialog}/__init__.py +0 -0
  73. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
  74. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
  75. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/dialog/save_file_dialog.py +0 -0
  76. {frontengine-1.0.33/frontengine/ui/page/control_center → frontengine-1.0.34/frontengine/ui/menu}/__init__.py +0 -0
  77. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/menu/help_menu.py +0 -0
  78. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/menu/how_to_menu.py +0 -0
  79. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/menu/language_menu.py +0 -0
  80. {frontengine-1.0.33/frontengine/ui/page/gif → frontengine-1.0.34/frontengine/ui/page}/__init__.py +0 -0
  81. {frontengine-1.0.33/frontengine/ui/page/image → frontengine-1.0.34/frontengine/ui/page/control_center}/__init__.py +0 -0
  82. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/control_center/control_center_ui.py +0 -0
  83. {frontengine-1.0.33/frontengine/ui/page/particle → frontengine-1.0.34/frontengine/ui/page/focus}/__init__.py +0 -0
  84. {frontengine-1.0.33/frontengine/ui/page/pet → frontengine-1.0.34/frontengine/ui/page/gif}/__init__.py +0 -0
  85. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
  86. {frontengine-1.0.33/frontengine/ui/page/presentation → frontengine-1.0.34/frontengine/ui/page/image}/__init__.py +0 -0
  87. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/image/image_setting_ui.py +0 -0
  88. {frontengine-1.0.33/frontengine/ui/page/scene_setting → frontengine-1.0.34/frontengine/ui/page/particle}/__init__.py +0 -0
  89. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
  90. {frontengine-1.0.33/frontengine/ui/page/scene_setting/scene_page → frontengine-1.0.34/frontengine/ui/page/pet}/__init__.py +0 -0
  91. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
  92. {frontengine-1.0.33/frontengine/ui/page/screen_care → frontengine-1.0.34/frontengine/ui/page/presentation}/__init__.py +0 -0
  93. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/presentation/presentation_setting_ui.py +0 -0
  94. {frontengine-1.0.33/frontengine/ui/page/sound_player → frontengine-1.0.34/frontengine/ui/page/scene_setting}/__init__.py +0 -0
  95. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
  96. {frontengine-1.0.33/frontengine/ui/page/text → frontengine-1.0.34/frontengine/ui/page/scene_setting/scene_page}/__init__.py +0 -0
  97. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
  98. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
  99. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
  100. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
  101. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
  102. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
  103. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
  104. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
  105. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
  106. {frontengine-1.0.33/frontengine/ui/page/video → frontengine-1.0.34/frontengine/ui/page/screen_care}/__init__.py +0 -0
  107. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +0 -0
  108. {frontengine-1.0.33/frontengine/ui/page/web → frontengine-1.0.34/frontengine/ui/page/sound_player}/__init__.py +0 -0
  109. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
  110. {frontengine-1.0.33/frontengine/user_setting → frontengine-1.0.34/frontengine/ui/page/text}/__init__.py +0 -0
  111. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/text/text_setting_ui.py +0 -0
  112. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/utils.py +0 -0
  113. {frontengine-1.0.33/frontengine/utils → frontengine-1.0.34/frontengine/ui/page/video}/__init__.py +0 -0
  114. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/video/video_setting_ui.py +0 -0
  115. {frontengine-1.0.33/frontengine/utils/audio_meter → frontengine-1.0.34/frontengine/ui/page/wallpaper}/__init__.py +0 -0
  116. {frontengine-1.0.33/frontengine/utils/autostart → frontengine-1.0.34/frontengine/ui/page/web}/__init__.py +0 -0
  117. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/web/web_setting_ui.py +0 -0
  118. {frontengine-1.0.33/frontengine/utils/break_reminder → frontengine-1.0.34/frontengine/user_setting}/__init__.py +0 -0
  119. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/user_setting/preset_repository.py +0 -0
  120. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/user_setting/scene_setting.py +0 -0
  121. {frontengine-1.0.33/frontengine/utils/browser → frontengine-1.0.34/frontengine/utils}/__init__.py +0 -0
  122. {frontengine-1.0.33/frontengine/utils/critical_exit → frontengine-1.0.34/frontengine/utils/audio_meter}/__init__.py +0 -0
  123. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
  124. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/audio_meter/screen_audio.py +0 -0
  125. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/audio_meter/system_audio_meter.py +0 -0
  126. {frontengine-1.0.33/frontengine/utils/file → frontengine-1.0.34/frontengine/utils/autostart}/__init__.py +0 -0
  127. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/autostart/autostart_service.py +0 -0
  128. {frontengine-1.0.33/frontengine/utils/focus_timer → frontengine-1.0.34/frontengine/utils/break_reminder}/__init__.py +0 -0
  129. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/break_reminder/break_reminder.py +0 -0
  130. {frontengine-1.0.33/frontengine/utils/hotkey → frontengine-1.0.34/frontengine/utils/browser}/__init__.py +0 -0
  131. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/browser/browser.py +0 -0
  132. {frontengine-1.0.33/frontengine/utils/input_watch → frontengine-1.0.34/frontengine/utils/critical_exit}/__init__.py +0 -0
  133. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
  134. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/critical_exit/critical_exit.py +0 -0
  135. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/critical_exit/win32_vk.py +0 -0
  136. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/exception/__init__.py +0 -0
  137. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/exception/exception_tags.py +0 -0
  138. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/exception/exceptions.py +0 -0
  139. {frontengine-1.0.33/frontengine/utils/json → frontengine-1.0.34/frontengine/utils/file}/__init__.py +0 -0
  140. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/file/open/__init__.py +0 -0
  141. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/file/open/open_file.py +0 -0
  142. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/file/save/__init__.py +0 -0
  143. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/file/save/save_file.py +0 -0
  144. {frontengine-1.0.33/frontengine/utils/logging → frontengine-1.0.34/frontengine/utils/focus_shield}/__init__.py +0 -0
  145. {frontengine-1.0.33/frontengine/utils/multi_language → frontengine-1.0.34/frontengine/utils/focus_timer}/__init__.py +0 -0
  146. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/focus_timer/focus_timer.py +0 -0
  147. {frontengine-1.0.33/frontengine/utils/pet_chat → frontengine-1.0.34/frontengine/utils/hotkey}/__init__.py +0 -0
  148. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/hotkey/hotkey_service.py +0 -0
  149. {frontengine-1.0.33/frontengine/utils/platform_info → frontengine-1.0.34/frontengine/utils/input_watch}/__init__.py +0 -0
  150. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/input_watch/input_watch_service.py +0 -0
  151. {frontengine-1.0.33/frontengine/utils/power_mode → frontengine-1.0.34/frontengine/utils/json}/__init__.py +0 -0
  152. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/json/json_file.py +0 -0
  153. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/json/json_repository.py +0 -0
  154. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/json_format/__init__.py +0 -0
  155. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/json_format/json_process.py +0 -0
  156. {frontengine-1.0.33/frontengine/utils/preset_schedule → frontengine-1.0.34/frontengine/utils/logging}/__init__.py +0 -0
  157. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/logging/loggin_instance.py +0 -0
  158. {frontengine-1.0.33/frontengine/utils/redirect_manager → frontengine-1.0.34/frontengine/utils/multi_language}/__init__.py +0 -0
  159. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/france.py +0 -0
  160. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/germany.py +0 -0
  161. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/italy.py +0 -0
  162. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/language_wrapper.py +0 -0
  163. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/russian.py +0 -0
  164. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
  165. {frontengine-1.0.33/frontengine/utils/smart_pause → frontengine-1.0.34/frontengine/utils/pet_chat}/__init__.py +0 -0
  166. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
  167. {frontengine-1.0.33/frontengine/utils/system_stats → frontengine-1.0.34/frontengine/utils/platform_info}/__init__.py +0 -0
  168. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/platform_info/platform_info.py +0 -0
  169. {frontengine-1.0.33/frontengine/utils/text_source → frontengine-1.0.34/frontengine/utils/playlist}/__init__.py +0 -0
  170. {frontengine-1.0.33/frontengine/utils/theme_schedule → frontengine-1.0.34/frontengine/utils/plugins}/__init__.py +0 -0
  171. {frontengine-1.0.33/frontengine/utils/weather → frontengine-1.0.34/frontengine/utils/power_mode}/__init__.py +0 -0
  172. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/power_mode/power_mode.py +0 -0
  173. {frontengine-1.0.33/frontengine/worker → frontengine-1.0.34/frontengine/utils/preset_schedule}/__init__.py +0 -0
  174. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
  175. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
  176. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
  177. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/system_stats/system_stats.py +0 -0
  178. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/text_source/text_source.py +0 -0
  179. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
  180. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/weather/weather_service.py +0 -0
  181. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/web_url.py +0 -0
  182. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/worker/qthread_worker.py +0 -0
  183. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/dependency_links.txt +0 -0
  184. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/entry_points.txt +0 -0
  185. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/requires.txt +0 -0
  186. {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/top_level.txt +0 -0
  187. {frontengine-1.0.33 → 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.33
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
@@ -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)
@@ -4,7 +4,7 @@ from pathlib import Path
4
4
  from typing import Dict, Optional, Type
5
5
 
6
6
  from PySide6.QtCore import QByteArray, QTimer, QCoreApplication
7
- from PySide6.QtGui import QIcon, Qt, QSurfaceFormat
7
+ from PySide6.QtGui import QIcon, Qt
8
8
  from PySide6.QtWidgets import QMainWindow, QApplication, QGridLayout, QTabWidget, QMenuBar, QWidget
9
9
  from qt_material import apply_stylesheet
10
10
 
@@ -18,6 +18,7 @@ from frontengine.ui.menu.preset_menu import (
18
18
  )
19
19
  from frontengine.ui.menu.settings_menu import build_settings_menu
20
20
  from frontengine.ui.page.control_center.control_center_ui import ControlCenterUI
21
+ from frontengine.ui.page.focus.focus_setting_ui import FocusSettingUI
21
22
  from frontengine.ui.page.gif.gif_setting_ui import GIFSettingUI
22
23
  from frontengine.ui.page.image.image_setting_ui import ImageSettingUI
23
24
  from frontengine.ui.page.particle.particle_setting_ui import ParticleSettingUI
@@ -28,6 +29,7 @@ from frontengine.ui.page.screen_care.screen_care_setting_ui import ScreenCareSet
28
29
  from frontengine.ui.page.sound_player.sound_player_setting_ui import SoundPlayerSettingUI
29
30
  from frontengine.ui.page.text.text_setting_ui import TextSettingUI
30
31
  from frontengine.ui.page.video.video_setting_ui import VideoSettingUI
32
+ from frontengine.ui.page.wallpaper.wallpaper_setting_ui import WallpaperSettingUI
31
33
  from frontengine.ui.page.web.web_setting_ui import WEBSettingUI
32
34
  from frontengine.user_setting.user_setting_file import (
33
35
  get_hotkey_bindings,
@@ -40,6 +42,7 @@ from frontengine.utils.critical_exit.win32_vk import keyboard_keys_table
40
42
  from frontengine.utils.hotkey.hotkey_service import HotkeyService
41
43
  from frontengine.utils.logging.loggin_instance import front_engine_logger
42
44
  from frontengine.utils.multi_language.language_wrapper import language_wrapper
45
+ from frontengine.utils.plugins.plugin_loader import load_plugins
43
46
  from frontengine.utils.preset_schedule.preset_schedule_service import PresetScheduleService
44
47
  from frontengine.utils.smart_pause.smart_pause_service import SmartPauseService
45
48
  from frontengine.utils.theme_schedule.theme_schedule_service import ThemeScheduleService
@@ -107,6 +110,8 @@ class FrontEngineMainUI(QMainWindow):
107
110
  self.pet_setting_ui = PetSettingUI()
108
111
  self.screen_care_setting_ui = ScreenCareSettingUI()
109
112
  self.presentation_setting_ui = PresentationSettingUI()
113
+ self.wallpaper_setting_ui = WallpaperSettingUI()
114
+ self.focus_setting_ui = FocusSettingUI()
110
115
 
111
116
  # 控制中心
112
117
  # Control Center
@@ -127,6 +132,13 @@ class FrontEngineMainUI(QMainWindow):
127
132
  self.menu_bar = QMenuBar()
128
133
  self.setMenuBar(self.menu_bar)
129
134
 
135
+ # 使用者明確開啟時才載入外掛(外掛與本程式同權限,無法沙箱化)
136
+ # Plugins load only when explicitly enabled; they run with our privileges.
137
+ loaded_plugins = load_plugins(
138
+ FrontEngine_EXTEND_TAB, enabled=bool(user_setting_dict.get("load_plugins")))
139
+ if loaded_plugins:
140
+ front_engine_logger.info(f"[FrontEngineMainUI] plugin tabs: {loaded_plugins}")
141
+
130
142
  # 加入各 Tab
131
143
  # Add tabs
132
144
  self._add_tabs()
@@ -215,6 +227,8 @@ class FrontEngineMainUI(QMainWindow):
215
227
  (self.pet_setting_ui, "tab_pet_text"),
216
228
  (self.screen_care_setting_ui, "tab_screen_care_text"),
217
229
  (self.presentation_setting_ui, "tab_presentation_text"),
230
+ (self.wallpaper_setting_ui, "tab_wallpaper_text"),
231
+ (self.focus_setting_ui, "tab_focus_text"),
218
232
  (self.control_center_ui, "tab_control_center_text"),
219
233
  ]
220
234
 
@@ -7,6 +7,9 @@ from PySide6.QtGui import QAction
7
7
  from PySide6.QtWidgets import QFileDialog, QInputDialog, QMessageBox
8
8
 
9
9
  from frontengine.user_setting.preset_repository import PresetRepository
10
+ from frontengine.utils.workshop.workshop_content import (
11
+ KIND_PET_PACK, KIND_PRESET, find_workshop_dir, preset_files, scan_workshop_items,
12
+ )
10
13
  from frontengine.user_setting.user_setting_file import user_setting_dict, write_user_setting
11
14
  from frontengine.utils.logging.loggin_instance import front_engine_logger
12
15
  from frontengine.utils.multi_language.language_wrapper import language_wrapper
@@ -296,6 +299,66 @@ def _import_package_action(ui: "FrontEngineMainUI") -> Callable[[], None]:
296
299
  return handler
297
300
 
298
301
 
302
+ def _workshop_action(ui: "FrontEngineMainUI") -> Callable[[], None]:
303
+ """
304
+ 匯入 Steam 創意工坊已訂閱的內容:掃描 Steam 下載的資料夾,把預設集複製進來,
305
+ 動作包則列出路徑供寵物頁選用。找不到 Steam 時請使用者自己指定路徑。
306
+ Import subscribed Steam Workshop content: copy presets in, and list pet packs
307
+ with their paths so the pet page can point at them. If Steam is not where we
308
+ expect, ask the user where it is.
309
+ """
310
+ def handler() -> None:
311
+ front_engine_logger.info("[PresetMenu] import workshop content")
312
+ title = _t("workshop_menu_import", "Import Workshop content...")
313
+ content_dir = find_workshop_dir()
314
+ if content_dir is None:
315
+ chosen = QFileDialog.getExistingDirectory(
316
+ ui, _t("workshop_pick_steam", "Where is Steam installed?"))
317
+ if not chosen:
318
+ return
319
+ content_dir = find_workshop_dir(extra_paths=[chosen])
320
+ if content_dir is None:
321
+ QMessageBox.information(
322
+ ui, title, _t("workshop_not_found", "No Workshop content found for FrontEngine."))
323
+ return
324
+ items = scan_workshop_items(content_dir)
325
+ if not items:
326
+ QMessageBox.information(
327
+ ui, title,
328
+ _t("workshop_no_items", "Nothing subscribed yet - subscribe in Steam first."))
329
+ return
330
+ imported, packs = _install_workshop_items(items)
331
+ lines = [
332
+ _t("workshop_imported_presets", "Presets imported: {count}").format(count=imported),
333
+ _t("workshop_found_packs", "Pet packs available: {count}").format(count=len(packs)),
334
+ ]
335
+ lines.extend(f"- {title_}: {path}" for title_, path in packs[:8])
336
+ QMessageBox.information(ui, title, chr(10).join(lines))
337
+
338
+ return handler
339
+
340
+
341
+ def _install_workshop_items(items) -> tuple:
342
+ """把預設集複製進本機的 presets/,並回傳可用的動作包 (名稱, 路徑)。"""
343
+ repository = PresetRepository()
344
+ imported = 0
345
+ packs = []
346
+ for item in items:
347
+ if item["kind"] == KIND_PET_PACK:
348
+ packs.append((item["title"], item["path"]))
349
+ continue
350
+ if item["kind"] != KIND_PRESET:
351
+ continue
352
+ for preset_path in preset_files(item["path"]):
353
+ try:
354
+ repository.import_preset(Path(preset_path))
355
+ imported += 1
356
+ except Exception as error: # pragma: no cover - third-party content
357
+ front_engine_logger.warning(
358
+ f"[PresetMenu] workshop preset failed: {preset_path} ({error!r})")
359
+ return (imported, packs)
360
+
361
+
299
362
  def build_preset_menu(ui: "FrontEngineMainUI") -> None:
300
363
  """
301
364
  Build the Presets menu with Save / Load / Delete / Export / Import actions.
@@ -314,6 +377,7 @@ def build_preset_menu(ui: "FrontEngineMainUI") -> None:
314
377
  ("preset_menu_import_package", "Import package (+media)...", _import_package_action),
315
378
  ("preset_menu_set_startup", "Set as startup preset...", _set_startup_action),
316
379
  ("preset_menu_clear_startup", "Clear startup preset", _clear_startup_action),
380
+ ("workshop_menu_import", "Import Workshop content...", _workshop_action),
317
381
  ):
318
382
  action = QAction(_t(label_key, fallback), menu)
319
383
  action.triggered.connect(callback_factory(ui))
@@ -58,6 +58,12 @@ def build_settings_menu(ui: "FrontEngineMainUI") -> None:
58
58
  restore_action.toggled.connect(lambda checked: _toggle_restore_session(checked))
59
59
  menu.addAction(restore_action)
60
60
  ui.restore_session_action = restore_action
61
+ plugins_action = QAction(_t("settings_menu_plugins", "Load plugins (advanced)"), menu)
62
+ plugins_action.setCheckable(True)
63
+ plugins_action.setChecked(bool(user_setting_dict.get("load_plugins")))
64
+ plugins_action.toggled.connect(lambda checked: _toggle_plugins(ui, checked))
65
+ menu.addAction(plugins_action)
66
+ ui.plugins_action = plugins_action
61
67
 
62
68
  menu.addSeparator()
63
69
  export_action = QAction(_t("settings_menu_export", "Export settings..."), menu)
@@ -89,6 +95,24 @@ def _toggle_restore_session(enabled: bool) -> None:
89
95
  """開關「還原上次工作階段」。"""
90
96
  user_setting_dict["restore_last_session"] = bool(enabled)
91
97
  write_user_setting()
98
+ def _toggle_plugins(ui: "FrontEngineMainUI", enabled: bool) -> None:
99
+ """
100
+ 開關外掛載入。外掛是 Python 程式、與本程式同權限,無法沙箱化,
101
+ 因此開啟時明確告知風險,並在下次啟動才生效。
102
+ Toggle plugin loading. Plugins are Python and run with this application's
103
+ own privileges — they cannot be sandboxed — so say so plainly when it is
104
+ switched on. It takes effect on the next launch.
105
+ """
106
+ user_setting_dict["load_plugins"] = bool(enabled)
107
+ write_user_setting()
108
+ if enabled:
109
+ QMessageBox.warning(
110
+ ui,
111
+ _t("settings_menu_plugins", "Load plugins (advanced)"),
112
+ _t("settings_plugins_warning",
113
+ "Plugins are Python code and run with the same privileges as FrontEngine. "
114
+ "Only install plugins you trust. Takes effect on the next launch."),
115
+ )
92
116
 
93
117
 
94
118
  def _toggle_theme_schedule(ui: "FrontEngineMainUI", enabled: bool) -> None: