frontengine 1.0.47__tar.gz → 1.0.49__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 (270) hide show
  1. {frontengine-1.0.47 → frontengine-1.0.49}/PKG-INFO +16 -1
  2. {frontengine-1.0.47 → frontengine-1.0.49}/README.md +15 -0
  3. frontengine-1.0.49/frontengine/show/canvas/whiteboard_widget.py +243 -0
  4. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/gaming/crosshair_widget.py +9 -8
  5. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/teleprompter/teleprompter_widget.py +3 -3
  6. frontengine-1.0.49/frontengine/ui/dialog/signage_dialog.py +100 -0
  7. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/main_ui.py +62 -3
  8. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/menu/settings_menu.py +18 -0
  9. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/control_center/control_center_ui.py +39 -12
  10. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/pet/pet_setting_ui.py +25 -4
  11. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/presentation/presentation_setting_ui.py +54 -7
  12. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/user_setting/user_setting_file.py +3 -0
  13. frontengine-1.0.49/frontengine/utils/audio_meter/microphone_meter.py +147 -0
  14. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/multi_language/english.py +12 -0
  15. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/multi_language/traditional_chinese.py +12 -0
  16. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/remote/remote_server.py +1 -3
  17. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/screen_privacy/capture_affinity.py +6 -3
  18. frontengine-1.0.49/frontengine/utils/signage/signage_service.py +156 -0
  19. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/virtual_camera/camera_feed.py +1 -1
  20. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/virtual_camera/virtual_camera.py +4 -4
  21. frontengine-1.0.49/frontengine/utils/workshop/__init__.py +0 -0
  22. frontengine-1.0.49/frontengine/worker/__init__.py +0 -0
  23. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine.egg-info/PKG-INFO +16 -1
  24. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine.egg-info/SOURCES.txt +6 -0
  25. {frontengine-1.0.47 → frontengine-1.0.49}/pyproject.toml +1 -1
  26. {frontengine-1.0.47 → frontengine-1.0.49}/LICENSE +0 -0
  27. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/__init__.py +0 -0
  28. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/__main__.py +0 -0
  29. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/__init__.py +0 -0
  30. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/base_widget.py +0 -0
  31. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/camera/__init__.py +0 -0
  32. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/camera/camera_widget.py +0 -0
  33. {frontengine-1.0.47/frontengine/show/capture → frontengine-1.0.49/frontengine/show/canvas}/__init__.py +0 -0
  34. {frontengine-1.0.47/frontengine/show/focus_shield → frontengine-1.0.49/frontengine/show/capture}/__init__.py +0 -0
  35. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/capture/region_capture.py +0 -0
  36. {frontengine-1.0.47/frontengine/show/gaming → frontengine-1.0.49/frontengine/show/focus_shield}/__init__.py +0 -0
  37. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/focus_shield/focus_shield_widget.py +0 -0
  38. {frontengine-1.0.47/frontengine/show/gif → frontengine-1.0.49/frontengine/show/gaming}/__init__.py +0 -0
  39. {frontengine-1.0.47/frontengine/show/image → frontengine-1.0.49/frontengine/show/gif}/__init__.py +0 -0
  40. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/gif/paint_gif.py +0 -0
  41. {frontengine-1.0.47/frontengine/show/load → frontengine-1.0.49/frontengine/show/image}/__init__.py +0 -0
  42. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/image/paint_image.py +0 -0
  43. {frontengine-1.0.47/frontengine/show/measure → frontengine-1.0.49/frontengine/show/load}/__init__.py +0 -0
  44. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/load/load_someone_make_ui.py +0 -0
  45. {frontengine-1.0.47/frontengine/show/monitor → frontengine-1.0.49/frontengine/show/measure}/__init__.py +0 -0
  46. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/measure/measure_widget.py +0 -0
  47. {frontengine-1.0.47/frontengine/show/notes → frontengine-1.0.49/frontengine/show/monitor}/__init__.py +0 -0
  48. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/monitor/monitor_widget.py +0 -0
  49. {frontengine-1.0.47/frontengine/show/particle → frontengine-1.0.49/frontengine/show/notes}/__init__.py +0 -0
  50. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/notes/sticky_note_widget.py +0 -0
  51. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/overlay_factory.py +0 -0
  52. {frontengine-1.0.47/frontengine/show/pet → frontengine-1.0.49/frontengine/show/particle}/__init__.py +0 -0
  53. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/particle/particle_ui.py +0 -0
  54. {frontengine-1.0.47/frontengine/show/presentation → frontengine-1.0.49/frontengine/show/pet}/__init__.py +0 -0
  55. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/pet/desktop_pet.py +0 -0
  56. {frontengine-1.0.47/frontengine/show/scene → frontengine-1.0.49/frontengine/show/presentation}/__init__.py +0 -0
  57. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/presentation/annotation_overlay.py +0 -0
  58. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/presentation/cursor_effects.py +0 -0
  59. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/presentation/keystroke_display.py +0 -0
  60. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/presentation/magnifier.py +0 -0
  61. {frontengine-1.0.47/frontengine/show/screen_care → frontengine-1.0.49/frontengine/show/scene}/__init__.py +0 -0
  62. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/scene/extend_graphic_scene.py +0 -0
  63. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/scene/extend_graphic_view.py +0 -0
  64. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/scene/scene.py +0 -0
  65. {frontengine-1.0.47/frontengine/show/sound_player → frontengine-1.0.49/frontengine/show/screen_care}/__init__.py +0 -0
  66. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/screen_care/color_vision_widget.py +0 -0
  67. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/screen_care/screen_filter.py +0 -0
  68. {frontengine-1.0.47/frontengine/show/spectrum → frontengine-1.0.49/frontengine/show/sound_player}/__init__.py +0 -0
  69. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/sound_player/sound_effect.py +0 -0
  70. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/sound_player/sound_player.py +0 -0
  71. {frontengine-1.0.47/frontengine/show/teleprompter → frontengine-1.0.49/frontengine/show/spectrum}/__init__.py +0 -0
  72. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/spectrum/spectrum_widget.py +0 -0
  73. {frontengine-1.0.47/frontengine/show/text → frontengine-1.0.49/frontengine/show/teleprompter}/__init__.py +0 -0
  74. {frontengine-1.0.47/frontengine/show/toast → frontengine-1.0.49/frontengine/show/text}/__init__.py +0 -0
  75. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/text/draw_text.py +0 -0
  76. {frontengine-1.0.47/frontengine/show/video → frontengine-1.0.49/frontengine/show/toast}/__init__.py +0 -0
  77. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/toast/toast_widget.py +0 -0
  78. {frontengine-1.0.47/frontengine/show/wallpaper → frontengine-1.0.49/frontengine/show/video}/__init__.py +0 -0
  79. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/video/video_player.py +0 -0
  80. {frontengine-1.0.47/frontengine/show/web → frontengine-1.0.49/frontengine/show/wallpaper}/__init__.py +0 -0
  81. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/wallpaper/wallpaper_widget.py +0 -0
  82. {frontengine-1.0.47/frontengine/system_tray → frontengine-1.0.49/frontengine/show/web}/__init__.py +0 -0
  83. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/web/webview.py +0 -0
  84. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/show/window_helpers.py +0 -0
  85. {frontengine-1.0.47/frontengine/ui → frontengine-1.0.49/frontengine/system_tray}/__init__.py +0 -0
  86. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/system_tray/extend_system_tray.py +0 -0
  87. {frontengine-1.0.47/frontengine/ui/color → frontengine-1.0.49/frontengine/ui}/__init__.py +0 -0
  88. {frontengine-1.0.47/frontengine/ui/dialog → frontengine-1.0.49/frontengine/ui/color}/__init__.py +0 -0
  89. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/color/global_color.py +0 -0
  90. {frontengine-1.0.47/frontengine/ui/menu → frontengine-1.0.49/frontengine/ui/dialog}/__init__.py +0 -0
  91. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/app_profile_dialog.py +0 -0
  92. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
  93. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/clipboard_dialog.py +0 -0
  94. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
  95. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/reminder_dialog.py +0 -0
  96. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/remote_control_dialog.py +0 -0
  97. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/save_file_dialog.py +0 -0
  98. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/screen_privacy_dialog.py +0 -0
  99. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/screen_text_dialog.py +0 -0
  100. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/smart_pause_dialog.py +0 -0
  101. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/usage_report_dialog.py +0 -0
  102. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/dialog/window_pin_dialog.py +0 -0
  103. {frontengine-1.0.47/frontengine/ui/page → frontengine-1.0.49/frontengine/ui/menu}/__init__.py +0 -0
  104. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/menu/help_menu.py +0 -0
  105. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/menu/how_to_menu.py +0 -0
  106. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/menu/language_menu.py +0 -0
  107. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/menu/preset_menu.py +0 -0
  108. {frontengine-1.0.47/frontengine/ui/page/control_center → frontengine-1.0.49/frontengine/ui/page}/__init__.py +0 -0
  109. {frontengine-1.0.47/frontengine/ui/page/focus → frontengine-1.0.49/frontengine/ui/page/control_center}/__init__.py +0 -0
  110. {frontengine-1.0.47/frontengine/ui/page/gif → frontengine-1.0.49/frontengine/ui/page/focus}/__init__.py +0 -0
  111. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/focus/focus_setting_ui.py +0 -0
  112. {frontengine-1.0.47/frontengine/ui/page/image → frontengine-1.0.49/frontengine/ui/page/gif}/__init__.py +0 -0
  113. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
  114. {frontengine-1.0.47/frontengine/ui/page/particle → frontengine-1.0.49/frontengine/ui/page/image}/__init__.py +0 -0
  115. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/image/image_setting_ui.py +0 -0
  116. {frontengine-1.0.47/frontengine/ui/page/pet → frontengine-1.0.49/frontengine/ui/page/particle}/__init__.py +0 -0
  117. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
  118. {frontengine-1.0.47/frontengine/ui/page/presentation → frontengine-1.0.49/frontengine/ui/page/pet}/__init__.py +0 -0
  119. {frontengine-1.0.47/frontengine/ui/page/scene_setting → frontengine-1.0.49/frontengine/ui/page/presentation}/__init__.py +0 -0
  120. {frontengine-1.0.47/frontengine/ui/page/scene_setting/scene_page → frontengine-1.0.49/frontengine/ui/page/scene_setting}/__init__.py +0 -0
  121. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
  122. {frontengine-1.0.47/frontengine/ui/page/screen_care → frontengine-1.0.49/frontengine/ui/page/scene_setting/scene_page}/__init__.py +0 -0
  123. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
  124. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
  125. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
  126. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
  127. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
  128. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
  129. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
  130. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
  131. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
  132. {frontengine-1.0.47/frontengine/ui/page/sound_player → frontengine-1.0.49/frontengine/ui/page/screen_care}/__init__.py +0 -0
  133. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +1 -1
  134. {frontengine-1.0.47/frontengine/ui/page/stream → frontengine-1.0.49/frontengine/ui/page/sound_player}/__init__.py +0 -0
  135. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
  136. {frontengine-1.0.47/frontengine/ui/page/text → frontengine-1.0.49/frontengine/ui/page/stream}/__init__.py +0 -0
  137. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/stream/stream_setting_ui.py +0 -0
  138. {frontengine-1.0.47/frontengine/ui/page/tools → frontengine-1.0.49/frontengine/ui/page/text}/__init__.py +0 -0
  139. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/text/text_setting_ui.py +0 -0
  140. {frontengine-1.0.47/frontengine/ui/page/video → frontengine-1.0.49/frontengine/ui/page/tools}/__init__.py +0 -0
  141. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/tools/tools_setting_ui.py +0 -0
  142. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/utils.py +0 -0
  143. {frontengine-1.0.47/frontengine/ui/page/wallpaper → frontengine-1.0.49/frontengine/ui/page/video}/__init__.py +0 -0
  144. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/video/video_setting_ui.py +0 -0
  145. {frontengine-1.0.47/frontengine/ui/page/web → frontengine-1.0.49/frontengine/ui/page/wallpaper}/__init__.py +0 -0
  146. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +0 -0
  147. {frontengine-1.0.47/frontengine/ui/page/widgets → frontengine-1.0.49/frontengine/ui/page/web}/__init__.py +0 -0
  148. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/web/web_setting_ui.py +0 -0
  149. {frontengine-1.0.47/frontengine/user_setting → frontengine-1.0.49/frontengine/ui/page/widgets}/__init__.py +0 -0
  150. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/ui/page/widgets/widgets_setting_ui.py +0 -0
  151. {frontengine-1.0.47/frontengine/utils → frontengine-1.0.49/frontengine/user_setting}/__init__.py +0 -0
  152. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/user_setting/preset_repository.py +0 -0
  153. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/user_setting/scene_setting.py +0 -0
  154. {frontengine-1.0.47/frontengine/utils/app_profile → frontengine-1.0.49/frontengine/utils}/__init__.py +0 -0
  155. {frontengine-1.0.47/frontengine/utils/audio_meter → frontengine-1.0.49/frontengine/utils/app_profile}/__init__.py +0 -0
  156. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/app_profile/app_profile_service.py +0 -0
  157. {frontengine-1.0.47/frontengine/utils/autostart → frontengine-1.0.49/frontengine/utils/audio_meter}/__init__.py +0 -0
  158. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
  159. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/audio_meter/loopback_capture.py +0 -0
  160. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/audio_meter/screen_audio.py +0 -0
  161. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/audio_meter/spectrum_analyzer.py +0 -0
  162. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/audio_meter/system_audio_meter.py +0 -0
  163. {frontengine-1.0.47/frontengine/utils/break_reminder → frontengine-1.0.49/frontengine/utils/autostart}/__init__.py +0 -0
  164. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/autostart/autostart_service.py +0 -0
  165. {frontengine-1.0.47/frontengine/utils/browser → frontengine-1.0.49/frontengine/utils/break_reminder}/__init__.py +0 -0
  166. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/break_reminder/break_reminder.py +0 -0
  167. {frontengine-1.0.47/frontengine/utils/clipboard → frontengine-1.0.49/frontengine/utils/browser}/__init__.py +0 -0
  168. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/browser/browser.py +0 -0
  169. {frontengine-1.0.47/frontengine/utils/color_vision → frontengine-1.0.49/frontengine/utils/clipboard}/__init__.py +0 -0
  170. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/clipboard/clipboard_history.py +0 -0
  171. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/clipboard/clipboard_watcher.py +0 -0
  172. {frontengine-1.0.47/frontengine/utils/critical_exit → frontengine-1.0.49/frontengine/utils/color_vision}/__init__.py +0 -0
  173. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/color_vision/color_vision.py +0 -0
  174. {frontengine-1.0.47/frontengine/utils/file → frontengine-1.0.49/frontengine/utils/critical_exit}/__init__.py +0 -0
  175. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
  176. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/critical_exit/critical_exit.py +0 -0
  177. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/critical_exit/win32_vk.py +0 -0
  178. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/exception/__init__.py +0 -0
  179. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/exception/exception_tags.py +0 -0
  180. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/exception/exceptions.py +0 -0
  181. {frontengine-1.0.47/frontengine/utils/focus_shield → frontengine-1.0.49/frontengine/utils/file}/__init__.py +0 -0
  182. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/file/open/__init__.py +0 -0
  183. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/file/open/open_file.py +0 -0
  184. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/file/save/__init__.py +0 -0
  185. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/file/save/save_file.py +0 -0
  186. {frontengine-1.0.47/frontengine/utils/focus_timer → frontengine-1.0.49/frontengine/utils/focus_shield}/__init__.py +0 -0
  187. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/focus_shield/focus_shield.py +0 -0
  188. {frontengine-1.0.47/frontengine/utils/hotkey → frontengine-1.0.49/frontengine/utils/focus_timer}/__init__.py +0 -0
  189. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/focus_timer/focus_timer.py +0 -0
  190. {frontengine-1.0.47/frontengine/utils/input_watch → frontengine-1.0.49/frontengine/utils/hotkey}/__init__.py +0 -0
  191. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/hotkey/hotkey_service.py +0 -0
  192. {frontengine-1.0.47/frontengine/utils/json → frontengine-1.0.49/frontengine/utils/input_watch}/__init__.py +0 -0
  193. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/input_watch/input_watch_service.py +0 -0
  194. {frontengine-1.0.47/frontengine/utils/logging → frontengine-1.0.49/frontengine/utils/json}/__init__.py +0 -0
  195. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/json/json_file.py +0 -0
  196. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/json/json_repository.py +0 -0
  197. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/json_format/__init__.py +0 -0
  198. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/json_format/json_process.py +0 -0
  199. {frontengine-1.0.47/frontengine/utils/measure → frontengine-1.0.49/frontengine/utils/logging}/__init__.py +0 -0
  200. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/logging/loggin_instance.py +0 -0
  201. {frontengine-1.0.47/frontengine/utils/midi → frontengine-1.0.49/frontengine/utils/measure}/__init__.py +0 -0
  202. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/measure/measure.py +0 -0
  203. {frontengine-1.0.47/frontengine/utils/multi_language → frontengine-1.0.49/frontengine/utils/midi}/__init__.py +0 -0
  204. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/midi/midi_input.py +0 -0
  205. {frontengine-1.0.47/frontengine/utils/now_playing → frontengine-1.0.49/frontengine/utils/multi_language}/__init__.py +0 -0
  206. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/multi_language/france.py +0 -0
  207. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/multi_language/germany.py +0 -0
  208. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/multi_language/italy.py +0 -0
  209. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/multi_language/language_wrapper.py +0 -0
  210. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/multi_language/russian.py +0 -0
  211. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
  212. {frontengine-1.0.47/frontengine/utils/obs → frontengine-1.0.49/frontengine/utils/now_playing}/__init__.py +0 -0
  213. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/now_playing/now_playing.py +0 -0
  214. {frontengine-1.0.47/frontengine/utils/pet_chat → frontengine-1.0.49/frontengine/utils/obs}/__init__.py +0 -0
  215. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/obs/obs_client.py +0 -0
  216. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/obs/obs_protocol.py +0 -0
  217. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/obs/websocket_frames.py +0 -0
  218. {frontengine-1.0.47/frontengine/utils/platform_info → frontengine-1.0.49/frontengine/utils/pet_chat}/__init__.py +0 -0
  219. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
  220. {frontengine-1.0.47/frontengine/utils/playlist → frontengine-1.0.49/frontengine/utils/platform_info}/__init__.py +0 -0
  221. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/platform_info/platform_info.py +0 -0
  222. {frontengine-1.0.47/frontengine/utils/plugins → frontengine-1.0.49/frontengine/utils/playlist}/__init__.py +0 -0
  223. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/playlist/playlist.py +0 -0
  224. {frontengine-1.0.47/frontengine/utils/power_mode → frontengine-1.0.49/frontengine/utils/plugins}/__init__.py +0 -0
  225. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/plugins/plugin_loader.py +0 -0
  226. {frontengine-1.0.47/frontengine/utils/preset_schedule → frontengine-1.0.49/frontengine/utils/power_mode}/__init__.py +0 -0
  227. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/power_mode/power_mode.py +0 -0
  228. {frontengine-1.0.47/frontengine/utils/recording → frontengine-1.0.49/frontengine/utils/preset_schedule}/__init__.py +0 -0
  229. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
  230. {frontengine-1.0.47/frontengine/utils/redirect_manager → frontengine-1.0.49/frontengine/utils/recording}/__init__.py +0 -0
  231. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/recording/frame_recorder.py +0 -0
  232. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/recording/gif_writer.py +0 -0
  233. {frontengine-1.0.47/frontengine/utils/reminder → frontengine-1.0.49/frontengine/utils/redirect_manager}/__init__.py +0 -0
  234. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
  235. {frontengine-1.0.47/frontengine/utils/remote → frontengine-1.0.49/frontengine/utils/reminder}/__init__.py +0 -0
  236. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/reminder/reminder_service.py +0 -0
  237. {frontengine-1.0.47/frontengine/utils/screen_privacy → frontengine-1.0.49/frontengine/utils/remote}/__init__.py +0 -0
  238. {frontengine-1.0.47/frontengine/utils/screen_text → frontengine-1.0.49/frontengine/utils/screen_privacy}/__init__.py +0 -0
  239. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/screen_privacy/share_watch.py +0 -0
  240. {frontengine-1.0.47/frontengine/utils/smart_pause → frontengine-1.0.49/frontengine/utils/screen_text}/__init__.py +0 -0
  241. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/screen_text/screen_text_service.py +0 -0
  242. {frontengine-1.0.47/frontengine/utils/soundboard → frontengine-1.0.49/frontengine/utils/signage}/__init__.py +0 -0
  243. {frontengine-1.0.47/frontengine/utils/system_stats → frontengine-1.0.49/frontengine/utils/smart_pause}/__init__.py +0 -0
  244. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/smart_pause/pause_rules.py +0 -0
  245. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
  246. {frontengine-1.0.47/frontengine/utils/text_source → frontengine-1.0.49/frontengine/utils/soundboard}/__init__.py +0 -0
  247. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/soundboard/soundboard.py +0 -0
  248. {frontengine-1.0.47/frontengine/utils/theme_schedule → frontengine-1.0.49/frontengine/utils/system_stats}/__init__.py +0 -0
  249. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/system_stats/system_stats.py +0 -0
  250. {frontengine-1.0.47/frontengine/utils/usage_tracking → frontengine-1.0.49/frontengine/utils/text_source}/__init__.py +0 -0
  251. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/text_source/text_source.py +0 -0
  252. {frontengine-1.0.47/frontengine/utils/virtual_camera → frontengine-1.0.49/frontengine/utils/theme_schedule}/__init__.py +0 -0
  253. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
  254. {frontengine-1.0.47/frontengine/utils/weather → frontengine-1.0.49/frontengine/utils/usage_tracking}/__init__.py +0 -0
  255. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/usage_tracking/usage_service.py +0 -0
  256. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/usage_tracking/usage_tracker.py +0 -0
  257. {frontengine-1.0.47/frontengine/utils/window_pin → frontengine-1.0.49/frontengine/utils/virtual_camera}/__init__.py +0 -0
  258. {frontengine-1.0.47/frontengine/utils/workshop → frontengine-1.0.49/frontengine/utils/weather}/__init__.py +0 -0
  259. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/weather/weather_service.py +0 -0
  260. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/web_url.py +0 -0
  261. {frontengine-1.0.47/frontengine/worker → frontengine-1.0.49/frontengine/utils/window_pin}/__init__.py +0 -0
  262. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/window_pin/window_layout.py +0 -0
  263. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/window_pin/window_pin.py +0 -0
  264. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/utils/workshop/workshop_content.py +0 -0
  265. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine/worker/qthread_worker.py +0 -0
  266. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine.egg-info/dependency_links.txt +0 -0
  267. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine.egg-info/entry_points.txt +0 -0
  268. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine.egg-info/requires.txt +0 -0
  269. {frontengine-1.0.47 → frontengine-1.0.49}/frontengine.egg-info/top_level.txt +0 -0
  270. {frontengine-1.0.47 → frontengine-1.0.49}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.47
3
+ Version: 1.0.49
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
@@ -204,6 +204,21 @@ Four things on the **Stream** tab, for playing and for going live:
204
204
 
205
205
  ---
206
206
 
207
+ ## Signage, whiteboard and lip-sync
208
+
209
+ - **Signage mode** (Settings) — rotate through a list of presets on a timer and
210
+ put the main window away, for a machine left running as a display. The
211
+ rotation wraps, so it keeps cycling.
212
+ - **Whiteboard** (Presenting tab) — an infinite canvas: drag with the middle
213
+ button to pan, scroll to zoom, and save what you drew as an image. Strokes
214
+ live in canvas coordinates, so panning and zooming leaves them where they
215
+ belong.
216
+ - **Pet lip-sync** — the pet can react to your **microphone** instead of your
217
+ speakers, so it moves while you talk. Like the speaker reaction this reads
218
+ only the meter — a number, not sound. Windows only.
219
+
220
+ ---
221
+
207
222
  ## Remote control
208
223
 
209
224
  From **Settings → Remote control**:
@@ -180,6 +180,21 @@ Four things on the **Stream** tab, for playing and for going live:
180
180
 
181
181
  ---
182
182
 
183
+ ## Signage, whiteboard and lip-sync
184
+
185
+ - **Signage mode** (Settings) — rotate through a list of presets on a timer and
186
+ put the main window away, for a machine left running as a display. The
187
+ rotation wraps, so it keeps cycling.
188
+ - **Whiteboard** (Presenting tab) — an infinite canvas: drag with the middle
189
+ button to pan, scroll to zoom, and save what you drew as an image. Strokes
190
+ live in canvas coordinates, so panning and zooming leaves them where they
191
+ belong.
192
+ - **Pet lip-sync** — the pet can react to your **microphone** instead of your
193
+ speakers, so it moves while you talk. Like the speaker reaction this reads
194
+ only the meter — a number, not sound. Windows only.
195
+
196
+ ---
197
+
183
198
  ## Remote control
184
199
 
185
200
  From **Settings → Remote control**:
@@ -0,0 +1,243 @@
1
+ """
2
+ 無限畫布白板:可以一直往外畫、拖曳平移、滾輪縮放,最後存成圖片。
3
+
4
+ 和簡報分頁的塗鴉層不同的是,筆畫存的是「畫布座標」而不是螢幕座標,所以平移
5
+ 縮放之後線條還在它原本該在的地方——這正是「無限」的意思。
6
+
7
+ An infinite whiteboard: keep drawing outwards, drag to pan, scroll to zoom, and
8
+ save the result as an image.
9
+
10
+ Unlike the presenting page's annotation layer, strokes are stored in canvas
11
+ coordinates rather than screen ones, so panning and zooming leaves them where
12
+ they belong - which is what makes it infinite.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from pathlib import Path
17
+ from typing import Any, Dict, List, Optional, Tuple
18
+
19
+ from PySide6.QtCore import QPoint, QPointF, QRectF, Qt
20
+ from PySide6.QtGui import QColor, QImage, QPainter, QPen
21
+
22
+ from frontengine.show.base_widget import BaseWidget
23
+ from frontengine.show.window_helpers import apply_overlay_window_flags
24
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
25
+
26
+ DEFAULT_COLOR = "#ffd54f"
27
+ DEFAULT_WIDTH = 4
28
+ MIN_ZOOM = 0.2
29
+ MAX_ZOOM = 5.0
30
+ ZOOM_STEP = 1.15
31
+ _SAVE_MARGIN = 24
32
+
33
+
34
+ def clamp_zoom(value: Any, fallback: float = 1.0) -> float:
35
+ """縮放倍率夾在看得清楚又不會迷路的範圍。"""
36
+ try:
37
+ return max(MIN_ZOOM, min(MAX_ZOOM, float(value)))
38
+ except (TypeError, ValueError):
39
+ return fallback
40
+
41
+
42
+ def to_canvas(point: Tuple[float, float], offset: Tuple[float, float], zoom: float) -> Tuple[float, float]:
43
+ """螢幕座標 -> 畫布座標。"""
44
+ scale = clamp_zoom(zoom)
45
+ return ((point[0] - offset[0]) / scale, (point[1] - offset[1]) / scale)
46
+
47
+
48
+ def to_screen(point: Tuple[float, float], offset: Tuple[float, float], zoom: float) -> Tuple[float, float]:
49
+ """畫布座標 -> 螢幕座標。"""
50
+ scale = clamp_zoom(zoom)
51
+ return (point[0] * scale + offset[0], point[1] * scale + offset[1])
52
+
53
+
54
+ def content_bounds(strokes: List[Dict[str, Any]]) -> Optional[Tuple[float, float, float, float]]:
55
+ """
56
+ 所有筆畫涵蓋的畫布範圍 (left, top, right, bottom);沒有筆畫回傳 None。
57
+ 存檔時用它決定要輸出多大,才不會存出一大片空白。
58
+ The canvas area the strokes cover, or None when there are none. Saving uses
59
+ it to size the image, so the result is not mostly blank.
60
+ """
61
+ points = [point for stroke in strokes for point in stroke.get("points", [])]
62
+ if not points:
63
+ return None
64
+ xs = [point[0] for point in points]
65
+ ys = [point[1] for point in points]
66
+ return (min(xs), min(ys), max(xs), max(ys))
67
+
68
+
69
+ class WhiteboardWidget(BaseWidget):
70
+ """
71
+ 無限畫布。筆畫以畫布座標保存,畫的時候才換算成螢幕座標。
72
+ """
73
+
74
+ def __init__(self, color: str = DEFAULT_COLOR, width: int = DEFAULT_WIDTH) -> None:
75
+ front_engine_logger.info("[WhiteboardWidget] Init")
76
+ super().__init__()
77
+ candidate = QColor(color)
78
+ self.color = candidate if candidate.isValid() else QColor(DEFAULT_COLOR)
79
+ self.pen_width = max(1, int(width))
80
+ self.opacity = 1.0
81
+ self.overlay_lockable = False
82
+ self.strokes: List[Dict[str, Any]] = []
83
+ self.zoom = 1.0
84
+ self.offset = [0.0, 0.0]
85
+ self._stroke: Optional[Dict[str, Any]] = None
86
+ self._pan_origin: Optional[QPoint] = None
87
+ apply_overlay_window_flags(self, show_on_bottom=False, allow_input=True)
88
+
89
+ # --- drawing ---------------------------------------------------------
90
+ def begin_stroke(self, point: QPoint) -> None:
91
+ """開始一筆。"""
92
+ self._stroke = {"color": self.color.name(), "width": self.pen_width,
93
+ "points": [to_canvas((point.x(), point.y()), self.offset, self.zoom)]}
94
+
95
+ def extend_stroke(self, point: QPoint) -> None:
96
+ if self._stroke is not None:
97
+ self._stroke["points"].append(to_canvas((point.x(), point.y()), self.offset, self.zoom))
98
+ self.update()
99
+
100
+ def end_stroke(self) -> bool:
101
+ """
102
+ 收筆。只有一個點的「筆畫」是誤點,丟掉不留。
103
+ Finish. A one-point stroke is a stray click and is discarded.
104
+ """
105
+ stroke, self._stroke = self._stroke, None
106
+ if stroke is None or len(stroke["points"]) < 2:
107
+ return False
108
+ self.strokes.append(stroke)
109
+ self.update()
110
+ return True
111
+
112
+ def undo(self) -> bool:
113
+ """收回最後一筆。"""
114
+ if not self.strokes:
115
+ return False
116
+ self.strokes.pop()
117
+ self.update()
118
+ return True
119
+
120
+ def clear(self) -> None:
121
+ self.strokes = []
122
+ self._stroke = None
123
+ self.update()
124
+
125
+ def set_pen(self, color: Optional[str] = None, width: Optional[int] = None) -> None:
126
+ if color is not None:
127
+ candidate = QColor(color)
128
+ if candidate.isValid():
129
+ self.color = candidate
130
+ if width is not None:
131
+ self.pen_width = max(1, int(width))
132
+
133
+ # --- navigating ------------------------------------------------------
134
+ def pan(self, dx: float, dy: float) -> None:
135
+ """平移畫布。"""
136
+ self.offset[0] += float(dx)
137
+ self.offset[1] += float(dy)
138
+ self.update()
139
+
140
+ def zoom_at(self, point: QPoint, factor: float) -> float:
141
+ """
142
+ 以游標為中心縮放:游標底下的那一點在縮放前後要停在原地,
143
+ 不然放大時畫面會整個跑掉。
144
+ Zoom around the cursor so whatever is under it stays put; otherwise the
145
+ view lurches away every time you zoom.
146
+ """
147
+ before = to_canvas((point.x(), point.y()), self.offset, self.zoom)
148
+ self.zoom = clamp_zoom(self.zoom * float(factor))
149
+ after = to_screen(before, self.offset, self.zoom)
150
+ self.offset[0] += point.x() - after[0]
151
+ self.offset[1] += point.y() - after[1]
152
+ self.update()
153
+ return self.zoom
154
+
155
+ def reset_view(self) -> None:
156
+ """回到原點與原始比例(筆畫都還在)。"""
157
+ self.zoom = 1.0
158
+ self.offset = [0.0, 0.0]
159
+ self.update()
160
+
161
+ # --- events ----------------------------------------------------------
162
+ def mousePressEvent(self, event) -> None:
163
+ if event.button() == Qt.MouseButton.LeftButton:
164
+ self.begin_stroke(event.position().toPoint())
165
+ elif event.button() == Qt.MouseButton.MiddleButton:
166
+ self._pan_origin = event.position().toPoint()
167
+ super().mousePressEvent(event)
168
+
169
+ def mouseMoveEvent(self, event) -> None:
170
+ point = event.position().toPoint()
171
+ if self._pan_origin is not None:
172
+ self.pan(point.x() - self._pan_origin.x(), point.y() - self._pan_origin.y())
173
+ self._pan_origin = point
174
+ else:
175
+ self.extend_stroke(point)
176
+ super().mouseMoveEvent(event)
177
+
178
+ def mouseReleaseEvent(self, event) -> None:
179
+ if event.button() == Qt.MouseButton.MiddleButton:
180
+ self._pan_origin = None
181
+ else:
182
+ self.end_stroke()
183
+ super().mouseReleaseEvent(event)
184
+
185
+ def wheelEvent(self, event) -> None:
186
+ step = ZOOM_STEP if event.angleDelta().y() > 0 else 1.0 / ZOOM_STEP
187
+ self.zoom_at(event.position().toPoint(), step)
188
+
189
+ # --- output ----------------------------------------------------------
190
+ def save_image(self, path: str) -> Optional[str]:
191
+ """
192
+ 把所有筆畫存成一張圖(只涵蓋畫過的範圍)。沒有筆畫就不存,回傳 None。
193
+ Save the strokes as an image covering only the area drawn on. Nothing
194
+ drawn means nothing saved.
195
+ """
196
+ bounds = content_bounds(self.strokes)
197
+ if bounds is None:
198
+ return None
199
+ left, top, right, bottom = bounds
200
+ width = int(right - left) + _SAVE_MARGIN * 2
201
+ height = int(bottom - top) + _SAVE_MARGIN * 2
202
+ image = QImage(max(1, width), max(1, height), QImage.Format.Format_ARGB32)
203
+ image.fill(Qt.GlobalColor.transparent)
204
+ painter = QPainter(image)
205
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
206
+ for stroke in self.strokes:
207
+ self._paint_stroke(painter, stroke,
208
+ offset=(_SAVE_MARGIN - left, _SAVE_MARGIN - top), zoom=1.0)
209
+ painter.end()
210
+ target = Path(path)
211
+ try:
212
+ target.parent.mkdir(parents=True, exist_ok=True)
213
+ if not image.save(str(target)):
214
+ return None
215
+ except OSError as error:
216
+ front_engine_logger.warning(f"[WhiteboardWidget] save failed: {error!r}")
217
+ return None
218
+ front_engine_logger.info(f"[WhiteboardWidget] saved | {target}")
219
+ return str(target)
220
+
221
+ def _paint_stroke(self, painter: QPainter, stroke: Dict[str, Any],
222
+ offset, zoom: float) -> None:
223
+ points = stroke.get("points", [])
224
+ if len(points) < 2:
225
+ return
226
+ pen = QPen(QColor(stroke.get("color", DEFAULT_COLOR)),
227
+ max(1.0, float(stroke.get("width", DEFAULT_WIDTH)) * zoom))
228
+ pen.setCapStyle(Qt.PenCapStyle.RoundCap)
229
+ pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
230
+ painter.setPen(pen)
231
+ previous = to_screen(points[0], offset, zoom)
232
+ for point in points[1:]:
233
+ current = to_screen(point, offset, zoom)
234
+ painter.drawLine(QPointF(*previous), QPointF(*current))
235
+ previous = current
236
+
237
+ def draw_content(self, painter: QPainter) -> None:
238
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
239
+ painter.fillRect(QRectF(self.rect()), QColor(0, 0, 0, 120))
240
+ for stroke in self.strokes:
241
+ self._paint_stroke(painter, stroke, self.offset, self.zoom)
242
+ if self._stroke is not None:
243
+ self._paint_stroke(painter, self._stroke, self.offset, self.zoom)
@@ -13,10 +13,10 @@ stacks windows and not something to work around.
13
13
  """
14
14
  from __future__ import annotations
15
15
 
16
- from typing import Tuple
16
+ from typing import Any, Optional, Tuple
17
17
 
18
18
  from PySide6.QtCore import QPointF, Qt
19
- from PySide6.QtGui import QColor, QPainter, QPen
19
+ from PySide6.QtGui import QColor, QPainter, QPen, QScreen
20
20
 
21
21
  from frontengine.show.base_widget import BaseWidget
22
22
  from frontengine.utils.logging.loggin_instance import front_engine_logger
@@ -35,13 +35,13 @@ MIN_SIZE = 4
35
35
  MAX_SIZE = 200
36
36
 
37
37
 
38
- def normalize_style(style) -> str:
38
+ def normalize_style(style: Any) -> str:
39
39
  """把準星樣式正規化;不認得的一律當十字。"""
40
40
  name = str(style or "").strip().lower()
41
41
  return name if name in STYLES else STYLE_CROSS
42
42
 
43
43
 
44
- def clamp_size(value, fallback: int = DEFAULT_SIZE) -> int:
44
+ def clamp_size(value: Any, fallback: int = DEFAULT_SIZE) -> int:
45
45
  """準星尺寸夾在看得見又不誇張的範圍。"""
46
46
  try:
47
47
  return max(MIN_SIZE, min(MAX_SIZE, int(value)))
@@ -49,7 +49,7 @@ def clamp_size(value, fallback: int = DEFAULT_SIZE) -> int:
49
49
  return fallback
50
50
 
51
51
 
52
- def clamp_thickness(value, fallback: int = DEFAULT_THICKNESS) -> int:
52
+ def clamp_thickness(value: Any, fallback: int = DEFAULT_THICKNESS) -> int:
53
53
  """線寬夾在 1~12。"""
54
54
  try:
55
55
  return max(1, min(12, int(value)))
@@ -57,7 +57,7 @@ def clamp_thickness(value, fallback: int = DEFAULT_THICKNESS) -> int:
57
57
  return fallback
58
58
 
59
59
 
60
- def clamp_gap(value, size: int = DEFAULT_SIZE, fallback: int = DEFAULT_GAP) -> int:
60
+ def clamp_gap(value: Any, size: int = DEFAULT_SIZE, fallback: int = DEFAULT_GAP) -> int:
61
61
  """
62
62
  中央空隙不能大到把準星吃光,所以上限跟著尺寸走。
63
63
  The centre gap cannot swallow the crosshair, so its ceiling follows the size.
@@ -100,7 +100,8 @@ class CrosshairWidget(BaseWidget):
100
100
  self.opacity = 1.0
101
101
  self.resize(self.size * 2 + 8, self.size * 2 + 8)
102
102
 
103
- def set_crosshair(self, style=None, color=None, size=None,
103
+ def set_crosshair(self, style: Optional[str] = None, color: Optional[str] = None,
104
+ size: Optional[int] = None,
104
105
  thickness=None, gap=None) -> None:
105
106
  """一次改好幾項設定,改完立刻重畫。"""
106
107
  if style is not None:
@@ -119,7 +120,7 @@ class CrosshairWidget(BaseWidget):
119
120
  self.centre_on_screen()
120
121
  self.update()
121
122
 
122
- def centre_on_screen(self, screen=None) -> None:
123
+ def centre_on_screen(self, screen: Optional[QScreen] = None) -> None:
123
124
  """擺到螢幕正中央(準星要對準的是畫面中心)。"""
124
125
  from PySide6.QtGui import QGuiApplication
125
126
 
@@ -12,7 +12,7 @@ and it stops at the last line rather than snapping back to the top mid-read.
12
12
  """
13
13
  from __future__ import annotations
14
14
 
15
- from typing import List, Optional
15
+ from typing import Any, List, Optional
16
16
 
17
17
  from PySide6.QtCore import Qt, QTimer
18
18
  from PySide6.QtGui import QColor, QFont, QFontMetrics, QPainter, QTransform
@@ -31,7 +31,7 @@ _TICK_MS = 33
31
31
  _SIDE_MARGIN = 40
32
32
 
33
33
 
34
- def clamp_speed(value, fallback: int = DEFAULT_SPEED) -> int:
34
+ def clamp_speed(value: Any, fallback: int = DEFAULT_SPEED) -> int:
35
35
  """捲動速度夾在讀得完又跟得上的範圍。"""
36
36
  try:
37
37
  return max(MIN_SPEED, min(MAX_SPEED, int(value)))
@@ -39,7 +39,7 @@ def clamp_speed(value, fallback: int = DEFAULT_SPEED) -> int:
39
39
  return fallback
40
40
 
41
41
 
42
- def clamp_font_size(value, fallback: int = DEFAULT_FONT_SIZE) -> int:
42
+ def clamp_font_size(value: Any, fallback: int = DEFAULT_FONT_SIZE) -> int:
43
43
  """字級夾在看得見的範圍。"""
44
44
  try:
45
45
  return max(MIN_FONT_SIZE, min(MAX_FONT_SIZE, int(value)))
@@ -0,0 +1,100 @@
1
+ """
2
+ 看板模式設定:依序輪播一組預設集,適合把機器擺著當展示螢幕。
3
+
4
+ Signage settings: rotate through a set of presets, for a machine left running
5
+ as a display.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Dict, Optional
10
+
11
+ from PySide6.QtWidgets import (
12
+ QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QLabel, QPlainTextEdit, QSpinBox, QWidget,
13
+ )
14
+
15
+ from frontengine.user_setting.user_setting_file import user_setting_dict, write_user_setting
16
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
17
+ from frontengine.utils.multi_language.language_wrapper import language_wrapper
18
+ from frontengine.utils.signage.signage_service import (
19
+ DEFAULT_INTERVAL_MINUTES, MAX_INTERVAL_MINUTES, MIN_INTERVAL_MINUTES, clamp_interval,
20
+ normalize_playlist,
21
+ )
22
+
23
+ SETTING_KEY = "signage"
24
+ DEFAULT_SETTINGS: Dict[str, Any] = {
25
+ "enabled": False, "presets": [], "interval_minutes": DEFAULT_INTERVAL_MINUTES,
26
+ "hide_window": True,
27
+ }
28
+
29
+
30
+ def _t(key: str, fallback: str) -> str:
31
+ return language_wrapper.language_word_dict.get(key, fallback)
32
+
33
+
34
+ def current_settings() -> Dict[str, Any]:
35
+ """目前的看板設定(缺項目補預設值)。"""
36
+ stored = user_setting_dict.get(SETTING_KEY)
37
+ settings = dict(DEFAULT_SETTINGS)
38
+ if isinstance(stored, dict):
39
+ settings.update({key: stored[key] for key in DEFAULT_SETTINGS if key in stored})
40
+ settings["presets"] = normalize_playlist(settings.get("presets"))
41
+ settings["interval_minutes"] = clamp_interval(settings.get("interval_minutes"))
42
+ return settings
43
+
44
+
45
+ class SignageDialog(QDialog):
46
+ """設定要輪播哪些預設集、多久換一次。"""
47
+
48
+ def __init__(self, parent: Optional[QWidget] = None) -> None:
49
+ front_engine_logger.info("[SignageDialog] Init")
50
+ super().__init__(parent)
51
+ self.setWindowTitle(_t("signage_title", "Signage mode"))
52
+ settings = current_settings()
53
+
54
+ self.enable_checkbox = QCheckBox(_t("signage_enable", "Rotate presets"))
55
+ self.enable_checkbox.setChecked(bool(settings.get("enabled")))
56
+ self.presets_label = QLabel(_t("signage_presets_label", "Presets, one per line:"))
57
+ self.presets_edit = QPlainTextEdit("\n".join(settings.get("presets", [])))
58
+ self.interval_label = QLabel(_t("signage_interval_label", "Change every (minutes)"))
59
+ self.interval_spinbox = QSpinBox()
60
+ self.interval_spinbox.setRange(MIN_INTERVAL_MINUTES, MAX_INTERVAL_MINUTES)
61
+ self.interval_spinbox.setValue(settings.get("interval_minutes"))
62
+ self.hide_checkbox = QCheckBox(
63
+ _t("signage_hide_window", "Hide the main window while rotating"))
64
+ self.hide_checkbox.setChecked(bool(settings.get("hide_window", True)))
65
+ self.hint_label = QLabel(
66
+ _t("signage_hint",
67
+ "Presets are saved from the Presets menu. The rotation wraps at the end, so a "
68
+ "machine left running keeps cycling."))
69
+ self.hint_label.setWordWrap(True)
70
+
71
+ self.button_box = QDialogButtonBox(
72
+ QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
73
+ self.button_box.accepted.connect(self.accept)
74
+ self.button_box.rejected.connect(self.reject)
75
+
76
+ layout = QGridLayout(self)
77
+ layout.addWidget(self.enable_checkbox, 0, 0, 1, 2)
78
+ layout.addWidget(self.presets_label, 1, 0, 1, 2)
79
+ layout.addWidget(self.presets_edit, 2, 0, 1, 2)
80
+ layout.addWidget(self.interval_label, 3, 0)
81
+ layout.addWidget(self.interval_spinbox, 3, 1)
82
+ layout.addWidget(self.hide_checkbox, 4, 0, 1, 2)
83
+ layout.addWidget(self.hint_label, 5, 0, 1, 2)
84
+ layout.addWidget(self.button_box, 6, 0, 1, 2)
85
+
86
+ def settings(self) -> Dict[str, Any]:
87
+ """對話框上目前的設定。"""
88
+ return {
89
+ "enabled": self.enable_checkbox.isChecked(),
90
+ "presets": normalize_playlist(self.presets_edit.toPlainText()),
91
+ "interval_minutes": self.interval_spinbox.value(),
92
+ "hide_window": self.hide_checkbox.isChecked(),
93
+ }
94
+
95
+ def accept(self) -> None:
96
+ settings = self.settings()
97
+ user_setting_dict[SETTING_KEY] = settings
98
+ write_user_setting()
99
+ front_engine_logger.info(f"[SignageDialog] saved | {len(settings['presets'])} preset(s)")
100
+ super().accept()
@@ -2,7 +2,7 @@ import os
2
2
  import sys
3
3
  from os import getcwd
4
4
  from pathlib import Path
5
- from typing import Dict, Optional, Type
5
+ from typing import Any, Dict, Optional, Type
6
6
 
7
7
  from PySide6.QtCore import QByteArray, QTimer, QCoreApplication
8
8
  from PySide6.QtGui import QIcon, Qt
@@ -53,6 +53,8 @@ from frontengine.ui.dialog.remote_control_dialog import (
53
53
  MIDI_KEY, current_bindings, remote_enabled,
54
54
  )
55
55
  from frontengine.ui.dialog.screen_privacy_dialog import current_settings as current_privacy_settings
56
+ from frontengine.ui.dialog.signage_dialog import current_settings as current_signage_settings
57
+ from frontengine.utils.signage.signage_service import SignageService
56
58
  from frontengine.utils.midi.midi_input import MidiInput, binding_key
57
59
  from frontengine.utils.remote.remote_server import RemoteServer
58
60
  from frontengine.ui.dialog.smart_pause_dialog import current_rules
@@ -72,6 +74,20 @@ from frontengine.utils.theme_schedule.theme_schedule_service import ThemeSchedul
72
74
  FrontEngine_EXTEND_TAB: Dict[str, Type[QWidget]] = {}
73
75
 
74
76
 
77
+ def tray_can_restore_window(window: Any) -> bool:
78
+ """
79
+ 系統匣能不能把主視窗叫回來。任何「把主視窗藏起來」的功能都得先問這件事,
80
+ 不然藏掉的就是唯一能把該功能關掉的畫面。
81
+ Whether the tray can bring the main window back. Anything that hides the
82
+ window has to ask first: otherwise what it hides is the only screen that
83
+ can turn that same thing off.
84
+ """
85
+ tray = getattr(window, "system_tray", None)
86
+ return bool(ExtendSystemTray.isSystemTrayAvailable()
87
+ and getattr(window, "show_system_tray_ray", False)
88
+ and tray is not None and tray.isVisible())
89
+
90
+
75
91
  class FrontEngineMainUI(QMainWindow):
76
92
  """
77
93
  FrontEngine 主視窗
@@ -235,6 +251,13 @@ class FrontEngineMainUI(QMainWindow):
235
251
  if user_setting_dict.get(MIDI_KEY):
236
252
  self.midi_input.start(user_setting_dict.get("midi_device") or 0)
237
253
 
254
+ # 看板模式:依序輪播預設集,主視窗可以收起來
255
+ # Signage mode: rotate presets, with the main window out of the way.
256
+ self.signage_service = SignageService(config_provider=current_signage_settings)
257
+ self.signage_service.preset_due.connect(lambda name: apply_named_preset(self, name))
258
+ if current_signage_settings().get("enabled"):
259
+ self.start_signage()
260
+
238
261
  # 分享畫面時把覆蓋層藏出擷取結果(自己仍看得到)
239
262
  # Hide the overlays from the capture while sharing; they stay on screen here.
240
263
  self.share_watch_service = ShareWatchService(config_provider=current_privacy_settings)
@@ -312,9 +335,10 @@ class FrontEngineMainUI(QMainWindow):
312
335
  """
313
336
  sources = (
314
337
  (self.screen_care_setting_ui, ("filter_widget_list", "ruler_widget_list",
315
- "break_overlay_list")),
338
+ "break_overlay_list", "color_vision_widget_list")),
316
339
  (self.presentation_setting_ui, ("annotation_widget_list", "cursor_widget_list",
317
- "keystroke_widget_list", "magnifier_widget_list")),
340
+ "keystroke_widget_list", "magnifier_widget_list",
341
+ "whiteboard_widget_list")),
318
342
  (self.focus_setting_ui, ("dim_widget_list", "mask_widget_list")),
319
343
  )
320
344
  for page, attributes in sources:
@@ -325,6 +349,12 @@ class FrontEngineMainUI(QMainWindow):
325
349
  # The wallpaper page keys its widgets by monitor, so hand over the values.
326
350
  self.control_center_ui.register_overlay_source(
327
351
  lambda: list(self.wallpaper_setting_ui.wallpaper_widgets.values()))
352
+ self.control_center_ui.register_overlay_source(
353
+ lambda: getattr(self.web_setting_ui, "dashboard_widgets", []))
354
+ # 批次關閉之後,沒有覆蓋層在聽就把全域輸入監聽收掉
355
+ # After a batch close nothing is listening, so drop the global input hook.
356
+ self.control_center_ui.register_cleanup(
357
+ self.presentation_setting_ui.release_input_watch)
328
358
  for attribute in ("spectrum_widget_list", "monitor_widget_list",
329
359
  "now_playing_widget_list", "note_widget_list"):
330
360
  self.control_center_ui.register_overlay_source(
@@ -508,6 +538,34 @@ class FrontEngineMainUI(QMainWindow):
508
538
  if action:
509
539
  self._handle_hotkey(action)
510
540
 
541
+ def start_signage(self) -> Optional[str]:
542
+ """開始輪播;設定要求、而且托盤能把視窗叫回來時才收起主視窗。"""
543
+ first = self.signage_service.start()
544
+ if first is not None and current_signage_settings().get("hide_window", True):
545
+ self._hide_for_signage()
546
+ return first
547
+
548
+ def _hide_for_signage(self) -> None:
549
+ """
550
+ 收起主視窗——但只在系統匣真的能把它叫回來的時候。沒有系統匣就等於
551
+ 把唯一能關掉輪播的畫面藏起來,重開也只會再被藏一次,使用者就只能去
552
+ 手動改設定檔了。
553
+ Put the main window away - but only when the tray can actually bring it
554
+ back. Without a tray this would hide the only screen that can turn the
555
+ rotation off, and restarting just hides it again, leaving the settings
556
+ file as the only way out.
557
+ """
558
+ if tray_can_restore_window(self):
559
+ self.hide()
560
+ return
561
+ front_engine_logger.info(
562
+ "[FrontEngineMainUI] signage kept the window visible: no tray to restore it from")
563
+
564
+ def stop_signage(self) -> None:
565
+ """停止輪播並把主視窗放回來。"""
566
+ self.signage_service.stop()
567
+ self.show()
568
+
511
569
  def _on_sharing_changed(self, sharing: bool, match: str) -> None:
512
570
  """
513
571
  偵測到會議程式開著/關掉時,自動把覆蓋層藏出擷取結果或放回去。
@@ -573,6 +631,7 @@ class FrontEngineMainUI(QMainWindow):
573
631
  # The background services to stop on close, outermost first.
574
632
  _CLOSING_SERVICES = (
575
633
  "preset_schedule_service", "theme_schedule_service", "usage_service",
634
+ "signage_service",
576
635
  "remote_server", "midi_input", "share_watch_service", "reminder_service",
577
636
  "app_profile_service", "smart_pause_service", "hotkey_service",
578
637
  )
@@ -12,6 +12,7 @@ from frontengine.ui.dialog.hotkey_settings_dialog import HotkeySettingsDialog
12
12
  from frontengine.ui.dialog.reminder_dialog import ReminderDialog
13
13
  from frontengine.ui.dialog.remote_control_dialog import RemoteControlDialog
14
14
  from frontengine.ui.dialog.screen_privacy_dialog import ScreenPrivacyDialog
15
+ from frontengine.ui.dialog.signage_dialog import SignageDialog
15
16
  from frontengine.ui.dialog.smart_pause_dialog import SmartPauseDialog
16
17
  from frontengine.ui.dialog.usage_report_dialog import UsageReportDialog
17
18
  from frontengine.user_setting.user_setting_file import (
@@ -89,6 +90,11 @@ def build_settings_menu(ui: "FrontEngineMainUI") -> None:
89
90
  menu.addAction(reminder_action)
90
91
  ui.reminder_action = reminder_action
91
92
 
93
+ signage_action = QAction(_t("settings_menu_signage", "Signage mode..."), menu)
94
+ signage_action.triggered.connect(lambda: _open_signage_dialog(ui))
95
+ menu.addAction(signage_action)
96
+ ui.signage_action = signage_action
97
+
92
98
  remote_action = QAction(_t("settings_menu_remote", "Remote control..."), menu)
93
99
  remote_action.triggered.connect(lambda: _open_remote_dialog(ui))
94
100
  menu.addAction(remote_action)
@@ -127,6 +133,18 @@ def build_settings_menu(ui: "FrontEngineMainUI") -> None:
127
133
  menu.addAction(import_action)
128
134
 
129
135
 
136
+ def _open_signage_dialog(ui: "FrontEngineMainUI") -> None:
137
+ """開看板設定;按下確定後立刻套用新的輪播狀態。"""
138
+ front_engine_logger.info("[SettingsMenu] open SignageDialog")
139
+ dialog = SignageDialog(ui)
140
+ if dialog.exec() != QDialog.DialogCode.Accepted:
141
+ return
142
+ if dialog.settings().get("enabled"):
143
+ ui.start_signage()
144
+ else:
145
+ ui.stop_signage()
146
+
147
+
130
148
  def _open_remote_dialog(ui: "FrontEngineMainUI") -> None:
131
149
  """開遙控設定,並把它接上 MIDI 的「學習」流程。"""
132
150
  front_engine_logger.info("[SettingsMenu] open RemoteControlDialog")