frontengine 1.0.45__tar.gz → 1.0.46__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 (256) hide show
  1. {frontengine-1.0.45 → frontengine-1.0.46}/PKG-INFO +23 -1
  2. {frontengine-1.0.45 → frontengine-1.0.46}/README.md +22 -0
  3. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/base_widget.py +6 -0
  4. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/focus_shield/focus_shield_widget.py +3 -0
  5. frontengine-1.0.46/frontengine/ui/dialog/screen_privacy_dialog.py +98 -0
  6. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/main_ui.py +26 -0
  7. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/menu/settings_menu.py +7 -0
  8. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/control_center/control_center_ui.py +45 -2
  9. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/user_setting/user_setting_file.py +3 -0
  10. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/multi_language/english.py +9 -0
  11. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/multi_language/traditional_chinese.py +9 -0
  12. frontengine-1.0.46/frontengine/utils/screen_privacy/capture_affinity.py +120 -0
  13. frontengine-1.0.46/frontengine/utils/screen_privacy/share_watch.py +128 -0
  14. frontengine-1.0.46/frontengine/worker/__init__.py +0 -0
  15. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine.egg-info/PKG-INFO +23 -1
  16. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine.egg-info/SOURCES.txt +4 -0
  17. {frontengine-1.0.45 → frontengine-1.0.46}/pyproject.toml +1 -1
  18. {frontengine-1.0.45 → frontengine-1.0.46}/LICENSE +0 -0
  19. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/__init__.py +0 -0
  20. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/__main__.py +0 -0
  21. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/__init__.py +0 -0
  22. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/camera/__init__.py +0 -0
  23. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/camera/camera_widget.py +0 -0
  24. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/capture/__init__.py +0 -0
  25. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/capture/region_capture.py +0 -0
  26. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/focus_shield/__init__.py +0 -0
  27. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/gaming/__init__.py +0 -0
  28. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/gaming/crosshair_widget.py +0 -0
  29. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/gif/__init__.py +0 -0
  30. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/gif/paint_gif.py +0 -0
  31. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/image/__init__.py +0 -0
  32. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/image/paint_image.py +0 -0
  33. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/load/__init__.py +0 -0
  34. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/load/load_someone_make_ui.py +0 -0
  35. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/measure/__init__.py +0 -0
  36. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/measure/measure_widget.py +0 -0
  37. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/monitor/__init__.py +0 -0
  38. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/monitor/monitor_widget.py +0 -0
  39. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/notes/__init__.py +0 -0
  40. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/notes/sticky_note_widget.py +0 -0
  41. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/overlay_factory.py +0 -0
  42. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/particle/__init__.py +0 -0
  43. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/particle/particle_ui.py +0 -0
  44. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/pet/__init__.py +0 -0
  45. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/pet/desktop_pet.py +0 -0
  46. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/presentation/__init__.py +0 -0
  47. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/presentation/annotation_overlay.py +0 -0
  48. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/presentation/cursor_effects.py +0 -0
  49. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/presentation/keystroke_display.py +0 -0
  50. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/presentation/magnifier.py +0 -0
  51. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/scene/__init__.py +0 -0
  52. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/scene/extend_graphic_scene.py +0 -0
  53. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/scene/extend_graphic_view.py +0 -0
  54. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/scene/scene.py +0 -0
  55. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/screen_care/__init__.py +0 -0
  56. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/screen_care/color_vision_widget.py +0 -0
  57. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/screen_care/screen_filter.py +0 -0
  58. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/sound_player/__init__.py +0 -0
  59. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/sound_player/sound_effect.py +0 -0
  60. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/sound_player/sound_player.py +0 -0
  61. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/spectrum/__init__.py +0 -0
  62. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/spectrum/spectrum_widget.py +0 -0
  63. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/teleprompter/__init__.py +0 -0
  64. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/teleprompter/teleprompter_widget.py +0 -0
  65. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/text/__init__.py +0 -0
  66. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/text/draw_text.py +0 -0
  67. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/toast/__init__.py +0 -0
  68. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/toast/toast_widget.py +0 -0
  69. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/video/__init__.py +0 -0
  70. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/video/video_player.py +0 -0
  71. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/wallpaper/__init__.py +0 -0
  72. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/wallpaper/wallpaper_widget.py +0 -0
  73. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/web/__init__.py +0 -0
  74. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/web/webview.py +0 -0
  75. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/show/window_helpers.py +0 -0
  76. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/system_tray/__init__.py +0 -0
  77. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/system_tray/extend_system_tray.py +0 -0
  78. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/__init__.py +0 -0
  79. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/color/__init__.py +0 -0
  80. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/color/global_color.py +0 -0
  81. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/dialog/__init__.py +0 -0
  82. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/dialog/app_profile_dialog.py +0 -0
  83. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
  84. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/dialog/clipboard_dialog.py +0 -0
  85. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
  86. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/dialog/reminder_dialog.py +0 -0
  87. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/dialog/save_file_dialog.py +0 -0
  88. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/dialog/screen_text_dialog.py +0 -0
  89. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/dialog/smart_pause_dialog.py +0 -0
  90. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/dialog/usage_report_dialog.py +0 -0
  91. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/dialog/window_pin_dialog.py +0 -0
  92. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/menu/__init__.py +0 -0
  93. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/menu/help_menu.py +0 -0
  94. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/menu/how_to_menu.py +0 -0
  95. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/menu/language_menu.py +0 -0
  96. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/menu/preset_menu.py +0 -0
  97. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/__init__.py +0 -0
  98. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/control_center/__init__.py +0 -0
  99. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/focus/__init__.py +0 -0
  100. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/focus/focus_setting_ui.py +0 -0
  101. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/gif/__init__.py +0 -0
  102. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
  103. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/image/__init__.py +0 -0
  104. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/image/image_setting_ui.py +0 -0
  105. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/particle/__init__.py +0 -0
  106. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
  107. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/pet/__init__.py +0 -0
  108. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
  109. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/presentation/__init__.py +0 -0
  110. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/presentation/presentation_setting_ui.py +0 -0
  111. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/__init__.py +0 -0
  112. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
  113. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/scene_page/__init__.py +0 -0
  114. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
  115. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
  116. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
  117. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
  118. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
  119. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
  120. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
  121. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
  122. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
  123. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/screen_care/__init__.py +0 -0
  124. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +0 -0
  125. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/sound_player/__init__.py +0 -0
  126. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
  127. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/stream/__init__.py +0 -0
  128. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/stream/stream_setting_ui.py +0 -0
  129. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/text/__init__.py +0 -0
  130. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/text/text_setting_ui.py +0 -0
  131. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/tools/__init__.py +0 -0
  132. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/tools/tools_setting_ui.py +0 -0
  133. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/utils.py +0 -0
  134. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/video/__init__.py +0 -0
  135. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/video/video_setting_ui.py +0 -0
  136. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/wallpaper/__init__.py +0 -0
  137. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +0 -0
  138. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/web/__init__.py +0 -0
  139. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/web/web_setting_ui.py +0 -0
  140. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/widgets/__init__.py +0 -0
  141. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/ui/page/widgets/widgets_setting_ui.py +0 -0
  142. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/user_setting/__init__.py +0 -0
  143. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/user_setting/preset_repository.py +0 -0
  144. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/user_setting/scene_setting.py +0 -0
  145. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/__init__.py +0 -0
  146. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/app_profile/__init__.py +0 -0
  147. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/app_profile/app_profile_service.py +0 -0
  148. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/audio_meter/__init__.py +0 -0
  149. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
  150. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/audio_meter/loopback_capture.py +0 -0
  151. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/audio_meter/screen_audio.py +0 -0
  152. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/audio_meter/spectrum_analyzer.py +0 -0
  153. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/audio_meter/system_audio_meter.py +0 -0
  154. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/autostart/__init__.py +0 -0
  155. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/autostart/autostart_service.py +0 -0
  156. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/break_reminder/__init__.py +0 -0
  157. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/break_reminder/break_reminder.py +0 -0
  158. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/browser/__init__.py +0 -0
  159. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/browser/browser.py +0 -0
  160. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/clipboard/__init__.py +0 -0
  161. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/clipboard/clipboard_history.py +0 -0
  162. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/clipboard/clipboard_watcher.py +0 -0
  163. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/color_vision/__init__.py +0 -0
  164. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/color_vision/color_vision.py +0 -0
  165. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/critical_exit/__init__.py +0 -0
  166. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
  167. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/critical_exit/critical_exit.py +0 -0
  168. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/critical_exit/win32_vk.py +0 -0
  169. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/exception/__init__.py +0 -0
  170. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/exception/exception_tags.py +0 -0
  171. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/exception/exceptions.py +0 -0
  172. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/file/__init__.py +0 -0
  173. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/file/open/__init__.py +0 -0
  174. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/file/open/open_file.py +0 -0
  175. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/file/save/__init__.py +0 -0
  176. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/file/save/save_file.py +0 -0
  177. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/focus_shield/__init__.py +0 -0
  178. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/focus_shield/focus_shield.py +0 -0
  179. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/focus_timer/__init__.py +0 -0
  180. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/focus_timer/focus_timer.py +0 -0
  181. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/hotkey/__init__.py +0 -0
  182. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/hotkey/hotkey_service.py +0 -0
  183. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/input_watch/__init__.py +0 -0
  184. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/input_watch/input_watch_service.py +0 -0
  185. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/json/__init__.py +0 -0
  186. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/json/json_file.py +0 -0
  187. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/json/json_repository.py +0 -0
  188. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/json_format/__init__.py +0 -0
  189. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/json_format/json_process.py +0 -0
  190. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/logging/__init__.py +0 -0
  191. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/logging/loggin_instance.py +0 -0
  192. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/measure/__init__.py +0 -0
  193. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/measure/measure.py +0 -0
  194. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/multi_language/__init__.py +0 -0
  195. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/multi_language/france.py +0 -0
  196. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/multi_language/germany.py +0 -0
  197. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/multi_language/italy.py +0 -0
  198. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/multi_language/language_wrapper.py +0 -0
  199. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/multi_language/russian.py +0 -0
  200. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
  201. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/now_playing/__init__.py +0 -0
  202. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/now_playing/now_playing.py +0 -0
  203. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/obs/__init__.py +0 -0
  204. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/obs/obs_client.py +0 -0
  205. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/obs/obs_protocol.py +0 -0
  206. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/obs/websocket_frames.py +0 -0
  207. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/pet_chat/__init__.py +0 -0
  208. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
  209. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/platform_info/__init__.py +0 -0
  210. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/platform_info/platform_info.py +0 -0
  211. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/playlist/__init__.py +0 -0
  212. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/playlist/playlist.py +0 -0
  213. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/plugins/__init__.py +0 -0
  214. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/plugins/plugin_loader.py +0 -0
  215. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/power_mode/__init__.py +0 -0
  216. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/power_mode/power_mode.py +0 -0
  217. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/preset_schedule/__init__.py +0 -0
  218. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
  219. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/recording/__init__.py +0 -0
  220. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/recording/frame_recorder.py +0 -0
  221. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/recording/gif_writer.py +0 -0
  222. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/redirect_manager/__init__.py +0 -0
  223. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
  224. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/reminder/__init__.py +0 -0
  225. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/reminder/reminder_service.py +0 -0
  226. {frontengine-1.0.45/frontengine/utils/screen_text → frontengine-1.0.46/frontengine/utils/screen_privacy}/__init__.py +0 -0
  227. {frontengine-1.0.45/frontengine/utils/smart_pause → frontengine-1.0.46/frontengine/utils/screen_text}/__init__.py +0 -0
  228. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/screen_text/screen_text_service.py +0 -0
  229. {frontengine-1.0.45/frontengine/utils/soundboard → frontengine-1.0.46/frontengine/utils/smart_pause}/__init__.py +0 -0
  230. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/smart_pause/pause_rules.py +0 -0
  231. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
  232. {frontengine-1.0.45/frontengine/utils/system_stats → frontengine-1.0.46/frontengine/utils/soundboard}/__init__.py +0 -0
  233. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/soundboard/soundboard.py +0 -0
  234. {frontengine-1.0.45/frontengine/utils/text_source → frontengine-1.0.46/frontengine/utils/system_stats}/__init__.py +0 -0
  235. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/system_stats/system_stats.py +0 -0
  236. {frontengine-1.0.45/frontengine/utils/theme_schedule → frontengine-1.0.46/frontengine/utils/text_source}/__init__.py +0 -0
  237. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/text_source/text_source.py +0 -0
  238. {frontengine-1.0.45/frontengine/utils/usage_tracking → frontengine-1.0.46/frontengine/utils/theme_schedule}/__init__.py +0 -0
  239. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
  240. {frontengine-1.0.45/frontengine/utils/weather → frontengine-1.0.46/frontengine/utils/usage_tracking}/__init__.py +0 -0
  241. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/usage_tracking/usage_service.py +0 -0
  242. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/usage_tracking/usage_tracker.py +0 -0
  243. {frontengine-1.0.45/frontengine/utils/window_pin → frontengine-1.0.46/frontengine/utils/weather}/__init__.py +0 -0
  244. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/weather/weather_service.py +0 -0
  245. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/web_url.py +0 -0
  246. {frontengine-1.0.45/frontengine/utils/workshop → frontengine-1.0.46/frontengine/utils/window_pin}/__init__.py +0 -0
  247. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/window_pin/window_layout.py +0 -0
  248. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/window_pin/window_pin.py +0 -0
  249. {frontengine-1.0.45/frontengine/worker → frontengine-1.0.46/frontengine/utils/workshop}/__init__.py +0 -0
  250. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/utils/workshop/workshop_content.py +0 -0
  251. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine/worker/qthread_worker.py +0 -0
  252. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine.egg-info/dependency_links.txt +0 -0
  253. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine.egg-info/entry_points.txt +0 -0
  254. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine.egg-info/requires.txt +0 -0
  255. {frontengine-1.0.45 → frontengine-1.0.46}/frontengine.egg-info/top_level.txt +0 -0
  256. {frontengine-1.0.45 → frontengine-1.0.46}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.45
3
+ Version: 1.0.46
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
@@ -196,6 +196,28 @@ Four things on the **Stream** tab, for playing and for going live:
196
196
 
197
197
  ---
198
198
 
199
+ ## Screen-sharing privacy
200
+
201
+ Your overlays are for you, not for the people you are sharing with. From
202
+ **Settings → Screen-sharing privacy**, FrontEngine can take them out of the
203
+ capture while a meeting app is open:
204
+
205
+ - **They stay on your own screen.** Only the captured copy is blank — this uses
206
+ Windows' `WDA_EXCLUDEFROMCAPTURE`, an OS-level flag that conferencing apps and
207
+ recorders honour.
208
+ - **Masks are the exception.** A distraction mask exists to cover something, so
209
+ it deliberately stays visible in the capture.
210
+ - **The trigger is your list.** Windows has no dependable "am I being captured"
211
+ API, so it watches for window titles you name — which also catches a meeting
212
+ held in a browser tab, where the executable is just the browser.
213
+
214
+ There is a manual **Hide from capture** button in the control center too.
215
+
216
+ > This is privacy, not security: it defeats the ordinary capture path, and it
217
+ > never hides anything from the person sitting at the desk. Windows only.
218
+
219
+ ---
220
+
199
221
  ## Screen time, clipboard and layouts
200
222
 
201
223
  Three things that remember, all of them **off until you switch them on** and
@@ -172,6 +172,28 @@ Four things on the **Stream** tab, for playing and for going live:
172
172
 
173
173
  ---
174
174
 
175
+ ## Screen-sharing privacy
176
+
177
+ Your overlays are for you, not for the people you are sharing with. From
178
+ **Settings → Screen-sharing privacy**, FrontEngine can take them out of the
179
+ capture while a meeting app is open:
180
+
181
+ - **They stay on your own screen.** Only the captured copy is blank — this uses
182
+ Windows' `WDA_EXCLUDEFROMCAPTURE`, an OS-level flag that conferencing apps and
183
+ recorders honour.
184
+ - **Masks are the exception.** A distraction mask exists to cover something, so
185
+ it deliberately stays visible in the capture.
186
+ - **The trigger is your list.** Windows has no dependable "am I being captured"
187
+ API, so it watches for window titles you name — which also catches a meeting
188
+ held in a browser tab, where the executable is just the browser.
189
+
190
+ There is a manual **Hide from capture** button in the control center too.
191
+
192
+ > This is privacy, not security: it defeats the ordinary capture path, and it
193
+ > never hides anything from the person sitting at the desk. Windows only.
194
+
195
+ ---
196
+
175
197
  ## Screen time, clipboard and layouts
176
198
 
177
199
  Three things that remember, all of them **off until you switch them on** and
@@ -33,6 +33,12 @@ class BaseWidget(QWidget):
33
33
  # 畫質檔位:影響更新頻率上限與算圖縮放(見 utils/power_mode)
34
34
  # Quality tier: caps the refresh rate and scales rendering (see utils/power_mode).
35
35
  self.quality_tier: str = DEFAULT_TIER
36
+ # 分享畫面時要不要一起藏起來。遮蔽用的覆蓋層要留在畫面上——
37
+ # 那正是它存在的理由,藏了就等於沒遮。
38
+ # Whether this hides along with the others while sharing. A masking
39
+ # overlay must stay visible in the capture: hiding it would defeat
40
+ # the only thing it is for.
41
+ self.keep_in_capture: bool = False
36
42
  self._drag_origin = None
37
43
  self._geometry_restored = False
38
44
 
@@ -118,6 +118,9 @@ class DistractionMaskWidget(BaseWidget):
118
118
  f"[DistractionMaskWidget] Init | region={region}, percent={percent}")
119
119
  super().__init__()
120
120
  self.opacity = 1.0
121
+ # 這一層是拿來遮住東西的,分享畫面時必須留在擷取結果裡
122
+ # This layer exists to cover something, so it stays in the capture.
123
+ self.keep_in_capture = True
121
124
  self.region = region
122
125
  self.percent = clamp_percent(percent)
123
126
  self.mask_color = QColor(color) if QColor(color).isValid() else QColor("#101010")
@@ -0,0 +1,98 @@
1
+ """
2
+ 螢幕分享隱私設定:偵測到會議程式時,自動把覆蓋層藏出擷取畫面。
3
+
4
+ Windows 沒有可靠的「我正在被擷取嗎」API,所以判斷依據是使用者自己列出的
5
+ 會議程式有沒有開著視窗。這是刻意的:與其猜錯,不如讓使用者說了算。
6
+
7
+ Screen-sharing privacy: hide the overlays from the capture when a meeting app
8
+ turns up.
9
+
10
+ Windows offers no dependable "am I being captured" API, so the trigger is
11
+ whether one of the apps the user listed has a window open. That is deliberate -
12
+ better their list than our guess.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from typing import Any, Dict, Optional
17
+
18
+ from PySide6.QtWidgets import (
19
+ QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QWidget,
20
+ )
21
+
22
+ from frontengine.user_setting.user_setting_file import user_setting_dict, write_user_setting
23
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
24
+ from frontengine.utils.multi_language.language_wrapper import language_wrapper
25
+ from frontengine.utils.screen_privacy import capture_affinity
26
+ from frontengine.utils.screen_privacy.share_watch import DEFAULT_SHARING_APPS
27
+ from frontengine.utils.smart_pause.pause_rules import parse_app_list
28
+
29
+ SETTING_KEY = "screen_share_privacy"
30
+ DEFAULT_SETTINGS: Dict[str, Any] = {"enabled": False, "apps": list(DEFAULT_SHARING_APPS)}
31
+
32
+
33
+ def _t(key: str, fallback: str) -> str:
34
+ return language_wrapper.language_word_dict.get(key, fallback)
35
+
36
+
37
+ def current_settings() -> Dict[str, Any]:
38
+ """目前的分享隱私設定(缺項目補預設值)。"""
39
+ stored = user_setting_dict.get(SETTING_KEY)
40
+ settings = dict(DEFAULT_SETTINGS)
41
+ if isinstance(stored, dict):
42
+ settings.update({key: stored[key] for key in DEFAULT_SETTINGS if key in stored})
43
+ settings["apps"] = parse_app_list(settings.get("apps")) or list(DEFAULT_SHARING_APPS)
44
+ return settings
45
+
46
+
47
+ class ScreenPrivacyDialog(QDialog):
48
+ """設定哪些程式算是「正在分享」,以及要不要自動隱藏。"""
49
+
50
+ def __init__(self, parent: Optional[QWidget] = None) -> None:
51
+ front_engine_logger.info("[ScreenPrivacyDialog] Init")
52
+ super().__init__(parent)
53
+ self.setWindowTitle(_t("screen_privacy_title", "Screen-sharing privacy"))
54
+ settings = current_settings()
55
+
56
+ self.enable_checkbox = QCheckBox(
57
+ _t("screen_privacy_enable", "Hide overlays while one of these apps is open"))
58
+ self.enable_checkbox.setChecked(bool(settings.get("enabled")))
59
+ self.apps_label = QLabel(_t("screen_privacy_apps_label", "Meeting and capture apps:"))
60
+ self.apps_edit = QLineEdit(", ".join(settings.get("apps", [])))
61
+ self.apps_edit.setPlaceholderText("zoom, teams, discord")
62
+ self.hint_label = QLabel(
63
+ _t("screen_privacy_hint",
64
+ "Matched against window titles, so a meeting held in a browser tab is caught "
65
+ "too. Your overlays stay on your own screen - only the capture is blank. "
66
+ "Masks stay visible, since covering something is what they are for.")
67
+ if capture_affinity.available() else
68
+ _t("screen_privacy_unsupported",
69
+ "Hiding overlays from screen capture is Windows only."))
70
+ self.hint_label.setWordWrap(True)
71
+
72
+ self.button_box = QDialogButtonBox(
73
+ QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
74
+ self.button_box.accepted.connect(self.accept)
75
+ self.button_box.rejected.connect(self.reject)
76
+
77
+ layout = QGridLayout(self)
78
+ layout.addWidget(self.enable_checkbox, 0, 0, 1, 2)
79
+ layout.addWidget(self.apps_label, 1, 0, 1, 2)
80
+ layout.addWidget(self.apps_edit, 2, 0, 1, 2)
81
+ layout.addWidget(self.hint_label, 3, 0, 1, 2)
82
+ layout.addWidget(self.button_box, 4, 0, 1, 2)
83
+ for widget in (self.enable_checkbox, self.apps_edit):
84
+ widget.setEnabled(capture_affinity.available())
85
+
86
+ def settings(self) -> Dict[str, Any]:
87
+ """對話框上目前的設定。"""
88
+ return {
89
+ "enabled": self.enable_checkbox.isChecked(),
90
+ "apps": parse_app_list(self.apps_edit.text()) or list(DEFAULT_SHARING_APPS),
91
+ }
92
+
93
+ def accept(self) -> None:
94
+ settings = self.settings()
95
+ user_setting_dict[SETTING_KEY] = settings
96
+ write_user_setting()
97
+ front_engine_logger.info(f"[ScreenPrivacyDialog] saved | {settings}")
98
+ super().accept()
@@ -49,7 +49,9 @@ from frontengine.utils.logging.loggin_instance import front_engine_logger
49
49
  from frontengine.utils.multi_language.language_wrapper import language_wrapper
50
50
  from frontengine.utils.plugins.plugin_loader import load_plugins
51
51
  from frontengine.utils.preset_schedule.preset_schedule_service import PresetScheduleService
52
+ from frontengine.ui.dialog.screen_privacy_dialog import current_settings as current_privacy_settings
52
53
  from frontengine.ui.dialog.smart_pause_dialog import current_rules
54
+ from frontengine.utils.screen_privacy.share_watch import ShareWatchService
53
55
  from frontengine.utils.app_profile.app_profile_service import AppProfileService
54
56
  from frontengine.utils.clipboard.clipboard_history import DEFAULT_LIMIT, ClipboardHistory
55
57
  from frontengine.utils.clipboard.clipboard_watcher import ClipboardWatcher
@@ -209,6 +211,12 @@ class FrontEngineMainUI(QMainWindow):
209
211
  )
210
212
  self.app_profile_service.start()
211
213
 
214
+ # 分享畫面時把覆蓋層藏出擷取結果(自己仍看得到)
215
+ # Hide the overlays from the capture while sharing; they stay on screen here.
216
+ self.share_watch_service = ShareWatchService(config_provider=current_privacy_settings)
217
+ self.share_watch_service.sharing_changed.connect(self._on_sharing_changed)
218
+ self.share_watch_service.start()
219
+
212
220
  # 使用時間統計:預設關閉,只寫本機檔案,使用者可隨時清除
213
221
  # Usage tracking: off by default, local file only, clearable at any time.
214
222
  self.usage_tracker = UsageTracker(JsonRepository(Path(getcwd()) / USAGE_FILE))
@@ -460,6 +468,22 @@ class FrontEngineMainUI(QMainWindow):
460
468
  self.control_center_ui.show_all()
461
469
  self._smart_pause_hid = False
462
470
 
471
+ def _on_sharing_changed(self, sharing: bool, match: str) -> None:
472
+ """
473
+ 偵測到會議程式開著/關掉時,自動把覆蓋層藏出擷取結果或放回去。
474
+ 遮蔽用的覆蓋層不受影響——它們本來就是要被看到的。
475
+ Hide the overlays from the capture when a meeting app appears, and put
476
+ them back when it goes. Masking overlays are left alone: being seen is
477
+ the whole point of them.
478
+ """
479
+ front_engine_logger.info(
480
+ f"[MainUI] screen sharing={sharing}" + (f" | {match}" if match else ""))
481
+ self.control_center_ui.set_hide_from_capture(sharing)
482
+ if sharing:
483
+ self._last_toast = show_toast(
484
+ language_wrapper.language_word_dict.get(
485
+ "screen_privacy_toast", "Overlays hidden from screen capture"))
486
+
463
487
  def _show_reminder(self, label: str) -> None:
464
488
  """提醒到期:在畫面上方顯示一張會自己消失的提示卡。"""
465
489
  front_engine_logger.info(f"[MainUI] reminder | {label}")
@@ -526,6 +550,8 @@ class FrontEngineMainUI(QMainWindow):
526
550
  self.tools_setting_ui.stop_camera()
527
551
  if getattr(self, "stream_setting_ui", None) is not None:
528
552
  self.stream_setting_ui.soundboard.stop_all()
553
+ if getattr(self, "share_watch_service", None) is not None:
554
+ self.share_watch_service.stop()
529
555
  if getattr(self, "reminder_service", None) is not None:
530
556
  self.reminder_service.stop()
531
557
  if getattr(self, "app_profile_service", None) is not None:
@@ -10,6 +10,7 @@ from frontengine.ui.dialog.app_profile_dialog import AppProfileDialog
10
10
  from frontengine.ui.dialog.clipboard_dialog import ClipboardDialog
11
11
  from frontengine.ui.dialog.hotkey_settings_dialog import HotkeySettingsDialog
12
12
  from frontengine.ui.dialog.reminder_dialog import ReminderDialog
13
+ from frontengine.ui.dialog.screen_privacy_dialog import ScreenPrivacyDialog
13
14
  from frontengine.ui.dialog.smart_pause_dialog import SmartPauseDialog
14
15
  from frontengine.ui.dialog.usage_report_dialog import UsageReportDialog
15
16
  from frontengine.user_setting.user_setting_file import (
@@ -87,6 +88,12 @@ def build_settings_menu(ui: "FrontEngineMainUI") -> None:
87
88
  menu.addAction(reminder_action)
88
89
  ui.reminder_action = reminder_action
89
90
 
91
+ privacy_action = QAction(
92
+ _t("settings_menu_screen_privacy", "Screen-sharing privacy..."), menu)
93
+ privacy_action.triggered.connect(lambda: _open_dialog(ui, ScreenPrivacyDialog))
94
+ menu.addAction(privacy_action)
95
+ ui.screen_privacy_action = privacy_action
96
+
90
97
  usage_action = QAction(_t("settings_menu_usage", "Screen time..."), menu)
91
98
  usage_action.triggered.connect(lambda: _open_usage_dialog(ui))
92
99
  menu.addAction(usage_action)
@@ -16,6 +16,7 @@ from frontengine.ui.page.web.web_setting_ui import WEBSettingUI
16
16
  from frontengine.user_setting.user_setting_file import clear_overlay_geometry
17
17
  from frontengine.utils.logging.loggin_instance import front_engine_logger
18
18
  from frontengine.utils.multi_language.language_wrapper import language_wrapper
19
+ from frontengine.utils.screen_privacy import capture_affinity
19
20
  from frontengine.utils.power_mode.power_mode import (
20
21
  TIER_BALANCED, TIER_HIGH, TIER_SAVER, normalize_tier,
21
22
  )
@@ -94,6 +95,10 @@ class ControlCenterUI(QWidget):
94
95
  self._extra_overlay_sources: list = []
95
96
  self.low_power_button = self._create_button(
96
97
  "control_center_low_power_on", self.toggle_low_power)
98
+ self._hidden_from_capture = False
99
+ self.capture_button = self._create_button(
100
+ "control_center_hide_from_capture", self.toggle_hide_from_capture)
101
+ self.capture_button.setEnabled(capture_affinity.available())
97
102
  self.clear_all_button = self._create_button("control_center_close_all", self.clear_all)
98
103
 
99
104
  # 畫質檔位:比省電模式更細,一次套用到所有覆蓋層
@@ -135,8 +140,9 @@ class ControlCenterUI(QWidget):
135
140
  self.grid_layout.addWidget(self.low_power_button, 13, 0)
136
141
  self.grid_layout.addWidget(self.quality_label, 14, 0)
137
142
  self.grid_layout.addWidget(self.quality_combobox, 15, 0)
138
- self.grid_layout.addWidget(self.clear_all_button, 16, 0)
139
- self.grid_layout.addWidget(self.log_panel_scroll_area, 0, 1, 17, 1) # rowSpan covers every button
143
+ self.grid_layout.addWidget(self.capture_button, 16, 0)
144
+ self.grid_layout.addWidget(self.clear_all_button, 17, 0)
145
+ self.grid_layout.addWidget(self.log_panel_scroll_area, 0, 1, 18, 1) # rowSpan covers every button
140
146
  self.setLayout(self.grid_layout)
141
147
 
142
148
  # Redirect
@@ -353,6 +359,43 @@ class ControlCenterUI(QWidget):
353
359
  )
354
360
  )
355
361
 
362
+ def set_hide_from_capture(self, hidden: bool) -> int:
363
+ """
364
+ 分享畫面時把覆蓋層藏起來(自己看得到、對方看不到)。標了
365
+ keep_in_capture 的覆蓋層不會被藏——遮蔽層本來就是要被看到的。
366
+ Hide the overlays from screen capture: still visible here, blank for the
367
+ people you are sharing with. Overlays marked keep_in_capture are left
368
+ alone, since a mask is meant to be seen.
369
+ """
370
+ self._hidden_from_capture = bool(hidden)
371
+ applied = 0
372
+
373
+ def apply(widget) -> None:
374
+ nonlocal applied
375
+ if getattr(widget, "keep_in_capture", False):
376
+ capture_affinity.apply_to_widget(widget, False)
377
+ return
378
+ if capture_affinity.apply_to_widget(widget, self._hidden_from_capture):
379
+ applied += 1
380
+
381
+ self._for_each_overlay(apply)
382
+ front_engine_logger.info(
383
+ f"ControlCenterUI set_hide_from_capture | hidden={hidden}, applied={applied}")
384
+ self.capture_button.setText(
385
+ language_wrapper.language_word_dict.get(
386
+ "control_center_show_in_capture" if self._hidden_from_capture
387
+ else "control_center_hide_from_capture",
388
+ "Show in capture" if self._hidden_from_capture else "Hide from capture"))
389
+ return applied
390
+
391
+ def toggle_hide_from_capture(self) -> None:
392
+ """切換分享時是否隱藏覆蓋層。"""
393
+ self.set_hide_from_capture(not self._hidden_from_capture)
394
+
395
+ @property
396
+ def hidden_from_capture(self) -> bool:
397
+ return self._hidden_from_capture
398
+
356
399
  def toggle_low_power(self) -> None:
357
400
  """切換省電模式 / Toggle low-power mode."""
358
401
  self.set_low_power(not getattr(self, "_low_power", False))
@@ -54,6 +54,9 @@ user_setting_dict: Dict[str, Any] = {
54
54
  # Sticky note contents and positions, and whether to reopen them on launch.
55
55
  "sticky_notes": [],
56
56
  "sticky_notes_restore": False,
57
+ # 分享畫面時自動把覆蓋層藏出擷取結果(預設關閉)
58
+ # Hide overlays from the capture while sharing; off by default.
59
+ "screen_share_privacy": {"enabled": False, "apps": []},
57
60
  # 使用時間統計(只寫本機檔案、預設關閉)
58
61
  # Usage tracking: local file only, off by default.
59
62
  "usage_tracking": False,
@@ -542,4 +542,13 @@ english_word_dict = {
542
542
  "stream_obs_record": 'Toggle recording',
543
543
  "stream_obs_stream": 'Toggle streaming',
544
544
  "stream_hint": 'The crosshair is only drawn on top - no game is modified. OBS control uses its own WebSocket; the password is kept in your local settings.',
545
+ "control_center_hide_from_capture": 'Hide from capture',
546
+ "control_center_show_in_capture": 'Show in capture',
547
+ "settings_menu_screen_privacy": 'Screen-sharing privacy...',
548
+ "screen_privacy_title": 'Screen-sharing privacy',
549
+ "screen_privacy_enable": 'Hide overlays while one of these apps is open',
550
+ "screen_privacy_apps_label": 'Meeting and capture apps:',
551
+ "screen_privacy_hint": 'Matched against window titles, so a meeting held in a browser tab is caught too. Your overlays stay on your own screen - only the capture is blank. Masks stay visible, since covering something is what they are for.',
552
+ "screen_privacy_unsupported": 'Hiding overlays from screen capture is Windows only.',
553
+ "screen_privacy_toast": 'Overlays hidden from screen capture',
545
554
  }
@@ -541,4 +541,13 @@ traditional_chinese_word_dict = {
541
541
  "stream_obs_record": '切換錄影',
542
542
  "stream_obs_stream": '切換直播',
543
543
  "stream_hint": '準星只是疊在畫面上的一層,不會更動任何遊戲。OBS 控制走它自己的 WebSocket,密碼只留在本機設定檔裡。',
544
+ "control_center_hide_from_capture": '分享時隱藏',
545
+ "control_center_show_in_capture": '分享時顯示',
546
+ "settings_menu_screen_privacy": '螢幕分享隱私…',
547
+ "screen_privacy_title": '螢幕分享隱私',
548
+ "screen_privacy_enable": '這些程式開著時,把覆蓋層藏出擷取畫面',
549
+ "screen_privacy_apps_label": '會議與擷取程式:',
550
+ "screen_privacy_hint": '比對的是視窗標題,所以在瀏覽器分頁裡開的會議也抓得到。覆蓋層仍會顯示在你自己的螢幕上,只有擷取畫面那一份是空的。遮蔽層不會被藏起來——遮住東西正是它存在的理由。',
551
+ "screen_privacy_unsupported": '把覆蓋層藏出螢幕擷取僅支援 Windows。',
552
+ "screen_privacy_toast": '已把覆蓋層藏出螢幕擷取',
544
553
  }
@@ -0,0 +1,120 @@
1
+ """
2
+ 把視窗排除在螢幕擷取之外:分享畫面時,自己看得到覆蓋層,對方看不到。
3
+
4
+ 用的是 Win32 的 `SetWindowDisplayAffinity(WDA_EXCLUDEFROMCAPTURE)`。這是作業系統
5
+ 層級的旗標——會議軟體、錄影軟體、螢幕擷取 API 拿到的畫面裡那塊會是空的,
6
+ 而本機螢幕上照常顯示。僅 Windows 10 2004 以後有效,其他平台一律回報做不到。
7
+
8
+ **這是「隱私」不是「保全」**:它擋的是正常的擷取管道,不是防止有心人用其他
9
+ 方式取得畫面,也不會讓視窗對使用者自己隱形。
10
+
11
+ Keep a window out of screen captures: you still see your overlays while the
12
+ people you are sharing with do not.
13
+
14
+ This is Win32's `SetWindowDisplayAffinity(WDA_EXCLUDEFROMCAPTURE)` - an
15
+ OS-level flag, so conferencing apps, recorders and capture APIs all receive
16
+ blank pixels there while the local display is unchanged. Windows 10 2004 and
17
+ later; everywhere else it reports that it cannot be done.
18
+
19
+ **This is privacy, not security**: it defeats the ordinary capture path, not a
20
+ determined attacker, and it never hides the window from the person at the desk.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import sys
25
+ from typing import Optional
26
+
27
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
28
+
29
+ # WDA_NONE:正常擷取;WDA_EXCLUDEFROMCAPTURE:擷取時整片留白
30
+ # WDA_NONE captures normally; WDA_EXCLUDEFROMCAPTURE comes out blank.
31
+ WDA_NONE = 0x00000000
32
+ WDA_EXCLUDEFROMCAPTURE = 0x00000011
33
+
34
+
35
+ def available() -> bool:
36
+ """這個平台能不能控制擷取可見性。"""
37
+ return sys.platform == "win32"
38
+
39
+
40
+ def _user32():
41
+ """拿到宣告好 argtypes 的 user32;非 Windows 回傳 None。"""
42
+ if not available():
43
+ return None
44
+ import ctypes
45
+ from ctypes import wintypes
46
+
47
+ user32 = ctypes.windll.user32
48
+ user32.SetWindowDisplayAffinity.argtypes = [wintypes.HWND, wintypes.DWORD]
49
+ user32.SetWindowDisplayAffinity.restype = wintypes.BOOL
50
+ user32.GetWindowDisplayAffinity.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.DWORD)]
51
+ user32.GetWindowDisplayAffinity.restype = wintypes.BOOL
52
+ return user32
53
+
54
+
55
+ def set_excluded_from_capture(handle: int, excluded: bool) -> bool:
56
+ """
57
+ 設定某個視窗要不要被排除在擷取之外;成功回傳 True。
58
+ Set whether a window is excluded from capture; True when it took effect.
59
+ """
60
+ user32 = _user32()
61
+ if user32 is None or not handle:
62
+ return False
63
+ try:
64
+ from ctypes import wintypes
65
+
66
+ affinity = WDA_EXCLUDEFROMCAPTURE if excluded else WDA_NONE
67
+ result = user32.SetWindowDisplayAffinity(wintypes.HWND(int(handle)), affinity)
68
+ front_engine_logger.debug(
69
+ f"[CaptureAffinity] {handle} excluded={excluded} ok={bool(result)}")
70
+ return bool(result)
71
+ except Exception as error: # pragma: no cover - Win32 boundary
72
+ front_engine_logger.warning(f"[CaptureAffinity] failed: {error!r}")
73
+ return False
74
+
75
+
76
+ def is_excluded_from_capture(handle: int) -> Optional[bool]:
77
+ """某個視窗目前是不是被排除在擷取之外;問不到回傳 None。"""
78
+ user32 = _user32()
79
+ if user32 is None or not handle:
80
+ return None
81
+ try:
82
+ import ctypes
83
+ from ctypes import wintypes
84
+
85
+ affinity = wintypes.DWORD()
86
+ if not user32.GetWindowDisplayAffinity(wintypes.HWND(int(handle)),
87
+ ctypes.byref(affinity)):
88
+ return None
89
+ return affinity.value == WDA_EXCLUDEFROMCAPTURE
90
+ except Exception as error: # pragma: no cover - Win32 boundary
91
+ front_engine_logger.debug(f"[CaptureAffinity] read failed: {error!r}")
92
+ return None
93
+
94
+
95
+ def apply_to_widget(widget, excluded: bool) -> bool:
96
+ """
97
+ 對一個 Qt widget 套用擷取可見性。視窗還沒建立原生 handle 時回傳 False,
98
+ 呼叫端可以等 show() 之後再試一次。
99
+ Apply capture visibility to a Qt widget. False when it has no native handle
100
+ yet, so the caller can try again after show().
101
+ """
102
+ if widget is None:
103
+ return False
104
+ try:
105
+ handle = int(widget.winId())
106
+ except (RuntimeError, ValueError): # pragma: no cover - widget torn down
107
+ return False
108
+ return set_excluded_from_capture(handle, excluded)
109
+
110
+
111
+ def apply_to_widgets(widgets, excluded: bool) -> int:
112
+ """對一批 widget 套用,回傳成功的數量。"""
113
+ applied = 0
114
+ for widget in list(widgets or ()):
115
+ try:
116
+ if apply_to_widget(widget, excluded):
117
+ applied += 1
118
+ except RuntimeError: # pragma: no cover - widget closed mid-loop
119
+ continue
120
+ return applied
@@ -0,0 +1,128 @@
1
+ """
2
+ 偵測「可能正在分享畫面」:看使用者指定的會議程式在不在。
3
+
4
+ Windows 沒有可靠的「我現在被擷取了嗎」API,所以這裡用的是誠實的近似:
5
+ 使用者自己列出會議軟體(Zoom、Teams、Discord…),只要那些程式有開著視窗,
6
+ 就當作可能在分享。這是刻意選擇的做法——與其猜錯,不如讓使用者說了算。
7
+
8
+ Spot "probably sharing the screen" by watching for the meeting apps the user
9
+ named.
10
+
11
+ Windows has no dependable "am I being captured" API, so this is an honest
12
+ approximation: the user lists their conferencing apps, and any of them having a
13
+ window open counts as possibly sharing. That is deliberate - better to let the
14
+ user decide than to guess wrong.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ from typing import Any, Callable, Dict, List, Optional
19
+
20
+ from PySide6.QtCore import QObject, QTimer, Signal
21
+
22
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
23
+ from frontengine.utils.smart_pause.pause_rules import normalize_app_name, parse_app_list
24
+ from frontengine.utils.window_pin.window_pin import list_windows
25
+
26
+ # 常見的會議/串流程式,當作預設值;使用者可以自己改
27
+ # The usual conferencing and streaming apps, as a starting point the user edits.
28
+ DEFAULT_SHARING_APPS = ("zoom", "teams", "ms-teams", "discord", "obs64", "obs",
29
+ "slack", "webex", "gotomeeting")
30
+ DEFAULT_INTERVAL_MS = 3000
31
+
32
+
33
+ def running_app_names(lister: Optional[Callable[[], List]] = None) -> List[str]:
34
+ """
35
+ 目前有視窗的程式名稱(正規化過)。用視窗列舉而不是列舉所有程序:
36
+ 看得見的視窗才可能被分享,也不需要更高的權限。
37
+ The normalized names of apps that currently have a window. Window
38
+ enumeration rather than a full process list: only visible windows can be
39
+ shared, and it needs no extra privileges.
40
+ """
41
+ names = []
42
+ for _handle, title in (lister or list_windows)() or []:
43
+ name = normalize_app_name(title)
44
+ if name and name not in names:
45
+ names.append(name)
46
+ return names
47
+
48
+
49
+ def sharing_app_running(watched: Any, window_titles: List[str]) -> Optional[str]:
50
+ """
51
+ 有沒有被盯著的程式開著視窗。比對是「視窗標題裡含有那個名字」——會議軟體
52
+ 的標題通常帶著自己的名字(例如 "Zoom Meeting"),比對執行檔名反而抓不到
53
+ 瀏覽器裡開的會議。回傳第一個命中的名字,沒有就 None。
54
+ Whether one of the watched apps has a window. Matching is "the title
55
+ contains the name", because meeting windows usually carry their app's name
56
+ ("Zoom Meeting") while an executable-name match would miss a meeting held
57
+ in a browser tab. Returns the first hit, or None.
58
+ """
59
+ wanted = parse_app_list(watched) or list(DEFAULT_SHARING_APPS)
60
+ haystack = [title.lower() for title in window_titles]
61
+ for name in wanted:
62
+ if any(name in title for title in haystack):
63
+ return name
64
+ return None
65
+
66
+
67
+ class ShareWatchService(QObject):
68
+ """
69
+ 定時檢查有沒有會議程式開著,狀態改變時發出訊號。視窗來源與設定都可注入。
70
+ Poll for a conferencing app and signal when that changes. Both the window
71
+ source and the configuration are injectable.
72
+ """
73
+
74
+ sharing_changed = Signal(bool, str) # (可能在分享, 命中的程式名稱)
75
+
76
+ def __init__(self, config_provider: Optional[Callable[[], Dict[str, Any]]] = None,
77
+ window_provider: Optional[Callable[[], List]] = None,
78
+ interval_ms: int = DEFAULT_INTERVAL_MS,
79
+ parent: Optional[QObject] = None) -> None:
80
+ super().__init__(parent)
81
+ self._config_provider = config_provider or (lambda: {})
82
+ self._window_provider = window_provider or list_windows
83
+ self._sharing = False
84
+ self._match = ""
85
+ self._timer = QTimer(self)
86
+ self._timer.setInterval(max(1000, int(interval_ms)))
87
+ self._timer.timeout.connect(self.poll_once)
88
+
89
+ @property
90
+ def sharing(self) -> bool:
91
+ return self._sharing
92
+
93
+ @property
94
+ def match(self) -> str:
95
+ """目前是哪個程式讓它判斷成「在分享」。"""
96
+ return self._match
97
+
98
+ def start(self) -> None:
99
+ front_engine_logger.info("[ShareWatch] start")
100
+ self._timer.start()
101
+
102
+ def stop(self) -> None:
103
+ front_engine_logger.info("[ShareWatch] stop")
104
+ self._timer.stop()
105
+
106
+ def poll_once(self) -> None:
107
+ """檢查一次(測試會直接呼叫)。"""
108
+ try:
109
+ config = self._config_provider() or {}
110
+ if not config.get("enabled", False):
111
+ self._update(False, "")
112
+ return
113
+ titles = [str(title) for _handle, title in (self._window_provider() or [])]
114
+ match = sharing_app_running(config.get("apps"), titles)
115
+ except Exception as error: # pragma: no cover - defensive boundary
116
+ front_engine_logger.warning(f"[ShareWatch] poll error: {error!r}")
117
+ return
118
+ self._update(match is not None, match or "")
119
+
120
+ def _update(self, sharing: bool, match: str) -> None:
121
+ if sharing == self._sharing:
122
+ self._match = match
123
+ return
124
+ self._sharing = sharing
125
+ self._match = match
126
+ front_engine_logger.info(
127
+ f"[ShareWatch] sharing={sharing}" + (f" | {match}" if match else ""))
128
+ self.sharing_changed.emit(sharing, match)
File without changes