frontengine 1.0.34__tar.gz → 1.0.36__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.34 → frontengine-1.0.36}/PKG-INFO +54 -1
- {frontengine-1.0.34 → frontengine-1.0.36}/README.md +53 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/base_widget.py +25 -0
- frontengine-1.0.36/frontengine/show/camera/camera_widget.py +182 -0
- frontengine-1.0.36/frontengine/show/capture/region_capture.py +180 -0
- frontengine-1.0.36/frontengine/show/measure/measure_widget.py +227 -0
- frontengine-1.0.36/frontengine/show/monitor/monitor_widget.py +237 -0
- frontengine-1.0.36/frontengine/show/notes/sticky_note_widget.py +154 -0
- frontengine-1.0.36/frontengine/show/spectrum/spectrum_widget.py +167 -0
- frontengine-1.0.36/frontengine/show/toast/toast_widget.py +120 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/wallpaper/wallpaper_widget.py +16 -2
- frontengine-1.0.36/frontengine/ui/dialog/app_profile_dialog.py +115 -0
- frontengine-1.0.36/frontengine/ui/dialog/reminder_dialog.py +139 -0
- frontengine-1.0.36/frontengine/ui/dialog/smart_pause_dialog.py +87 -0
- frontengine-1.0.36/frontengine/ui/dialog/window_pin_dialog.py +129 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/main_ui.py +100 -9
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/menu/settings_menu.py +37 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/control_center/control_center_ui.py +68 -3
- frontengine-1.0.36/frontengine/ui/page/tools/tools_setting_ui.py +279 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +110 -24
- frontengine-1.0.36/frontengine/ui/page/widgets/widgets_setting_ui.py +302 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/user_setting/user_setting_file.py +16 -0
- frontengine-1.0.36/frontengine/utils/app_profile/app_profile_service.py +102 -0
- frontengine-1.0.36/frontengine/utils/audio_meter/loopback_capture.py +267 -0
- frontengine-1.0.36/frontengine/utils/audio_meter/spectrum_analyzer.py +143 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/audio_meter/system_audio_meter.py +55 -0
- frontengine-1.0.36/frontengine/utils/measure/measure.py +116 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/multi_language/english.py +77 -1
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/multi_language/traditional_chinese.py +77 -2
- frontengine-1.0.36/frontengine/utils/now_playing/now_playing.py +232 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/platform_info/platform_info.py +85 -0
- frontengine-1.0.36/frontengine/utils/playlist/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/plugins/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/power_mode/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/power_mode/power_mode.py +72 -0
- frontengine-1.0.36/frontengine/utils/preset_schedule/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/redirect_manager/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/reminder/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/reminder/reminder_service.py +181 -0
- frontengine-1.0.36/frontengine/utils/smart_pause/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/smart_pause/pause_rules.py +104 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/smart_pause/smart_pause_service.py +64 -9
- frontengine-1.0.36/frontengine/utils/system_stats/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/text_source/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/theme_schedule/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/weather/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/window_pin/__init__.py +0 -0
- frontengine-1.0.36/frontengine/utils/window_pin/window_pin.py +167 -0
- frontengine-1.0.36/frontengine/utils/workshop/__init__.py +0 -0
- frontengine-1.0.36/frontengine/worker/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine.egg-info/PKG-INFO +54 -1
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine.egg-info/SOURCES.txt +35 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/pyproject.toml +1 -1
- frontengine-1.0.34/frontengine/utils/power_mode/power_mode.py +0 -29
- {frontengine-1.0.34 → frontengine-1.0.36}/LICENSE +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/__main__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/show/focus_shield → frontengine-1.0.36/frontengine/show/camera}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/show/gif → frontengine-1.0.36/frontengine/show/capture}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/show/image → frontengine-1.0.36/frontengine/show/focus_shield}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/focus_shield/focus_shield_widget.py +0 -0
- {frontengine-1.0.34/frontengine/show/load → frontengine-1.0.36/frontengine/show/gif}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/gif/paint_gif.py +0 -0
- {frontengine-1.0.34/frontengine/show/particle → frontengine-1.0.36/frontengine/show/image}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/image/paint_image.py +0 -0
- {frontengine-1.0.34/frontengine/show/pet → frontengine-1.0.36/frontengine/show/load}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/load/load_someone_make_ui.py +0 -0
- {frontengine-1.0.34/frontengine/show/presentation → frontengine-1.0.36/frontengine/show/measure}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/show/scene → frontengine-1.0.36/frontengine/show/monitor}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/show/screen_care → frontengine-1.0.36/frontengine/show/notes}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/overlay_factory.py +0 -0
- {frontengine-1.0.34/frontengine/show/sound_player → frontengine-1.0.36/frontengine/show/particle}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/particle/particle_ui.py +0 -0
- {frontengine-1.0.34/frontengine/show/text → frontengine-1.0.36/frontengine/show/pet}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/pet/desktop_pet.py +0 -0
- {frontengine-1.0.34/frontengine/show/video → frontengine-1.0.36/frontengine/show/presentation}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/presentation/annotation_overlay.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/presentation/cursor_effects.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/presentation/keystroke_display.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/presentation/magnifier.py +0 -0
- {frontengine-1.0.34/frontengine/show/wallpaper → frontengine-1.0.36/frontengine/show/scene}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/scene/extend_graphic_scene.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/scene/extend_graphic_view.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/scene/scene.py +0 -0
- {frontengine-1.0.34/frontengine/show/web → frontengine-1.0.36/frontengine/show/screen_care}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/screen_care/screen_filter.py +0 -0
- {frontengine-1.0.34/frontengine/system_tray → frontengine-1.0.36/frontengine/show/sound_player}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/sound_player/sound_effect.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/sound_player/sound_player.py +0 -0
- {frontengine-1.0.34/frontengine/ui → frontengine-1.0.36/frontengine/show/spectrum}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/ui/color → frontengine-1.0.36/frontengine/show/text}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/text/draw_text.py +0 -0
- {frontengine-1.0.34/frontengine/ui/dialog → frontengine-1.0.36/frontengine/show/toast}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/ui/menu → frontengine-1.0.36/frontengine/show/video}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/video/video_player.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page → frontengine-1.0.36/frontengine/show/wallpaper}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/control_center → frontengine-1.0.36/frontengine/show/web}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/web/webview.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/show/window_helpers.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/focus → frontengine-1.0.36/frontengine/system_tray}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/system_tray/extend_system_tray.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/gif → frontengine-1.0.36/frontengine/ui}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/image → frontengine-1.0.36/frontengine/ui/color}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/color/global_color.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/particle → frontengine-1.0.36/frontengine/ui/dialog}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/dialog/save_file_dialog.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/pet → frontengine-1.0.36/frontengine/ui/menu}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/menu/help_menu.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/menu/how_to_menu.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/menu/language_menu.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/menu/preset_menu.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/presentation → frontengine-1.0.36/frontengine/ui/page}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/scene_setting → frontengine-1.0.36/frontengine/ui/page/control_center}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/scene_setting/scene_page → frontengine-1.0.36/frontengine/ui/page/focus}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/focus/focus_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/screen_care → frontengine-1.0.36/frontengine/ui/page/gif}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/sound_player → frontengine-1.0.36/frontengine/ui/page/image}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/image/image_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/text → frontengine-1.0.36/frontengine/ui/page/particle}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/video → frontengine-1.0.36/frontengine/ui/page/pet}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/wallpaper → frontengine-1.0.36/frontengine/ui/page/presentation}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/presentation/presentation_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/ui/page/web → frontengine-1.0.36/frontengine/ui/page/scene_setting}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
- {frontengine-1.0.34/frontengine/user_setting → frontengine-1.0.36/frontengine/ui/page/scene_setting/scene_page}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/utils → frontengine-1.0.36/frontengine/ui/page/screen_care}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/utils/audio_meter → frontengine-1.0.36/frontengine/ui/page/sound_player}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/utils/autostart → frontengine-1.0.36/frontengine/ui/page/text}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/text/text_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/utils/break_reminder → frontengine-1.0.36/frontengine/ui/page/tools}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/utils.py +0 -0
- {frontengine-1.0.34/frontengine/utils/browser → frontengine-1.0.36/frontengine/ui/page/video}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/video/video_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/utils/critical_exit → frontengine-1.0.36/frontengine/ui/page/wallpaper}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/utils/file → frontengine-1.0.36/frontengine/ui/page/web}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/ui/page/web/web_setting_ui.py +0 -0
- {frontengine-1.0.34/frontengine/utils/focus_shield → frontengine-1.0.36/frontengine/ui/page/widgets}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/utils/focus_timer → frontengine-1.0.36/frontengine/user_setting}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/user_setting/preset_repository.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/user_setting/scene_setting.py +0 -0
- {frontengine-1.0.34/frontengine/utils/hotkey → frontengine-1.0.36/frontengine/utils}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/utils/input_watch → frontengine-1.0.36/frontengine/utils/app_profile}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/utils/json → frontengine-1.0.36/frontengine/utils/audio_meter}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/audio_meter/screen_audio.py +0 -0
- {frontengine-1.0.34/frontengine/utils/logging → frontengine-1.0.36/frontengine/utils/autostart}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/autostart/autostart_service.py +0 -0
- {frontengine-1.0.34/frontengine/utils/multi_language → frontengine-1.0.36/frontengine/utils/break_reminder}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/break_reminder/break_reminder.py +0 -0
- {frontengine-1.0.34/frontengine/utils/pet_chat → frontengine-1.0.36/frontengine/utils/browser}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/browser/browser.py +0 -0
- {frontengine-1.0.34/frontengine/utils/platform_info → frontengine-1.0.36/frontengine/utils/critical_exit}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/critical_exit/critical_exit.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/critical_exit/win32_vk.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/exception/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/exception/exception_tags.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/exception/exceptions.py +0 -0
- {frontengine-1.0.34/frontengine/utils/playlist → frontengine-1.0.36/frontengine/utils/file}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/file/open/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/file/open/open_file.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/file/save/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/file/save/save_file.py +0 -0
- {frontengine-1.0.34/frontengine/utils/plugins → frontengine-1.0.36/frontengine/utils/focus_shield}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/focus_shield/focus_shield.py +0 -0
- {frontengine-1.0.34/frontengine/utils/power_mode → frontengine-1.0.36/frontengine/utils/focus_timer}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/focus_timer/focus_timer.py +0 -0
- {frontengine-1.0.34/frontengine/utils/preset_schedule → frontengine-1.0.36/frontengine/utils/hotkey}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/hotkey/hotkey_service.py +0 -0
- {frontengine-1.0.34/frontengine/utils/redirect_manager → frontengine-1.0.36/frontengine/utils/input_watch}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/input_watch/input_watch_service.py +0 -0
- {frontengine-1.0.34/frontengine/utils/smart_pause → frontengine-1.0.36/frontengine/utils/json}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/json/json_file.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/json/json_repository.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/json_format/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/json_format/json_process.py +0 -0
- {frontengine-1.0.34/frontengine/utils/system_stats → frontengine-1.0.36/frontengine/utils/logging}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/logging/loggin_instance.py +0 -0
- {frontengine-1.0.34/frontengine/utils/text_source → frontengine-1.0.36/frontengine/utils/measure}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/utils/theme_schedule → frontengine-1.0.36/frontengine/utils/multi_language}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/multi_language/france.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/multi_language/germany.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/multi_language/italy.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/multi_language/language_wrapper.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/multi_language/russian.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
- {frontengine-1.0.34/frontengine/utils/weather → frontengine-1.0.36/frontengine/utils/now_playing}/__init__.py +0 -0
- {frontengine-1.0.34/frontengine/utils/workshop → frontengine-1.0.36/frontengine/utils/pet_chat}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
- {frontengine-1.0.34/frontengine/worker → frontengine-1.0.36/frontengine/utils/platform_info}/__init__.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/playlist/playlist.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/plugins/plugin_loader.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/system_stats/system_stats.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/text_source/text_source.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/weather/weather_service.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/web_url.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/utils/workshop/workshop_content.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine/worker/qthread_worker.py +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine.egg-info/dependency_links.txt +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine.egg-info/entry_points.txt +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine.egg-info/requires.txt +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/frontengine.egg-info/top_level.txt +0 -0
- {frontengine-1.0.34 → frontengine-1.0.36}/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.36
|
|
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
|
|
@@ -113,6 +113,59 @@ Play a folder of images and animations *beneath* every window, from the
|
|
|
113
113
|
|
|
114
114
|
---
|
|
115
115
|
|
|
116
|
+
## Widgets
|
|
117
|
+
|
|
118
|
+
Four desktop widgets on the **Widgets** tab:
|
|
119
|
+
|
|
120
|
+
- **Audio spectrum** — bars or a ring, log-spaced bands, smoothed with a
|
|
121
|
+
fast-attack/slow-decay follower and peak markers that drift down.
|
|
122
|
+
> Unlike the pet and the wallpaper, which read only the output *meter*, a
|
|
123
|
+
> spectrum needs the actual samples to compute frequencies — so this one
|
|
124
|
+
> **captures the system output stream**. The samples are analysed in memory,
|
|
125
|
+
> never written to disk or sent anywhere, and capture stops the moment you
|
|
126
|
+
> stop the spectrum. Windows only.
|
|
127
|
+
- **Now playing** — the current track from Windows' media controls when the
|
|
128
|
+
optional `winsdk` bindings are installed; otherwise the name of the app that
|
|
129
|
+
is actually making sound.
|
|
130
|
+
- **System monitor** — CPU, memory and disk as small sparklines. An average
|
|
131
|
+
hides a stall; a line does not.
|
|
132
|
+
- **Sticky notes** — editable cards that float above every window and keep
|
|
133
|
+
their text, colour and position between sessions.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Tools
|
|
138
|
+
|
|
139
|
+
Measuring and capture tools on the **Tools** tab:
|
|
140
|
+
|
|
141
|
+
- **Colour picker / pixel ruler / protractor** — click to sample or measure;
|
|
142
|
+
the result is copied straight to the clipboard as `#rrggbb`, `rgb(...)`,
|
|
143
|
+
`hsl(...)` or a CSS custom property.
|
|
144
|
+
- **Region capture** — drag out an area; it lands on the clipboard and can be
|
|
145
|
+
saved to a file.
|
|
146
|
+
- **Pin a window** — keep another program's window on top, or fade it, while
|
|
147
|
+
you work against it. Only stacking and opacity are touched, never window
|
|
148
|
+
content. Windows only.
|
|
149
|
+
- **Camera** — your webcam in a circle, rounded box or rectangle, shown
|
|
150
|
+
locally and never recorded.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Rules
|
|
155
|
+
|
|
156
|
+
Things that decide for themselves, from the **Settings** menu:
|
|
157
|
+
|
|
158
|
+
- **Smart pause** — stand the overlays down while a fullscreen app is running,
|
|
159
|
+
while the machine is on battery, or while a named app has focus.
|
|
160
|
+
- **App profiles** — apply a saved preset when you switch to a given app.
|
|
161
|
+
- **Reminders** — every N minutes, or once a day at a set time, shown as a
|
|
162
|
+
toast that closes itself.
|
|
163
|
+
|
|
164
|
+
The control center also carries a **quality tier** (high / balanced / saver)
|
|
165
|
+
that caps every overlay's refresh rate and drops its render resolution.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
116
169
|
## Community content
|
|
117
170
|
|
|
118
171
|
- **Steam Workshop** — subscribed items are picked up from Steam's own
|
|
@@ -89,6 +89,59 @@ Play a folder of images and animations *beneath* every window, from the
|
|
|
89
89
|
|
|
90
90
|
---
|
|
91
91
|
|
|
92
|
+
## Widgets
|
|
93
|
+
|
|
94
|
+
Four desktop widgets on the **Widgets** tab:
|
|
95
|
+
|
|
96
|
+
- **Audio spectrum** — bars or a ring, log-spaced bands, smoothed with a
|
|
97
|
+
fast-attack/slow-decay follower and peak markers that drift down.
|
|
98
|
+
> Unlike the pet and the wallpaper, which read only the output *meter*, a
|
|
99
|
+
> spectrum needs the actual samples to compute frequencies — so this one
|
|
100
|
+
> **captures the system output stream**. The samples are analysed in memory,
|
|
101
|
+
> never written to disk or sent anywhere, and capture stops the moment you
|
|
102
|
+
> stop the spectrum. Windows only.
|
|
103
|
+
- **Now playing** — the current track from Windows' media controls when the
|
|
104
|
+
optional `winsdk` bindings are installed; otherwise the name of the app that
|
|
105
|
+
is actually making sound.
|
|
106
|
+
- **System monitor** — CPU, memory and disk as small sparklines. An average
|
|
107
|
+
hides a stall; a line does not.
|
|
108
|
+
- **Sticky notes** — editable cards that float above every window and keep
|
|
109
|
+
their text, colour and position between sessions.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Tools
|
|
114
|
+
|
|
115
|
+
Measuring and capture tools on the **Tools** tab:
|
|
116
|
+
|
|
117
|
+
- **Colour picker / pixel ruler / protractor** — click to sample or measure;
|
|
118
|
+
the result is copied straight to the clipboard as `#rrggbb`, `rgb(...)`,
|
|
119
|
+
`hsl(...)` or a CSS custom property.
|
|
120
|
+
- **Region capture** — drag out an area; it lands on the clipboard and can be
|
|
121
|
+
saved to a file.
|
|
122
|
+
- **Pin a window** — keep another program's window on top, or fade it, while
|
|
123
|
+
you work against it. Only stacking and opacity are touched, never window
|
|
124
|
+
content. Windows only.
|
|
125
|
+
- **Camera** — your webcam in a circle, rounded box or rectangle, shown
|
|
126
|
+
locally and never recorded.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Rules
|
|
131
|
+
|
|
132
|
+
Things that decide for themselves, from the **Settings** menu:
|
|
133
|
+
|
|
134
|
+
- **Smart pause** — stand the overlays down while a fullscreen app is running,
|
|
135
|
+
while the machine is on battery, or while a named app has focus.
|
|
136
|
+
- **App profiles** — apply a saved preset when you switch to a given app.
|
|
137
|
+
- **Reminders** — every N minutes, or once a day at a set time, shown as a
|
|
138
|
+
toast that closes itself.
|
|
139
|
+
|
|
140
|
+
The control center also carries a **quality tier** (high / balanced / saver)
|
|
141
|
+
that caps every overlay's refresh rate and drops its render resolution.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
92
145
|
## Community content
|
|
93
146
|
|
|
94
147
|
- **Steam Workshop** — subscribed items are picked up from Steam's own
|
|
@@ -8,6 +8,7 @@ from PySide6.QtWidgets import QWidget
|
|
|
8
8
|
from frontengine.show.window_helpers import apply_overlay_window_flags, load_overlay_icon
|
|
9
9
|
from frontengine.user_setting.user_setting_file import get_overlay_geometry, save_overlay_geometry
|
|
10
10
|
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
11
|
+
from frontengine.utils.power_mode.power_mode import DEFAULT_TIER, normalize_tier, tier_render_scale
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class BaseWidget(QWidget):
|
|
@@ -29,6 +30,9 @@ class BaseWidget(QWidget):
|
|
|
29
30
|
# 綠幕背景色:設了就以不透明色填滿背景,方便 OBS 去背
|
|
30
31
|
# Chroma key colour: fills the background opaquely so OBS can key it out.
|
|
31
32
|
self.background_color: Optional[QColor] = None
|
|
33
|
+
# 畫質檔位:影響更新頻率上限與算圖縮放(見 utils/power_mode)
|
|
34
|
+
# Quality tier: caps the refresh rate and scales rendering (see utils/power_mode).
|
|
35
|
+
self.quality_tier: str = DEFAULT_TIER
|
|
32
36
|
self._drag_origin = None
|
|
33
37
|
self._geometry_restored = False
|
|
34
38
|
|
|
@@ -44,6 +48,27 @@ class BaseWidget(QWidget):
|
|
|
44
48
|
front_engine_logger.info(f"{self.__class__.__name__} set_ui_variable | opacity: {opacity}")
|
|
45
49
|
self.opacity = opacity
|
|
46
50
|
|
|
51
|
+
def set_quality_tier(self, tier: str) -> None:
|
|
52
|
+
"""
|
|
53
|
+
設定畫質檔位。基底類別只記下來並通知子類別,實際怎麼省由子類別決定
|
|
54
|
+
(例如放慢自己的計時器、用較低解析度算圖)。
|
|
55
|
+
Set the quality tier. The base class records it and tells the subclass;
|
|
56
|
+
what to actually give up - timer rate, render resolution - is the
|
|
57
|
+
subclass's call.
|
|
58
|
+
"""
|
|
59
|
+
self.quality_tier = normalize_tier(tier)
|
|
60
|
+
front_engine_logger.info(
|
|
61
|
+
f"{self.__class__.__name__} set_quality_tier | tier: {self.quality_tier}")
|
|
62
|
+
self.apply_quality_tier()
|
|
63
|
+
self.update()
|
|
64
|
+
|
|
65
|
+
def apply_quality_tier(self) -> None:
|
|
66
|
+
"""子類別覆寫這裡,把新的檔位套到自己的計時器/算圖上。"""
|
|
67
|
+
|
|
68
|
+
def render_scale(self) -> float:
|
|
69
|
+
"""目前檔位的算圖縮放,1.0 表示原生解析度。"""
|
|
70
|
+
return tier_render_scale(self.quality_tier)
|
|
71
|
+
|
|
47
72
|
def set_background_color(self, color) -> None:
|
|
48
73
|
"""
|
|
49
74
|
設定綠幕背景色(傳 None 取消)。背景以不透明色填滿,內容再依原本的
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
攝影機覆蓋層:把本機攝影機的畫面放在桌面上(直播用的圓形頭像框那種)。
|
|
3
|
+
畫面只在本機顯示,不錄影、不上傳;關掉覆蓋層就停止擷取。
|
|
4
|
+
|
|
5
|
+
A camera overlay: put the local webcam on the desktop - the round talking-head
|
|
6
|
+
frame streamers use. The picture is shown locally only; nothing is recorded or
|
|
7
|
+
uploaded, and closing the overlay stops the camera.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import List, Optional, Tuple
|
|
12
|
+
|
|
13
|
+
from PySide6.QtCore import QRect, Qt
|
|
14
|
+
from PySide6.QtGui import QColor, QPainter, QPainterPath, QPen, QPixmap
|
|
15
|
+
from PySide6.QtMultimedia import QCamera, QMediaCaptureSession, QMediaDevices, QVideoSink
|
|
16
|
+
|
|
17
|
+
from frontengine.show.base_widget import BaseWidget
|
|
18
|
+
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
19
|
+
|
|
20
|
+
SHAPE_RECTANGLE = "rectangle"
|
|
21
|
+
SHAPE_CIRCLE = "circle"
|
|
22
|
+
SHAPE_ROUNDED = "rounded"
|
|
23
|
+
SHAPES = (SHAPE_RECTANGLE, SHAPE_CIRCLE, SHAPE_ROUNDED)
|
|
24
|
+
|
|
25
|
+
DEFAULT_BORDER = 4
|
|
26
|
+
_CORNER_RADIUS = 18
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def normalize_shape(shape) -> str:
|
|
30
|
+
"""把外框形狀正規化;不認得的一律當方形。"""
|
|
31
|
+
name = str(shape or "").strip().lower()
|
|
32
|
+
return name if name in SHAPES else SHAPE_RECTANGLE
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def list_cameras() -> List[Tuple[str, str]]:
|
|
36
|
+
"""
|
|
37
|
+
可用的攝影機 [(id, 顯示名稱)];沒有裝置或查詢失敗回傳空清單。
|
|
38
|
+
Available cameras as (id, description); [] when there are none.
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
return [(bytes(device.id()).decode("utf-8", "replace"), device.description())
|
|
42
|
+
for device in QMediaDevices.videoInputs()]
|
|
43
|
+
except Exception as error: # pragma: no cover - depends on the platform backend
|
|
44
|
+
front_engine_logger.warning(f"[CameraWidget] listing cameras failed: {error!r}")
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def shape_path(shape: str, rect: QRect) -> QPainterPath:
|
|
49
|
+
"""外框形狀對應的裁切路徑。"""
|
|
50
|
+
path = QPainterPath()
|
|
51
|
+
name = normalize_shape(shape)
|
|
52
|
+
if name == SHAPE_CIRCLE:
|
|
53
|
+
size = min(rect.width(), rect.height())
|
|
54
|
+
square = QRect(rect.x() + (rect.width() - size) // 2,
|
|
55
|
+
rect.y() + (rect.height() - size) // 2, size, size)
|
|
56
|
+
path.addEllipse(square)
|
|
57
|
+
elif name == SHAPE_ROUNDED:
|
|
58
|
+
path.addRoundedRect(rect, _CORNER_RADIUS, _CORNER_RADIUS)
|
|
59
|
+
else:
|
|
60
|
+
path.addRect(rect)
|
|
61
|
+
return path
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class CameraWidget(BaseWidget):
|
|
65
|
+
"""
|
|
66
|
+
攝影機畫面覆蓋層。影像來源可注入(測試直接餵 QPixmap),因此不需要真的
|
|
67
|
+
攝影機也能驗證裁切與繪製。
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, shape: str = SHAPE_CIRCLE, border: int = DEFAULT_BORDER,
|
|
71
|
+
mirrored: bool = True) -> None:
|
|
72
|
+
front_engine_logger.info(f"[CameraWidget] Init | shape={shape}")
|
|
73
|
+
super().__init__()
|
|
74
|
+
self.opacity = 1.0
|
|
75
|
+
self.shape = normalize_shape(shape)
|
|
76
|
+
self.border = max(0, int(border))
|
|
77
|
+
self.mirrored = bool(mirrored)
|
|
78
|
+
self.border_color = QColor("#ffffff")
|
|
79
|
+
self.frame: Optional[QPixmap] = None
|
|
80
|
+
self._camera: Optional[QCamera] = None
|
|
81
|
+
self._session: Optional[QMediaCaptureSession] = None
|
|
82
|
+
self._sink: Optional[QVideoSink] = None
|
|
83
|
+
self.resize(240, 240)
|
|
84
|
+
|
|
85
|
+
def set_shape(self, shape: str) -> None:
|
|
86
|
+
self.shape = normalize_shape(shape)
|
|
87
|
+
self.update()
|
|
88
|
+
|
|
89
|
+
def set_border(self, border: int) -> None:
|
|
90
|
+
self.border = max(0, int(border))
|
|
91
|
+
self.update()
|
|
92
|
+
|
|
93
|
+
def set_mirrored(self, mirrored: bool) -> None:
|
|
94
|
+
"""自拍鏡像:看自己的時候左右相反比較自然。"""
|
|
95
|
+
self.mirrored = bool(mirrored)
|
|
96
|
+
self.update()
|
|
97
|
+
|
|
98
|
+
def set_frame(self, pixmap: Optional[QPixmap]) -> None:
|
|
99
|
+
"""直接給一張畫面(正式由攝影機推送,測試直接餵)。"""
|
|
100
|
+
self.frame = pixmap if pixmap is not None and not pixmap.isNull() else None
|
|
101
|
+
self.update()
|
|
102
|
+
|
|
103
|
+
def start(self, camera_id: Optional[str] = None) -> bool:
|
|
104
|
+
"""
|
|
105
|
+
打開攝影機開始顯示;沒有裝置或啟動失敗回傳 False。
|
|
106
|
+
Open the camera. False when there is no device or it refuses to start.
|
|
107
|
+
"""
|
|
108
|
+
devices = QMediaDevices.videoInputs()
|
|
109
|
+
if not devices:
|
|
110
|
+
front_engine_logger.warning("[CameraWidget] no camera available")
|
|
111
|
+
return False
|
|
112
|
+
chosen = devices[0]
|
|
113
|
+
if camera_id:
|
|
114
|
+
for device in devices:
|
|
115
|
+
if bytes(device.id()).decode("utf-8", "replace") == camera_id:
|
|
116
|
+
chosen = device
|
|
117
|
+
break
|
|
118
|
+
try:
|
|
119
|
+
self.stop()
|
|
120
|
+
self._camera = QCamera(chosen)
|
|
121
|
+
self._sink = QVideoSink(self)
|
|
122
|
+
self._sink.videoFrameChanged.connect(self._on_frame)
|
|
123
|
+
self._session = QMediaCaptureSession()
|
|
124
|
+
self._session.setCamera(self._camera)
|
|
125
|
+
self._session.setVideoSink(self._sink)
|
|
126
|
+
self._camera.start()
|
|
127
|
+
except Exception as error: # pragma: no cover - depends on the backend
|
|
128
|
+
front_engine_logger.warning(f"[CameraWidget] start failed: {error!r}")
|
|
129
|
+
self.stop()
|
|
130
|
+
return False
|
|
131
|
+
front_engine_logger.info(f"[CameraWidget] started | {chosen.description()}")
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
def stop(self) -> None:
|
|
135
|
+
"""關閉攝影機(覆蓋層關掉時一定會走到這裡)。"""
|
|
136
|
+
if self._camera is not None:
|
|
137
|
+
try:
|
|
138
|
+
self._camera.stop()
|
|
139
|
+
except RuntimeError: # pragma: no cover - already torn down
|
|
140
|
+
pass
|
|
141
|
+
self._camera = None
|
|
142
|
+
self._session = None
|
|
143
|
+
self._sink = None
|
|
144
|
+
|
|
145
|
+
def _on_frame(self, video_frame) -> None: # pragma: no cover - needs a real camera
|
|
146
|
+
image = video_frame.toImage() if video_frame is not None else None
|
|
147
|
+
if image is None or image.isNull():
|
|
148
|
+
return
|
|
149
|
+
self.set_frame(QPixmap.fromImage(image))
|
|
150
|
+
|
|
151
|
+
def draw_content(self, painter: QPainter) -> None:
|
|
152
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
|
153
|
+
path = shape_path(self.shape, self.rect())
|
|
154
|
+
painter.save()
|
|
155
|
+
painter.setClipPath(path)
|
|
156
|
+
if self.frame is not None:
|
|
157
|
+
scaled = self.frame.scaled(
|
|
158
|
+
self.size(), Qt.AspectRatioMode.KeepAspectRatioByExpanding,
|
|
159
|
+
Qt.TransformationMode.SmoothTransformation)
|
|
160
|
+
if self.mirrored:
|
|
161
|
+
scaled = scaled.transformed(_mirror_transform())
|
|
162
|
+
painter.drawPixmap(
|
|
163
|
+
QRect((self.width() - scaled.width()) // 2,
|
|
164
|
+
(self.height() - scaled.height()) // 2,
|
|
165
|
+
scaled.width(), scaled.height()), scaled)
|
|
166
|
+
else:
|
|
167
|
+
painter.fillRect(self.rect(), QColor(0, 0, 0, 160))
|
|
168
|
+
painter.restore()
|
|
169
|
+
if self.border > 0:
|
|
170
|
+
painter.setPen(QPen(self.border_color, self.border))
|
|
171
|
+
painter.drawPath(path)
|
|
172
|
+
|
|
173
|
+
def closeEvent(self, event) -> None:
|
|
174
|
+
self.stop()
|
|
175
|
+
super().closeEvent(event)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _mirror_transform():
|
|
179
|
+
"""左右翻轉用的變換矩陣。"""
|
|
180
|
+
from PySide6.QtGui import QTransform
|
|
181
|
+
|
|
182
|
+
return QTransform().scale(-1, 1)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
區域截圖:拖出一塊範圍,放開後把那塊畫面抓下來,可以存檔或複製到剪貼簿,
|
|
3
|
+
也可以直接交給塗鴉層繼續標註。
|
|
4
|
+
|
|
5
|
+
Region capture: drag out an area, and on release grab it - to a file, to the
|
|
6
|
+
clipboard, or straight into the annotation layer to mark up.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Callable, Optional
|
|
12
|
+
|
|
13
|
+
from PySide6.QtCore import QPoint, QRect, Qt
|
|
14
|
+
from PySide6.QtGui import QColor, QGuiApplication, QPainter, QPen, QPixmap
|
|
15
|
+
|
|
16
|
+
from frontengine.show.base_widget import BaseWidget
|
|
17
|
+
from frontengine.show.window_helpers import apply_overlay_window_flags
|
|
18
|
+
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
19
|
+
|
|
20
|
+
MIN_CAPTURE_SIZE = 4
|
|
21
|
+
_DIM_COLOR = QColor(0, 0, 0, 110)
|
|
22
|
+
_EDGE_COLOR = "#4dd0e1"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def normalize_rect(start: QPoint, end: QPoint) -> QRect:
|
|
26
|
+
"""
|
|
27
|
+
把兩個角落整理成正的矩形(不管往哪個方向拖)。
|
|
28
|
+
Two corners to a positive rectangle, whichever way the drag went.
|
|
29
|
+
"""
|
|
30
|
+
left, right = sorted((int(start.x()), int(end.x())))
|
|
31
|
+
top, bottom = sorted((int(start.y()), int(end.y())))
|
|
32
|
+
return QRect(left, top, right - left, bottom - top)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_usable(rect: QRect) -> bool:
|
|
36
|
+
"""太小的框(多半是誤點)不算一次截圖。"""
|
|
37
|
+
return rect.width() >= MIN_CAPTURE_SIZE and rect.height() >= MIN_CAPTURE_SIZE
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def safe_capture_path(folder: str, name: str) -> Optional[Path]:
|
|
41
|
+
"""
|
|
42
|
+
組出儲存路徑並擋掉路徑穿越:檔名只取最後一段,且結果必須仍在資料夾底下。
|
|
43
|
+
Build the save path and refuse path traversal: only the final component of
|
|
44
|
+
the name is used, and the result must still sit inside the folder.
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
base = Path(folder).resolve()
|
|
48
|
+
candidate = (base / Path(str(name)).name).resolve()
|
|
49
|
+
except (OSError, ValueError):
|
|
50
|
+
return None
|
|
51
|
+
if base != candidate.parent:
|
|
52
|
+
front_engine_logger.warning(f"[RegionCapture] refusing path outside {base}: {name!r}")
|
|
53
|
+
return None
|
|
54
|
+
return candidate
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RegionCaptureWidget(BaseWidget):
|
|
58
|
+
"""
|
|
59
|
+
全螢幕的截圖框選層:按住拖曳畫出範圍,放開就擷取。擷取函式與完成後的
|
|
60
|
+
處理都可注入,所以整個流程不需要真的螢幕也測得到。
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, on_captured: Optional[Callable[[QPixmap, QRect], None]] = None) -> None:
|
|
64
|
+
front_engine_logger.info("[RegionCaptureWidget] Init")
|
|
65
|
+
super().__init__()
|
|
66
|
+
self.opacity = 1.0
|
|
67
|
+
self.overlay_lockable = False
|
|
68
|
+
self.origin: Optional[QPoint] = None
|
|
69
|
+
self.current: Optional[QPoint] = None
|
|
70
|
+
self.captured: Optional[QPixmap] = None
|
|
71
|
+
self._on_captured = on_captured
|
|
72
|
+
self._grabber = self._default_grabber
|
|
73
|
+
apply_overlay_window_flags(self, show_on_bottom=False, allow_input=True)
|
|
74
|
+
self.setCursor(Qt.CursorShape.CrossCursor)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def _default_grabber(rect: QRect): # pragma: no cover - needs a real screen
|
|
78
|
+
screen = QGuiApplication.primaryScreen()
|
|
79
|
+
if screen is None:
|
|
80
|
+
return None
|
|
81
|
+
return screen.grabWindow(0, rect.x(), rect.y(), rect.width(), rect.height())
|
|
82
|
+
|
|
83
|
+
def set_grabber(self, grabber) -> None:
|
|
84
|
+
self._grabber = grabber or self._default_grabber
|
|
85
|
+
|
|
86
|
+
def selection(self) -> Optional[QRect]:
|
|
87
|
+
"""目前框出來的範圍;還沒開始拖就是 None。"""
|
|
88
|
+
if self.origin is None or self.current is None:
|
|
89
|
+
return None
|
|
90
|
+
return normalize_rect(self.origin, self.current)
|
|
91
|
+
|
|
92
|
+
# --- interaction -----------------------------------------------------
|
|
93
|
+
def begin(self, point: QPoint) -> None:
|
|
94
|
+
self.origin = QPoint(point)
|
|
95
|
+
self.current = QPoint(point)
|
|
96
|
+
self.update()
|
|
97
|
+
|
|
98
|
+
def extend(self, point: QPoint) -> None:
|
|
99
|
+
if self.origin is not None:
|
|
100
|
+
self.current = QPoint(point)
|
|
101
|
+
self.update()
|
|
102
|
+
|
|
103
|
+
def finish(self, point: QPoint) -> Optional[QPixmap]:
|
|
104
|
+
"""
|
|
105
|
+
放開滑鼠:框太小就當作取消,否則擷取那塊畫面並回傳。
|
|
106
|
+
Releasing the mouse: too small counts as a cancel, otherwise grab that
|
|
107
|
+
part of the screen and hand it back.
|
|
108
|
+
"""
|
|
109
|
+
self.extend(point)
|
|
110
|
+
rect = self.selection()
|
|
111
|
+
self.origin = self.current = None
|
|
112
|
+
if rect is None or not is_usable(rect):
|
|
113
|
+
self.update()
|
|
114
|
+
return None
|
|
115
|
+
try:
|
|
116
|
+
pixmap = self._grabber(rect)
|
|
117
|
+
except Exception as error: # pragma: no cover - screen grab boundary
|
|
118
|
+
front_engine_logger.warning(f"[RegionCaptureWidget] grab failed: {error!r}")
|
|
119
|
+
pixmap = None
|
|
120
|
+
if pixmap is None or pixmap.isNull():
|
|
121
|
+
self.update()
|
|
122
|
+
return None
|
|
123
|
+
self.captured = pixmap
|
|
124
|
+
if self._on_captured is not None:
|
|
125
|
+
self._on_captured(pixmap, rect)
|
|
126
|
+
self.update()
|
|
127
|
+
return pixmap
|
|
128
|
+
|
|
129
|
+
def mousePressEvent(self, event) -> None:
|
|
130
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
|
131
|
+
self.begin(event.position().toPoint())
|
|
132
|
+
super().mousePressEvent(event)
|
|
133
|
+
|
|
134
|
+
def mouseMoveEvent(self, event) -> None:
|
|
135
|
+
self.extend(event.position().toPoint())
|
|
136
|
+
super().mouseMoveEvent(event)
|
|
137
|
+
|
|
138
|
+
def mouseReleaseEvent(self, event) -> None:
|
|
139
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
|
140
|
+
self.finish(event.position().toPoint())
|
|
141
|
+
self.close()
|
|
142
|
+
super().mouseReleaseEvent(event)
|
|
143
|
+
|
|
144
|
+
# --- outputs ---------------------------------------------------------
|
|
145
|
+
def copy_to_clipboard(self) -> bool:
|
|
146
|
+
"""把剛擷取的畫面放進剪貼簿。"""
|
|
147
|
+
if self.captured is None or self.captured.isNull():
|
|
148
|
+
return False
|
|
149
|
+
clipboard = QGuiApplication.clipboard()
|
|
150
|
+
if clipboard is None: # pragma: no cover - no clipboard at all
|
|
151
|
+
return False
|
|
152
|
+
clipboard.setPixmap(self.captured)
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
def save_to(self, folder: str, name: str) -> Optional[str]:
|
|
156
|
+
"""存成檔案並回傳實際路徑;沒有畫面或路徑不合法時回傳 None。"""
|
|
157
|
+
if self.captured is None or self.captured.isNull():
|
|
158
|
+
return None
|
|
159
|
+
path = safe_capture_path(folder, name)
|
|
160
|
+
if path is None:
|
|
161
|
+
return None
|
|
162
|
+
if not self.captured.save(str(path)):
|
|
163
|
+
front_engine_logger.warning(f"[RegionCaptureWidget] save failed: {path}")
|
|
164
|
+
return None
|
|
165
|
+
front_engine_logger.info(f"[RegionCaptureWidget] saved | {path}")
|
|
166
|
+
return str(path)
|
|
167
|
+
|
|
168
|
+
# --- painting --------------------------------------------------------
|
|
169
|
+
def draw_content(self, painter: QPainter) -> None:
|
|
170
|
+
rect = self.selection()
|
|
171
|
+
painter.fillRect(self.rect(), _DIM_COLOR)
|
|
172
|
+
if rect is None or rect.isEmpty():
|
|
173
|
+
return
|
|
174
|
+
# 框內回復原本亮度,框線標出範圍
|
|
175
|
+
# Clear the selection back to full brightness and outline it.
|
|
176
|
+
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Clear)
|
|
177
|
+
painter.fillRect(rect, Qt.GlobalColor.transparent)
|
|
178
|
+
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
|
|
179
|
+
painter.setPen(QPen(QColor(_EDGE_COLOR), 2))
|
|
180
|
+
painter.drawRect(rect)
|