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