frontengine 1.0.36__tar.gz → 1.0.38__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 (234) hide show
  1. {frontengine-1.0.36 → frontengine-1.0.38}/PKG-INFO +26 -1
  2. {frontengine-1.0.36 → frontengine-1.0.38}/README.md +25 -0
  3. frontengine-1.0.38/frontengine/show/screen_care/color_vision_widget.py +147 -0
  4. frontengine-1.0.38/frontengine/ui/dialog/clipboard_dialog.py +147 -0
  5. frontengine-1.0.38/frontengine/ui/dialog/usage_report_dialog.py +120 -0
  6. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/main_ui.py +58 -0
  7. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/settings_menu.py +35 -0
  8. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +78 -5
  9. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/tools/tools_setting_ui.py +61 -2
  10. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/user_setting/user_setting_file.py +12 -0
  11. frontengine-1.0.38/frontengine/utils/clipboard/clipboard_history.py +179 -0
  12. frontengine-1.0.38/frontengine/utils/clipboard/clipboard_watcher.py +75 -0
  13. frontengine-1.0.38/frontengine/utils/color_vision/color_vision.py +113 -0
  14. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/english.py +31 -0
  15. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/traditional_chinese.py +31 -0
  16. frontengine-1.0.38/frontengine/utils/usage_tracking/usage_service.py +85 -0
  17. frontengine-1.0.38/frontengine/utils/usage_tracking/usage_tracker.py +218 -0
  18. frontengine-1.0.38/frontengine/utils/window_pin/__init__.py +0 -0
  19. frontengine-1.0.38/frontengine/utils/window_pin/window_layout.py +135 -0
  20. frontengine-1.0.38/frontengine/utils/workshop/__init__.py +0 -0
  21. frontengine-1.0.38/frontengine/worker/__init__.py +0 -0
  22. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/PKG-INFO +26 -1
  23. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/SOURCES.txt +12 -0
  24. {frontengine-1.0.36 → frontengine-1.0.38}/pyproject.toml +1 -1
  25. {frontengine-1.0.36 → frontengine-1.0.38}/LICENSE +0 -0
  26. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/__init__.py +0 -0
  27. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/__main__.py +0 -0
  28. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/__init__.py +0 -0
  29. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/base_widget.py +0 -0
  30. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/camera/__init__.py +0 -0
  31. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/camera/camera_widget.py +0 -0
  32. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/capture/__init__.py +0 -0
  33. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/capture/region_capture.py +0 -0
  34. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/focus_shield/__init__.py +0 -0
  35. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/focus_shield/focus_shield_widget.py +0 -0
  36. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/gif/__init__.py +0 -0
  37. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/gif/paint_gif.py +0 -0
  38. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/image/__init__.py +0 -0
  39. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/image/paint_image.py +0 -0
  40. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/load/__init__.py +0 -0
  41. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/load/load_someone_make_ui.py +0 -0
  42. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/measure/__init__.py +0 -0
  43. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/measure/measure_widget.py +0 -0
  44. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/monitor/__init__.py +0 -0
  45. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/monitor/monitor_widget.py +0 -0
  46. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/notes/__init__.py +0 -0
  47. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/notes/sticky_note_widget.py +0 -0
  48. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/overlay_factory.py +0 -0
  49. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/particle/__init__.py +0 -0
  50. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/particle/particle_ui.py +0 -0
  51. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/pet/__init__.py +0 -0
  52. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/pet/desktop_pet.py +0 -0
  53. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/presentation/__init__.py +0 -0
  54. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/presentation/annotation_overlay.py +0 -0
  55. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/presentation/cursor_effects.py +0 -0
  56. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/presentation/keystroke_display.py +0 -0
  57. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/presentation/magnifier.py +0 -0
  58. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/scene/__init__.py +0 -0
  59. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/scene/extend_graphic_scene.py +0 -0
  60. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/scene/extend_graphic_view.py +0 -0
  61. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/scene/scene.py +0 -0
  62. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/screen_care/__init__.py +0 -0
  63. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/screen_care/screen_filter.py +0 -0
  64. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/sound_player/__init__.py +0 -0
  65. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/sound_player/sound_effect.py +0 -0
  66. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/sound_player/sound_player.py +0 -0
  67. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/spectrum/__init__.py +0 -0
  68. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/spectrum/spectrum_widget.py +0 -0
  69. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/text/__init__.py +0 -0
  70. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/text/draw_text.py +0 -0
  71. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/toast/__init__.py +0 -0
  72. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/toast/toast_widget.py +0 -0
  73. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/video/__init__.py +0 -0
  74. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/video/video_player.py +0 -0
  75. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/wallpaper/__init__.py +0 -0
  76. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/wallpaper/wallpaper_widget.py +0 -0
  77. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/web/__init__.py +0 -0
  78. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/web/webview.py +0 -0
  79. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/window_helpers.py +0 -0
  80. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/system_tray/__init__.py +0 -0
  81. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/system_tray/extend_system_tray.py +0 -0
  82. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/__init__.py +0 -0
  83. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/color/__init__.py +0 -0
  84. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/color/global_color.py +0 -0
  85. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/__init__.py +0 -0
  86. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/app_profile_dialog.py +0 -0
  87. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
  88. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
  89. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/reminder_dialog.py +0 -0
  90. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/save_file_dialog.py +0 -0
  91. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/smart_pause_dialog.py +0 -0
  92. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/window_pin_dialog.py +0 -0
  93. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/__init__.py +0 -0
  94. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/help_menu.py +0 -0
  95. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/how_to_menu.py +0 -0
  96. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/language_menu.py +0 -0
  97. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/preset_menu.py +0 -0
  98. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/__init__.py +0 -0
  99. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/control_center/__init__.py +0 -0
  100. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/control_center/control_center_ui.py +0 -0
  101. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/focus/__init__.py +0 -0
  102. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/focus/focus_setting_ui.py +0 -0
  103. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/gif/__init__.py +0 -0
  104. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
  105. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/image/__init__.py +0 -0
  106. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/image/image_setting_ui.py +0 -0
  107. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/particle/__init__.py +0 -0
  108. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
  109. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/pet/__init__.py +0 -0
  110. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
  111. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/presentation/__init__.py +0 -0
  112. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/presentation/presentation_setting_ui.py +0 -0
  113. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/__init__.py +0 -0
  114. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
  115. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/__init__.py +0 -0
  116. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
  117. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
  118. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
  119. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
  120. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
  121. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
  122. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
  123. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
  124. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
  125. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/screen_care/__init__.py +0 -0
  126. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/sound_player/__init__.py +0 -0
  127. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
  128. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/text/__init__.py +0 -0
  129. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/text/text_setting_ui.py +0 -0
  130. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/tools/__init__.py +0 -0
  131. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/utils.py +0 -0
  132. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/video/__init__.py +0 -0
  133. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/video/video_setting_ui.py +0 -0
  134. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/wallpaper/__init__.py +0 -0
  135. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +0 -0
  136. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/web/__init__.py +0 -0
  137. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/web/web_setting_ui.py +0 -0
  138. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/widgets/__init__.py +0 -0
  139. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/widgets/widgets_setting_ui.py +0 -0
  140. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/user_setting/__init__.py +0 -0
  141. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/user_setting/preset_repository.py +0 -0
  142. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/user_setting/scene_setting.py +0 -0
  143. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/__init__.py +0 -0
  144. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/app_profile/__init__.py +0 -0
  145. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/app_profile/app_profile_service.py +0 -0
  146. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/__init__.py +0 -0
  147. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
  148. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/loopback_capture.py +0 -0
  149. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/screen_audio.py +0 -0
  150. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/spectrum_analyzer.py +0 -0
  151. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/system_audio_meter.py +0 -0
  152. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/autostart/__init__.py +0 -0
  153. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/autostart/autostart_service.py +0 -0
  154. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/break_reminder/__init__.py +0 -0
  155. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/break_reminder/break_reminder.py +0 -0
  156. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/browser/__init__.py +0 -0
  157. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/browser/browser.py +0 -0
  158. {frontengine-1.0.36/frontengine/utils/critical_exit → frontengine-1.0.38/frontengine/utils/clipboard}/__init__.py +0 -0
  159. {frontengine-1.0.36/frontengine/utils/file → frontengine-1.0.38/frontengine/utils/color_vision}/__init__.py +0 -0
  160. {frontengine-1.0.36/frontengine/utils/focus_shield → frontengine-1.0.38/frontengine/utils/critical_exit}/__init__.py +0 -0
  161. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
  162. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/critical_exit/critical_exit.py +0 -0
  163. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/critical_exit/win32_vk.py +0 -0
  164. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/exception/__init__.py +0 -0
  165. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/exception/exception_tags.py +0 -0
  166. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/exception/exceptions.py +0 -0
  167. {frontengine-1.0.36/frontengine/utils/focus_timer → frontengine-1.0.38/frontengine/utils/file}/__init__.py +0 -0
  168. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/file/open/__init__.py +0 -0
  169. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/file/open/open_file.py +0 -0
  170. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/file/save/__init__.py +0 -0
  171. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/file/save/save_file.py +0 -0
  172. {frontengine-1.0.36/frontengine/utils/hotkey → frontengine-1.0.38/frontengine/utils/focus_shield}/__init__.py +0 -0
  173. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/focus_shield/focus_shield.py +0 -0
  174. {frontengine-1.0.36/frontengine/utils/input_watch → frontengine-1.0.38/frontengine/utils/focus_timer}/__init__.py +0 -0
  175. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/focus_timer/focus_timer.py +0 -0
  176. {frontengine-1.0.36/frontengine/utils/json → frontengine-1.0.38/frontengine/utils/hotkey}/__init__.py +0 -0
  177. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/hotkey/hotkey_service.py +0 -0
  178. {frontengine-1.0.36/frontengine/utils/logging → frontengine-1.0.38/frontengine/utils/input_watch}/__init__.py +0 -0
  179. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/input_watch/input_watch_service.py +0 -0
  180. {frontengine-1.0.36/frontengine/utils/measure → frontengine-1.0.38/frontengine/utils/json}/__init__.py +0 -0
  181. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/json/json_file.py +0 -0
  182. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/json/json_repository.py +0 -0
  183. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/json_format/__init__.py +0 -0
  184. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/json_format/json_process.py +0 -0
  185. {frontengine-1.0.36/frontengine/utils/multi_language → frontengine-1.0.38/frontengine/utils/logging}/__init__.py +0 -0
  186. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/logging/loggin_instance.py +0 -0
  187. {frontengine-1.0.36/frontengine/utils/now_playing → frontengine-1.0.38/frontengine/utils/measure}/__init__.py +0 -0
  188. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/measure/measure.py +0 -0
  189. {frontengine-1.0.36/frontengine/utils/pet_chat → frontengine-1.0.38/frontengine/utils/multi_language}/__init__.py +0 -0
  190. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/france.py +0 -0
  191. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/germany.py +0 -0
  192. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/italy.py +0 -0
  193. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/language_wrapper.py +0 -0
  194. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/russian.py +0 -0
  195. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
  196. {frontengine-1.0.36/frontengine/utils/platform_info → frontengine-1.0.38/frontengine/utils/now_playing}/__init__.py +0 -0
  197. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/now_playing/now_playing.py +0 -0
  198. {frontengine-1.0.36/frontengine/utils/playlist → frontengine-1.0.38/frontengine/utils/pet_chat}/__init__.py +0 -0
  199. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
  200. {frontengine-1.0.36/frontengine/utils/plugins → frontengine-1.0.38/frontengine/utils/platform_info}/__init__.py +0 -0
  201. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/platform_info/platform_info.py +0 -0
  202. {frontengine-1.0.36/frontengine/utils/power_mode → frontengine-1.0.38/frontengine/utils/playlist}/__init__.py +0 -0
  203. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/playlist/playlist.py +0 -0
  204. {frontengine-1.0.36/frontengine/utils/preset_schedule → frontengine-1.0.38/frontengine/utils/plugins}/__init__.py +0 -0
  205. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/plugins/plugin_loader.py +0 -0
  206. {frontengine-1.0.36/frontengine/utils/redirect_manager → frontengine-1.0.38/frontengine/utils/power_mode}/__init__.py +0 -0
  207. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/power_mode/power_mode.py +0 -0
  208. {frontengine-1.0.36/frontengine/utils/reminder → frontengine-1.0.38/frontengine/utils/preset_schedule}/__init__.py +0 -0
  209. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
  210. {frontengine-1.0.36/frontengine/utils/smart_pause → frontengine-1.0.38/frontengine/utils/redirect_manager}/__init__.py +0 -0
  211. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
  212. {frontengine-1.0.36/frontengine/utils/system_stats → frontengine-1.0.38/frontengine/utils/reminder}/__init__.py +0 -0
  213. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/reminder/reminder_service.py +0 -0
  214. {frontengine-1.0.36/frontengine/utils/text_source → frontengine-1.0.38/frontengine/utils/smart_pause}/__init__.py +0 -0
  215. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/smart_pause/pause_rules.py +0 -0
  216. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
  217. {frontengine-1.0.36/frontengine/utils/theme_schedule → frontengine-1.0.38/frontengine/utils/system_stats}/__init__.py +0 -0
  218. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/system_stats/system_stats.py +0 -0
  219. {frontengine-1.0.36/frontengine/utils/weather → frontengine-1.0.38/frontengine/utils/text_source}/__init__.py +0 -0
  220. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/text_source/text_source.py +0 -0
  221. {frontengine-1.0.36/frontengine/utils/window_pin → frontengine-1.0.38/frontengine/utils/theme_schedule}/__init__.py +0 -0
  222. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
  223. {frontengine-1.0.36/frontengine/utils/workshop → frontengine-1.0.38/frontengine/utils/usage_tracking}/__init__.py +0 -0
  224. {frontengine-1.0.36/frontengine/worker → frontengine-1.0.38/frontengine/utils/weather}/__init__.py +0 -0
  225. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/weather/weather_service.py +0 -0
  226. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/web_url.py +0 -0
  227. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/window_pin/window_pin.py +0 -0
  228. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/workshop/workshop_content.py +0 -0
  229. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/worker/qthread_worker.py +0 -0
  230. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/dependency_links.txt +0 -0
  231. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/entry_points.txt +0 -0
  232. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/requires.txt +0 -0
  233. {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/top_level.txt +0 -0
  234. {frontengine-1.0.36 → frontengine-1.0.38}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.36
3
+ Version: 1.0.38
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
@@ -166,6 +166,31 @@ that caps every overlay's refresh rate and drops its render resolution.
166
166
 
167
167
  ---
168
168
 
169
+ ## Screen time, clipboard and layouts
170
+
171
+ Three things that remember, all of them **off until you switch them on** and
172
+ all of them **local — nothing here is ever sent anywhere**:
173
+
174
+ - **Screen time** (Settings → Screen time) — which apps had focus and for how
175
+ long, with a daily breakdown and a seven-day summary. It pauses while you are
176
+ away from the keyboard, keeps 60 days at most, and **clearing deletes the
177
+ file itself**. While it is off, no file is created at all.
178
+ - **Clipboard history** (Settings → Clipboard history) — search what you copied
179
+ and pin the phrases you reuse. Clipboards routinely hold passwords, so this
180
+ is kept **in memory only** unless you separately tick "keep between
181
+ sessions".
182
+ - **Window layouts** (Tools tab) — save where every window sits and put them
183
+ back later. Windows are matched by title, and one that is not on screen is
184
+ skipped rather than guessed at. Windows only.
185
+
186
+ The **Screen care** tab also gains **colour-vision simulation** — protanopia,
187
+ deuteranopia, tritanopia and achromatopsia at an adjustable severity, using the
188
+ Machado et al. (2009) model. Unlike the other overlays it is opaque, because
189
+ showing what someone else sees means repainting the screen rather than tinting
190
+ it.
191
+
192
+ ---
193
+
169
194
  ## Community content
170
195
 
171
196
  - **Steam Workshop** — subscribed items are picked up from Steam's own
@@ -142,6 +142,31 @@ that caps every overlay's refresh rate and drops its render resolution.
142
142
 
143
143
  ---
144
144
 
145
+ ## Screen time, clipboard and layouts
146
+
147
+ Three things that remember, all of them **off until you switch them on** and
148
+ all of them **local — nothing here is ever sent anywhere**:
149
+
150
+ - **Screen time** (Settings → Screen time) — which apps had focus and for how
151
+ long, with a daily breakdown and a seven-day summary. It pauses while you are
152
+ away from the keyboard, keeps 60 days at most, and **clearing deletes the
153
+ file itself**. While it is off, no file is created at all.
154
+ - **Clipboard history** (Settings → Clipboard history) — search what you copied
155
+ and pin the phrases you reuse. Clipboards routinely hold passwords, so this
156
+ is kept **in memory only** unless you separately tick "keep between
157
+ sessions".
158
+ - **Window layouts** (Tools tab) — save where every window sits and put them
159
+ back later. Windows are matched by title, and one that is not on screen is
160
+ skipped rather than guessed at. Windows only.
161
+
162
+ The **Screen care** tab also gains **colour-vision simulation** — protanopia,
163
+ deuteranopia, tritanopia and achromatopsia at an adjustable severity, using the
164
+ Machado et al. (2009) model. Unlike the other overlays it is opaque, because
165
+ showing what someone else sees means repainting the screen rather than tinting
166
+ it.
167
+
168
+ ---
169
+
145
170
  ## Community content
146
171
 
147
172
  - **Steam Workshop** — subscribed items are picked up from Steam's own
@@ -0,0 +1,147 @@
1
+ """
2
+ 色覺模擬覆蓋層:抓一張畫面、套上色覺缺陷的轉換、再整片蓋回去。
3
+
4
+ 這一層和其他覆蓋層不同:它必須是不透明的,因為「模擬看不清楚的顏色」沒辦法
5
+ 用半透明疊色做到——得真的把底下的畫面重畫一次。畫面只在記憶體裡轉換,不存檔。
6
+
7
+ A colour-vision overlay: grab the screen, apply the deficiency transform, and
8
+ paint the result back over everything.
9
+
10
+ It is opaque, unlike the other overlays: simulating a colour deficiency cannot
11
+ be done by tinting, it needs the picture underneath redrawn. The grab is
12
+ transformed in memory and never saved.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from typing import Optional
17
+
18
+ import numpy
19
+ from PySide6.QtCore import Qt, QTimer
20
+ from PySide6.QtGui import QGuiApplication, QImage, QPainter, QPixmap
21
+
22
+ from frontengine.show.base_widget import BaseWidget
23
+ from frontengine.utils.color_vision.color_vision import (
24
+ KIND_DEUTERANOPIA, clamp_severity, normalize_kind, simulate_array,
25
+ )
26
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
27
+ from frontengine.utils.power_mode.power_mode import tier_interval
28
+
29
+ # 色覺模擬不需要很高的更新率,五分之一秒足夠,也省下大量重繪
30
+ # The simulation does not need a high refresh rate; five per second is plenty
31
+ # and saves a great deal of redrawing.
32
+ REFRESH_INTERVAL_MS = 200
33
+
34
+
35
+ def image_to_array(image: QImage) -> Optional[numpy.ndarray]:
36
+ """
37
+ QImage -> H x W x 3 的 uint8 陣列。每列可能有補齊位元組,所以要照
38
+ bytesPerLine 切,不能直接 reshape。
39
+ QImage to an H x W x 3 uint8 array. Rows can be padded, so slice by
40
+ bytesPerLine rather than reshaping the raw buffer.
41
+ """
42
+ if image is None or image.isNull():
43
+ return None
44
+ converted = image.convertToFormat(QImage.Format.Format_RGB888)
45
+ width, height, stride = converted.width(), converted.height(), converted.bytesPerLine()
46
+ if width <= 0 or height <= 0:
47
+ return None
48
+ buffer = numpy.frombuffer(converted.constBits(), dtype=numpy.uint8, count=stride * height)
49
+ return buffer.reshape((height, stride))[:, : width * 3].reshape(
50
+ (height, width, 3)).copy()
51
+
52
+
53
+ def array_to_image(pixels: numpy.ndarray) -> Optional[QImage]:
54
+ """H x W x 3 的 uint8 陣列 -> QImage(複製一份,不依賴呼叫端的緩衝區)。"""
55
+ data = numpy.ascontiguousarray(pixels, dtype=numpy.uint8)
56
+ if data.ndim != 3 or data.shape[2] != 3:
57
+ return None
58
+ height, width, _channels = data.shape
59
+ image = QImage(data.tobytes(), width, height, width * 3, QImage.Format.Format_RGB888)
60
+ return image.copy()
61
+
62
+
63
+ class ColorVisionWidget(BaseWidget):
64
+ """
65
+ 色覺模擬層。擷取函式可注入,所以不需要真的螢幕也測得到轉換流程。
66
+ """
67
+
68
+ def __init__(self, kind: str = KIND_DEUTERANOPIA, severity: float = 1.0) -> None:
69
+ front_engine_logger.info(f"[ColorVisionWidget] Init | kind={kind}")
70
+ super().__init__()
71
+ self.kind = normalize_kind(kind)
72
+ self.severity = clamp_severity(severity)
73
+ self.opacity = 1.0
74
+ self.simulated: Optional[QPixmap] = None
75
+ self._grabber = self._default_grabber
76
+ self._timer = QTimer(self)
77
+ self._timer.timeout.connect(self.refresh)
78
+ # 這一層是不透明的:它要蓋掉底下的畫面,而不是疊在上面
79
+ # This layer is opaque - it replaces what is underneath rather than
80
+ # tinting it.
81
+ self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False)
82
+
83
+ @staticmethod
84
+ def _default_grabber(rect): # pragma: no cover - needs a real screen
85
+ screen = QGuiApplication.primaryScreen()
86
+ if screen is None:
87
+ return None
88
+ return screen.grabWindow(0, rect.x(), rect.y(), rect.width(), rect.height())
89
+
90
+ def set_grabber(self, grabber) -> None:
91
+ """設定螢幕擷取函式(測試會換成假的)。"""
92
+ self._grabber = grabber or self._default_grabber
93
+
94
+ def set_simulation(self, kind: Optional[str] = None,
95
+ severity: Optional[float] = None) -> None:
96
+ """換一種色覺類型或嚴重程度,立刻重畫。"""
97
+ if kind is not None:
98
+ self.kind = normalize_kind(kind)
99
+ if severity is not None:
100
+ self.severity = clamp_severity(severity)
101
+ self.refresh()
102
+
103
+ def start(self) -> None:
104
+ self.refresh()
105
+ self._timer.start(tier_interval(REFRESH_INTERVAL_MS, self.quality_tier))
106
+
107
+ def stop(self) -> None:
108
+ self._timer.stop()
109
+
110
+ def apply_quality_tier(self) -> None:
111
+ if self._timer.isActive():
112
+ self._timer.start(tier_interval(REFRESH_INTERVAL_MS, self.quality_tier))
113
+
114
+ def refresh(self) -> None:
115
+ """抓一次畫面、轉換、準備重畫。"""
116
+ geometry = self.geometry()
117
+ try:
118
+ # 擷取時先把自己藏起來,否則會拍到上一次的模擬結果,越疊越糊
119
+ # Hide first, or the grab catches the previous simulation and the
120
+ # picture degrades with every pass.
121
+ was_visible = self.isVisible()
122
+ if was_visible:
123
+ self.setVisible(False)
124
+ pixmap = self._grabber(geometry)
125
+ if was_visible:
126
+ self.setVisible(True)
127
+ except Exception as error: # pragma: no cover - screen grab boundary
128
+ front_engine_logger.warning(f"[ColorVisionWidget] grab failed: {error!r}")
129
+ return
130
+ if pixmap is None or pixmap.isNull():
131
+ return
132
+ pixels = image_to_array(pixmap.toImage())
133
+ if pixels is None:
134
+ return
135
+ image = array_to_image(simulate_array(pixels, self.kind, self.severity))
136
+ if image is not None:
137
+ self.simulated = QPixmap.fromImage(image)
138
+ self.update()
139
+
140
+ def draw_content(self, painter: QPainter) -> None:
141
+ if self.simulated is None or self.simulated.isNull():
142
+ return
143
+ painter.drawPixmap(self.rect(), self.simulated)
144
+
145
+ def closeEvent(self, event) -> None:
146
+ self.stop()
147
+ super().closeEvent(event)
@@ -0,0 +1,147 @@
1
+ """
2
+ 剪貼簿歷史視窗:搜尋、釘選、貼回去。
3
+
4
+ 預設不記錄;要記錄得自己在這裡打開。除非再額外勾選「保存到檔案」,否則
5
+ 內容只留在記憶體,關掉程式就沒了——剪貼簿裡常有密碼,這是刻意的預設。
6
+
7
+ The clipboard history window: search, pin, put back.
8
+
9
+ Recording is off until switched on here, and unless "keep between sessions" is
10
+ also ticked the history lives in memory only and is gone when the app closes.
11
+ Clipboards routinely hold passwords; that default is deliberate.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from typing import Optional
16
+
17
+ from PySide6.QtCore import Qt
18
+ from PySide6.QtGui import QGuiApplication
19
+ from PySide6.QtWidgets import (
20
+ QAbstractItemView, QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QLabel, QLineEdit,
21
+ QListWidget, QListWidgetItem, QPushButton, QWidget,
22
+ )
23
+
24
+ from frontengine.user_setting.user_setting_file import user_setting_dict, write_user_setting
25
+ from frontengine.utils.clipboard.clipboard_history import ClipboardHistory, preview
26
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
27
+ from frontengine.utils.multi_language.language_wrapper import language_wrapper
28
+
29
+ _PIN_MARK = "📌 "
30
+
31
+
32
+ def _t(key: str, fallback: str) -> str:
33
+ return language_wrapper.language_word_dict.get(key, fallback)
34
+
35
+
36
+ class ClipboardDialog(QDialog):
37
+ """瀏覽剪貼簿歷史並貼回去。"""
38
+
39
+ def __init__(self, history: ClipboardHistory, parent: Optional[QWidget] = None) -> None:
40
+ front_engine_logger.info("[ClipboardDialog] Init")
41
+ super().__init__(parent)
42
+ self.setWindowTitle(_t("clipboard_title", "Clipboard history"))
43
+ self.history = history
44
+
45
+ self.search_edit = QLineEdit()
46
+ self.search_edit.setPlaceholderText(_t("clipboard_search", "Search..."))
47
+ self.search_edit.textChanged.connect(self.reload_entries)
48
+
49
+ self.entry_list = QListWidget()
50
+ self.entry_list.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
51
+ self.entry_list.itemDoubleClicked.connect(lambda _item: self.copy_selected())
52
+
53
+ self.copy_button = QPushButton(_t("clipboard_copy", "Copy"))
54
+ self.copy_button.clicked.connect(self.copy_selected)
55
+ self.pin_button = QPushButton(_t("clipboard_pin", "Pin / unpin"))
56
+ self.pin_button.clicked.connect(self.toggle_pin_selected)
57
+ self.remove_button = QPushButton(_t("clipboard_remove", "Remove"))
58
+ self.remove_button.clicked.connect(self.remove_selected)
59
+ self.clear_button = QPushButton(_t("clipboard_clear", "Clear (keep pinned)"))
60
+ self.clear_button.clicked.connect(self.clear_history)
61
+
62
+ self.persist_checkbox = QCheckBox(
63
+ _t("clipboard_persist", "Keep this history between sessions"))
64
+ self.persist_checkbox.setChecked(bool(user_setting_dict.get("clipboard_persist")))
65
+ self.persist_checkbox.toggled.connect(self._store_persist)
66
+ self.hint_label = QLabel(
67
+ _t("clipboard_hint",
68
+ "History stays on this machine and is never sent anywhere. Unless you tick "
69
+ "the box above it is kept in memory only, because clipboards often hold "
70
+ "passwords."))
71
+ self.hint_label.setWordWrap(True)
72
+
73
+ self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
74
+ self.button_box.rejected.connect(self.reject)
75
+
76
+ layout = QGridLayout(self)
77
+ layout.addWidget(self.search_edit, 0, 0, 1, 4)
78
+ layout.addWidget(self.entry_list, 1, 0, 1, 4)
79
+ layout.addWidget(self.copy_button, 2, 0)
80
+ layout.addWidget(self.pin_button, 2, 1)
81
+ layout.addWidget(self.remove_button, 2, 2)
82
+ layout.addWidget(self.clear_button, 2, 3)
83
+ layout.addWidget(self.persist_checkbox, 3, 0, 1, 4)
84
+ layout.addWidget(self.hint_label, 4, 0, 1, 4)
85
+ layout.addWidget(self.button_box, 5, 0, 1, 4)
86
+
87
+ self.reload_entries()
88
+
89
+ def reload_entries(self) -> int:
90
+ """依搜尋字串重畫清單,回傳顯示的筆數。"""
91
+ self.entry_list.clear()
92
+ for entry in self.history.search(self.search_edit.text()):
93
+ label = (_PIN_MARK if entry["pinned"] else "") + preview(entry["text"])
94
+ item = QListWidgetItem(label)
95
+ item.setData(Qt.ItemDataRole.UserRole, entry["text"])
96
+ item.setToolTip(entry["text"])
97
+ self.entry_list.addItem(item)
98
+ return self.entry_list.count()
99
+
100
+ def selected_text(self) -> Optional[str]:
101
+ item = self.entry_list.currentItem()
102
+ if item is None:
103
+ return None
104
+ return str(item.data(Qt.ItemDataRole.UserRole))
105
+
106
+ def copy_selected(self) -> bool:
107
+ """把選到的內容放回剪貼簿。"""
108
+ text = self.selected_text()
109
+ if text is None:
110
+ return False
111
+ clipboard = QGuiApplication.clipboard()
112
+ if clipboard is None: # pragma: no cover - no clipboard at all
113
+ return False
114
+ clipboard.setText(text)
115
+ return True
116
+
117
+ def toggle_pin_selected(self) -> bool:
118
+ text = self.selected_text()
119
+ if text is None:
120
+ return False
121
+ current = next((entry for entry in self.history.entries if entry["text"] == text), None)
122
+ if current is None:
123
+ return False
124
+ self.history.set_pinned(text, not current["pinned"])
125
+ self.reload_entries()
126
+ return True
127
+
128
+ def remove_selected(self) -> bool:
129
+ text = self.selected_text()
130
+ if text is None:
131
+ return False
132
+ removed = self.history.remove(text)
133
+ self.reload_entries()
134
+ return removed
135
+
136
+ def clear_history(self) -> None:
137
+ """清空未釘選的紀錄。"""
138
+ self.history.clear(keep_pinned=True)
139
+ self.reload_entries()
140
+
141
+ def _store_persist(self, enabled: bool) -> None:
142
+ user_setting_dict["clipboard_persist"] = bool(enabled)
143
+ if not enabled:
144
+ # 取消保存時把已經寫下去的內容一併移除,不留殘影
145
+ # Turning saving off also clears what was already written down.
146
+ user_setting_dict["clipboard_entries"] = []
147
+ write_user_setting()
@@ -0,0 +1,120 @@
1
+ """
2
+ 使用時間報告:今天各程式用了多久,以及最近七天的總量。
3
+
4
+ 這份資料只存在本機的 frontengine_usage.json,不會傳到任何地方;統計預設關閉,
5
+ 而且這裡就有「清除」按鈕,按下去連檔案一起刪掉。
6
+
7
+ The usage report: how long each app had focus today, plus the last seven days.
8
+
9
+ The data lives only in the local frontengine_usage.json and goes nowhere else.
10
+ Tracking is off by default, and the clear button here deletes the file itself.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from typing import Optional
15
+
16
+ from PySide6.QtWidgets import (
17
+ QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QHeaderView, QLabel, QPushButton,
18
+ QTableWidget, QTableWidgetItem, QWidget,
19
+ )
20
+
21
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
22
+ from frontengine.utils.multi_language.language_wrapper import language_wrapper
23
+ from frontengine.utils.usage_tracking.usage_tracker import UsageTracker, format_duration
24
+
25
+
26
+ def _t(key: str, fallback: str) -> str:
27
+ return language_wrapper.language_word_dict.get(key, fallback)
28
+
29
+
30
+ def share_percent(seconds: float, total: float) -> int:
31
+ """某個程式佔今天的百分比;今天還沒有紀錄就是 0。"""
32
+ if total <= 0:
33
+ return 0
34
+ return int(round(max(0.0, min(1.0, seconds / total)) * 100))
35
+
36
+
37
+ class UsageReportDialog(QDialog):
38
+ """顯示今天的使用時間,並提供開關與清除。"""
39
+
40
+ def __init__(self, tracker: UsageTracker, parent: Optional[QWidget] = None,
41
+ enabled: bool = False, on_toggle=None, on_clear=None) -> None:
42
+ front_engine_logger.info("[UsageReportDialog] Init")
43
+ super().__init__(parent)
44
+ self.setWindowTitle(_t("usage_title", "Screen time"))
45
+ self.tracker = tracker
46
+ self._on_toggle = on_toggle
47
+ self._on_clear = on_clear
48
+
49
+ self.enable_checkbox = QCheckBox(_t("usage_enable", "Track which apps I use"))
50
+ self.enable_checkbox.setChecked(bool(enabled))
51
+ self.enable_checkbox.toggled.connect(self._toggle)
52
+
53
+ self.total_label = QLabel()
54
+ self.table = QTableWidget(0, 3)
55
+ self.table.setHorizontalHeaderLabels([
56
+ _t("usage_app_column", "Application"),
57
+ _t("usage_time_column", "Time"),
58
+ _t("usage_share_column", "Share"),
59
+ ])
60
+ self.table.horizontalHeader().setSectionResizeMode(
61
+ 0, QHeaderView.ResizeMode.Stretch)
62
+ self.table.verticalHeader().setVisible(False)
63
+
64
+ self.week_label = QLabel()
65
+ self.week_label.setWordWrap(True)
66
+ self.refresh_button = QPushButton(_t("usage_refresh", "Refresh"))
67
+ self.refresh_button.clicked.connect(self.reload_report)
68
+ self.clear_button = QPushButton(_t("usage_clear", "Clear history"))
69
+ self.clear_button.clicked.connect(self.clear_history)
70
+ self.hint_label = QLabel(
71
+ _t("usage_hint",
72
+ "This is stored on this machine only and is never sent anywhere. "
73
+ "Clearing deletes the file itself."))
74
+ self.hint_label.setWordWrap(True)
75
+
76
+ self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
77
+ self.button_box.rejected.connect(self.reject)
78
+
79
+ layout = QGridLayout(self)
80
+ layout.addWidget(self.enable_checkbox, 0, 0, 1, 2)
81
+ layout.addWidget(self.total_label, 1, 0, 1, 2)
82
+ layout.addWidget(self.table, 2, 0, 1, 2)
83
+ layout.addWidget(self.week_label, 3, 0, 1, 2)
84
+ layout.addWidget(self.refresh_button, 4, 0)
85
+ layout.addWidget(self.clear_button, 4, 1)
86
+ layout.addWidget(self.hint_label, 5, 0, 1, 2)
87
+ layout.addWidget(self.button_box, 6, 0, 1, 2)
88
+
89
+ self.reload_report()
90
+
91
+ def reload_report(self) -> int:
92
+ """重畫今天的排行,回傳列數。"""
93
+ total = self.tracker.total_seconds()
94
+ self.total_label.setText(
95
+ _t("usage_total", "Today: {total}").format(total=format_duration(total)))
96
+ rows = self.tracker.top_apps(limit=12)
97
+ self.table.setRowCount(len(rows))
98
+ for index, (app, seconds) in enumerate(rows):
99
+ self.table.setItem(index, 0, QTableWidgetItem(app))
100
+ self.table.setItem(index, 1, QTableWidgetItem(format_duration(seconds)))
101
+ self.table.setItem(index, 2, QTableWidgetItem(f"{share_percent(seconds, total)}%"))
102
+ self.week_label.setText(self.week_summary())
103
+ return len(rows)
104
+
105
+ def week_summary(self) -> str:
106
+ """最近七天的一行摘要。"""
107
+ parts = [f"{day[5:]} {format_duration(seconds)}"
108
+ for day, seconds in self.tracker.recent_days(7)]
109
+ return " · ".join(parts)
110
+
111
+ def clear_history(self) -> None:
112
+ """清掉所有統計(含磁碟上的檔案)。"""
113
+ self.tracker.clear()
114
+ if self._on_clear is not None:
115
+ self._on_clear()
116
+ self.reload_report()
117
+
118
+ def _toggle(self, enabled: bool) -> None:
119
+ if self._on_toggle is not None:
120
+ self._on_toggle(bool(enabled))
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import sys
3
+ from os import getcwd
3
4
  from pathlib import Path
4
5
  from typing import Dict, Optional, Type
5
6
 
@@ -49,6 +50,11 @@ from frontengine.utils.plugins.plugin_loader import load_plugins
49
50
  from frontengine.utils.preset_schedule.preset_schedule_service import PresetScheduleService
50
51
  from frontengine.ui.dialog.smart_pause_dialog import current_rules
51
52
  from frontengine.utils.app_profile.app_profile_service import AppProfileService
53
+ from frontengine.utils.clipboard.clipboard_history import DEFAULT_LIMIT, ClipboardHistory
54
+ from frontengine.utils.clipboard.clipboard_watcher import ClipboardWatcher
55
+ from frontengine.utils.json.json_repository import JsonRepository
56
+ from frontengine.utils.usage_tracking.usage_service import UsageTrackingService
57
+ from frontengine.utils.usage_tracking.usage_tracker import USAGE_FILE, UsageTracker
52
58
  from frontengine.utils.reminder.reminder_service import ReminderService
53
59
  from frontengine.utils.smart_pause.smart_pause_service import SmartPauseService
54
60
  from frontengine.utils.theme_schedule.theme_schedule_service import ThemeScheduleService
@@ -201,6 +207,23 @@ class FrontEngineMainUI(QMainWindow):
201
207
  )
202
208
  self.app_profile_service.start()
203
209
 
210
+ # 使用時間統計:預設關閉,只寫本機檔案,使用者可隨時清除
211
+ # Usage tracking: off by default, local file only, clearable at any time.
212
+ self.usage_tracker = UsageTracker(JsonRepository(Path(getcwd()) / USAGE_FILE))
213
+ self.usage_service = UsageTrackingService(self.usage_tracker)
214
+ if user_setting_dict.get("usage_tracking"):
215
+ self.usage_service.start()
216
+
217
+ # 剪貼簿歷史:預設關閉;未勾選保存時只留在記憶體
218
+ # Clipboard history: off by default, memory only unless saving is on.
219
+ self.clipboard_history = ClipboardHistory(
220
+ limit=user_setting_dict.get("clipboard_limit", DEFAULT_LIMIT))
221
+ if user_setting_dict.get("clipboard_persist"):
222
+ self.clipboard_history.load(user_setting_dict.get("clipboard_entries"))
223
+ self.clipboard_watcher = ClipboardWatcher(self.clipboard_history)
224
+ if user_setting_dict.get("clipboard_history"):
225
+ self.clipboard_watcher.start()
226
+
204
227
  # 自訂提醒(喝水、久坐、每日鬧鐘),到期時以提示卡顯示
205
228
  # Custom reminders - water, posture, a daily alarm - shown as a toast.
206
229
  self.reminder_service = ReminderService(
@@ -436,6 +459,36 @@ class FrontEngineMainUI(QMainWindow):
436
459
  front_engine_logger.info(f"[MainUI] reminder | {label}")
437
460
  self._last_toast = show_toast(label)
438
461
 
462
+ def _persist_clipboard(self) -> None:
463
+ """
464
+ 只有使用者勾選「保存到檔案」時才把剪貼簿歷史寫下來;沒勾就確保
465
+ 設定裡不留任何內容。
466
+ Write the clipboard history down only when the user asked for it; when
467
+ they did not, make sure nothing is left behind in the settings.
468
+ """
469
+ if user_setting_dict.get("clipboard_persist"):
470
+ user_setting_dict["clipboard_entries"] = self.clipboard_history.to_list()
471
+ else:
472
+ user_setting_dict["clipboard_entries"] = []
473
+
474
+ def set_usage_tracking(self, enabled: bool) -> None:
475
+ """開關使用時間統計並記住選擇。"""
476
+ user_setting_dict["usage_tracking"] = bool(enabled)
477
+ write_user_setting()
478
+ if enabled:
479
+ self.usage_service.start()
480
+ else:
481
+ self.usage_service.stop()
482
+
483
+ def set_clipboard_history(self, enabled: bool) -> None:
484
+ """開關剪貼簿歷史紀錄並記住選擇。"""
485
+ user_setting_dict["clipboard_history"] = bool(enabled)
486
+ write_user_setting()
487
+ if enabled:
488
+ self.clipboard_watcher.start()
489
+ else:
490
+ self.clipboard_watcher.stop()
491
+
439
492
  def refresh_rule_services(self) -> None:
440
493
  """
441
494
  設定對話框按下確定後呼叫:規則與對照表都是每次輪詢重讀,所以只要把
@@ -454,6 +507,11 @@ class FrontEngineMainUI(QMainWindow):
454
507
  self.preset_schedule_service.stop()
455
508
  if getattr(self, "theme_schedule_service", None) is not None:
456
509
  self.theme_schedule_service.stop()
510
+ if getattr(self, "usage_service", None) is not None:
511
+ self.usage_service.stop()
512
+ if getattr(self, "clipboard_watcher", None) is not None:
513
+ self.clipboard_watcher.stop()
514
+ self._persist_clipboard()
457
515
  if getattr(self, "widgets_setting_ui", None) is not None:
458
516
  self.widgets_setting_ui.save_notes()
459
517
  self.widgets_setting_ui.stop_spectrum()
@@ -7,9 +7,11 @@ from PySide6.QtGui import QAction
7
7
  from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox
8
8
 
9
9
  from frontengine.ui.dialog.app_profile_dialog import AppProfileDialog
10
+ from frontengine.ui.dialog.clipboard_dialog import ClipboardDialog
10
11
  from frontengine.ui.dialog.hotkey_settings_dialog import HotkeySettingsDialog
11
12
  from frontengine.ui.dialog.reminder_dialog import ReminderDialog
12
13
  from frontengine.ui.dialog.smart_pause_dialog import SmartPauseDialog
14
+ from frontengine.ui.dialog.usage_report_dialog import UsageReportDialog
13
15
  from frontengine.user_setting.user_setting_file import (
14
16
  export_user_setting,
15
17
  import_user_setting,
@@ -85,6 +87,23 @@ def build_settings_menu(ui: "FrontEngineMainUI") -> None:
85
87
  menu.addAction(reminder_action)
86
88
  ui.reminder_action = reminder_action
87
89
 
90
+ usage_action = QAction(_t("settings_menu_usage", "Screen time..."), menu)
91
+ usage_action.triggered.connect(lambda: _open_usage_dialog(ui))
92
+ menu.addAction(usage_action)
93
+ ui.usage_action = usage_action
94
+
95
+ clipboard_action = QAction(_t("settings_menu_clipboard", "Clipboard history..."), menu)
96
+ clipboard_action.triggered.connect(lambda: _open_clipboard_dialog(ui))
97
+ menu.addAction(clipboard_action)
98
+ ui.clipboard_action = clipboard_action
99
+
100
+ clipboard_toggle = QAction(_t("settings_menu_clipboard_record", "Record clipboard"), menu)
101
+ clipboard_toggle.setCheckable(True)
102
+ clipboard_toggle.setChecked(bool(user_setting_dict.get("clipboard_history")))
103
+ clipboard_toggle.toggled.connect(lambda checked: ui.set_clipboard_history(checked))
104
+ menu.addAction(clipboard_toggle)
105
+ ui.clipboard_toggle_action = clipboard_toggle
106
+
88
107
  menu.addSeparator()
89
108
  export_action = QAction(_t("settings_menu_export", "Export settings..."), menu)
90
109
  export_action.triggered.connect(lambda: _export_settings(ui))
@@ -95,6 +114,22 @@ def build_settings_menu(ui: "FrontEngineMainUI") -> None:
95
114
  menu.addAction(import_action)
96
115
 
97
116
 
117
+ def _open_usage_dialog(ui: "FrontEngineMainUI") -> None:
118
+ """開使用時間報告;開關與清除都由對話框直接作用在主視窗的服務上。"""
119
+ front_engine_logger.info("[SettingsMenu] open UsageReportDialog")
120
+ dialog = UsageReportDialog(
121
+ ui.usage_tracker, ui,
122
+ enabled=bool(user_setting_dict.get("usage_tracking")),
123
+ on_toggle=ui.set_usage_tracking)
124
+ dialog.exec()
125
+
126
+
127
+ def _open_clipboard_dialog(ui: "FrontEngineMainUI") -> None:
128
+ """開剪貼簿歷史。"""
129
+ front_engine_logger.info("[SettingsMenu] open ClipboardDialog")
130
+ ClipboardDialog(ui.clipboard_history, ui).exec()
131
+
132
+
98
133
  def _open_dialog(ui: "FrontEngineMainUI", dialog_class) -> bool:
99
134
  """
100
135
  開一個設定對話框並回傳使用者是否按下確定。三個規則類對話框都自己負責寫回