frontengine 1.0.65__tar.gz → 1.0.67__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 (265) hide show
  1. {frontengine-1.0.65 → frontengine-1.0.67}/PKG-INFO +1 -1
  2. frontengine-1.0.67/frontengine/show/replica/replica_widget.py +118 -0
  3. frontengine-1.0.67/frontengine/ui/dialog/window_replica_dialog.py +124 -0
  4. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/main_ui.py +4 -0
  5. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/gif/gif_setting_ui.py +2 -0
  6. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/image/image_setting_ui.py +2 -0
  7. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/particle/particle_setting_ui.py +15 -2
  8. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/text/text_setting_ui.py +2 -0
  9. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/tools/tools_setting_ui.py +22 -1
  10. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/utils.py +57 -4
  11. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/video/video_setting_ui.py +2 -0
  12. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/web/web_setting_ui.py +2 -0
  13. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/multi_language/english.py +8 -0
  14. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/multi_language/france.py +8 -0
  15. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/multi_language/germany.py +8 -0
  16. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/multi_language/italy.py +8 -0
  17. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/multi_language/russian.py +8 -0
  18. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/multi_language/simplified_chinese.py +8 -0
  19. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/multi_language/traditional_chinese.py +8 -0
  20. frontengine-1.0.67/frontengine/utils/window_replica/__init__.py +0 -0
  21. frontengine-1.0.67/frontengine/utils/window_replica/dwm_thumbnail.py +189 -0
  22. frontengine-1.0.67/frontengine/utils/workshop/__init__.py +0 -0
  23. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine.egg-info/PKG-INFO +1 -1
  24. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine.egg-info/SOURCES.txt +5 -0
  25. {frontengine-1.0.65 → frontengine-1.0.67}/pyproject.toml +1 -1
  26. {frontengine-1.0.65 → frontengine-1.0.67}/LICENSE +0 -0
  27. {frontengine-1.0.65 → frontengine-1.0.67}/README.md +0 -0
  28. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/__init__.py +0 -0
  29. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/__main__.py +0 -0
  30. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/__init__.py +0 -0
  31. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/base_widget.py +0 -0
  32. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/camera/__init__.py +0 -0
  33. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/camera/camera_widget.py +0 -0
  34. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/canvas/__init__.py +0 -0
  35. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/canvas/whiteboard_widget.py +0 -0
  36. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/capture/__init__.py +0 -0
  37. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/capture/region_capture.py +0 -0
  38. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/focus_shield/__init__.py +0 -0
  39. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/focus_shield/focus_shield_widget.py +0 -0
  40. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/gif/__init__.py +0 -0
  41. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/gif/paint_gif.py +0 -0
  42. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/image/__init__.py +0 -0
  43. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/image/paint_image.py +0 -0
  44. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/load/__init__.py +0 -0
  45. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/load/load_someone_make_ui.py +0 -0
  46. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/measure/__init__.py +0 -0
  47. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/measure/measure_widget.py +0 -0
  48. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/monitor/__init__.py +0 -0
  49. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/monitor/monitor_widget.py +0 -0
  50. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/notes/__init__.py +0 -0
  51. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/notes/sticky_note_widget.py +0 -0
  52. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/overlay_factory.py +0 -0
  53. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/particle/__init__.py +0 -0
  54. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/particle/particle_ui.py +0 -0
  55. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/pet/__init__.py +0 -0
  56. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/pet/desktop_pet.py +0 -0
  57. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/presentation/__init__.py +0 -0
  58. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/presentation/annotation_overlay.py +0 -0
  59. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/presentation/cursor_effects.py +0 -0
  60. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/presentation/keystroke_display.py +0 -0
  61. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/presentation/magnifier.py +0 -0
  62. {frontengine-1.0.65/frontengine/show/scene → frontengine-1.0.67/frontengine/show/replica}/__init__.py +0 -0
  63. {frontengine-1.0.65/frontengine/show/screen_care → frontengine-1.0.67/frontengine/show/scene}/__init__.py +0 -0
  64. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/scene/extend_graphic_scene.py +0 -0
  65. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/scene/extend_graphic_view.py +0 -0
  66. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/scene/scene.py +0 -0
  67. {frontengine-1.0.65/frontengine/show/sound_player → frontengine-1.0.67/frontengine/show/screen_care}/__init__.py +0 -0
  68. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/screen_care/color_vision_widget.py +0 -0
  69. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/screen_care/screen_filter.py +0 -0
  70. {frontengine-1.0.65/frontengine/show/spectrum → frontengine-1.0.67/frontengine/show/sound_player}/__init__.py +0 -0
  71. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/sound_player/sound_effect.py +0 -0
  72. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/sound_player/sound_player.py +0 -0
  73. {frontengine-1.0.65/frontengine/show/text → frontengine-1.0.67/frontengine/show/spectrum}/__init__.py +0 -0
  74. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/spectrum/spectrum_widget.py +0 -0
  75. {frontengine-1.0.65/frontengine/show/toast → frontengine-1.0.67/frontengine/show/text}/__init__.py +0 -0
  76. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/text/draw_text.py +0 -0
  77. {frontengine-1.0.65/frontengine/show/video → frontengine-1.0.67/frontengine/show/toast}/__init__.py +0 -0
  78. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/toast/toast_widget.py +0 -0
  79. {frontengine-1.0.65/frontengine/show/wallpaper → frontengine-1.0.67/frontengine/show/video}/__init__.py +0 -0
  80. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/video/video_player.py +0 -0
  81. {frontengine-1.0.65/frontengine/show/web → frontengine-1.0.67/frontengine/show/wallpaper}/__init__.py +0 -0
  82. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/wallpaper/wallpaper_widget.py +0 -0
  83. {frontengine-1.0.65/frontengine/system_tray → frontengine-1.0.67/frontengine/show/web}/__init__.py +0 -0
  84. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/web/webview.py +0 -0
  85. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/show/window_helpers.py +0 -0
  86. {frontengine-1.0.65/frontengine/ui → frontengine-1.0.67/frontengine/system_tray}/__init__.py +0 -0
  87. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/system_tray/extend_system_tray.py +0 -0
  88. {frontengine-1.0.65/frontengine/ui/color → frontengine-1.0.67/frontengine/ui}/__init__.py +0 -0
  89. {frontengine-1.0.65/frontengine/ui/dialog → frontengine-1.0.67/frontengine/ui/color}/__init__.py +0 -0
  90. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/color/global_color.py +0 -0
  91. {frontengine-1.0.65/frontengine/ui/menu → frontengine-1.0.67/frontengine/ui/dialog}/__init__.py +0 -0
  92. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/app_profile_dialog.py +0 -0
  93. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
  94. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/clipboard_dialog.py +0 -0
  95. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
  96. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/how_to_use_dialog.py +0 -0
  97. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/reminder_dialog.py +0 -0
  98. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/remote_control_dialog.py +0 -0
  99. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/screen_privacy_dialog.py +0 -0
  100. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/screen_text_dialog.py +0 -0
  101. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/screensaver_dialog.py +0 -0
  102. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/signage_dialog.py +0 -0
  103. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/smart_pause_dialog.py +0 -0
  104. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/usage_report_dialog.py +0 -0
  105. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/dialog/window_pin_dialog.py +0 -0
  106. {frontengine-1.0.65/frontengine/ui/nav → frontengine-1.0.67/frontengine/ui/menu}/__init__.py +0 -0
  107. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/menu/help_menu.py +0 -0
  108. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/menu/how_to_menu.py +0 -0
  109. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/menu/language_menu.py +0 -0
  110. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/menu/preset_menu.py +0 -0
  111. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/menu/settings_menu.py +0 -0
  112. {frontengine-1.0.65/frontengine/ui/page → frontengine-1.0.67/frontengine/ui/nav}/__init__.py +0 -0
  113. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/nav/sidebar.py +0 -0
  114. {frontengine-1.0.65/frontengine/ui/page/control_center → frontengine-1.0.67/frontengine/ui/page}/__init__.py +0 -0
  115. {frontengine-1.0.65/frontengine/ui/page/focus → frontengine-1.0.67/frontengine/ui/page/control_center}/__init__.py +0 -0
  116. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/control_center/control_center_ui.py +0 -0
  117. {frontengine-1.0.65/frontengine/ui/page/gif → frontengine-1.0.67/frontengine/ui/page/focus}/__init__.py +0 -0
  118. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/focus/focus_setting_ui.py +0 -0
  119. {frontengine-1.0.65/frontengine/ui/page/image → frontengine-1.0.67/frontengine/ui/page/gif}/__init__.py +0 -0
  120. {frontengine-1.0.65/frontengine/ui/page/particle → frontengine-1.0.67/frontengine/ui/page/image}/__init__.py +0 -0
  121. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/layout_kit.py +0 -0
  122. {frontengine-1.0.65/frontengine/ui/page/pet → frontengine-1.0.67/frontengine/ui/page/particle}/__init__.py +0 -0
  123. {frontengine-1.0.65/frontengine/ui/page/presentation → frontengine-1.0.67/frontengine/ui/page/pet}/__init__.py +0 -0
  124. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
  125. {frontengine-1.0.65/frontengine/ui/page/scene_setting → frontengine-1.0.67/frontengine/ui/page/presentation}/__init__.py +0 -0
  126. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/presentation/presentation_setting_ui.py +0 -0
  127. {frontengine-1.0.65/frontengine/ui/page/scene_setting/scene_page → frontengine-1.0.67/frontengine/ui/page/scene_setting}/__init__.py +0 -0
  128. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
  129. {frontengine-1.0.65/frontengine/ui/page/screen_care → frontengine-1.0.67/frontengine/ui/page/scene_setting/scene_page}/__init__.py +0 -0
  130. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
  131. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
  132. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
  133. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
  134. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
  135. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
  136. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
  137. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
  138. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
  139. {frontengine-1.0.65/frontengine/ui/page/sound_player → frontengine-1.0.67/frontengine/ui/page/screen_care}/__init__.py +0 -0
  140. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +0 -0
  141. {frontengine-1.0.65/frontengine/ui/page/text → frontengine-1.0.67/frontengine/ui/page/sound_player}/__init__.py +0 -0
  142. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
  143. {frontengine-1.0.65/frontengine/ui/page/tools → frontengine-1.0.67/frontengine/ui/page/text}/__init__.py +0 -0
  144. {frontengine-1.0.65/frontengine/ui/page/video → frontengine-1.0.67/frontengine/ui/page/tools}/__init__.py +0 -0
  145. {frontengine-1.0.65/frontengine/ui/page/wallpaper → frontengine-1.0.67/frontengine/ui/page/video}/__init__.py +0 -0
  146. {frontengine-1.0.65/frontengine/ui/page/web → frontengine-1.0.67/frontengine/ui/page/wallpaper}/__init__.py +0 -0
  147. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +0 -0
  148. {frontengine-1.0.65/frontengine/ui/page/widgets → frontengine-1.0.67/frontengine/ui/page/web}/__init__.py +0 -0
  149. {frontengine-1.0.65/frontengine/ui/style → frontengine-1.0.67/frontengine/ui/page/widgets}/__init__.py +0 -0
  150. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/page/widgets/widgets_setting_ui.py +0 -0
  151. {frontengine-1.0.65/frontengine/user_setting → frontengine-1.0.67/frontengine/ui/style}/__init__.py +0 -0
  152. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/ui/style/app_style.py +0 -0
  153. {frontengine-1.0.65/frontengine/utils → frontengine-1.0.67/frontengine/user_setting}/__init__.py +0 -0
  154. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/user_setting/preset_repository.py +0 -0
  155. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/user_setting/scene_setting.py +0 -0
  156. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/user_setting/user_setting_file.py +0 -0
  157. {frontengine-1.0.65/frontengine/utils/app_profile → frontengine-1.0.67/frontengine/utils}/__init__.py +0 -0
  158. {frontengine-1.0.65/frontengine/utils/audio_meter → frontengine-1.0.67/frontengine/utils/app_profile}/__init__.py +0 -0
  159. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/app_profile/app_profile_service.py +0 -0
  160. {frontengine-1.0.65/frontengine/utils/autostart → frontengine-1.0.67/frontengine/utils/audio_meter}/__init__.py +0 -0
  161. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
  162. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/audio_meter/loopback_capture.py +0 -0
  163. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/audio_meter/microphone_meter.py +0 -0
  164. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/audio_meter/screen_audio.py +0 -0
  165. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/audio_meter/spectrum_analyzer.py +0 -0
  166. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/audio_meter/system_audio_meter.py +0 -0
  167. {frontengine-1.0.65/frontengine/utils/break_reminder → frontengine-1.0.67/frontengine/utils/autostart}/__init__.py +0 -0
  168. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/autostart/autostart_service.py +0 -0
  169. {frontengine-1.0.65/frontengine/utils/browser → frontengine-1.0.67/frontengine/utils/break_reminder}/__init__.py +0 -0
  170. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/break_reminder/break_reminder.py +0 -0
  171. {frontengine-1.0.65/frontengine/utils/clipboard → frontengine-1.0.67/frontengine/utils/browser}/__init__.py +0 -0
  172. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/browser/browser.py +0 -0
  173. {frontengine-1.0.65/frontengine/utils/color_vision → frontengine-1.0.67/frontengine/utils/clipboard}/__init__.py +0 -0
  174. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/clipboard/clipboard_history.py +0 -0
  175. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/clipboard/clipboard_watcher.py +0 -0
  176. {frontengine-1.0.65/frontengine/utils/critical_exit → frontengine-1.0.67/frontengine/utils/color_vision}/__init__.py +0 -0
  177. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/color_vision/color_vision.py +0 -0
  178. {frontengine-1.0.65/frontengine/utils/focus_shield → frontengine-1.0.67/frontengine/utils/critical_exit}/__init__.py +0 -0
  179. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
  180. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/critical_exit/critical_exit.py +0 -0
  181. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/critical_exit/win32_vk.py +0 -0
  182. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/exception/__init__.py +0 -0
  183. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/exception/exception_tags.py +0 -0
  184. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/exception/exceptions.py +0 -0
  185. {frontengine-1.0.65/frontengine/utils/focus_timer → frontengine-1.0.67/frontengine/utils/focus_shield}/__init__.py +0 -0
  186. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/focus_shield/focus_shield.py +0 -0
  187. {frontengine-1.0.65/frontengine/utils/hotkey → frontengine-1.0.67/frontengine/utils/focus_timer}/__init__.py +0 -0
  188. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/focus_timer/focus_timer.py +0 -0
  189. {frontengine-1.0.65/frontengine/utils/input_watch → frontengine-1.0.67/frontengine/utils/hotkey}/__init__.py +0 -0
  190. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/hotkey/hotkey_service.py +0 -0
  191. {frontengine-1.0.65/frontengine/utils/json → frontengine-1.0.67/frontengine/utils/input_watch}/__init__.py +0 -0
  192. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/input_watch/input_watch_service.py +0 -0
  193. {frontengine-1.0.65/frontengine/utils/logging → frontengine-1.0.67/frontengine/utils/json}/__init__.py +0 -0
  194. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/json/json_file.py +0 -0
  195. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/json/json_repository.py +0 -0
  196. {frontengine-1.0.65/frontengine/utils/measure → frontengine-1.0.67/frontengine/utils/logging}/__init__.py +0 -0
  197. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/logging/loggin_instance.py +0 -0
  198. {frontengine-1.0.65/frontengine/utils/midi → frontengine-1.0.67/frontengine/utils/measure}/__init__.py +0 -0
  199. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/measure/measure.py +0 -0
  200. {frontengine-1.0.65/frontengine/utils/multi_language → frontengine-1.0.67/frontengine/utils/midi}/__init__.py +0 -0
  201. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/midi/midi_input.py +0 -0
  202. {frontengine-1.0.65/frontengine/utils/now_playing → frontengine-1.0.67/frontengine/utils/multi_language}/__init__.py +0 -0
  203. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/multi_language/language_wrapper.py +0 -0
  204. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/multi_language/retranslate.py +0 -0
  205. {frontengine-1.0.65/frontengine/utils/pet_chat → frontengine-1.0.67/frontengine/utils/now_playing}/__init__.py +0 -0
  206. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/now_playing/now_playing.py +0 -0
  207. {frontengine-1.0.65/frontengine/utils/platform_info → frontengine-1.0.67/frontengine/utils/pet_chat}/__init__.py +0 -0
  208. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
  209. {frontengine-1.0.65/frontengine/utils/playlist → frontengine-1.0.67/frontengine/utils/platform_info}/__init__.py +0 -0
  210. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/platform_info/platform_info.py +0 -0
  211. {frontengine-1.0.65/frontengine/utils/plugins → frontengine-1.0.67/frontengine/utils/playlist}/__init__.py +0 -0
  212. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/playlist/playlist.py +0 -0
  213. {frontengine-1.0.65/frontengine/utils/power_mode → frontengine-1.0.67/frontengine/utils/plugins}/__init__.py +0 -0
  214. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/plugins/plugin_loader.py +0 -0
  215. {frontengine-1.0.65/frontengine/utils/preset_schedule → frontengine-1.0.67/frontengine/utils/power_mode}/__init__.py +0 -0
  216. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/power_mode/power_mode.py +0 -0
  217. {frontengine-1.0.65/frontengine/utils/recording → frontengine-1.0.67/frontengine/utils/preset_schedule}/__init__.py +0 -0
  218. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
  219. {frontengine-1.0.65/frontengine/utils/redirect_manager → frontengine-1.0.67/frontengine/utils/recording}/__init__.py +0 -0
  220. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/recording/frame_recorder.py +0 -0
  221. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/recording/gif_writer.py +0 -0
  222. {frontengine-1.0.65/frontengine/utils/reminder → frontengine-1.0.67/frontengine/utils/redirect_manager}/__init__.py +0 -0
  223. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
  224. {frontengine-1.0.65/frontengine/utils/remote → frontengine-1.0.67/frontengine/utils/reminder}/__init__.py +0 -0
  225. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/reminder/reminder_service.py +0 -0
  226. {frontengine-1.0.65/frontengine/utils/screen_privacy → frontengine-1.0.67/frontengine/utils/remote}/__init__.py +0 -0
  227. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/remote/remote_server.py +0 -0
  228. {frontengine-1.0.65/frontengine/utils/screen_text → frontengine-1.0.67/frontengine/utils/screen_privacy}/__init__.py +0 -0
  229. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/screen_privacy/capture_affinity.py +0 -0
  230. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/screen_privacy/share_watch.py +0 -0
  231. {frontengine-1.0.65/frontengine/utils/screensaver → frontengine-1.0.67/frontengine/utils/screen_text}/__init__.py +0 -0
  232. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/screen_text/screen_text_service.py +0 -0
  233. {frontengine-1.0.65/frontengine/utils/signage → frontengine-1.0.67/frontengine/utils/screensaver}/__init__.py +0 -0
  234. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/screensaver/screensaver_service.py +0 -0
  235. {frontengine-1.0.65/frontengine/utils/smart_pause → frontengine-1.0.67/frontengine/utils/signage}/__init__.py +0 -0
  236. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/signage/signage_service.py +0 -0
  237. {frontengine-1.0.65/frontengine/utils/steam → frontengine-1.0.67/frontengine/utils/smart_pause}/__init__.py +0 -0
  238. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/smart_pause/pause_rules.py +0 -0
  239. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
  240. {frontengine-1.0.65/frontengine/utils/system_stats → frontengine-1.0.67/frontengine/utils/steam}/__init__.py +0 -0
  241. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/steam/steam_language.py +0 -0
  242. {frontengine-1.0.65/frontengine/utils/text_source → frontengine-1.0.67/frontengine/utils/system_stats}/__init__.py +0 -0
  243. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/system_stats/system_stats.py +0 -0
  244. {frontengine-1.0.65/frontengine/utils/theme_schedule → frontengine-1.0.67/frontengine/utils/text_source}/__init__.py +0 -0
  245. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/text_source/text_source.py +0 -0
  246. {frontengine-1.0.65/frontengine/utils/usage_tracking → frontengine-1.0.67/frontengine/utils/theme_schedule}/__init__.py +0 -0
  247. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
  248. {frontengine-1.0.65/frontengine/utils/virtual_camera → frontengine-1.0.67/frontengine/utils/usage_tracking}/__init__.py +0 -0
  249. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/usage_tracking/usage_service.py +0 -0
  250. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/usage_tracking/usage_tracker.py +0 -0
  251. {frontengine-1.0.65/frontengine/utils/weather → frontengine-1.0.67/frontengine/utils/virtual_camera}/__init__.py +0 -0
  252. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/virtual_camera/camera_feed.py +0 -0
  253. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/virtual_camera/virtual_camera.py +0 -0
  254. {frontengine-1.0.65/frontengine/utils/window_pin → frontengine-1.0.67/frontengine/utils/weather}/__init__.py +0 -0
  255. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/weather/weather_service.py +0 -0
  256. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/web_url.py +0 -0
  257. {frontengine-1.0.65/frontengine/utils/workshop → frontengine-1.0.67/frontengine/utils/window_pin}/__init__.py +0 -0
  258. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/window_pin/window_layout.py +0 -0
  259. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/window_pin/window_pin.py +0 -0
  260. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine/utils/workshop/workshop_content.py +0 -0
  261. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine.egg-info/dependency_links.txt +0 -0
  262. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine.egg-info/entry_points.txt +0 -0
  263. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine.egg-info/requires.txt +0 -0
  264. {frontengine-1.0.65 → frontengine-1.0.67}/frontengine.egg-info/top_level.txt +0 -0
  265. {frontengine-1.0.65 → frontengine-1.0.67}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.65
3
+ Version: 1.0.67
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
@@ -0,0 +1,118 @@
1
+ """
2
+ 視窗複本覆蓋層:一個小視窗,裡面是另一個視窗的即時畫面。
3
+
4
+ 和其他覆蓋層不同,這一個**不能點擊穿透**:使用者要能把它拖到順手的位置、拉大縮小、
5
+ 關掉。所以它是一個可以拖曳的無邊框視窗,而不是一層蓋在畫面上的東西。
6
+
7
+ A window replica overlay: a small window showing another window, live.
8
+
9
+ Unlike the other overlays this one must **not** pass clicks through: the user has
10
+ to be able to drag it somewhere convenient, resize it and close it. So it is a
11
+ draggable frameless window rather than a sheet laid over the screen.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from typing import Optional
16
+
17
+ from PySide6.QtCore import QPoint, Qt
18
+ from PySide6.QtGui import QColor, QPainter
19
+ from PySide6.QtWidgets import QWidget
20
+
21
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
22
+ from frontengine.utils.window_replica.dwm_thumbnail import (
23
+ DEFAULT_REPLICA_WIDTH, MIN_REPLICA_SIZE, DwmThumbnail, available, fit_within,
24
+ )
25
+
26
+
27
+ class WindowReplicaWidget(QWidget):
28
+ """顯示另一個視窗即時畫面的小視窗。"""
29
+
30
+ def __init__(self, source_handle: int, title: str = "",
31
+ opacity_percent: int = 100, parent: Optional[QWidget] = None) -> None:
32
+ front_engine_logger.info(f"[WindowReplicaWidget] Init | source={source_handle}")
33
+ super().__init__(parent)
34
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
35
+ self.setWindowFlags(
36
+ Qt.WindowType.FramelessWindowHint
37
+ | Qt.WindowType.WindowStaysOnTopHint
38
+ | Qt.WindowType.Tool)
39
+ self.setWindowTitle(title or "FrontEngine")
40
+ self.setMinimumSize(MIN_REPLICA_SIZE, MIN_REPLICA_SIZE)
41
+
42
+ self.source_handle = int(source_handle)
43
+ self.source_title = title
44
+ self.opacity_percent = max(1, min(100, int(opacity_percent)))
45
+ self.thumbnail = DwmThumbnail()
46
+ self._drag_origin: Optional[QPoint] = None
47
+
48
+ self.resize(DEFAULT_REPLICA_WIDTH, int(DEFAULT_REPLICA_WIDTH * 9 / 16))
49
+
50
+ def start(self) -> bool:
51
+ """
52
+ 接上來源視窗並依它的長寬比調整大小。接不上就回 False,呼叫端不要把一個
53
+ 空視窗留在畫面上。
54
+ Attach to the source and take its aspect ratio. False when it could not
55
+ attach, so the caller does not leave an empty window on screen.
56
+ """
57
+ if not available():
58
+ front_engine_logger.info("[WindowReplicaWidget] not supported on this platform")
59
+ return False
60
+ # 縮圖要接在「這個視窗的 handle」上,所以必須先讓它有 handle。
61
+ # The thumbnail attaches to this window's handle, so it needs one first.
62
+ self.show()
63
+ if not self.thumbnail.register(int(self.winId()), self.source_handle):
64
+ return False
65
+ source_size = self.thumbnail.source_size()
66
+ width, height = fit_within(source_size, (DEFAULT_REPLICA_WIDTH,
67
+ int(DEFAULT_REPLICA_WIDTH * 9 / 16)))
68
+ self.resize(width, height)
69
+ self._refresh()
70
+ return True
71
+
72
+ def _refresh(self) -> None:
73
+ self.thumbnail.update(self.width(), self.height(),
74
+ opacity=int(self.opacity_percent * 255 / 100))
75
+
76
+ def set_opacity_percent(self, percent: int) -> None:
77
+ self.opacity_percent = max(1, min(100, int(percent)))
78
+ self._refresh()
79
+
80
+ def resizeEvent(self, event) -> None:
81
+ super().resizeEvent(event)
82
+ self._refresh()
83
+
84
+ def paintEvent(self, event) -> None:
85
+ # DWM 把縮圖合成上來之前,先鋪一層不透明底色。少了這一步,來源還沒畫上來的
86
+ # 那一瞬間會透出後面的東西,看起來像破圖。
87
+ # An opaque backing before DWM composites the thumbnail. Without it the
88
+ # moment before the source arrives shows whatever is behind, which reads
89
+ # as a rendering fault.
90
+ painter = QPainter(self)
91
+ painter.fillRect(self.rect(), QColor(0, 0, 0))
92
+
93
+ def mousePressEvent(self, event) -> None:
94
+ if event.button() == Qt.MouseButton.LeftButton:
95
+ self._drag_origin = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
96
+ event.accept()
97
+
98
+ def mouseMoveEvent(self, event) -> None:
99
+ if self._drag_origin is not None and event.buttons() & Qt.MouseButton.LeftButton:
100
+ self.move(event.globalPosition().toPoint() - self._drag_origin)
101
+ event.accept()
102
+
103
+ def mouseReleaseEvent(self, event) -> None:
104
+ self._drag_origin = None
105
+ event.accept()
106
+
107
+ def mouseDoubleClickEvent(self, event) -> None:
108
+ """雙擊關掉——沒有標題列,總得有一個看得懂的出口。"""
109
+ self.close()
110
+ event.accept()
111
+
112
+ def closeEvent(self, event) -> None:
113
+ # 縮圖是系統資源,視窗沒了也不會自己消失。從標題列以外的任何路徑關閉時
114
+ # closeEvent 是唯一會被呼叫到的地方,所以收尾放這裡。
115
+ # The thumbnail is a system resource and does not go away with the
116
+ # window. closeEvent is the one place every close path reaches.
117
+ self.thumbnail.unregister()
118
+ super().closeEvent(event)
@@ -0,0 +1,124 @@
1
+ """
2
+ 挑一個視窗,開一個顯示它即時畫面的小視窗。
3
+
4
+ 和「釘選視窗」的差別在於原視窗動不動:釘選會把它搬到最上層,複本讓它留在原地。
5
+ Pick a window and open a small one showing it live. The difference from pinning
6
+ is whether the original moves: pinning brings it to the top, a replica leaves it
7
+ where it is.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from typing import List, Optional, Tuple
12
+
13
+ from PySide6.QtCore import Qt
14
+ from PySide6.QtWidgets import (
15
+ QAbstractItemView, QDialog, QDialogButtonBox, QGridLayout, QLabel, QListWidget,
16
+ QListWidgetItem, QPushButton, QSlider, QWidget,
17
+ )
18
+
19
+ from frontengine.show.replica.replica_widget import WindowReplicaWidget
20
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
21
+ from frontengine.utils.multi_language.language_wrapper import language_wrapper
22
+ from frontengine.utils.multi_language.retranslate import retranslator, tr
23
+ from frontengine.utils.window_pin import window_pin
24
+ from frontengine.utils.window_replica.dwm_thumbnail import available
25
+
26
+
27
+ def _t(key: str, fallback: str) -> str:
28
+ return language_wrapper.language_word_dict.get(key, fallback)
29
+
30
+
31
+ class WindowReplicaDialog(QDialog):
32
+ """列出目前的視窗,讓使用者挑一個做成即時複本。"""
33
+
34
+ def __init__(self, parent: Optional[QWidget] = None, lister=None,
35
+ replica_factory=WindowReplicaWidget) -> None:
36
+ front_engine_logger.info("[WindowReplicaDialog] Init")
37
+ super().__init__(parent)
38
+ self.setWindowTitle(_t("window_replica_title", "Replicate a window"))
39
+ self._lister = lister or window_pin.list_windows
40
+ self._replica_factory = replica_factory
41
+ self.replica_widget_list: List[WindowReplicaWidget] = []
42
+
43
+ self.window_list = QListWidget()
44
+ self.window_list.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
45
+ self.refresh_button = tr(QPushButton(), "window_replica_refresh", "Refresh")
46
+ self.refresh_button.clicked.connect(self.reload_windows)
47
+ self.show_button = tr(QPushButton(), "window_replica_show", "Show replica")
48
+ self.show_button.clicked.connect(self.start_replica)
49
+
50
+ self.opacity_label = tr(QLabel(), "window_replica_opacity", "Opacity")
51
+ self.opacity_slider = QSlider(Qt.Orientation.Horizontal)
52
+ self.opacity_slider.setRange(10, 100)
53
+ self.opacity_slider.setValue(100)
54
+
55
+ hint_key, hint_fallback = (
56
+ ("window_replica_hint",
57
+ "A second window showing the chosen one live; the original stays where it "
58
+ "is. Drag the replica to move it, double-click it to close it. Windows only.")
59
+ if available() else
60
+ ("window_replica_unsupported", "Window replicas are Windows only."))
61
+ self.hint_label = QLabel(_t(hint_key, hint_fallback))
62
+ retranslator.bind(self.hint_label, hint_key, hint_fallback)
63
+ self.hint_label.setWordWrap(True)
64
+
65
+ self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
66
+ self.button_box.rejected.connect(self.reject)
67
+
68
+ layout = QGridLayout(self)
69
+ layout.addWidget(self.window_list, 0, 0, 1, 2)
70
+ layout.addWidget(self.refresh_button, 1, 0)
71
+ layout.addWidget(self.show_button, 1, 1)
72
+ layout.addWidget(self.opacity_label, 2, 0)
73
+ layout.addWidget(self.opacity_slider, 2, 1)
74
+ layout.addWidget(self.hint_label, 3, 0, 1, 2)
75
+ layout.addWidget(self.button_box, 4, 0, 1, 2)
76
+
77
+ self.show_button.setEnabled(available())
78
+ self.reload_windows()
79
+
80
+ def reload_windows(self) -> List[Tuple[int, str]]:
81
+ """重新抓一次視窗清單。"""
82
+ self.window_list.clear()
83
+ windows = self._lister() or []
84
+ for handle, title in windows:
85
+ item = QListWidgetItem(title)
86
+ item.setData(Qt.ItemDataRole.UserRole, int(handle))
87
+ self.window_list.addItem(item)
88
+ return windows
89
+
90
+ def selected(self) -> Optional[Tuple[int, str]]:
91
+ """目前選到的 (handle, 標題);沒選就是 None。"""
92
+ item = self.window_list.currentItem()
93
+ if item is None:
94
+ return None
95
+ return int(item.data(Qt.ItemDataRole.UserRole)), item.text()
96
+
97
+ def start_replica(self) -> Optional[WindowReplicaWidget]:
98
+ """
99
+ 對選到的視窗開一個複本。接不上就把它收掉再回 None——留一個永遠黑著的
100
+ 小視窗在畫面上,比什麼都不做更糟。
101
+ Open a replica of the selected window. If it cannot attach, the widget is
102
+ closed and this returns None: leaving a permanently black window on
103
+ screen is worse than doing nothing.
104
+ """
105
+ chosen = self.selected()
106
+ if chosen is None:
107
+ return None
108
+ handle, title = chosen
109
+ replica = self._replica_factory(handle, title, self.opacity_slider.value(), None)
110
+ if not replica.start():
111
+ front_engine_logger.info(f"[WindowReplicaDialog] could not replicate {title!r}")
112
+ replica.close()
113
+ return None
114
+ self.replica_widget_list.append(replica)
115
+ return replica
116
+
117
+ def close_all(self) -> None:
118
+ """關掉這個對話框開過的所有複本。"""
119
+ for replica in list(self.replica_widget_list):
120
+ try:
121
+ replica.close()
122
+ except RuntimeError: # pragma: no cover - 底層物件已消失
123
+ pass
124
+ self.replica_widget_list.clear()
@@ -888,6 +888,10 @@ class FrontEngineMainUI(QMainWindow):
888
888
  self.widgets_setting_ui.stop_spectrum()
889
889
  if getattr(self, "tools_setting_ui", None) is not None:
890
890
  self.tools_setting_ui.release_pinned_windows()
891
+ # 複本註冊的是 DWM 縮圖,那是系統資源:視窗沒了也不會自己消失。
892
+ # A replica holds a DWM thumbnail, a system resource that does not
893
+ # go away just because the process is closing.
894
+ self.tools_setting_ui.close_replicas()
891
895
  self.tools_setting_ui.stop_camera()
892
896
  self.tools_setting_ui.stop_virtual_camera()
893
897
 
@@ -14,6 +14,7 @@ from frontengine.ui.page.utils import (
14
14
  enable_file_drop,
15
15
  reload_recent_combobox,
16
16
  resolve_preferred_monitor,
17
+ resolve_span,
17
18
  show_on_primary_screen,
18
19
  show_on_selected_monitor,
19
20
  )
@@ -133,6 +134,7 @@ class GIFSettingUI(SettingPage):
133
134
  widget, self.fullscreen_checkbox, monitor
134
135
  ),
135
136
  preferred_monitor_index=resolve_preferred_monitor(self.target_monitor_combobox),
137
+ span_all_screens=resolve_span(self.target_monitor_combobox),
136
138
  )
137
139
 
138
140
  def choose_and_copy_file_to_cwd_gif_dir_then_play(self) -> None:
@@ -17,6 +17,7 @@ from frontengine.ui.page.utils import (
17
17
  list_media_files,
18
18
  reload_recent_combobox,
19
19
  resolve_preferred_monitor,
20
+ resolve_span,
20
21
  show_on_primary_screen,
21
22
  show_on_selected_monitor,
22
23
  )
@@ -155,6 +156,7 @@ class ImageSettingUI(SettingPage):
155
156
  widget, self.fullscreen_checkbox, monitor
156
157
  ),
157
158
  preferred_monitor_index=resolve_preferred_monitor(self.target_monitor_combobox),
159
+ span_all_screens=resolve_span(self.target_monitor_combobox),
158
160
  )
159
161
  if slideshow:
160
162
  self._slideshow_timer.start(self._slideshow_interval_ms())
@@ -15,6 +15,8 @@ from frontengine.ui.page.utils import (
15
15
  enable_file_drop,
16
16
  reload_recent_combobox,
17
17
  resolve_preferred_monitor,
18
+ resolve_span,
19
+ virtual_desktop_geometry,
18
20
  )
19
21
  from frontengine.user_setting.user_setting_file import add_recent_file
20
22
  from frontengine.utils.logging.loggin_instance import front_engine_logger
@@ -149,9 +151,19 @@ class ParticleSettingUI(SettingPage):
149
151
  message_box.exec()
150
152
  return
151
153
 
154
+ span = resolve_span(self.target_monitor_combobox)
155
+
152
156
  def factory(monitor):
153
- target = monitor or QGuiApplication.primaryScreen()
154
- geometry = target.availableGeometry()
157
+ # 粒子場的大小在建構時就固定了,所以橫跨模式必須拿整個桌面的尺寸來建,
158
+ # 不能建成單一螢幕大小再把視窗拉寬——那樣粒子只會落在其中一塊。
159
+ # The particle field is sized at construction, so spanning has to
160
+ # build it at the whole-desktop size. Building it screen-sized and
161
+ # then widening the window would leave the particles in one corner.
162
+ if span:
163
+ geometry = virtual_desktop_geometry()
164
+ else:
165
+ target = monitor or QGuiApplication.primaryScreen()
166
+ geometry = target.availableGeometry()
155
167
  return self._create_particle_widget(geometry.width(), geometry.height())
156
168
 
157
169
  def present_on_monitor(widget: ParticleOpenGLWidget, monitor, _idx: int) -> None:
@@ -166,6 +178,7 @@ class ParticleSettingUI(SettingPage):
166
178
  present_primary=lambda widget: widget.showMaximized(),
167
179
  present_on_monitor=present_on_monitor,
168
180
  preferred_monitor_index=resolve_preferred_monitor(self.target_monitor_combobox),
181
+ span_all_screens=resolve_span(self.target_monitor_combobox),
169
182
  )
170
183
 
171
184
  def choose_and_copy_file_to_cwd_image_dir_then_play(self) -> None:
@@ -14,6 +14,7 @@ from frontengine.ui.page.utils import (
14
14
  build_target_monitor_combobox,
15
15
  dispatch_to_monitors,
16
16
  resolve_preferred_monitor,
17
+ resolve_span,
17
18
  )
18
19
  from frontengine.ui.page.layout_kit import SettingPage
19
20
  from frontengine.utils.logging.loggin_instance import front_engine_logger
@@ -322,4 +323,5 @@ class TextSettingUI(SettingPage):
322
323
  present_primary=lambda widget: widget.showFullScreen(),
323
324
  present_on_monitor=present_on_monitor,
324
325
  preferred_monitor_index=resolve_preferred_monitor(self.target_monitor_combobox),
326
+ span_all_screens=resolve_span(self.target_monitor_combobox),
325
327
  )
@@ -25,6 +25,7 @@ from frontengine.ui.dialog.screen_text_dialog import (
25
25
  ScreenTextDialog, ask_for_consent, has_consent,
26
26
  )
27
27
  from frontengine.ui.dialog.window_pin_dialog import WindowPinDialog
28
+ from frontengine.ui.dialog.window_replica_dialog import WindowReplicaDialog
28
29
  from frontengine.user_setting.user_setting_file import user_setting_dict, write_user_setting
29
30
  from frontengine.ui.page.utils import coerce_int
30
31
  from frontengine.ui.page.layout_kit import SettingPage
@@ -85,6 +86,7 @@ class ToolsSettingUI(SettingPage):
85
86
  # raises RuntimeError on touch and "copy last" was a silent no-op.
86
87
  self.last_capture: Optional[QPixmap] = None
87
88
  self.pin_dialog: Optional[WindowPinDialog] = None
89
+ self.replica_dialog: Optional[WindowReplicaDialog] = None
88
90
 
89
91
  self._build_measure_row()
90
92
  self._build_capture_row()
@@ -95,6 +97,11 @@ class ToolsSettingUI(SettingPage):
95
97
  self.pin_button = tr(QPushButton(), "tools_pin_window", "Pin a window...")
96
98
  self.pin_button.clicked.connect(self.open_pin_dialog)
97
99
 
100
+ # 視窗複本:原視窗留在原地,另外開一個小視窗顯示它的即時畫面
101
+ # Window replica: the original stays put, a small window shows it live
102
+ self.replica_button = tr(QPushButton(), "tools_replicate_window", "Replicate a window...")
103
+ self.replica_button.clicked.connect(self.open_replica_dialog)
104
+
98
105
  # 視窗版面:記下每個視窗的位置,之後一鍵擺回去
99
106
  # Window layouts: remember where the windows are and put them back later.
100
107
  self.layout_label = tr(QLabel(), "tools_layout_label", "Window layout")
@@ -154,7 +161,7 @@ class ToolsSettingUI(SettingPage):
154
161
  windows.add_row("tools_name", self.layout_name_edit, "Name")
155
162
  windows.add_inline(self.layout_save_button)
156
163
  windows.add_row("tools_saved", self.layout_combobox, "Saved")
157
- windows.add_inline(self.layout_restore_button, self.pin_button)
164
+ windows.add_inline(self.layout_restore_button, self.pin_button, self.replica_button)
158
165
 
159
166
  self.add_body_widget(self.hint_label)
160
167
  self.finish_body()
@@ -366,6 +373,20 @@ class ToolsSettingUI(SettingPage):
366
373
  self.pin_dialog.exec()
367
374
  return self.pin_dialog
368
375
 
376
+ def open_replica_dialog(self) -> WindowReplicaDialog:
377
+ """開視窗複本對話框(關掉主程式時會把開過的複本收乾淨)。"""
378
+ self.replica_dialog = WindowReplicaDialog(self)
379
+ self.replica_dialog.exec()
380
+ return self.replica_dialog
381
+
382
+ def close_replicas(self) -> None:
383
+ """關掉開過的視窗複本(主程式關閉時呼叫)。"""
384
+ if self.replica_dialog is not None:
385
+ try:
386
+ self.replica_dialog.close_all()
387
+ except RuntimeError:
388
+ self.replica_dialog = None
389
+
369
390
  def release_pinned_windows(self) -> None:
370
391
  """把釘過的別人視窗放開(主程式關閉時呼叫)。"""
371
392
  if self.pin_dialog is not None:
@@ -1,7 +1,7 @@
1
1
  from pathlib import Path
2
2
  from typing import Callable, Optional
3
3
 
4
- from PySide6.QtCore import QEvent, QObject
4
+ from PySide6.QtCore import QEvent, QObject, QRect
5
5
  from PySide6.QtGui import QGuiApplication, QScreen
6
6
  from PySide6.QtWidgets import QCheckBox, QComboBox, QDialog, QGridLayout, QLabel, QPushButton, QWidget
7
7
 
@@ -96,6 +96,8 @@ def dispatch_to_monitors(
96
96
  present_primary: Callable[[QWidget], None],
97
97
  present_on_monitor: Callable[[QWidget, QScreen, int], None],
98
98
  preferred_monitor_index: Optional[int] = None,
99
+ span_all_screens: bool = False,
100
+ screens: Optional[list] = None,
99
101
  ) -> None:
100
102
  """
101
103
  Run the canonical monitor-dispatch flow the setting pages share:
@@ -108,7 +110,21 @@ def dispatch_to_monitors(
108
110
  monitor so the user is not prompted; out-of-range values fall back
109
111
  to the prompt flow.
110
112
  """
111
- monitors = QGuiApplication.screens()
113
+ monitors = QGuiApplication.screens() if screens is None else screens
114
+
115
+ # 橫跨模式:一個覆蓋層蓋滿整個桌面,而不是每個螢幕各一個。要放在最前面判斷,
116
+ # 因為它和「問使用者要哪一個螢幕」是互斥的——已經選了全部就沒什麼好問的。
117
+ # Spanning: one overlay over the whole desktop rather than one per screen.
118
+ # It is decided first because it rules out asking which screen to use: the
119
+ # user has already said all of them.
120
+ if span_all_screens and len(monitors) > 1:
121
+ widget = factory(None)
122
+ geometry = virtual_desktop_geometry(monitors)
123
+ widget.setGeometry(geometry)
124
+ widget.show()
125
+ widget.raise_()
126
+ return
127
+
112
128
  if not show_all_screen and len(monitors) <= 1:
113
129
  widget = factory(None)
114
130
  present_primary(widget)
@@ -138,19 +154,56 @@ def dispatch_to_monitors(
138
154
  present_on_monitor(widget, monitor, index)
139
155
 
140
156
 
141
- def build_target_monitor_combobox() -> QComboBox:
157
+ # 「橫跨所有螢幕」在下拉選單裡用這個值標記,和「第 N 個螢幕」的整數區分開。
158
+ # The "span every screen" entry carries this instead of a monitor index.
159
+ SPAN_ALL_DATA = "span"
160
+
161
+
162
+ def virtual_desktop_geometry(screens=None) -> QRect:
163
+ """
164
+ 所有螢幕聯集起來的矩形,也就是整個桌面的範圍。
165
+
166
+ 橫跨模式要的是一個蓋滿這塊範圍的覆蓋層,而不是每個螢幕各一個。螢幕不一定排成
167
+ 一直線也不一定同樣高,所以要用聯集而不是把寬度加起來。
168
+ The union of every screen: the whole desktop. Spanning wants one overlay
169
+ covering that, not one per screen. Screens are not necessarily in a row or
170
+ the same height, so this unites rather than summing widths.
171
+ """
172
+ geometry = QRect()
173
+ for screen in (QGuiApplication.screens() if screens is None else screens):
174
+ geometry = geometry.united(screen.geometry())
175
+ return geometry
176
+
177
+
178
+ def build_target_monitor_combobox(screens=None) -> QComboBox:
142
179
  """
143
180
  Build a combobox listing the current monitors plus an initial "Ask"
144
181
  entry. Selection "Ask" (data=None) preserves today's prompt flow;
145
182
  selecting a monitor index pre-selects that screen.
183
+
184
+ 多螢幕時多一個「橫跨所有螢幕」。只有一個螢幕時不提供——那和一般顯示完全一樣,
185
+ 列出來只會讓人以為自己漏掉了什麼。
186
+ On a multi-monitor system there is also a "span every screen" entry. It is
187
+ not offered on a single screen, where it would do exactly what the normal
188
+ path does and only leave the user wondering what they missed.
146
189
  """
190
+ monitors = QGuiApplication.screens() if screens is None else screens
147
191
  combobox = QComboBox()
148
192
  combobox.addItem(language_wrapper.language_word_dict.get("target_monitor_ask", "Ask"), None)
149
- for index, _ in enumerate(QGuiApplication.screens()):
193
+ for index, _ in enumerate(monitors):
150
194
  combobox.addItem(str(index), index)
195
+ if len(monitors) > 1:
196
+ combobox.addItem(
197
+ language_wrapper.language_word_dict.get("target_monitor_span", "Span all screens"),
198
+ SPAN_ALL_DATA)
151
199
  return combobox
152
200
 
153
201
 
202
+ def resolve_span(combobox: Optional[QComboBox]) -> bool:
203
+ """使用者是不是選了「橫跨所有螢幕」。"""
204
+ return combobox is not None and combobox.currentData() == SPAN_ALL_DATA
205
+
206
+
154
207
  def resolve_preferred_monitor(combobox: Optional[QComboBox]) -> Optional[int]:
155
208
  """Return the selected monitor index, or None for the Ask entry."""
156
209
  if combobox is None:
@@ -14,6 +14,7 @@ from frontengine.ui.page.utils import (
14
14
  enable_file_drop,
15
15
  reload_recent_combobox,
16
16
  resolve_preferred_monitor,
17
+ resolve_span,
17
18
  show_on_primary_screen,
18
19
  show_on_selected_monitor,
19
20
  )
@@ -154,6 +155,7 @@ class VideoSettingUI(SettingPage):
154
155
  present_primary=lambda widget: show_on_primary_screen(widget, self.fullscreen_checkbox),
155
156
  present_on_monitor=present_on_monitor,
156
157
  preferred_monitor_index=resolve_preferred_monitor(self.target_monitor_combobox),
158
+ span_all_screens=resolve_span(self.target_monitor_combobox),
157
159
  )
158
160
 
159
161
  def choose_and_copy_file_to_cwd_video_dir_then_play(self) -> None:
@@ -12,6 +12,7 @@ from frontengine.ui.page.utils import (
12
12
  build_target_monitor_combobox,
13
13
  dispatch_to_monitors,
14
14
  resolve_preferred_monitor,
15
+ resolve_span,
15
16
  )
16
17
  from frontengine.utils.logging.loggin_instance import front_engine_logger
17
18
  from frontengine.utils.multi_language.language_wrapper import language_wrapper
@@ -303,4 +304,5 @@ class WEBSettingUI(SettingPage):
303
304
  present_primary=lambda widget: widget.showFullScreen(),
304
305
  present_on_monitor=present_on_monitor,
305
306
  preferred_monitor_index=resolve_preferred_monitor(self.target_monitor_combobox),
307
+ span_all_screens=resolve_span(self.target_monitor_combobox),
306
308
  )
@@ -46,6 +46,14 @@ english_word_dict = {
46
46
  "tools_shape": "Shape",
47
47
  "tools_name": "Name",
48
48
  "tools_saved": "Saved",
49
+ "target_monitor_span": "Span all screens",
50
+ "tools_replicate_window": "Replicate a window...",
51
+ "window_replica_title": "Replicate a window",
52
+ "window_replica_refresh": "Refresh",
53
+ "window_replica_show": "Show replica",
54
+ "window_replica_opacity": "Opacity",
55
+ "window_replica_hint": "A second window showing the chosen one live; the original stays where it is. Drag the replica to move it, double-click it to close it. Windows only.",
56
+ "window_replica_unsupported": "Window replicas are Windows only.",
49
57
  "settings_menu_screensaver": "Screensaver...",
50
58
  "screensaver_title": "Screensaver",
51
59
  "screensaver_enable": "Show something when idle",
@@ -47,6 +47,14 @@ french_word_dict = {
47
47
  "tools_shape": "Forme",
48
48
  "tools_name": "Nom",
49
49
  "tools_saved": "Enregistrés",
50
+ "target_monitor_span": "Sur tous les écrans",
51
+ "tools_replicate_window": "Dupliquer une fenêtre...",
52
+ "window_replica_title": "Dupliquer une fenêtre",
53
+ "window_replica_refresh": "Actualiser",
54
+ "window_replica_show": "Afficher le duplicata",
55
+ "window_replica_opacity": "Opacité",
56
+ "window_replica_hint": "Une seconde fenêtre affiche la fenêtre choisie en direct ; l'originale reste à sa place. Faites glisser pour la déplacer, double-cliquez pour la fermer. Windows uniquement.",
57
+ "window_replica_unsupported": "Les duplicatas de fenêtre sont réservés à Windows.",
50
58
  "settings_menu_screensaver": "Économiseur d'écran...",
51
59
  "screensaver_title": "Économiseur d'écran",
52
60
  "screensaver_enable": "Afficher quelque chose en cas d'inactivité",
@@ -47,6 +47,14 @@ germany_word_dict = {
47
47
  "tools_shape": "Form",
48
48
  "tools_name": "Name",
49
49
  "tools_saved": "Gespeichert",
50
+ "target_monitor_span": "Über alle Bildschirme",
51
+ "tools_replicate_window": "Fenster duplizieren...",
52
+ "window_replica_title": "Fenster duplizieren",
53
+ "window_replica_refresh": "Aktualisieren",
54
+ "window_replica_show": "Duplikat anzeigen",
55
+ "window_replica_opacity": "Deckkraft",
56
+ "window_replica_hint": "Ein zweites Fenster zeigt das gewählte live; das Original bleibt, wo es ist. Ziehen zum Verschieben, Doppelklick zum Schließen. Nur Windows.",
57
+ "window_replica_unsupported": "Fensterduplikate gibt es nur unter Windows.",
50
58
  "settings_menu_screensaver": "Bildschirmschoner...",
51
59
  "screensaver_title": "Bildschirmschoner",
52
60
  "screensaver_enable": "Bei Untätigkeit etwas anzeigen",
@@ -47,6 +47,14 @@ italian_word_dict = {
47
47
  "tools_shape": "Forma",
48
48
  "tools_name": "Nome",
49
49
  "tools_saved": "Salvati",
50
+ "target_monitor_span": "Su tutti gli schermi",
51
+ "tools_replicate_window": "Duplica una finestra...",
52
+ "window_replica_title": "Duplica una finestra",
53
+ "window_replica_refresh": "Aggiorna",
54
+ "window_replica_show": "Mostra il duplicato",
55
+ "window_replica_opacity": "Opacità",
56
+ "window_replica_hint": "Una seconda finestra mostra quella scelta in diretta; l'originale resta dov'è. Trascina per spostarla, doppio clic per chiuderla. Solo Windows.",
57
+ "window_replica_unsupported": "I duplicati di finestra sono solo per Windows.",
50
58
  "settings_menu_screensaver": "Salvaschermo...",
51
59
  "screensaver_title": "Salvaschermo",
52
60
  "screensaver_enable": "Mostra qualcosa quando sei inattivo",
@@ -47,6 +47,14 @@ russian_word_dict = {
47
47
  "tools_shape": "Форма",
48
48
  "tools_name": "Имя",
49
49
  "tools_saved": "Сохранённые",
50
+ "target_monitor_span": "На все экраны",
51
+ "tools_replicate_window": "Копия окна...",
52
+ "window_replica_title": "Копия окна",
53
+ "window_replica_refresh": "Обновить",
54
+ "window_replica_show": "Показать копию",
55
+ "window_replica_opacity": "Непрозрачность",
56
+ "window_replica_hint": "Второе окно показывает выбранное вживую, оригинал остаётся на месте. Перетащите, чтобы переместить, двойной щелчок закрывает. Только Windows.",
57
+ "window_replica_unsupported": "Копии окон работают только в Windows.",
50
58
  "settings_menu_screensaver": "Заставка...",
51
59
  "screensaver_title": "Заставка",
52
60
  "screensaver_enable": "Показывать что-нибудь при простое",
@@ -47,6 +47,14 @@ simplified_chinese_word_dict = {
47
47
  "tools_shape": "形状",
48
48
  "tools_name": "名称",
49
49
  "tools_saved": "已保存",
50
+ "target_monitor_span": "横跨所有屏幕",
51
+ "tools_replicate_window": "窗口副本⋯",
52
+ "window_replica_title": "窗口副本",
53
+ "window_replica_refresh": "刷新",
54
+ "window_replica_show": "显示副本",
55
+ "window_replica_opacity": "透明度",
56
+ "window_replica_hint": "另外开一个小窗口显示选定窗口的实时画面,原窗口留在原地不动。拖动可以移动它,双击关闭。仅限 Windows。",
57
+ "window_replica_unsupported": "窗口副本仅限 Windows。",
50
58
  "settings_menu_screensaver": "屏幕保护⋯",
51
59
  "screensaver_title": "屏幕保护",
52
60
  "screensaver_enable": "空闲时显示东西",
@@ -47,6 +47,14 @@ traditional_chinese_word_dict = {
47
47
  "tools_shape": "形狀",
48
48
  "tools_name": "名稱",
49
49
  "tools_saved": "已儲存",
50
+ "target_monitor_span": "橫跨所有螢幕",
51
+ "tools_replicate_window": "視窗複本⋯",
52
+ "window_replica_title": "視窗複本",
53
+ "window_replica_refresh": "重新整理",
54
+ "window_replica_show": "顯示複本",
55
+ "window_replica_opacity": "透明度",
56
+ "window_replica_hint": "另外開一個小視窗顯示選定視窗的即時畫面,原視窗留在原地不動。拖曳可以搬動它,連點兩下關閉。僅限 Windows。",
57
+ "window_replica_unsupported": "視窗複本僅限 Windows。",
50
58
  "settings_menu_screensaver": "螢幕保護⋯",
51
59
  "screensaver_title": "螢幕保護",
52
60
  "screensaver_enable": "閒置時顯示東西",