frontengine 1.0.35__tar.gz → 1.0.36__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 (222) hide show
  1. {frontengine-1.0.35 → frontengine-1.0.36}/PKG-INFO +54 -1
  2. {frontengine-1.0.35 → frontengine-1.0.36}/README.md +53 -0
  3. frontengine-1.0.36/frontengine/show/camera/camera_widget.py +182 -0
  4. frontengine-1.0.36/frontengine/show/capture/region_capture.py +180 -0
  5. frontengine-1.0.36/frontengine/show/measure/measure_widget.py +227 -0
  6. frontengine-1.0.36/frontengine/show/monitor/monitor_widget.py +237 -0
  7. frontengine-1.0.36/frontengine/show/notes/sticky_note_widget.py +154 -0
  8. frontengine-1.0.36/frontengine/show/spectrum/spectrum_widget.py +167 -0
  9. frontengine-1.0.36/frontengine/ui/dialog/window_pin_dialog.py +129 -0
  10. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/main_ui.py +21 -0
  11. frontengine-1.0.36/frontengine/ui/page/tools/tools_setting_ui.py +279 -0
  12. frontengine-1.0.36/frontengine/ui/page/widgets/widgets_setting_ui.py +302 -0
  13. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/user_setting/user_setting_file.py +4 -0
  14. frontengine-1.0.36/frontengine/utils/audio_meter/loopback_capture.py +267 -0
  15. frontengine-1.0.36/frontengine/utils/audio_meter/spectrum_analyzer.py +143 -0
  16. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/audio_meter/system_audio_meter.py +55 -0
  17. frontengine-1.0.36/frontengine/utils/measure/measure.py +116 -0
  18. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/multi_language/english.py +47 -0
  19. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/multi_language/traditional_chinese.py +47 -0
  20. frontengine-1.0.36/frontengine/utils/now_playing/now_playing.py +232 -0
  21. frontengine-1.0.36/frontengine/utils/preset_schedule/__init__.py +0 -0
  22. frontengine-1.0.36/frontengine/utils/redirect_manager/__init__.py +0 -0
  23. frontengine-1.0.36/frontengine/utils/reminder/__init__.py +0 -0
  24. frontengine-1.0.36/frontengine/utils/smart_pause/__init__.py +0 -0
  25. frontengine-1.0.36/frontengine/utils/system_stats/__init__.py +0 -0
  26. frontengine-1.0.36/frontengine/utils/text_source/__init__.py +0 -0
  27. frontengine-1.0.36/frontengine/utils/theme_schedule/__init__.py +0 -0
  28. frontengine-1.0.36/frontengine/utils/weather/__init__.py +0 -0
  29. frontengine-1.0.36/frontengine/utils/window_pin/__init__.py +0 -0
  30. frontengine-1.0.36/frontengine/utils/window_pin/window_pin.py +167 -0
  31. frontengine-1.0.36/frontengine/utils/workshop/__init__.py +0 -0
  32. frontengine-1.0.36/frontengine/worker/__init__.py +0 -0
  33. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine.egg-info/PKG-INFO +54 -1
  34. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine.egg-info/SOURCES.txt +25 -0
  35. {frontengine-1.0.35 → frontengine-1.0.36}/pyproject.toml +1 -1
  36. {frontengine-1.0.35 → frontengine-1.0.36}/LICENSE +0 -0
  37. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/__init__.py +0 -0
  38. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/__main__.py +0 -0
  39. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/__init__.py +0 -0
  40. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/base_widget.py +0 -0
  41. {frontengine-1.0.35/frontengine/show/focus_shield → frontengine-1.0.36/frontengine/show/camera}/__init__.py +0 -0
  42. {frontengine-1.0.35/frontengine/show/gif → frontengine-1.0.36/frontengine/show/capture}/__init__.py +0 -0
  43. {frontengine-1.0.35/frontengine/show/image → frontengine-1.0.36/frontengine/show/focus_shield}/__init__.py +0 -0
  44. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/focus_shield/focus_shield_widget.py +0 -0
  45. {frontengine-1.0.35/frontengine/show/load → frontengine-1.0.36/frontengine/show/gif}/__init__.py +0 -0
  46. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/gif/paint_gif.py +0 -0
  47. {frontengine-1.0.35/frontengine/show/particle → frontengine-1.0.36/frontengine/show/image}/__init__.py +0 -0
  48. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/image/paint_image.py +0 -0
  49. {frontengine-1.0.35/frontengine/show/pet → frontengine-1.0.36/frontengine/show/load}/__init__.py +0 -0
  50. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/load/load_someone_make_ui.py +0 -0
  51. {frontengine-1.0.35/frontengine/show/presentation → frontengine-1.0.36/frontengine/show/measure}/__init__.py +0 -0
  52. {frontengine-1.0.35/frontengine/show/scene → frontengine-1.0.36/frontengine/show/monitor}/__init__.py +0 -0
  53. {frontengine-1.0.35/frontengine/show/screen_care → frontengine-1.0.36/frontengine/show/notes}/__init__.py +0 -0
  54. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/overlay_factory.py +0 -0
  55. {frontengine-1.0.35/frontengine/show/sound_player → frontengine-1.0.36/frontengine/show/particle}/__init__.py +0 -0
  56. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/particle/particle_ui.py +0 -0
  57. {frontengine-1.0.35/frontengine/show/text → frontengine-1.0.36/frontengine/show/pet}/__init__.py +0 -0
  58. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/pet/desktop_pet.py +0 -0
  59. {frontengine-1.0.35/frontengine/show/toast → frontengine-1.0.36/frontengine/show/presentation}/__init__.py +0 -0
  60. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/presentation/annotation_overlay.py +0 -0
  61. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/presentation/cursor_effects.py +0 -0
  62. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/presentation/keystroke_display.py +0 -0
  63. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/presentation/magnifier.py +0 -0
  64. {frontengine-1.0.35/frontengine/show/video → frontengine-1.0.36/frontengine/show/scene}/__init__.py +0 -0
  65. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/scene/extend_graphic_scene.py +0 -0
  66. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/scene/extend_graphic_view.py +0 -0
  67. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/scene/scene.py +0 -0
  68. {frontengine-1.0.35/frontengine/show/wallpaper → frontengine-1.0.36/frontengine/show/screen_care}/__init__.py +0 -0
  69. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/screen_care/screen_filter.py +0 -0
  70. {frontengine-1.0.35/frontengine/show/web → frontengine-1.0.36/frontengine/show/sound_player}/__init__.py +0 -0
  71. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/sound_player/sound_effect.py +0 -0
  72. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/sound_player/sound_player.py +0 -0
  73. {frontengine-1.0.35/frontengine/system_tray → frontengine-1.0.36/frontengine/show/spectrum}/__init__.py +0 -0
  74. {frontengine-1.0.35/frontengine/ui → frontengine-1.0.36/frontengine/show/text}/__init__.py +0 -0
  75. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/text/draw_text.py +0 -0
  76. {frontengine-1.0.35/frontengine/ui/color → frontengine-1.0.36/frontengine/show/toast}/__init__.py +0 -0
  77. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/toast/toast_widget.py +0 -0
  78. {frontengine-1.0.35/frontengine/ui/dialog → frontengine-1.0.36/frontengine/show/video}/__init__.py +0 -0
  79. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/video/video_player.py +0 -0
  80. {frontengine-1.0.35/frontengine/ui/menu → frontengine-1.0.36/frontengine/show/wallpaper}/__init__.py +0 -0
  81. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/wallpaper/wallpaper_widget.py +0 -0
  82. {frontengine-1.0.35/frontengine/ui/page → frontengine-1.0.36/frontengine/show/web}/__init__.py +0 -0
  83. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/web/webview.py +0 -0
  84. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/show/window_helpers.py +0 -0
  85. {frontengine-1.0.35/frontengine/ui/page/control_center → frontengine-1.0.36/frontengine/system_tray}/__init__.py +0 -0
  86. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/system_tray/extend_system_tray.py +0 -0
  87. {frontengine-1.0.35/frontengine/ui/page/focus → frontengine-1.0.36/frontengine/ui}/__init__.py +0 -0
  88. {frontengine-1.0.35/frontengine/ui/page/gif → frontengine-1.0.36/frontengine/ui/color}/__init__.py +0 -0
  89. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/color/global_color.py +0 -0
  90. {frontengine-1.0.35/frontengine/ui/page/image → frontengine-1.0.36/frontengine/ui/dialog}/__init__.py +0 -0
  91. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/dialog/app_profile_dialog.py +0 -0
  92. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
  93. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
  94. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/dialog/reminder_dialog.py +0 -0
  95. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/dialog/save_file_dialog.py +0 -0
  96. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/dialog/smart_pause_dialog.py +0 -0
  97. {frontengine-1.0.35/frontengine/ui/page/particle → frontengine-1.0.36/frontengine/ui/menu}/__init__.py +0 -0
  98. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/menu/help_menu.py +0 -0
  99. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/menu/how_to_menu.py +0 -0
  100. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/menu/language_menu.py +0 -0
  101. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/menu/preset_menu.py +0 -0
  102. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/menu/settings_menu.py +0 -0
  103. {frontengine-1.0.35/frontengine/ui/page/pet → frontengine-1.0.36/frontengine/ui/page}/__init__.py +0 -0
  104. {frontengine-1.0.35/frontengine/ui/page/presentation → frontengine-1.0.36/frontengine/ui/page/control_center}/__init__.py +0 -0
  105. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/control_center/control_center_ui.py +0 -0
  106. {frontengine-1.0.35/frontengine/ui/page/scene_setting → frontengine-1.0.36/frontengine/ui/page/focus}/__init__.py +0 -0
  107. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/focus/focus_setting_ui.py +0 -0
  108. {frontengine-1.0.35/frontengine/ui/page/scene_setting/scene_page → frontengine-1.0.36/frontengine/ui/page/gif}/__init__.py +0 -0
  109. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
  110. {frontengine-1.0.35/frontengine/ui/page/screen_care → frontengine-1.0.36/frontengine/ui/page/image}/__init__.py +0 -0
  111. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/image/image_setting_ui.py +0 -0
  112. {frontengine-1.0.35/frontengine/ui/page/sound_player → frontengine-1.0.36/frontengine/ui/page/particle}/__init__.py +0 -0
  113. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
  114. {frontengine-1.0.35/frontengine/ui/page/text → frontengine-1.0.36/frontengine/ui/page/pet}/__init__.py +0 -0
  115. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
  116. {frontengine-1.0.35/frontengine/ui/page/video → frontengine-1.0.36/frontengine/ui/page/presentation}/__init__.py +0 -0
  117. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/presentation/presentation_setting_ui.py +0 -0
  118. {frontengine-1.0.35/frontengine/ui/page/wallpaper → frontengine-1.0.36/frontengine/ui/page/scene_setting}/__init__.py +0 -0
  119. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
  120. {frontengine-1.0.35/frontengine/ui/page/web → frontengine-1.0.36/frontengine/ui/page/scene_setting/scene_page}/__init__.py +0 -0
  121. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
  122. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
  123. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
  124. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
  125. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
  126. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
  127. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
  128. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
  129. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
  130. {frontengine-1.0.35/frontengine/user_setting → frontengine-1.0.36/frontengine/ui/page/screen_care}/__init__.py +0 -0
  131. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +0 -0
  132. {frontengine-1.0.35/frontengine/utils → frontengine-1.0.36/frontengine/ui/page/sound_player}/__init__.py +0 -0
  133. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
  134. {frontengine-1.0.35/frontengine/utils/app_profile → frontengine-1.0.36/frontengine/ui/page/text}/__init__.py +0 -0
  135. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/text/text_setting_ui.py +0 -0
  136. {frontengine-1.0.35/frontengine/utils/audio_meter → frontengine-1.0.36/frontengine/ui/page/tools}/__init__.py +0 -0
  137. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/utils.py +0 -0
  138. {frontengine-1.0.35/frontengine/utils/autostart → frontengine-1.0.36/frontengine/ui/page/video}/__init__.py +0 -0
  139. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/video/video_setting_ui.py +0 -0
  140. {frontengine-1.0.35/frontengine/utils/break_reminder → frontengine-1.0.36/frontengine/ui/page/wallpaper}/__init__.py +0 -0
  141. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +0 -0
  142. {frontengine-1.0.35/frontengine/utils/browser → frontengine-1.0.36/frontengine/ui/page/web}/__init__.py +0 -0
  143. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/ui/page/web/web_setting_ui.py +0 -0
  144. {frontengine-1.0.35/frontengine/utils/critical_exit → frontengine-1.0.36/frontengine/ui/page/widgets}/__init__.py +0 -0
  145. {frontengine-1.0.35/frontengine/utils/file → frontengine-1.0.36/frontengine/user_setting}/__init__.py +0 -0
  146. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/user_setting/preset_repository.py +0 -0
  147. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/user_setting/scene_setting.py +0 -0
  148. {frontengine-1.0.35/frontengine/utils/focus_shield → frontengine-1.0.36/frontengine/utils}/__init__.py +0 -0
  149. {frontengine-1.0.35/frontengine/utils/focus_timer → frontengine-1.0.36/frontengine/utils/app_profile}/__init__.py +0 -0
  150. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/app_profile/app_profile_service.py +0 -0
  151. {frontengine-1.0.35/frontengine/utils/hotkey → frontengine-1.0.36/frontengine/utils/audio_meter}/__init__.py +0 -0
  152. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
  153. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/audio_meter/screen_audio.py +0 -0
  154. {frontengine-1.0.35/frontengine/utils/input_watch → frontengine-1.0.36/frontengine/utils/autostart}/__init__.py +0 -0
  155. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/autostart/autostart_service.py +0 -0
  156. {frontengine-1.0.35/frontengine/utils/json → frontengine-1.0.36/frontengine/utils/break_reminder}/__init__.py +0 -0
  157. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/break_reminder/break_reminder.py +0 -0
  158. {frontengine-1.0.35/frontengine/utils/logging → frontengine-1.0.36/frontengine/utils/browser}/__init__.py +0 -0
  159. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/browser/browser.py +0 -0
  160. {frontengine-1.0.35/frontengine/utils/multi_language → frontengine-1.0.36/frontengine/utils/critical_exit}/__init__.py +0 -0
  161. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
  162. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/critical_exit/critical_exit.py +0 -0
  163. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/critical_exit/win32_vk.py +0 -0
  164. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/exception/__init__.py +0 -0
  165. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/exception/exception_tags.py +0 -0
  166. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/exception/exceptions.py +0 -0
  167. {frontengine-1.0.35/frontengine/utils/pet_chat → frontengine-1.0.36/frontengine/utils/file}/__init__.py +0 -0
  168. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/file/open/__init__.py +0 -0
  169. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/file/open/open_file.py +0 -0
  170. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/file/save/__init__.py +0 -0
  171. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/file/save/save_file.py +0 -0
  172. {frontengine-1.0.35/frontengine/utils/platform_info → frontengine-1.0.36/frontengine/utils/focus_shield}/__init__.py +0 -0
  173. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/focus_shield/focus_shield.py +0 -0
  174. {frontengine-1.0.35/frontengine/utils/playlist → frontengine-1.0.36/frontengine/utils/focus_timer}/__init__.py +0 -0
  175. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/focus_timer/focus_timer.py +0 -0
  176. {frontengine-1.0.35/frontengine/utils/plugins → frontengine-1.0.36/frontengine/utils/hotkey}/__init__.py +0 -0
  177. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/hotkey/hotkey_service.py +0 -0
  178. {frontengine-1.0.35/frontengine/utils/power_mode → frontengine-1.0.36/frontengine/utils/input_watch}/__init__.py +0 -0
  179. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/input_watch/input_watch_service.py +0 -0
  180. {frontengine-1.0.35/frontengine/utils/preset_schedule → frontengine-1.0.36/frontengine/utils/json}/__init__.py +0 -0
  181. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/json/json_file.py +0 -0
  182. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/json/json_repository.py +0 -0
  183. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/json_format/__init__.py +0 -0
  184. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/json_format/json_process.py +0 -0
  185. {frontengine-1.0.35/frontengine/utils/redirect_manager → frontengine-1.0.36/frontengine/utils/logging}/__init__.py +0 -0
  186. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/logging/loggin_instance.py +0 -0
  187. {frontengine-1.0.35/frontengine/utils/reminder → frontengine-1.0.36/frontengine/utils/measure}/__init__.py +0 -0
  188. {frontengine-1.0.35/frontengine/utils/smart_pause → frontengine-1.0.36/frontengine/utils/multi_language}/__init__.py +0 -0
  189. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/multi_language/france.py +0 -0
  190. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/multi_language/germany.py +0 -0
  191. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/multi_language/italy.py +0 -0
  192. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/multi_language/language_wrapper.py +0 -0
  193. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/multi_language/russian.py +0 -0
  194. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
  195. {frontengine-1.0.35/frontengine/utils/system_stats → frontengine-1.0.36/frontengine/utils/now_playing}/__init__.py +0 -0
  196. {frontengine-1.0.35/frontengine/utils/text_source → frontengine-1.0.36/frontengine/utils/pet_chat}/__init__.py +0 -0
  197. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
  198. {frontengine-1.0.35/frontengine/utils/theme_schedule → frontengine-1.0.36/frontengine/utils/platform_info}/__init__.py +0 -0
  199. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/platform_info/platform_info.py +0 -0
  200. {frontengine-1.0.35/frontengine/utils/weather → frontengine-1.0.36/frontengine/utils/playlist}/__init__.py +0 -0
  201. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/playlist/playlist.py +0 -0
  202. {frontengine-1.0.35/frontengine/utils/workshop → frontengine-1.0.36/frontengine/utils/plugins}/__init__.py +0 -0
  203. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/plugins/plugin_loader.py +0 -0
  204. {frontengine-1.0.35/frontengine/worker → frontengine-1.0.36/frontengine/utils/power_mode}/__init__.py +0 -0
  205. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/power_mode/power_mode.py +0 -0
  206. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
  207. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
  208. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/reminder/reminder_service.py +0 -0
  209. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/smart_pause/pause_rules.py +0 -0
  210. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
  211. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/system_stats/system_stats.py +0 -0
  212. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/text_source/text_source.py +0 -0
  213. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
  214. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/weather/weather_service.py +0 -0
  215. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/web_url.py +0 -0
  216. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/utils/workshop/workshop_content.py +0 -0
  217. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine/worker/qthread_worker.py +0 -0
  218. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine.egg-info/dependency_links.txt +0 -0
  219. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine.egg-info/entry_points.txt +0 -0
  220. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine.egg-info/requires.txt +0 -0
  221. {frontengine-1.0.35 → frontengine-1.0.36}/frontengine.egg-info/top_level.txt +0 -0
  222. {frontengine-1.0.35 → frontengine-1.0.36}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.35
3
+ Version: 1.0.36
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
@@ -113,6 +113,59 @@ Play a folder of images and animations *beneath* every window, from the
113
113
 
114
114
  ---
115
115
 
116
+ ## Widgets
117
+
118
+ Four desktop widgets on the **Widgets** tab:
119
+
120
+ - **Audio spectrum** — bars or a ring, log-spaced bands, smoothed with a
121
+ fast-attack/slow-decay follower and peak markers that drift down.
122
+ > Unlike the pet and the wallpaper, which read only the output *meter*, a
123
+ > spectrum needs the actual samples to compute frequencies — so this one
124
+ > **captures the system output stream**. The samples are analysed in memory,
125
+ > never written to disk or sent anywhere, and capture stops the moment you
126
+ > stop the spectrum. Windows only.
127
+ - **Now playing** — the current track from Windows' media controls when the
128
+ optional `winsdk` bindings are installed; otherwise the name of the app that
129
+ is actually making sound.
130
+ - **System monitor** — CPU, memory and disk as small sparklines. An average
131
+ hides a stall; a line does not.
132
+ - **Sticky notes** — editable cards that float above every window and keep
133
+ their text, colour and position between sessions.
134
+
135
+ ---
136
+
137
+ ## Tools
138
+
139
+ Measuring and capture tools on the **Tools** tab:
140
+
141
+ - **Colour picker / pixel ruler / protractor** — click to sample or measure;
142
+ the result is copied straight to the clipboard as `#rrggbb`, `rgb(...)`,
143
+ `hsl(...)` or a CSS custom property.
144
+ - **Region capture** — drag out an area; it lands on the clipboard and can be
145
+ saved to a file.
146
+ - **Pin a window** — keep another program's window on top, or fade it, while
147
+ you work against it. Only stacking and opacity are touched, never window
148
+ content. Windows only.
149
+ - **Camera** — your webcam in a circle, rounded box or rectangle, shown
150
+ locally and never recorded.
151
+
152
+ ---
153
+
154
+ ## Rules
155
+
156
+ Things that decide for themselves, from the **Settings** menu:
157
+
158
+ - **Smart pause** — stand the overlays down while a fullscreen app is running,
159
+ while the machine is on battery, or while a named app has focus.
160
+ - **App profiles** — apply a saved preset when you switch to a given app.
161
+ - **Reminders** — every N minutes, or once a day at a set time, shown as a
162
+ toast that closes itself.
163
+
164
+ The control center also carries a **quality tier** (high / balanced / saver)
165
+ that caps every overlay's refresh rate and drops its render resolution.
166
+
167
+ ---
168
+
116
169
  ## Community content
117
170
 
118
171
  - **Steam Workshop** — subscribed items are picked up from Steam's own
@@ -89,6 +89,59 @@ Play a folder of images and animations *beneath* every window, from the
89
89
 
90
90
  ---
91
91
 
92
+ ## Widgets
93
+
94
+ Four desktop widgets on the **Widgets** tab:
95
+
96
+ - **Audio spectrum** — bars or a ring, log-spaced bands, smoothed with a
97
+ fast-attack/slow-decay follower and peak markers that drift down.
98
+ > Unlike the pet and the wallpaper, which read only the output *meter*, a
99
+ > spectrum needs the actual samples to compute frequencies — so this one
100
+ > **captures the system output stream**. The samples are analysed in memory,
101
+ > never written to disk or sent anywhere, and capture stops the moment you
102
+ > stop the spectrum. Windows only.
103
+ - **Now playing** — the current track from Windows' media controls when the
104
+ optional `winsdk` bindings are installed; otherwise the name of the app that
105
+ is actually making sound.
106
+ - **System monitor** — CPU, memory and disk as small sparklines. An average
107
+ hides a stall; a line does not.
108
+ - **Sticky notes** — editable cards that float above every window and keep
109
+ their text, colour and position between sessions.
110
+
111
+ ---
112
+
113
+ ## Tools
114
+
115
+ Measuring and capture tools on the **Tools** tab:
116
+
117
+ - **Colour picker / pixel ruler / protractor** — click to sample or measure;
118
+ the result is copied straight to the clipboard as `#rrggbb`, `rgb(...)`,
119
+ `hsl(...)` or a CSS custom property.
120
+ - **Region capture** — drag out an area; it lands on the clipboard and can be
121
+ saved to a file.
122
+ - **Pin a window** — keep another program's window on top, or fade it, while
123
+ you work against it. Only stacking and opacity are touched, never window
124
+ content. Windows only.
125
+ - **Camera** — your webcam in a circle, rounded box or rectangle, shown
126
+ locally and never recorded.
127
+
128
+ ---
129
+
130
+ ## Rules
131
+
132
+ Things that decide for themselves, from the **Settings** menu:
133
+
134
+ - **Smart pause** — stand the overlays down while a fullscreen app is running,
135
+ while the machine is on battery, or while a named app has focus.
136
+ - **App profiles** — apply a saved preset when you switch to a given app.
137
+ - **Reminders** — every N minutes, or once a day at a set time, shown as a
138
+ toast that closes itself.
139
+
140
+ The control center also carries a **quality tier** (high / balanced / saver)
141
+ that caps every overlay's refresh rate and drops its render resolution.
142
+
143
+ ---
144
+
92
145
  ## Community content
93
146
 
94
147
  - **Steam Workshop** — subscribed items are picked up from Steam's own
@@ -0,0 +1,182 @@
1
+ """
2
+ 攝影機覆蓋層:把本機攝影機的畫面放在桌面上(直播用的圓形頭像框那種)。
3
+ 畫面只在本機顯示,不錄影、不上傳;關掉覆蓋層就停止擷取。
4
+
5
+ A camera overlay: put the local webcam on the desktop - the round talking-head
6
+ frame streamers use. The picture is shown locally only; nothing is recorded or
7
+ uploaded, and closing the overlay stops the camera.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from typing import List, Optional, Tuple
12
+
13
+ from PySide6.QtCore import QRect, Qt
14
+ from PySide6.QtGui import QColor, QPainter, QPainterPath, QPen, QPixmap
15
+ from PySide6.QtMultimedia import QCamera, QMediaCaptureSession, QMediaDevices, QVideoSink
16
+
17
+ from frontengine.show.base_widget import BaseWidget
18
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
19
+
20
+ SHAPE_RECTANGLE = "rectangle"
21
+ SHAPE_CIRCLE = "circle"
22
+ SHAPE_ROUNDED = "rounded"
23
+ SHAPES = (SHAPE_RECTANGLE, SHAPE_CIRCLE, SHAPE_ROUNDED)
24
+
25
+ DEFAULT_BORDER = 4
26
+ _CORNER_RADIUS = 18
27
+
28
+
29
+ def normalize_shape(shape) -> str:
30
+ """把外框形狀正規化;不認得的一律當方形。"""
31
+ name = str(shape or "").strip().lower()
32
+ return name if name in SHAPES else SHAPE_RECTANGLE
33
+
34
+
35
+ def list_cameras() -> List[Tuple[str, str]]:
36
+ """
37
+ 可用的攝影機 [(id, 顯示名稱)];沒有裝置或查詢失敗回傳空清單。
38
+ Available cameras as (id, description); [] when there are none.
39
+ """
40
+ try:
41
+ return [(bytes(device.id()).decode("utf-8", "replace"), device.description())
42
+ for device in QMediaDevices.videoInputs()]
43
+ except Exception as error: # pragma: no cover - depends on the platform backend
44
+ front_engine_logger.warning(f"[CameraWidget] listing cameras failed: {error!r}")
45
+ return []
46
+
47
+
48
+ def shape_path(shape: str, rect: QRect) -> QPainterPath:
49
+ """外框形狀對應的裁切路徑。"""
50
+ path = QPainterPath()
51
+ name = normalize_shape(shape)
52
+ if name == SHAPE_CIRCLE:
53
+ size = min(rect.width(), rect.height())
54
+ square = QRect(rect.x() + (rect.width() - size) // 2,
55
+ rect.y() + (rect.height() - size) // 2, size, size)
56
+ path.addEllipse(square)
57
+ elif name == SHAPE_ROUNDED:
58
+ path.addRoundedRect(rect, _CORNER_RADIUS, _CORNER_RADIUS)
59
+ else:
60
+ path.addRect(rect)
61
+ return path
62
+
63
+
64
+ class CameraWidget(BaseWidget):
65
+ """
66
+ 攝影機畫面覆蓋層。影像來源可注入(測試直接餵 QPixmap),因此不需要真的
67
+ 攝影機也能驗證裁切與繪製。
68
+ """
69
+
70
+ def __init__(self, shape: str = SHAPE_CIRCLE, border: int = DEFAULT_BORDER,
71
+ mirrored: bool = True) -> None:
72
+ front_engine_logger.info(f"[CameraWidget] Init | shape={shape}")
73
+ super().__init__()
74
+ self.opacity = 1.0
75
+ self.shape = normalize_shape(shape)
76
+ self.border = max(0, int(border))
77
+ self.mirrored = bool(mirrored)
78
+ self.border_color = QColor("#ffffff")
79
+ self.frame: Optional[QPixmap] = None
80
+ self._camera: Optional[QCamera] = None
81
+ self._session: Optional[QMediaCaptureSession] = None
82
+ self._sink: Optional[QVideoSink] = None
83
+ self.resize(240, 240)
84
+
85
+ def set_shape(self, shape: str) -> None:
86
+ self.shape = normalize_shape(shape)
87
+ self.update()
88
+
89
+ def set_border(self, border: int) -> None:
90
+ self.border = max(0, int(border))
91
+ self.update()
92
+
93
+ def set_mirrored(self, mirrored: bool) -> None:
94
+ """自拍鏡像:看自己的時候左右相反比較自然。"""
95
+ self.mirrored = bool(mirrored)
96
+ self.update()
97
+
98
+ def set_frame(self, pixmap: Optional[QPixmap]) -> None:
99
+ """直接給一張畫面(正式由攝影機推送,測試直接餵)。"""
100
+ self.frame = pixmap if pixmap is not None and not pixmap.isNull() else None
101
+ self.update()
102
+
103
+ def start(self, camera_id: Optional[str] = None) -> bool:
104
+ """
105
+ 打開攝影機開始顯示;沒有裝置或啟動失敗回傳 False。
106
+ Open the camera. False when there is no device or it refuses to start.
107
+ """
108
+ devices = QMediaDevices.videoInputs()
109
+ if not devices:
110
+ front_engine_logger.warning("[CameraWidget] no camera available")
111
+ return False
112
+ chosen = devices[0]
113
+ if camera_id:
114
+ for device in devices:
115
+ if bytes(device.id()).decode("utf-8", "replace") == camera_id:
116
+ chosen = device
117
+ break
118
+ try:
119
+ self.stop()
120
+ self._camera = QCamera(chosen)
121
+ self._sink = QVideoSink(self)
122
+ self._sink.videoFrameChanged.connect(self._on_frame)
123
+ self._session = QMediaCaptureSession()
124
+ self._session.setCamera(self._camera)
125
+ self._session.setVideoSink(self._sink)
126
+ self._camera.start()
127
+ except Exception as error: # pragma: no cover - depends on the backend
128
+ front_engine_logger.warning(f"[CameraWidget] start failed: {error!r}")
129
+ self.stop()
130
+ return False
131
+ front_engine_logger.info(f"[CameraWidget] started | {chosen.description()}")
132
+ return True
133
+
134
+ def stop(self) -> None:
135
+ """關閉攝影機(覆蓋層關掉時一定會走到這裡)。"""
136
+ if self._camera is not None:
137
+ try:
138
+ self._camera.stop()
139
+ except RuntimeError: # pragma: no cover - already torn down
140
+ pass
141
+ self._camera = None
142
+ self._session = None
143
+ self._sink = None
144
+
145
+ def _on_frame(self, video_frame) -> None: # pragma: no cover - needs a real camera
146
+ image = video_frame.toImage() if video_frame is not None else None
147
+ if image is None or image.isNull():
148
+ return
149
+ self.set_frame(QPixmap.fromImage(image))
150
+
151
+ def draw_content(self, painter: QPainter) -> None:
152
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
153
+ path = shape_path(self.shape, self.rect())
154
+ painter.save()
155
+ painter.setClipPath(path)
156
+ if self.frame is not None:
157
+ scaled = self.frame.scaled(
158
+ self.size(), Qt.AspectRatioMode.KeepAspectRatioByExpanding,
159
+ Qt.TransformationMode.SmoothTransformation)
160
+ if self.mirrored:
161
+ scaled = scaled.transformed(_mirror_transform())
162
+ painter.drawPixmap(
163
+ QRect((self.width() - scaled.width()) // 2,
164
+ (self.height() - scaled.height()) // 2,
165
+ scaled.width(), scaled.height()), scaled)
166
+ else:
167
+ painter.fillRect(self.rect(), QColor(0, 0, 0, 160))
168
+ painter.restore()
169
+ if self.border > 0:
170
+ painter.setPen(QPen(self.border_color, self.border))
171
+ painter.drawPath(path)
172
+
173
+ def closeEvent(self, event) -> None:
174
+ self.stop()
175
+ super().closeEvent(event)
176
+
177
+
178
+ def _mirror_transform():
179
+ """左右翻轉用的變換矩陣。"""
180
+ from PySide6.QtGui import QTransform
181
+
182
+ return QTransform().scale(-1, 1)
@@ -0,0 +1,180 @@
1
+ """
2
+ 區域截圖:拖出一塊範圍,放開後把那塊畫面抓下來,可以存檔或複製到剪貼簿,
3
+ 也可以直接交給塗鴉層繼續標註。
4
+
5
+ Region capture: drag out an area, and on release grab it - to a file, to the
6
+ clipboard, or straight into the annotation layer to mark up.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ from typing import Callable, Optional
12
+
13
+ from PySide6.QtCore import QPoint, QRect, Qt
14
+ from PySide6.QtGui import QColor, QGuiApplication, QPainter, QPen, QPixmap
15
+
16
+ from frontengine.show.base_widget import BaseWidget
17
+ from frontengine.show.window_helpers import apply_overlay_window_flags
18
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
19
+
20
+ MIN_CAPTURE_SIZE = 4
21
+ _DIM_COLOR = QColor(0, 0, 0, 110)
22
+ _EDGE_COLOR = "#4dd0e1"
23
+
24
+
25
+ def normalize_rect(start: QPoint, end: QPoint) -> QRect:
26
+ """
27
+ 把兩個角落整理成正的矩形(不管往哪個方向拖)。
28
+ Two corners to a positive rectangle, whichever way the drag went.
29
+ """
30
+ left, right = sorted((int(start.x()), int(end.x())))
31
+ top, bottom = sorted((int(start.y()), int(end.y())))
32
+ return QRect(left, top, right - left, bottom - top)
33
+
34
+
35
+ def is_usable(rect: QRect) -> bool:
36
+ """太小的框(多半是誤點)不算一次截圖。"""
37
+ return rect.width() >= MIN_CAPTURE_SIZE and rect.height() >= MIN_CAPTURE_SIZE
38
+
39
+
40
+ def safe_capture_path(folder: str, name: str) -> Optional[Path]:
41
+ """
42
+ 組出儲存路徑並擋掉路徑穿越:檔名只取最後一段,且結果必須仍在資料夾底下。
43
+ Build the save path and refuse path traversal: only the final component of
44
+ the name is used, and the result must still sit inside the folder.
45
+ """
46
+ try:
47
+ base = Path(folder).resolve()
48
+ candidate = (base / Path(str(name)).name).resolve()
49
+ except (OSError, ValueError):
50
+ return None
51
+ if base != candidate.parent:
52
+ front_engine_logger.warning(f"[RegionCapture] refusing path outside {base}: {name!r}")
53
+ return None
54
+ return candidate
55
+
56
+
57
+ class RegionCaptureWidget(BaseWidget):
58
+ """
59
+ 全螢幕的截圖框選層:按住拖曳畫出範圍,放開就擷取。擷取函式與完成後的
60
+ 處理都可注入,所以整個流程不需要真的螢幕也測得到。
61
+ """
62
+
63
+ def __init__(self, on_captured: Optional[Callable[[QPixmap, QRect], None]] = None) -> None:
64
+ front_engine_logger.info("[RegionCaptureWidget] Init")
65
+ super().__init__()
66
+ self.opacity = 1.0
67
+ self.overlay_lockable = False
68
+ self.origin: Optional[QPoint] = None
69
+ self.current: Optional[QPoint] = None
70
+ self.captured: Optional[QPixmap] = None
71
+ self._on_captured = on_captured
72
+ self._grabber = self._default_grabber
73
+ apply_overlay_window_flags(self, show_on_bottom=False, allow_input=True)
74
+ self.setCursor(Qt.CursorShape.CrossCursor)
75
+
76
+ @staticmethod
77
+ def _default_grabber(rect: QRect): # pragma: no cover - needs a real screen
78
+ screen = QGuiApplication.primaryScreen()
79
+ if screen is None:
80
+ return None
81
+ return screen.grabWindow(0, rect.x(), rect.y(), rect.width(), rect.height())
82
+
83
+ def set_grabber(self, grabber) -> None:
84
+ self._grabber = grabber or self._default_grabber
85
+
86
+ def selection(self) -> Optional[QRect]:
87
+ """目前框出來的範圍;還沒開始拖就是 None。"""
88
+ if self.origin is None or self.current is None:
89
+ return None
90
+ return normalize_rect(self.origin, self.current)
91
+
92
+ # --- interaction -----------------------------------------------------
93
+ def begin(self, point: QPoint) -> None:
94
+ self.origin = QPoint(point)
95
+ self.current = QPoint(point)
96
+ self.update()
97
+
98
+ def extend(self, point: QPoint) -> None:
99
+ if self.origin is not None:
100
+ self.current = QPoint(point)
101
+ self.update()
102
+
103
+ def finish(self, point: QPoint) -> Optional[QPixmap]:
104
+ """
105
+ 放開滑鼠:框太小就當作取消,否則擷取那塊畫面並回傳。
106
+ Releasing the mouse: too small counts as a cancel, otherwise grab that
107
+ part of the screen and hand it back.
108
+ """
109
+ self.extend(point)
110
+ rect = self.selection()
111
+ self.origin = self.current = None
112
+ if rect is None or not is_usable(rect):
113
+ self.update()
114
+ return None
115
+ try:
116
+ pixmap = self._grabber(rect)
117
+ except Exception as error: # pragma: no cover - screen grab boundary
118
+ front_engine_logger.warning(f"[RegionCaptureWidget] grab failed: {error!r}")
119
+ pixmap = None
120
+ if pixmap is None or pixmap.isNull():
121
+ self.update()
122
+ return None
123
+ self.captured = pixmap
124
+ if self._on_captured is not None:
125
+ self._on_captured(pixmap, rect)
126
+ self.update()
127
+ return pixmap
128
+
129
+ def mousePressEvent(self, event) -> None:
130
+ if event.button() == Qt.MouseButton.LeftButton:
131
+ self.begin(event.position().toPoint())
132
+ super().mousePressEvent(event)
133
+
134
+ def mouseMoveEvent(self, event) -> None:
135
+ self.extend(event.position().toPoint())
136
+ super().mouseMoveEvent(event)
137
+
138
+ def mouseReleaseEvent(self, event) -> None:
139
+ if event.button() == Qt.MouseButton.LeftButton:
140
+ self.finish(event.position().toPoint())
141
+ self.close()
142
+ super().mouseReleaseEvent(event)
143
+
144
+ # --- outputs ---------------------------------------------------------
145
+ def copy_to_clipboard(self) -> bool:
146
+ """把剛擷取的畫面放進剪貼簿。"""
147
+ if self.captured is None or self.captured.isNull():
148
+ return False
149
+ clipboard = QGuiApplication.clipboard()
150
+ if clipboard is None: # pragma: no cover - no clipboard at all
151
+ return False
152
+ clipboard.setPixmap(self.captured)
153
+ return True
154
+
155
+ def save_to(self, folder: str, name: str) -> Optional[str]:
156
+ """存成檔案並回傳實際路徑;沒有畫面或路徑不合法時回傳 None。"""
157
+ if self.captured is None or self.captured.isNull():
158
+ return None
159
+ path = safe_capture_path(folder, name)
160
+ if path is None:
161
+ return None
162
+ if not self.captured.save(str(path)):
163
+ front_engine_logger.warning(f"[RegionCaptureWidget] save failed: {path}")
164
+ return None
165
+ front_engine_logger.info(f"[RegionCaptureWidget] saved | {path}")
166
+ return str(path)
167
+
168
+ # --- painting --------------------------------------------------------
169
+ def draw_content(self, painter: QPainter) -> None:
170
+ rect = self.selection()
171
+ painter.fillRect(self.rect(), _DIM_COLOR)
172
+ if rect is None or rect.isEmpty():
173
+ return
174
+ # 框內回復原本亮度,框線標出範圍
175
+ # Clear the selection back to full brightness and outline it.
176
+ painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Clear)
177
+ painter.fillRect(rect, Qt.GlobalColor.transparent)
178
+ painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
179
+ painter.setPen(QPen(QColor(_EDGE_COLOR), 2))
180
+ painter.drawRect(rect)