frontengine 1.0.46__tar.gz → 1.0.47__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 (264) hide show
  1. {frontengine-1.0.46 → frontengine-1.0.47}/PKG-INFO +30 -1
  2. {frontengine-1.0.46 → frontengine-1.0.47}/README.md +29 -0
  3. frontengine-1.0.47/frontengine/ui/dialog/remote_control_dialog.py +207 -0
  4. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/main_ui.py +88 -30
  5. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/menu/settings_menu.py +16 -0
  6. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/tools/tools_setting_ui.py +114 -18
  7. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/user_setting/user_setting_file.py +6 -0
  8. frontengine-1.0.47/frontengine/utils/midi/midi_input.py +221 -0
  9. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/multi_language/english.py +20 -0
  10. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/multi_language/traditional_chinese.py +20 -0
  11. frontengine-1.0.47/frontengine/utils/remote/remote_server.py +235 -0
  12. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/screen_privacy/capture_affinity.py +1 -1
  13. frontengine-1.0.47/frontengine/utils/virtual_camera/camera_feed.py +103 -0
  14. frontengine-1.0.47/frontengine/utils/virtual_camera/virtual_camera.py +166 -0
  15. frontengine-1.0.47/frontengine/utils/window_pin/__init__.py +0 -0
  16. frontengine-1.0.47/frontengine/utils/workshop/__init__.py +0 -0
  17. frontengine-1.0.47/frontengine/worker/__init__.py +0 -0
  18. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine.egg-info/PKG-INFO +30 -1
  19. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine.egg-info/SOURCES.txt +8 -0
  20. {frontengine-1.0.46 → frontengine-1.0.47}/pyproject.toml +1 -1
  21. {frontengine-1.0.46 → frontengine-1.0.47}/LICENSE +0 -0
  22. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/__init__.py +0 -0
  23. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/__main__.py +0 -0
  24. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/__init__.py +0 -0
  25. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/base_widget.py +0 -0
  26. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/camera/__init__.py +0 -0
  27. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/camera/camera_widget.py +0 -0
  28. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/capture/__init__.py +0 -0
  29. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/capture/region_capture.py +0 -0
  30. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/focus_shield/__init__.py +0 -0
  31. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/focus_shield/focus_shield_widget.py +0 -0
  32. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/gaming/__init__.py +0 -0
  33. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/gaming/crosshair_widget.py +0 -0
  34. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/gif/__init__.py +0 -0
  35. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/gif/paint_gif.py +0 -0
  36. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/image/__init__.py +0 -0
  37. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/image/paint_image.py +0 -0
  38. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/load/__init__.py +0 -0
  39. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/load/load_someone_make_ui.py +0 -0
  40. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/measure/__init__.py +0 -0
  41. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/measure/measure_widget.py +0 -0
  42. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/monitor/__init__.py +0 -0
  43. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/monitor/monitor_widget.py +0 -0
  44. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/notes/__init__.py +0 -0
  45. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/notes/sticky_note_widget.py +0 -0
  46. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/overlay_factory.py +0 -0
  47. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/particle/__init__.py +0 -0
  48. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/particle/particle_ui.py +0 -0
  49. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/pet/__init__.py +0 -0
  50. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/pet/desktop_pet.py +0 -0
  51. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/presentation/__init__.py +0 -0
  52. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/presentation/annotation_overlay.py +0 -0
  53. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/presentation/cursor_effects.py +0 -0
  54. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/presentation/keystroke_display.py +0 -0
  55. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/presentation/magnifier.py +0 -0
  56. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/scene/__init__.py +0 -0
  57. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/scene/extend_graphic_scene.py +0 -0
  58. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/scene/extend_graphic_view.py +0 -0
  59. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/scene/scene.py +0 -0
  60. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/screen_care/__init__.py +0 -0
  61. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/screen_care/color_vision_widget.py +0 -0
  62. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/screen_care/screen_filter.py +0 -0
  63. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/sound_player/__init__.py +0 -0
  64. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/sound_player/sound_effect.py +0 -0
  65. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/sound_player/sound_player.py +0 -0
  66. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/spectrum/__init__.py +0 -0
  67. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/spectrum/spectrum_widget.py +0 -0
  68. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/teleprompter/__init__.py +0 -0
  69. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/teleprompter/teleprompter_widget.py +0 -0
  70. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/text/__init__.py +0 -0
  71. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/text/draw_text.py +0 -0
  72. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/toast/__init__.py +0 -0
  73. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/toast/toast_widget.py +0 -0
  74. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/video/__init__.py +0 -0
  75. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/video/video_player.py +0 -0
  76. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/wallpaper/__init__.py +0 -0
  77. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/wallpaper/wallpaper_widget.py +0 -0
  78. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/web/__init__.py +0 -0
  79. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/web/webview.py +0 -0
  80. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/show/window_helpers.py +0 -0
  81. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/system_tray/__init__.py +0 -0
  82. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/system_tray/extend_system_tray.py +0 -0
  83. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/__init__.py +0 -0
  84. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/color/__init__.py +0 -0
  85. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/color/global_color.py +0 -0
  86. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/__init__.py +0 -0
  87. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/app_profile_dialog.py +0 -0
  88. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
  89. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/clipboard_dialog.py +0 -0
  90. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
  91. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/reminder_dialog.py +0 -0
  92. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/save_file_dialog.py +0 -0
  93. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/screen_privacy_dialog.py +0 -0
  94. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/screen_text_dialog.py +0 -0
  95. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/smart_pause_dialog.py +0 -0
  96. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/usage_report_dialog.py +0 -0
  97. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/dialog/window_pin_dialog.py +0 -0
  98. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/menu/__init__.py +0 -0
  99. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/menu/help_menu.py +0 -0
  100. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/menu/how_to_menu.py +0 -0
  101. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/menu/language_menu.py +0 -0
  102. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/menu/preset_menu.py +0 -0
  103. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/__init__.py +0 -0
  104. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/control_center/__init__.py +0 -0
  105. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/control_center/control_center_ui.py +0 -0
  106. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/focus/__init__.py +0 -0
  107. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/focus/focus_setting_ui.py +0 -0
  108. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/gif/__init__.py +0 -0
  109. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
  110. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/image/__init__.py +0 -0
  111. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/image/image_setting_ui.py +0 -0
  112. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/particle/__init__.py +0 -0
  113. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
  114. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/pet/__init__.py +0 -0
  115. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
  116. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/presentation/__init__.py +0 -0
  117. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/presentation/presentation_setting_ui.py +0 -0
  118. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/__init__.py +0 -0
  119. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
  120. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/__init__.py +0 -0
  121. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
  122. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
  123. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
  124. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
  125. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
  126. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
  127. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
  128. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
  129. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
  130. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/screen_care/__init__.py +0 -0
  131. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +0 -0
  132. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/sound_player/__init__.py +0 -0
  133. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
  134. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/stream/__init__.py +0 -0
  135. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/stream/stream_setting_ui.py +0 -0
  136. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/text/__init__.py +0 -0
  137. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/text/text_setting_ui.py +0 -0
  138. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/tools/__init__.py +0 -0
  139. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/utils.py +0 -0
  140. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/video/__init__.py +0 -0
  141. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/video/video_setting_ui.py +0 -0
  142. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/wallpaper/__init__.py +0 -0
  143. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +0 -0
  144. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/web/__init__.py +0 -0
  145. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/web/web_setting_ui.py +0 -0
  146. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/widgets/__init__.py +0 -0
  147. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/ui/page/widgets/widgets_setting_ui.py +0 -0
  148. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/user_setting/__init__.py +0 -0
  149. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/user_setting/preset_repository.py +0 -0
  150. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/user_setting/scene_setting.py +0 -0
  151. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/__init__.py +0 -0
  152. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/app_profile/__init__.py +0 -0
  153. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/app_profile/app_profile_service.py +0 -0
  154. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/audio_meter/__init__.py +0 -0
  155. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
  156. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/audio_meter/loopback_capture.py +0 -0
  157. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/audio_meter/screen_audio.py +0 -0
  158. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/audio_meter/spectrum_analyzer.py +0 -0
  159. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/audio_meter/system_audio_meter.py +0 -0
  160. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/autostart/__init__.py +0 -0
  161. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/autostart/autostart_service.py +0 -0
  162. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/break_reminder/__init__.py +0 -0
  163. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/break_reminder/break_reminder.py +0 -0
  164. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/browser/__init__.py +0 -0
  165. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/browser/browser.py +0 -0
  166. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/clipboard/__init__.py +0 -0
  167. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/clipboard/clipboard_history.py +0 -0
  168. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/clipboard/clipboard_watcher.py +0 -0
  169. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/color_vision/__init__.py +0 -0
  170. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/color_vision/color_vision.py +0 -0
  171. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/critical_exit/__init__.py +0 -0
  172. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
  173. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/critical_exit/critical_exit.py +0 -0
  174. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/critical_exit/win32_vk.py +0 -0
  175. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/exception/__init__.py +0 -0
  176. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/exception/exception_tags.py +0 -0
  177. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/exception/exceptions.py +0 -0
  178. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/file/__init__.py +0 -0
  179. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/file/open/__init__.py +0 -0
  180. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/file/open/open_file.py +0 -0
  181. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/file/save/__init__.py +0 -0
  182. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/file/save/save_file.py +0 -0
  183. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/focus_shield/__init__.py +0 -0
  184. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/focus_shield/focus_shield.py +0 -0
  185. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/focus_timer/__init__.py +0 -0
  186. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/focus_timer/focus_timer.py +0 -0
  187. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/hotkey/__init__.py +0 -0
  188. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/hotkey/hotkey_service.py +0 -0
  189. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/input_watch/__init__.py +0 -0
  190. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/input_watch/input_watch_service.py +0 -0
  191. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/json/__init__.py +0 -0
  192. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/json/json_file.py +0 -0
  193. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/json/json_repository.py +0 -0
  194. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/json_format/__init__.py +0 -0
  195. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/json_format/json_process.py +0 -0
  196. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/logging/__init__.py +0 -0
  197. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/logging/loggin_instance.py +0 -0
  198. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/measure/__init__.py +0 -0
  199. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/measure/measure.py +0 -0
  200. {frontengine-1.0.46/frontengine/utils/multi_language → frontengine-1.0.47/frontengine/utils/midi}/__init__.py +0 -0
  201. {frontengine-1.0.46/frontengine/utils/now_playing → frontengine-1.0.47/frontengine/utils/multi_language}/__init__.py +0 -0
  202. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/multi_language/france.py +0 -0
  203. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/multi_language/germany.py +0 -0
  204. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/multi_language/italy.py +0 -0
  205. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/multi_language/language_wrapper.py +0 -0
  206. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/multi_language/russian.py +0 -0
  207. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
  208. {frontengine-1.0.46/frontengine/utils/obs → frontengine-1.0.47/frontengine/utils/now_playing}/__init__.py +0 -0
  209. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/now_playing/now_playing.py +0 -0
  210. {frontengine-1.0.46/frontengine/utils/pet_chat → frontengine-1.0.47/frontengine/utils/obs}/__init__.py +0 -0
  211. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/obs/obs_client.py +0 -0
  212. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/obs/obs_protocol.py +0 -0
  213. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/obs/websocket_frames.py +0 -0
  214. {frontengine-1.0.46/frontengine/utils/platform_info → frontengine-1.0.47/frontengine/utils/pet_chat}/__init__.py +0 -0
  215. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
  216. {frontengine-1.0.46/frontengine/utils/playlist → frontengine-1.0.47/frontengine/utils/platform_info}/__init__.py +0 -0
  217. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/platform_info/platform_info.py +0 -0
  218. {frontengine-1.0.46/frontengine/utils/plugins → frontengine-1.0.47/frontengine/utils/playlist}/__init__.py +0 -0
  219. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/playlist/playlist.py +0 -0
  220. {frontengine-1.0.46/frontengine/utils/power_mode → frontengine-1.0.47/frontengine/utils/plugins}/__init__.py +0 -0
  221. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/plugins/plugin_loader.py +0 -0
  222. {frontengine-1.0.46/frontengine/utils/preset_schedule → frontengine-1.0.47/frontengine/utils/power_mode}/__init__.py +0 -0
  223. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/power_mode/power_mode.py +0 -0
  224. {frontengine-1.0.46/frontengine/utils/recording → frontengine-1.0.47/frontengine/utils/preset_schedule}/__init__.py +0 -0
  225. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
  226. {frontengine-1.0.46/frontengine/utils/redirect_manager → frontengine-1.0.47/frontengine/utils/recording}/__init__.py +0 -0
  227. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/recording/frame_recorder.py +0 -0
  228. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/recording/gif_writer.py +0 -0
  229. {frontengine-1.0.46/frontengine/utils/reminder → frontengine-1.0.47/frontengine/utils/redirect_manager}/__init__.py +0 -0
  230. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
  231. {frontengine-1.0.46/frontengine/utils/screen_privacy → frontengine-1.0.47/frontengine/utils/reminder}/__init__.py +0 -0
  232. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/reminder/reminder_service.py +0 -0
  233. {frontengine-1.0.46/frontengine/utils/screen_text → frontengine-1.0.47/frontengine/utils/remote}/__init__.py +0 -0
  234. {frontengine-1.0.46/frontengine/utils/smart_pause → frontengine-1.0.47/frontengine/utils/screen_privacy}/__init__.py +0 -0
  235. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/screen_privacy/share_watch.py +0 -0
  236. {frontengine-1.0.46/frontengine/utils/soundboard → frontengine-1.0.47/frontengine/utils/screen_text}/__init__.py +0 -0
  237. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/screen_text/screen_text_service.py +0 -0
  238. {frontengine-1.0.46/frontengine/utils/system_stats → frontengine-1.0.47/frontengine/utils/smart_pause}/__init__.py +0 -0
  239. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/smart_pause/pause_rules.py +0 -0
  240. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
  241. {frontengine-1.0.46/frontengine/utils/text_source → frontengine-1.0.47/frontengine/utils/soundboard}/__init__.py +0 -0
  242. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/soundboard/soundboard.py +0 -0
  243. {frontengine-1.0.46/frontengine/utils/theme_schedule → frontengine-1.0.47/frontengine/utils/system_stats}/__init__.py +0 -0
  244. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/system_stats/system_stats.py +0 -0
  245. {frontengine-1.0.46/frontengine/utils/usage_tracking → frontengine-1.0.47/frontengine/utils/text_source}/__init__.py +0 -0
  246. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/text_source/text_source.py +0 -0
  247. {frontengine-1.0.46/frontengine/utils/weather → frontengine-1.0.47/frontengine/utils/theme_schedule}/__init__.py +0 -0
  248. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
  249. {frontengine-1.0.46/frontengine/utils/window_pin → frontengine-1.0.47/frontengine/utils/usage_tracking}/__init__.py +0 -0
  250. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/usage_tracking/usage_service.py +0 -0
  251. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/usage_tracking/usage_tracker.py +0 -0
  252. {frontengine-1.0.46/frontengine/utils/workshop → frontengine-1.0.47/frontengine/utils/virtual_camera}/__init__.py +0 -0
  253. {frontengine-1.0.46/frontengine/worker → frontengine-1.0.47/frontengine/utils/weather}/__init__.py +0 -0
  254. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/weather/weather_service.py +0 -0
  255. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/web_url.py +0 -0
  256. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/window_pin/window_layout.py +0 -0
  257. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/window_pin/window_pin.py +0 -0
  258. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/utils/workshop/workshop_content.py +0 -0
  259. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine/worker/qthread_worker.py +0 -0
  260. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine.egg-info/dependency_links.txt +0 -0
  261. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine.egg-info/entry_points.txt +0 -0
  262. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine.egg-info/requires.txt +0 -0
  263. {frontengine-1.0.46 → frontengine-1.0.47}/frontengine.egg-info/top_level.txt +0 -0
  264. {frontengine-1.0.46 → frontengine-1.0.47}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frontengine
3
- Version: 1.0.46
3
+ Version: 1.0.47
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
@@ -143,6 +143,14 @@ Measuring and capture tools on the **Tools** tab:
143
143
  `hsl(...)` or a CSS custom property.
144
144
  - **Region capture** — drag out an area; it lands on the clipboard and can be
145
145
  saved to a file.
146
+ - **Virtual camera** — drag out an area and send it, overlays and all, as a
147
+ webcam that Zoom, Teams or Discord can pick as their video source. Needs the
148
+ optional `pyvirtualcam` package and a virtual camera driver on the system
149
+ (OBS installs one); without either, the button says so rather than failing
150
+ quietly.
151
+ - **Camera** — any video input works, including capture cards, and the device
152
+ list can be refreshed without restarting since cards are usually plugged in
153
+ while the app is already running.
146
154
  - **Read text** — drag out an area to copy the text in it, translate it, or ask
147
155
  a question about it.
148
156
  > This is the **only** feature here that sends screen content off the
@@ -196,6 +204,27 @@ Four things on the **Stream** tab, for playing and for going live:
196
204
 
197
205
  ---
198
206
 
207
+ ## Remote control
208
+
209
+ From **Settings → Remote control**:
210
+
211
+ - **Your phone** — turn it on and FrontEngine serves a small page on your local
212
+ network; open the link on a phone and the buttons drive the same actions the
213
+ global hotkeys do.
214
+ > This opens a port on your machine, so it is **off by default**. The link
215
+ > carries a token that is regenerated every time the server starts, so an old
216
+ > link stops working, and the page can only ask for the fixed action list —
217
+ > nothing else is reachable. It is plain HTTP: someone else on the same
218
+ > network could read the token and press the same buttons, which is a
219
+ > nuisance rather than a breach given what the buttons do, but leave it off
220
+ > on networks you do not trust.
221
+ - **A MIDI controller** — press *Learn*, move a knob or pad, and bind it to an
222
+ action. It uses Windows' built-in winmm, so no extra package is needed. A
223
+ knob fires once it reaches the top rather than repeatedly on the way, and
224
+ releasing a pad does not count as a second press.
225
+
226
+ ---
227
+
199
228
  ## Screen-sharing privacy
200
229
 
201
230
  Your overlays are for you, not for the people you are sharing with. From
@@ -119,6 +119,14 @@ Measuring and capture tools on the **Tools** tab:
119
119
  `hsl(...)` or a CSS custom property.
120
120
  - **Region capture** — drag out an area; it lands on the clipboard and can be
121
121
  saved to a file.
122
+ - **Virtual camera** — drag out an area and send it, overlays and all, as a
123
+ webcam that Zoom, Teams or Discord can pick as their video source. Needs the
124
+ optional `pyvirtualcam` package and a virtual camera driver on the system
125
+ (OBS installs one); without either, the button says so rather than failing
126
+ quietly.
127
+ - **Camera** — any video input works, including capture cards, and the device
128
+ list can be refreshed without restarting since cards are usually plugged in
129
+ while the app is already running.
122
130
  - **Read text** — drag out an area to copy the text in it, translate it, or ask
123
131
  a question about it.
124
132
  > This is the **only** feature here that sends screen content off the
@@ -172,6 +180,27 @@ Four things on the **Stream** tab, for playing and for going live:
172
180
 
173
181
  ---
174
182
 
183
+ ## Remote control
184
+
185
+ From **Settings → Remote control**:
186
+
187
+ - **Your phone** — turn it on and FrontEngine serves a small page on your local
188
+ network; open the link on a phone and the buttons drive the same actions the
189
+ global hotkeys do.
190
+ > This opens a port on your machine, so it is **off by default**. The link
191
+ > carries a token that is regenerated every time the server starts, so an old
192
+ > link stops working, and the page can only ask for the fixed action list —
193
+ > nothing else is reachable. It is plain HTTP: someone else on the same
194
+ > network could read the token and press the same buttons, which is a
195
+ > nuisance rather than a breach given what the buttons do, but leave it off
196
+ > on networks you do not trust.
197
+ - **A MIDI controller** — press *Learn*, move a knob or pad, and bind it to an
198
+ action. It uses Windows' built-in winmm, so no extra package is needed. A
199
+ knob fires once it reaches the top rather than repeatedly on the way, and
200
+ releasing a pad does not count as a second press.
201
+
202
+ ---
203
+
175
204
  ## Screen-sharing privacy
176
205
 
177
206
  Your overlays are for you, not for the people you are sharing with. From
@@ -0,0 +1,207 @@
1
+ """
2
+ 遙控設定:手機網頁遙控與 MIDI 控制器。
3
+
4
+ 手機遙控會在這台機器上開一個網路埠,所以預設關閉、每次啟動換新權杖、
5
+ 只接受固定的動作清單——這些在畫面上都寫清楚,不藏在文件裡。
6
+
7
+ Remote control settings: the phone web remote and a MIDI controller.
8
+
9
+ The phone remote opens a network port on this machine, so it is off by default,
10
+ mints a fresh token at every start and only accepts a fixed list of actions -
11
+ all of which the dialog says out loud rather than hiding in the docs.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from typing import Any, Dict, Optional
16
+
17
+ from PySide6.QtGui import QGuiApplication
18
+ from PySide6.QtWidgets import (
19
+ QAbstractItemView, QCheckBox, QComboBox, QDialog, QDialogButtonBox, QGridLayout, QLabel,
20
+ QPushButton, QTableWidget, QTableWidgetItem, QWidget,
21
+ )
22
+
23
+ from frontengine.user_setting.user_setting_file import user_setting_dict, write_user_setting
24
+ from frontengine.utils.logging.loggin_instance import front_engine_logger
25
+ from frontengine.utils.midi.midi_input import available as midi_available, list_devices
26
+ from frontengine.utils.multi_language.language_wrapper import language_wrapper
27
+ from frontengine.utils.remote.remote_server import ALLOWED_ACTIONS
28
+
29
+ MIDI_KEY = "midi_bindings"
30
+ REMOTE_KEY = "phone_remote"
31
+
32
+
33
+ def _t(key: str, fallback: str) -> str:
34
+ return language_wrapper.language_word_dict.get(key, fallback)
35
+
36
+
37
+ def current_bindings() -> Dict[str, str]:
38
+ """設定裡的 {MIDI 鍵: 動作},只留允許清單裡的動作。"""
39
+ stored = user_setting_dict.get(MIDI_KEY)
40
+ if not isinstance(stored, dict):
41
+ return {}
42
+ return {str(key): str(value) for key, value in stored.items()
43
+ if str(value) in ALLOWED_ACTIONS}
44
+
45
+
46
+ def remote_enabled() -> bool:
47
+ settings = user_setting_dict.get(REMOTE_KEY)
48
+ return bool(settings.get("enabled")) if isinstance(settings, dict) else False
49
+
50
+
51
+ class RemoteControlDialog(QDialog):
52
+ """設定手機遙控與 MIDI 綁定。"""
53
+
54
+ def __init__(self, parent: Optional[QWidget] = None, remote=None, midi=None) -> None:
55
+ front_engine_logger.info("[RemoteControlDialog] Init")
56
+ super().__init__(parent)
57
+ self.setWindowTitle(_t("remote_title", "Remote control"))
58
+ self._remote = remote
59
+ self._midi = midi
60
+ self._learning = False
61
+
62
+ self.remote_checkbox = QCheckBox(_t("remote_enable", "Let my phone control FrontEngine"))
63
+ self.remote_checkbox.setChecked(remote_enabled())
64
+ self.remote_checkbox.toggled.connect(self._toggle_remote)
65
+ self.remote_url_label = QLabel(self.remote_status())
66
+ self.remote_url_label.setWordWrap(True)
67
+ self.remote_url_label.setTextInteractionFlags(
68
+ self.remote_url_label.textInteractionFlags().TextSelectableByMouse)
69
+ self.remote_copy_button = QPushButton(_t("remote_copy", "Copy link"))
70
+ self.remote_copy_button.clicked.connect(self.copy_link)
71
+ self.remote_hint = QLabel(
72
+ _t("remote_hint",
73
+ "This opens a port on this machine for your local network. The link carries a "
74
+ "one-time token that changes every time it starts, and only the buttons on the "
75
+ "page can be triggered - nothing else. It is plain HTTP, so treat it like any "
76
+ "other device on your network: someone else on the same network could read the "
77
+ "token and press the same buttons. Leave it off on networks you do not trust."))
78
+ self.remote_hint.setWordWrap(True)
79
+
80
+ self.midi_label = QLabel(_t("remote_midi_label", "MIDI controller"))
81
+ self.midi_combobox = QComboBox()
82
+ for index, name in list_devices():
83
+ self.midi_combobox.addItem(name, index)
84
+ if self.midi_combobox.count() == 0:
85
+ self.midi_combobox.addItem(_t("remote_midi_none", "No MIDI device found"), -1)
86
+ self.learn_button = QPushButton(_t("remote_midi_learn", "Learn"))
87
+ self.learn_button.clicked.connect(self.start_learning)
88
+ self.learn_button.setEnabled(midi_available())
89
+ self.remove_button = QPushButton(_t("remote_midi_remove", "Remove"))
90
+ self.remove_button.clicked.connect(self.remove_selected)
91
+
92
+ self.table = QTableWidget(0, 2)
93
+ self.table.setHorizontalHeaderLabels([
94
+ _t("remote_midi_control_column", "Control"),
95
+ _t("remote_midi_action_column", "Action"),
96
+ ])
97
+ self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
98
+ self.table.horizontalHeader().setStretchLastSection(True)
99
+
100
+ self.button_box = QDialogButtonBox(
101
+ QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
102
+ self.button_box.accepted.connect(self.accept)
103
+ self.button_box.rejected.connect(self.reject)
104
+
105
+ layout = QGridLayout(self)
106
+ layout.addWidget(self.remote_checkbox, 0, 0, 1, 3)
107
+ layout.addWidget(self.remote_url_label, 1, 0, 1, 2)
108
+ layout.addWidget(self.remote_copy_button, 1, 2)
109
+ layout.addWidget(self.remote_hint, 2, 0, 1, 3)
110
+ layout.addWidget(self.midi_label, 3, 0)
111
+ layout.addWidget(self.midi_combobox, 3, 1, 1, 2)
112
+ layout.addWidget(self.table, 4, 0, 1, 3)
113
+ layout.addWidget(self.learn_button, 5, 0)
114
+ layout.addWidget(self.remove_button, 5, 1)
115
+ layout.addWidget(self.button_box, 6, 0, 1, 3)
116
+
117
+ for control, action in current_bindings().items():
118
+ self.add_row(control, action)
119
+
120
+ # --- phone remote ----------------------------------------------------
121
+ def remote_status(self) -> str:
122
+ """目前該在手機上開什麼;沒開啟就說明它是關著的。"""
123
+ if self._remote is None or not getattr(self._remote, "running", False):
124
+ return _t("remote_off", "Off - nothing is listening.")
125
+ return self._remote.url()
126
+
127
+ def copy_link(self) -> bool:
128
+ """把連結複製起來,方便貼到手機上。"""
129
+ if self._remote is None or not getattr(self._remote, "running", False):
130
+ return False
131
+ clipboard = QGuiApplication.clipboard()
132
+ if clipboard is None: # pragma: no cover - no clipboard at all
133
+ return False
134
+ clipboard.setText(self._remote.url())
135
+ return True
136
+
137
+ def _toggle_remote(self, enabled: bool) -> None:
138
+ user_setting_dict[REMOTE_KEY] = {"enabled": bool(enabled)}
139
+ write_user_setting()
140
+ if self._remote is None:
141
+ return
142
+ if enabled:
143
+ self._remote.start()
144
+ else:
145
+ self._remote.stop()
146
+ self.remote_url_label.setText(self.remote_status())
147
+
148
+ # --- MIDI bindings ---------------------------------------------------
149
+ def add_row(self, control: str = "", action: str = "") -> None:
150
+ """加一列綁定。"""
151
+ row = self.table.rowCount()
152
+ self.table.insertRow(row)
153
+ self.table.setItem(row, 0, QTableWidgetItem(str(control)))
154
+ combobox = QComboBox()
155
+ combobox.addItems(list(ALLOWED_ACTIONS))
156
+ if action in ALLOWED_ACTIONS:
157
+ combobox.setCurrentText(action)
158
+ self.table.setCellWidget(row, 1, combobox)
159
+
160
+ def start_learning(self) -> None:
161
+ """按下之後,下一個收到的 MIDI 訊息就會變成新的一列。"""
162
+ self._learning = True
163
+ self.learn_button.setText(_t("remote_midi_listening", "Press a control..."))
164
+
165
+ @property
166
+ def learning(self) -> bool:
167
+ return self._learning
168
+
169
+ def learn(self, control: str) -> bool:
170
+ """收到 MIDI 訊息時由主視窗呼叫;沒在學習狀態就忽略。"""
171
+ if not self._learning:
172
+ return False
173
+ self._learning = False
174
+ self.learn_button.setText(_t("remote_midi_learn", "Learn"))
175
+ self.add_row(control, ALLOWED_ACTIONS[0])
176
+ return True
177
+
178
+ def remove_selected(self) -> bool:
179
+ row = self.table.currentRow()
180
+ if row < 0:
181
+ return False
182
+ self.table.removeRow(row)
183
+ return True
184
+
185
+ def bindings(self) -> Dict[str, str]:
186
+ """目前表格上的綁定。"""
187
+ bindings: Dict[str, str] = {}
188
+ for row in range(self.table.rowCount()):
189
+ item = self.table.item(row, 0)
190
+ combobox = self.table.cellWidget(row, 1)
191
+ control = item.text().strip() if item else ""
192
+ action = combobox.currentText() if combobox else ""
193
+ if control and action in ALLOWED_ACTIONS:
194
+ bindings[control] = action
195
+ return bindings
196
+
197
+ def midi_device(self) -> Any:
198
+ """選到的 MIDI 裝置索引。"""
199
+ return self.midi_combobox.currentData()
200
+
201
+ def accept(self) -> None:
202
+ bindings = self.bindings()
203
+ user_setting_dict[MIDI_KEY] = bindings
204
+ user_setting_dict["midi_device"] = self.midi_device()
205
+ write_user_setting()
206
+ front_engine_logger.info(f"[RemoteControlDialog] saved | {len(bindings)} binding(s)")
207
+ super().accept()
@@ -49,7 +49,12 @@ from frontengine.utils.logging.loggin_instance import front_engine_logger
49
49
  from frontengine.utils.multi_language.language_wrapper import language_wrapper
50
50
  from frontengine.utils.plugins.plugin_loader import load_plugins
51
51
  from frontengine.utils.preset_schedule.preset_schedule_service import PresetScheduleService
52
+ from frontengine.ui.dialog.remote_control_dialog import (
53
+ MIDI_KEY, current_bindings, remote_enabled,
54
+ )
52
55
  from frontengine.ui.dialog.screen_privacy_dialog import current_settings as current_privacy_settings
56
+ from frontengine.utils.midi.midi_input import MidiInput, binding_key
57
+ from frontengine.utils.remote.remote_server import RemoteServer
53
58
  from frontengine.ui.dialog.smart_pause_dialog import current_rules
54
59
  from frontengine.utils.screen_privacy.share_watch import ShareWatchService
55
60
  from frontengine.utils.app_profile.app_profile_service import AppProfileService
@@ -211,6 +216,25 @@ class FrontEngineMainUI(QMainWindow):
211
216
  )
212
217
  self.app_profile_service.start()
213
218
 
219
+ # 手機遙控與 MIDI:兩者都只呼叫既有的快速鍵動作,做不到別的事
220
+ # The phone remote and MIDI both call the existing hotkey actions and
221
+ # can do nothing beyond them.
222
+ # 兩者的訊息都來自別的執行緒(HTTP 伺服器、winmm),所以明確用
223
+ # QueuedConnection 接回 UI 執行緒——和全域快速鍵同樣的理由。
224
+ # Both deliver from another thread (the HTTP server, winmm), so an
225
+ # explicit QueuedConnection brings them back to the UI thread - the same
226
+ # reason the global hotkeys use one.
227
+ self.remote_server = RemoteServer()
228
+ self.remote_server.action_requested.connect(
229
+ self._handle_hotkey, Qt.ConnectionType.QueuedConnection)
230
+ if remote_enabled():
231
+ self.remote_server.start()
232
+ self.midi_input = MidiInput()
233
+ self.midi_input.message_received.connect(
234
+ self._on_midi_message, Qt.ConnectionType.QueuedConnection)
235
+ if user_setting_dict.get(MIDI_KEY):
236
+ self.midi_input.start(user_setting_dict.get("midi_device") or 0)
237
+
214
238
  # 分享畫面時把覆蓋層藏出擷取結果(自己仍看得到)
215
239
  # Hide the overlays from the capture while sharing; they stay on screen here.
216
240
  self.share_watch_service = ShareWatchService(config_provider=current_privacy_settings)
@@ -468,6 +492,22 @@ class FrontEngineMainUI(QMainWindow):
468
492
  self.control_center_ui.show_all()
469
493
  self._smart_pause_hid = False
470
494
 
495
+ def _on_midi_message(self, message: dict) -> None:
496
+ """
497
+ 處理一則 MIDI 訊息(已經由 QueuedConnection 帶回 UI 執行緒)。
498
+ 旋鈕轉到底才算一次按下,避免轉動途中連續觸發。
499
+ Handle one MIDI message, already brought back to the UI thread by the
500
+ queued connection. A knob only counts once it reaches the top, so
501
+ turning it does not fire repeatedly on the way.
502
+ """
503
+ if message.get("kind") == "note" and not message.get("pressed"):
504
+ return
505
+ if message.get("kind") == "control" and int(message.get("value", 0)) < 127:
506
+ return
507
+ action = current_bindings().get(binding_key(message))
508
+ if action:
509
+ self._handle_hotkey(action)
510
+
471
511
  def _on_sharing_changed(self, sharing: bool, match: str) -> None:
472
512
  """
473
513
  偵測到會議程式開著/關掉時,自動把覆蓋層藏出擷取結果或放回去。
@@ -529,51 +569,69 @@ class FrontEngineMainUI(QMainWindow):
529
569
  if getattr(self, "reminder_service", None) is not None:
530
570
  self.reminder_service.tracker.reset()
531
571
 
572
+ # 關閉時要停掉的背景服務,依相依性由外而內排列
573
+ # The background services to stop on close, outermost first.
574
+ _CLOSING_SERVICES = (
575
+ "preset_schedule_service", "theme_schedule_service", "usage_service",
576
+ "remote_server", "midi_input", "share_watch_service", "reminder_service",
577
+ "app_profile_service", "smart_pause_service", "hotkey_service",
578
+ )
579
+ # 關閉時要清空的覆蓋層清單
580
+ # The overlay lists to empty on close.
581
+ _CLOSING_WIDGET_LISTS = (
582
+ ("video_setting_ui", "video_widget_list"),
583
+ ("image_setting_ui", "image_widget_list"),
584
+ ("web_setting_ui", "web_widget_list"),
585
+ ("gif_setting_ui", "gif_widget_list"),
586
+ ("sound_player_setting_ui", "sound_widget_list"),
587
+ ("text_setting_ui", "text_widget_list"),
588
+ ("particle_setting_ui", "particle_list"),
589
+ ("pet_setting_ui", "pet_list"),
590
+ )
591
+
532
592
  def close(self) -> None:
533
593
  """關閉程式並清理資源 / Close application and clear resources"""
534
594
  if user_setting_dict.get("restore_last_session"):
535
595
  save_last_session(self)
536
- if getattr(self, "preset_schedule_service", None) is not None:
537
- self.preset_schedule_service.stop()
538
- if getattr(self, "theme_schedule_service", None) is not None:
539
- self.theme_schedule_service.stop()
540
- if getattr(self, "usage_service", None) is not None:
541
- self.usage_service.stop()
596
+ self._stop_services()
597
+ self._save_page_state()
598
+ self._save_window_geometry()
599
+ write_user_setting()
600
+ self._clear_overlays()
601
+ self.scene_setting_ui.close_scene()
602
+ super().close()
603
+ if self.main_app:
604
+ self.main_app.exit(0)
605
+
606
+ def _stop_services(self) -> None:
607
+ """停掉所有背景服務(沒建立起來的就跳過)。"""
608
+ for name in self._CLOSING_SERVICES:
609
+ service = getattr(self, name, None)
610
+ if service is not None:
611
+ service.stop()
542
612
  if getattr(self, "clipboard_watcher", None) is not None:
543
613
  self.clipboard_watcher.stop()
544
614
  self._persist_clipboard()
615
+
616
+ def _save_page_state(self) -> None:
617
+ """讓幾個分頁在關閉前把自己的東西收好。"""
545
618
  if getattr(self, "widgets_setting_ui", None) is not None:
546
619
  self.widgets_setting_ui.save_notes()
547
620
  self.widgets_setting_ui.stop_spectrum()
548
621
  if getattr(self, "tools_setting_ui", None) is not None:
549
622
  self.tools_setting_ui.release_pinned_windows()
550
623
  self.tools_setting_ui.stop_camera()
624
+ self.tools_setting_ui.stop_virtual_camera()
551
625
  if getattr(self, "stream_setting_ui", None) is not None:
552
626
  self.stream_setting_ui.soundboard.stop_all()
553
- if getattr(self, "share_watch_service", None) is not None:
554
- self.share_watch_service.stop()
555
- if getattr(self, "reminder_service", None) is not None:
556
- self.reminder_service.stop()
557
- if getattr(self, "app_profile_service", None) is not None:
558
- self.app_profile_service.stop()
559
- if getattr(self, "smart_pause_service", None) is not None:
560
- self.smart_pause_service.stop()
561
- if getattr(self, "hotkey_service", None) is not None:
562
- self.hotkey_service.stop()
563
- self._save_window_geometry()
564
- write_user_setting()
565
- self.video_setting_ui.video_widget_list.clear()
566
- self.image_setting_ui.image_widget_list.clear()
567
- self.web_setting_ui.web_widget_list.clear()
568
- self.gif_setting_ui.gif_widget_list.clear()
569
- self.sound_player_setting_ui.sound_widget_list.clear()
570
- self.text_setting_ui.text_widget_list.clear()
571
- self.particle_setting_ui.particle_list.clear()
572
- self.pet_setting_ui.pet_list.clear()
573
- self.scene_setting_ui.close_scene()
574
- super().close()
575
- if self.main_app:
576
- self.main_app.exit(0)
627
+
628
+ def _clear_overlays(self) -> None:
629
+ """清空各分頁記著的覆蓋層清單。"""
630
+ for page_name, attribute in self._CLOSING_WIDGET_LISTS:
631
+ page = getattr(self, page_name, None)
632
+ widget_list = getattr(page, attribute, None) if page is not None else None
633
+ if widget_list is not None:
634
+ widget_list.clear()
577
635
 
578
636
  @classmethod
579
637
  def debug_close(cls) -> None:
@@ -10,6 +10,7 @@ from frontengine.ui.dialog.app_profile_dialog import AppProfileDialog
10
10
  from frontengine.ui.dialog.clipboard_dialog import ClipboardDialog
11
11
  from frontengine.ui.dialog.hotkey_settings_dialog import HotkeySettingsDialog
12
12
  from frontengine.ui.dialog.reminder_dialog import ReminderDialog
13
+ from frontengine.ui.dialog.remote_control_dialog import RemoteControlDialog
13
14
  from frontengine.ui.dialog.screen_privacy_dialog import ScreenPrivacyDialog
14
15
  from frontengine.ui.dialog.smart_pause_dialog import SmartPauseDialog
15
16
  from frontengine.ui.dialog.usage_report_dialog import UsageReportDialog
@@ -88,6 +89,11 @@ def build_settings_menu(ui: "FrontEngineMainUI") -> None:
88
89
  menu.addAction(reminder_action)
89
90
  ui.reminder_action = reminder_action
90
91
 
92
+ remote_action = QAction(_t("settings_menu_remote", "Remote control..."), menu)
93
+ remote_action.triggered.connect(lambda: _open_remote_dialog(ui))
94
+ menu.addAction(remote_action)
95
+ ui.remote_action = remote_action
96
+
91
97
  privacy_action = QAction(
92
98
  _t("settings_menu_screen_privacy", "Screen-sharing privacy..."), menu)
93
99
  privacy_action.triggered.connect(lambda: _open_dialog(ui, ScreenPrivacyDialog))
@@ -121,6 +127,16 @@ def build_settings_menu(ui: "FrontEngineMainUI") -> None:
121
127
  menu.addAction(import_action)
122
128
 
123
129
 
130
+ def _open_remote_dialog(ui: "FrontEngineMainUI") -> None:
131
+ """開遙控設定,並把它接上 MIDI 的「學習」流程。"""
132
+ front_engine_logger.info("[SettingsMenu] open RemoteControlDialog")
133
+ dialog = RemoteControlDialog(ui, remote=getattr(ui, "remote_server", None),
134
+ midi=getattr(ui, "midi_input", None))
135
+ ui.remote_dialog = dialog
136
+ dialog.exec()
137
+ ui.remote_dialog = None
138
+
139
+
124
140
  def _open_usage_dialog(ui: "FrontEngineMainUI") -> None:
125
141
  """開使用時間報告;開關與清除都由對話框直接作用在主視窗的服務上。"""
126
142
  front_engine_logger.info("[SettingsMenu] open UsageReportDialog")