frontengine 1.0.45__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.
- {frontengine-1.0.45 → frontengine-1.0.47}/PKG-INFO +52 -1
- {frontengine-1.0.45 → frontengine-1.0.47}/README.md +51 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/base_widget.py +6 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/focus_shield/focus_shield_widget.py +3 -0
- frontengine-1.0.47/frontengine/ui/dialog/remote_control_dialog.py +207 -0
- frontengine-1.0.47/frontengine/ui/dialog/screen_privacy_dialog.py +98 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/main_ui.py +112 -28
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/menu/settings_menu.py +23 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/control_center/control_center_ui.py +45 -2
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/tools/tools_setting_ui.py +114 -18
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/user_setting/user_setting_file.py +9 -0
- frontengine-1.0.47/frontengine/utils/midi/midi_input.py +221 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/multi_language/english.py +29 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/multi_language/traditional_chinese.py +29 -0
- frontengine-1.0.47/frontengine/utils/remote/remote_server.py +235 -0
- frontengine-1.0.47/frontengine/utils/screen_privacy/capture_affinity.py +120 -0
- frontengine-1.0.47/frontengine/utils/screen_privacy/share_watch.py +128 -0
- frontengine-1.0.47/frontengine/utils/virtual_camera/camera_feed.py +103 -0
- frontengine-1.0.47/frontengine/utils/virtual_camera/virtual_camera.py +166 -0
- frontengine-1.0.47/frontengine/utils/weather/__init__.py +0 -0
- frontengine-1.0.47/frontengine/utils/window_pin/__init__.py +0 -0
- frontengine-1.0.47/frontengine/utils/workshop/__init__.py +0 -0
- frontengine-1.0.47/frontengine/worker/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine.egg-info/PKG-INFO +52 -1
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine.egg-info/SOURCES.txt +12 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/pyproject.toml +1 -1
- {frontengine-1.0.45 → frontengine-1.0.47}/LICENSE +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/__main__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/camera/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/camera/camera_widget.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/capture/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/capture/region_capture.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/focus_shield/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/gaming/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/gaming/crosshair_widget.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/gif/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/gif/paint_gif.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/image/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/image/paint_image.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/load/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/load/load_someone_make_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/measure/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/measure/measure_widget.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/monitor/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/monitor/monitor_widget.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/notes/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/notes/sticky_note_widget.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/overlay_factory.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/particle/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/particle/particle_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/pet/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/pet/desktop_pet.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/presentation/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/presentation/annotation_overlay.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/presentation/cursor_effects.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/presentation/keystroke_display.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/presentation/magnifier.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/scene/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/scene/extend_graphic_scene.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/scene/extend_graphic_view.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/scene/scene.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/screen_care/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/screen_care/color_vision_widget.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/screen_care/screen_filter.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/sound_player/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/sound_player/sound_effect.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/sound_player/sound_player.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/spectrum/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/spectrum/spectrum_widget.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/teleprompter/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/teleprompter/teleprompter_widget.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/text/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/text/draw_text.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/toast/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/toast/toast_widget.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/video/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/video/video_player.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/wallpaper/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/wallpaper/wallpaper_widget.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/web/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/web/webview.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/window_helpers.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/system_tray/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/system_tray/extend_system_tray.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/color/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/color/global_color.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/dialog/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/dialog/app_profile_dialog.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/dialog/clipboard_dialog.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/dialog/reminder_dialog.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/dialog/save_file_dialog.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/dialog/screen_text_dialog.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/dialog/smart_pause_dialog.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/dialog/usage_report_dialog.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/dialog/window_pin_dialog.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/menu/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/menu/help_menu.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/menu/how_to_menu.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/menu/language_menu.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/menu/preset_menu.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/control_center/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/focus/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/focus/focus_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/gif/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/image/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/image/image_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/particle/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/pet/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/presentation/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/presentation/presentation_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/screen_care/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/sound_player/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/stream/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/stream/stream_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/text/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/text/text_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/tools/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/utils.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/video/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/video/video_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/wallpaper/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/web/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/web/web_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/widgets/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/ui/page/widgets/widgets_setting_ui.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/user_setting/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/user_setting/preset_repository.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/user_setting/scene_setting.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/app_profile/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/app_profile/app_profile_service.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/audio_meter/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/audio_meter/loopback_capture.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/audio_meter/screen_audio.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/audio_meter/spectrum_analyzer.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/audio_meter/system_audio_meter.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/autostart/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/autostart/autostart_service.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/break_reminder/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/break_reminder/break_reminder.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/browser/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/browser/browser.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/clipboard/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/clipboard/clipboard_history.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/clipboard/clipboard_watcher.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/color_vision/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/color_vision/color_vision.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/critical_exit/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/critical_exit/critical_exit.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/critical_exit/win32_vk.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/exception/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/exception/exception_tags.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/exception/exceptions.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/file/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/file/open/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/file/open/open_file.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/file/save/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/file/save/save_file.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/focus_shield/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/focus_shield/focus_shield.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/focus_timer/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/focus_timer/focus_timer.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/hotkey/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/hotkey/hotkey_service.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/input_watch/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/input_watch/input_watch_service.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/json/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/json/json_file.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/json/json_repository.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/json_format/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/json_format/json_process.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/logging/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/logging/loggin_instance.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/measure/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/measure/measure.py +0 -0
- {frontengine-1.0.45/frontengine/utils/multi_language → frontengine-1.0.47/frontengine/utils/midi}/__init__.py +0 -0
- {frontengine-1.0.45/frontengine/utils/now_playing → frontengine-1.0.47/frontengine/utils/multi_language}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/multi_language/france.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/multi_language/germany.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/multi_language/italy.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/multi_language/language_wrapper.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/multi_language/russian.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
- {frontengine-1.0.45/frontengine/utils/obs → frontengine-1.0.47/frontengine/utils/now_playing}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/now_playing/now_playing.py +0 -0
- {frontengine-1.0.45/frontengine/utils/pet_chat → frontengine-1.0.47/frontengine/utils/obs}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/obs/obs_client.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/obs/obs_protocol.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/obs/websocket_frames.py +0 -0
- {frontengine-1.0.45/frontengine/utils/platform_info → frontengine-1.0.47/frontengine/utils/pet_chat}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
- {frontengine-1.0.45/frontengine/utils/playlist → frontengine-1.0.47/frontengine/utils/platform_info}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/platform_info/platform_info.py +0 -0
- {frontengine-1.0.45/frontengine/utils/plugins → frontengine-1.0.47/frontengine/utils/playlist}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/playlist/playlist.py +0 -0
- {frontengine-1.0.45/frontengine/utils/power_mode → frontengine-1.0.47/frontengine/utils/plugins}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/plugins/plugin_loader.py +0 -0
- {frontengine-1.0.45/frontengine/utils/preset_schedule → frontengine-1.0.47/frontengine/utils/power_mode}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/power_mode/power_mode.py +0 -0
- {frontengine-1.0.45/frontengine/utils/recording → frontengine-1.0.47/frontengine/utils/preset_schedule}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
- {frontengine-1.0.45/frontengine/utils/redirect_manager → frontengine-1.0.47/frontengine/utils/recording}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/recording/frame_recorder.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/recording/gif_writer.py +0 -0
- {frontengine-1.0.45/frontengine/utils/reminder → frontengine-1.0.47/frontengine/utils/redirect_manager}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
- {frontengine-1.0.45/frontengine/utils/screen_text → frontengine-1.0.47/frontengine/utils/reminder}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/reminder/reminder_service.py +0 -0
- {frontengine-1.0.45/frontengine/utils/smart_pause → frontengine-1.0.47/frontengine/utils/remote}/__init__.py +0 -0
- {frontengine-1.0.45/frontengine/utils/soundboard → frontengine-1.0.47/frontengine/utils/screen_privacy}/__init__.py +0 -0
- {frontengine-1.0.45/frontengine/utils/system_stats → frontengine-1.0.47/frontengine/utils/screen_text}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/screen_text/screen_text_service.py +0 -0
- {frontengine-1.0.45/frontengine/utils/text_source → frontengine-1.0.47/frontengine/utils/smart_pause}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/smart_pause/pause_rules.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
- {frontengine-1.0.45/frontengine/utils/theme_schedule → frontengine-1.0.47/frontengine/utils/soundboard}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/soundboard/soundboard.py +0 -0
- {frontengine-1.0.45/frontengine/utils/usage_tracking → frontengine-1.0.47/frontengine/utils/system_stats}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/system_stats/system_stats.py +0 -0
- {frontengine-1.0.45/frontengine/utils/weather → frontengine-1.0.47/frontengine/utils/text_source}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/text_source/text_source.py +0 -0
- {frontengine-1.0.45/frontengine/utils/window_pin → frontengine-1.0.47/frontengine/utils/theme_schedule}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
- {frontengine-1.0.45/frontengine/utils/workshop → frontengine-1.0.47/frontengine/utils/usage_tracking}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/usage_tracking/usage_service.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/usage_tracking/usage_tracker.py +0 -0
- {frontengine-1.0.45/frontengine/worker → frontengine-1.0.47/frontengine/utils/virtual_camera}/__init__.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/weather/weather_service.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/web_url.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/window_pin/window_layout.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/window_pin/window_pin.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/utils/workshop/workshop_content.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine/worker/qthread_worker.py +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine.egg-info/dependency_links.txt +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine.egg-info/entry_points.txt +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine.egg-info/requires.txt +0 -0
- {frontengine-1.0.45 → frontengine-1.0.47}/frontengine.egg-info/top_level.txt +0 -0
- {frontengine-1.0.45 → 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.
|
|
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,49 @@ 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
|
+
|
|
228
|
+
## Screen-sharing privacy
|
|
229
|
+
|
|
230
|
+
Your overlays are for you, not for the people you are sharing with. From
|
|
231
|
+
**Settings → Screen-sharing privacy**, FrontEngine can take them out of the
|
|
232
|
+
capture while a meeting app is open:
|
|
233
|
+
|
|
234
|
+
- **They stay on your own screen.** Only the captured copy is blank — this uses
|
|
235
|
+
Windows' `WDA_EXCLUDEFROMCAPTURE`, an OS-level flag that conferencing apps and
|
|
236
|
+
recorders honour.
|
|
237
|
+
- **Masks are the exception.** A distraction mask exists to cover something, so
|
|
238
|
+
it deliberately stays visible in the capture.
|
|
239
|
+
- **The trigger is your list.** Windows has no dependable "am I being captured"
|
|
240
|
+
API, so it watches for window titles you name — which also catches a meeting
|
|
241
|
+
held in a browser tab, where the executable is just the browser.
|
|
242
|
+
|
|
243
|
+
There is a manual **Hide from capture** button in the control center too.
|
|
244
|
+
|
|
245
|
+
> This is privacy, not security: it defeats the ordinary capture path, and it
|
|
246
|
+
> never hides anything from the person sitting at the desk. Windows only.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
199
250
|
## Screen time, clipboard and layouts
|
|
200
251
|
|
|
201
252
|
Three things that remember, all of them **off until you switch them on** and
|
|
@@ -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,49 @@ 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
|
+
|
|
204
|
+
## Screen-sharing privacy
|
|
205
|
+
|
|
206
|
+
Your overlays are for you, not for the people you are sharing with. From
|
|
207
|
+
**Settings → Screen-sharing privacy**, FrontEngine can take them out of the
|
|
208
|
+
capture while a meeting app is open:
|
|
209
|
+
|
|
210
|
+
- **They stay on your own screen.** Only the captured copy is blank — this uses
|
|
211
|
+
Windows' `WDA_EXCLUDEFROMCAPTURE`, an OS-level flag that conferencing apps and
|
|
212
|
+
recorders honour.
|
|
213
|
+
- **Masks are the exception.** A distraction mask exists to cover something, so
|
|
214
|
+
it deliberately stays visible in the capture.
|
|
215
|
+
- **The trigger is your list.** Windows has no dependable "am I being captured"
|
|
216
|
+
API, so it watches for window titles you name — which also catches a meeting
|
|
217
|
+
held in a browser tab, where the executable is just the browser.
|
|
218
|
+
|
|
219
|
+
There is a manual **Hide from capture** button in the control center too.
|
|
220
|
+
|
|
221
|
+
> This is privacy, not security: it defeats the ordinary capture path, and it
|
|
222
|
+
> never hides anything from the person sitting at the desk. Windows only.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
175
226
|
## Screen time, clipboard and layouts
|
|
176
227
|
|
|
177
228
|
Three things that remember, all of them **off until you switch them on** and
|
|
@@ -33,6 +33,12 @@ class BaseWidget(QWidget):
|
|
|
33
33
|
# 畫質檔位:影響更新頻率上限與算圖縮放(見 utils/power_mode)
|
|
34
34
|
# Quality tier: caps the refresh rate and scales rendering (see utils/power_mode).
|
|
35
35
|
self.quality_tier: str = DEFAULT_TIER
|
|
36
|
+
# 分享畫面時要不要一起藏起來。遮蔽用的覆蓋層要留在畫面上——
|
|
37
|
+
# 那正是它存在的理由,藏了就等於沒遮。
|
|
38
|
+
# Whether this hides along with the others while sharing. A masking
|
|
39
|
+
# overlay must stay visible in the capture: hiding it would defeat
|
|
40
|
+
# the only thing it is for.
|
|
41
|
+
self.keep_in_capture: bool = False
|
|
36
42
|
self._drag_origin = None
|
|
37
43
|
self._geometry_restored = False
|
|
38
44
|
|
{frontengine-1.0.45 → frontengine-1.0.47}/frontengine/show/focus_shield/focus_shield_widget.py
RENAMED
|
@@ -118,6 +118,9 @@ class DistractionMaskWidget(BaseWidget):
|
|
|
118
118
|
f"[DistractionMaskWidget] Init | region={region}, percent={percent}")
|
|
119
119
|
super().__init__()
|
|
120
120
|
self.opacity = 1.0
|
|
121
|
+
# 這一層是拿來遮住東西的,分享畫面時必須留在擷取結果裡
|
|
122
|
+
# This layer exists to cover something, so it stays in the capture.
|
|
123
|
+
self.keep_in_capture = True
|
|
121
124
|
self.region = region
|
|
122
125
|
self.percent = clamp_percent(percent)
|
|
123
126
|
self.mask_color = QColor(color) if QColor(color).isValid() else QColor("#101010")
|
|
@@ -0,0 +1,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()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
螢幕分享隱私設定:偵測到會議程式時,自動把覆蓋層藏出擷取畫面。
|
|
3
|
+
|
|
4
|
+
Windows 沒有可靠的「我正在被擷取嗎」API,所以判斷依據是使用者自己列出的
|
|
5
|
+
會議程式有沒有開著視窗。這是刻意的:與其猜錯,不如讓使用者說了算。
|
|
6
|
+
|
|
7
|
+
Screen-sharing privacy: hide the overlays from the capture when a meeting app
|
|
8
|
+
turns up.
|
|
9
|
+
|
|
10
|
+
Windows offers no dependable "am I being captured" API, so the trigger is
|
|
11
|
+
whether one of the apps the user listed has a window open. That is deliberate -
|
|
12
|
+
better their list than our guess.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any, Dict, Optional
|
|
17
|
+
|
|
18
|
+
from PySide6.QtWidgets import (
|
|
19
|
+
QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QWidget,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from frontengine.user_setting.user_setting_file import user_setting_dict, write_user_setting
|
|
23
|
+
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
24
|
+
from frontengine.utils.multi_language.language_wrapper import language_wrapper
|
|
25
|
+
from frontengine.utils.screen_privacy import capture_affinity
|
|
26
|
+
from frontengine.utils.screen_privacy.share_watch import DEFAULT_SHARING_APPS
|
|
27
|
+
from frontengine.utils.smart_pause.pause_rules import parse_app_list
|
|
28
|
+
|
|
29
|
+
SETTING_KEY = "screen_share_privacy"
|
|
30
|
+
DEFAULT_SETTINGS: Dict[str, Any] = {"enabled": False, "apps": list(DEFAULT_SHARING_APPS)}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _t(key: str, fallback: str) -> str:
|
|
34
|
+
return language_wrapper.language_word_dict.get(key, fallback)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def current_settings() -> Dict[str, Any]:
|
|
38
|
+
"""目前的分享隱私設定(缺項目補預設值)。"""
|
|
39
|
+
stored = user_setting_dict.get(SETTING_KEY)
|
|
40
|
+
settings = dict(DEFAULT_SETTINGS)
|
|
41
|
+
if isinstance(stored, dict):
|
|
42
|
+
settings.update({key: stored[key] for key in DEFAULT_SETTINGS if key in stored})
|
|
43
|
+
settings["apps"] = parse_app_list(settings.get("apps")) or list(DEFAULT_SHARING_APPS)
|
|
44
|
+
return settings
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ScreenPrivacyDialog(QDialog):
|
|
48
|
+
"""設定哪些程式算是「正在分享」,以及要不要自動隱藏。"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, parent: Optional[QWidget] = None) -> None:
|
|
51
|
+
front_engine_logger.info("[ScreenPrivacyDialog] Init")
|
|
52
|
+
super().__init__(parent)
|
|
53
|
+
self.setWindowTitle(_t("screen_privacy_title", "Screen-sharing privacy"))
|
|
54
|
+
settings = current_settings()
|
|
55
|
+
|
|
56
|
+
self.enable_checkbox = QCheckBox(
|
|
57
|
+
_t("screen_privacy_enable", "Hide overlays while one of these apps is open"))
|
|
58
|
+
self.enable_checkbox.setChecked(bool(settings.get("enabled")))
|
|
59
|
+
self.apps_label = QLabel(_t("screen_privacy_apps_label", "Meeting and capture apps:"))
|
|
60
|
+
self.apps_edit = QLineEdit(", ".join(settings.get("apps", [])))
|
|
61
|
+
self.apps_edit.setPlaceholderText("zoom, teams, discord")
|
|
62
|
+
self.hint_label = QLabel(
|
|
63
|
+
_t("screen_privacy_hint",
|
|
64
|
+
"Matched against window titles, so a meeting held in a browser tab is caught "
|
|
65
|
+
"too. Your overlays stay on your own screen - only the capture is blank. "
|
|
66
|
+
"Masks stay visible, since covering something is what they are for.")
|
|
67
|
+
if capture_affinity.available() else
|
|
68
|
+
_t("screen_privacy_unsupported",
|
|
69
|
+
"Hiding overlays from screen capture is Windows only."))
|
|
70
|
+
self.hint_label.setWordWrap(True)
|
|
71
|
+
|
|
72
|
+
self.button_box = QDialogButtonBox(
|
|
73
|
+
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
|
74
|
+
self.button_box.accepted.connect(self.accept)
|
|
75
|
+
self.button_box.rejected.connect(self.reject)
|
|
76
|
+
|
|
77
|
+
layout = QGridLayout(self)
|
|
78
|
+
layout.addWidget(self.enable_checkbox, 0, 0, 1, 2)
|
|
79
|
+
layout.addWidget(self.apps_label, 1, 0, 1, 2)
|
|
80
|
+
layout.addWidget(self.apps_edit, 2, 0, 1, 2)
|
|
81
|
+
layout.addWidget(self.hint_label, 3, 0, 1, 2)
|
|
82
|
+
layout.addWidget(self.button_box, 4, 0, 1, 2)
|
|
83
|
+
for widget in (self.enable_checkbox, self.apps_edit):
|
|
84
|
+
widget.setEnabled(capture_affinity.available())
|
|
85
|
+
|
|
86
|
+
def settings(self) -> Dict[str, Any]:
|
|
87
|
+
"""對話框上目前的設定。"""
|
|
88
|
+
return {
|
|
89
|
+
"enabled": self.enable_checkbox.isChecked(),
|
|
90
|
+
"apps": parse_app_list(self.apps_edit.text()) or list(DEFAULT_SHARING_APPS),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def accept(self) -> None:
|
|
94
|
+
settings = self.settings()
|
|
95
|
+
user_setting_dict[SETTING_KEY] = settings
|
|
96
|
+
write_user_setting()
|
|
97
|
+
front_engine_logger.info(f"[ScreenPrivacyDialog] saved | {settings}")
|
|
98
|
+
super().accept()
|
|
@@ -49,7 +49,14 @@ 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
|
+
)
|
|
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
|
|
52
58
|
from frontengine.ui.dialog.smart_pause_dialog import current_rules
|
|
59
|
+
from frontengine.utils.screen_privacy.share_watch import ShareWatchService
|
|
53
60
|
from frontengine.utils.app_profile.app_profile_service import AppProfileService
|
|
54
61
|
from frontengine.utils.clipboard.clipboard_history import DEFAULT_LIMIT, ClipboardHistory
|
|
55
62
|
from frontengine.utils.clipboard.clipboard_watcher import ClipboardWatcher
|
|
@@ -209,6 +216,31 @@ class FrontEngineMainUI(QMainWindow):
|
|
|
209
216
|
)
|
|
210
217
|
self.app_profile_service.start()
|
|
211
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
|
+
|
|
238
|
+
# 分享畫面時把覆蓋層藏出擷取結果(自己仍看得到)
|
|
239
|
+
# Hide the overlays from the capture while sharing; they stay on screen here.
|
|
240
|
+
self.share_watch_service = ShareWatchService(config_provider=current_privacy_settings)
|
|
241
|
+
self.share_watch_service.sharing_changed.connect(self._on_sharing_changed)
|
|
242
|
+
self.share_watch_service.start()
|
|
243
|
+
|
|
212
244
|
# 使用時間統計:預設關閉,只寫本機檔案,使用者可隨時清除
|
|
213
245
|
# Usage tracking: off by default, local file only, clearable at any time.
|
|
214
246
|
self.usage_tracker = UsageTracker(JsonRepository(Path(getcwd()) / USAGE_FILE))
|
|
@@ -460,6 +492,38 @@ class FrontEngineMainUI(QMainWindow):
|
|
|
460
492
|
self.control_center_ui.show_all()
|
|
461
493
|
self._smart_pause_hid = False
|
|
462
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
|
+
|
|
511
|
+
def _on_sharing_changed(self, sharing: bool, match: str) -> None:
|
|
512
|
+
"""
|
|
513
|
+
偵測到會議程式開著/關掉時,自動把覆蓋層藏出擷取結果或放回去。
|
|
514
|
+
遮蔽用的覆蓋層不受影響——它們本來就是要被看到的。
|
|
515
|
+
Hide the overlays from the capture when a meeting app appears, and put
|
|
516
|
+
them back when it goes. Masking overlays are left alone: being seen is
|
|
517
|
+
the whole point of them.
|
|
518
|
+
"""
|
|
519
|
+
front_engine_logger.info(
|
|
520
|
+
f"[MainUI] screen sharing={sharing}" + (f" | {match}" if match else ""))
|
|
521
|
+
self.control_center_ui.set_hide_from_capture(sharing)
|
|
522
|
+
if sharing:
|
|
523
|
+
self._last_toast = show_toast(
|
|
524
|
+
language_wrapper.language_word_dict.get(
|
|
525
|
+
"screen_privacy_toast", "Overlays hidden from screen capture"))
|
|
526
|
+
|
|
463
527
|
def _show_reminder(self, label: str) -> None:
|
|
464
528
|
"""提醒到期:在畫面上方顯示一張會自己消失的提示卡。"""
|
|
465
529
|
front_engine_logger.info(f"[MainUI] reminder | {label}")
|
|
@@ -505,49 +569,69 @@ class FrontEngineMainUI(QMainWindow):
|
|
|
505
569
|
if getattr(self, "reminder_service", None) is not None:
|
|
506
570
|
self.reminder_service.tracker.reset()
|
|
507
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
|
+
|
|
508
592
|
def close(self) -> None:
|
|
509
593
|
"""關閉程式並清理資源 / Close application and clear resources"""
|
|
510
594
|
if user_setting_dict.get("restore_last_session"):
|
|
511
595
|
save_last_session(self)
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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()
|
|
518
612
|
if getattr(self, "clipboard_watcher", None) is not None:
|
|
519
613
|
self.clipboard_watcher.stop()
|
|
520
614
|
self._persist_clipboard()
|
|
615
|
+
|
|
616
|
+
def _save_page_state(self) -> None:
|
|
617
|
+
"""讓幾個分頁在關閉前把自己的東西收好。"""
|
|
521
618
|
if getattr(self, "widgets_setting_ui", None) is not None:
|
|
522
619
|
self.widgets_setting_ui.save_notes()
|
|
523
620
|
self.widgets_setting_ui.stop_spectrum()
|
|
524
621
|
if getattr(self, "tools_setting_ui", None) is not None:
|
|
525
622
|
self.tools_setting_ui.release_pinned_windows()
|
|
526
623
|
self.tools_setting_ui.stop_camera()
|
|
624
|
+
self.tools_setting_ui.stop_virtual_camera()
|
|
527
625
|
if getattr(self, "stream_setting_ui", None) is not None:
|
|
528
626
|
self.stream_setting_ui.soundboard.stop_all()
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
self._save_window_geometry()
|
|
538
|
-
write_user_setting()
|
|
539
|
-
self.video_setting_ui.video_widget_list.clear()
|
|
540
|
-
self.image_setting_ui.image_widget_list.clear()
|
|
541
|
-
self.web_setting_ui.web_widget_list.clear()
|
|
542
|
-
self.gif_setting_ui.gif_widget_list.clear()
|
|
543
|
-
self.sound_player_setting_ui.sound_widget_list.clear()
|
|
544
|
-
self.text_setting_ui.text_widget_list.clear()
|
|
545
|
-
self.particle_setting_ui.particle_list.clear()
|
|
546
|
-
self.pet_setting_ui.pet_list.clear()
|
|
547
|
-
self.scene_setting_ui.close_scene()
|
|
548
|
-
super().close()
|
|
549
|
-
if self.main_app:
|
|
550
|
-
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()
|
|
551
635
|
|
|
552
636
|
@classmethod
|
|
553
637
|
def debug_close(cls) -> None:
|