frontengine 1.0.36__tar.gz → 1.0.38__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.36 → frontengine-1.0.38}/PKG-INFO +26 -1
- {frontengine-1.0.36 → frontengine-1.0.38}/README.md +25 -0
- frontengine-1.0.38/frontengine/show/screen_care/color_vision_widget.py +147 -0
- frontengine-1.0.38/frontengine/ui/dialog/clipboard_dialog.py +147 -0
- frontengine-1.0.38/frontengine/ui/dialog/usage_report_dialog.py +120 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/main_ui.py +58 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/settings_menu.py +35 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +78 -5
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/tools/tools_setting_ui.py +61 -2
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/user_setting/user_setting_file.py +12 -0
- frontengine-1.0.38/frontengine/utils/clipboard/clipboard_history.py +179 -0
- frontengine-1.0.38/frontengine/utils/clipboard/clipboard_watcher.py +75 -0
- frontengine-1.0.38/frontengine/utils/color_vision/color_vision.py +113 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/english.py +31 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/traditional_chinese.py +31 -0
- frontengine-1.0.38/frontengine/utils/usage_tracking/usage_service.py +85 -0
- frontengine-1.0.38/frontengine/utils/usage_tracking/usage_tracker.py +218 -0
- frontengine-1.0.38/frontengine/utils/window_pin/__init__.py +0 -0
- frontengine-1.0.38/frontengine/utils/window_pin/window_layout.py +135 -0
- frontengine-1.0.38/frontengine/utils/workshop/__init__.py +0 -0
- frontengine-1.0.38/frontengine/worker/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/PKG-INFO +26 -1
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/SOURCES.txt +12 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/pyproject.toml +1 -1
- {frontengine-1.0.36 → frontengine-1.0.38}/LICENSE +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/__main__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/base_widget.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/camera/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/camera/camera_widget.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/capture/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/capture/region_capture.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/focus_shield/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/focus_shield/focus_shield_widget.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/gif/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/gif/paint_gif.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/image/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/image/paint_image.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/load/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/load/load_someone_make_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/measure/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/measure/measure_widget.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/monitor/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/monitor/monitor_widget.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/notes/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/notes/sticky_note_widget.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/overlay_factory.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/particle/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/particle/particle_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/pet/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/pet/desktop_pet.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/presentation/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/presentation/annotation_overlay.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/presentation/cursor_effects.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/presentation/keystroke_display.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/presentation/magnifier.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/scene/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/scene/extend_graphic_scene.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/scene/extend_graphic_view.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/scene/scene.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/screen_care/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/screen_care/screen_filter.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/sound_player/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/sound_player/sound_effect.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/sound_player/sound_player.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/spectrum/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/spectrum/spectrum_widget.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/text/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/text/draw_text.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/toast/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/toast/toast_widget.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/video/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/video/video_player.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/wallpaper/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/wallpaper/wallpaper_widget.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/web/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/web/webview.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/show/window_helpers.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/system_tray/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/system_tray/extend_system_tray.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/color/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/color/global_color.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/app_profile_dialog.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/reminder_dialog.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/save_file_dialog.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/smart_pause_dialog.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/dialog/window_pin_dialog.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/help_menu.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/how_to_menu.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/language_menu.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/menu/preset_menu.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/control_center/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/control_center/control_center_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/focus/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/focus/focus_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/gif/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/image/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/image/image_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/particle/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/pet/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/presentation/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/presentation/presentation_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/screen_care/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/sound_player/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/text/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/text/text_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/tools/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/utils.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/video/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/video/video_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/wallpaper/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/web/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/web/web_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/widgets/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/ui/page/widgets/widgets_setting_ui.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/user_setting/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/user_setting/preset_repository.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/user_setting/scene_setting.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/app_profile/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/app_profile/app_profile_service.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/loopback_capture.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/screen_audio.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/spectrum_analyzer.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/audio_meter/system_audio_meter.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/autostart/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/autostart/autostart_service.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/break_reminder/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/break_reminder/break_reminder.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/browser/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/browser/browser.py +0 -0
- {frontengine-1.0.36/frontengine/utils/critical_exit → frontengine-1.0.38/frontengine/utils/clipboard}/__init__.py +0 -0
- {frontengine-1.0.36/frontengine/utils/file → frontengine-1.0.38/frontengine/utils/color_vision}/__init__.py +0 -0
- {frontengine-1.0.36/frontengine/utils/focus_shield → frontengine-1.0.38/frontengine/utils/critical_exit}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/critical_exit/critical_exit.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/critical_exit/win32_vk.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/exception/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/exception/exception_tags.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/exception/exceptions.py +0 -0
- {frontengine-1.0.36/frontengine/utils/focus_timer → frontengine-1.0.38/frontengine/utils/file}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/file/open/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/file/open/open_file.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/file/save/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/file/save/save_file.py +0 -0
- {frontengine-1.0.36/frontengine/utils/hotkey → frontengine-1.0.38/frontengine/utils/focus_shield}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/focus_shield/focus_shield.py +0 -0
- {frontengine-1.0.36/frontengine/utils/input_watch → frontengine-1.0.38/frontengine/utils/focus_timer}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/focus_timer/focus_timer.py +0 -0
- {frontengine-1.0.36/frontengine/utils/json → frontengine-1.0.38/frontengine/utils/hotkey}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/hotkey/hotkey_service.py +0 -0
- {frontengine-1.0.36/frontengine/utils/logging → frontengine-1.0.38/frontengine/utils/input_watch}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/input_watch/input_watch_service.py +0 -0
- {frontengine-1.0.36/frontengine/utils/measure → frontengine-1.0.38/frontengine/utils/json}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/json/json_file.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/json/json_repository.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/json_format/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/json_format/json_process.py +0 -0
- {frontengine-1.0.36/frontengine/utils/multi_language → frontengine-1.0.38/frontengine/utils/logging}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/logging/loggin_instance.py +0 -0
- {frontengine-1.0.36/frontengine/utils/now_playing → frontengine-1.0.38/frontengine/utils/measure}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/measure/measure.py +0 -0
- {frontengine-1.0.36/frontengine/utils/pet_chat → frontengine-1.0.38/frontengine/utils/multi_language}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/france.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/germany.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/italy.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/language_wrapper.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/russian.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
- {frontengine-1.0.36/frontengine/utils/platform_info → frontengine-1.0.38/frontengine/utils/now_playing}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/now_playing/now_playing.py +0 -0
- {frontengine-1.0.36/frontengine/utils/playlist → frontengine-1.0.38/frontengine/utils/pet_chat}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
- {frontengine-1.0.36/frontengine/utils/plugins → frontengine-1.0.38/frontengine/utils/platform_info}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/platform_info/platform_info.py +0 -0
- {frontengine-1.0.36/frontengine/utils/power_mode → frontengine-1.0.38/frontengine/utils/playlist}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/playlist/playlist.py +0 -0
- {frontengine-1.0.36/frontengine/utils/preset_schedule → frontengine-1.0.38/frontengine/utils/plugins}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/plugins/plugin_loader.py +0 -0
- {frontengine-1.0.36/frontengine/utils/redirect_manager → frontengine-1.0.38/frontengine/utils/power_mode}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/power_mode/power_mode.py +0 -0
- {frontengine-1.0.36/frontengine/utils/reminder → frontengine-1.0.38/frontengine/utils/preset_schedule}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
- {frontengine-1.0.36/frontengine/utils/smart_pause → frontengine-1.0.38/frontengine/utils/redirect_manager}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
- {frontengine-1.0.36/frontengine/utils/system_stats → frontengine-1.0.38/frontengine/utils/reminder}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/reminder/reminder_service.py +0 -0
- {frontengine-1.0.36/frontengine/utils/text_source → frontengine-1.0.38/frontengine/utils/smart_pause}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/smart_pause/pause_rules.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
- {frontengine-1.0.36/frontengine/utils/theme_schedule → frontengine-1.0.38/frontengine/utils/system_stats}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/system_stats/system_stats.py +0 -0
- {frontengine-1.0.36/frontengine/utils/weather → frontengine-1.0.38/frontengine/utils/text_source}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/text_source/text_source.py +0 -0
- {frontengine-1.0.36/frontengine/utils/window_pin → frontengine-1.0.38/frontengine/utils/theme_schedule}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
- {frontengine-1.0.36/frontengine/utils/workshop → frontengine-1.0.38/frontengine/utils/usage_tracking}/__init__.py +0 -0
- {frontengine-1.0.36/frontengine/worker → frontengine-1.0.38/frontengine/utils/weather}/__init__.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/weather/weather_service.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/web_url.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/window_pin/window_pin.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/utils/workshop/workshop_content.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine/worker/qthread_worker.py +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/dependency_links.txt +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/entry_points.txt +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/requires.txt +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/frontengine.egg-info/top_level.txt +0 -0
- {frontengine-1.0.36 → frontengine-1.0.38}/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.38
|
|
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
|
|
@@ -166,6 +166,31 @@ that caps every overlay's refresh rate and drops its render resolution.
|
|
|
166
166
|
|
|
167
167
|
---
|
|
168
168
|
|
|
169
|
+
## Screen time, clipboard and layouts
|
|
170
|
+
|
|
171
|
+
Three things that remember, all of them **off until you switch them on** and
|
|
172
|
+
all of them **local — nothing here is ever sent anywhere**:
|
|
173
|
+
|
|
174
|
+
- **Screen time** (Settings → Screen time) — which apps had focus and for how
|
|
175
|
+
long, with a daily breakdown and a seven-day summary. It pauses while you are
|
|
176
|
+
away from the keyboard, keeps 60 days at most, and **clearing deletes the
|
|
177
|
+
file itself**. While it is off, no file is created at all.
|
|
178
|
+
- **Clipboard history** (Settings → Clipboard history) — search what you copied
|
|
179
|
+
and pin the phrases you reuse. Clipboards routinely hold passwords, so this
|
|
180
|
+
is kept **in memory only** unless you separately tick "keep between
|
|
181
|
+
sessions".
|
|
182
|
+
- **Window layouts** (Tools tab) — save where every window sits and put them
|
|
183
|
+
back later. Windows are matched by title, and one that is not on screen is
|
|
184
|
+
skipped rather than guessed at. Windows only.
|
|
185
|
+
|
|
186
|
+
The **Screen care** tab also gains **colour-vision simulation** — protanopia,
|
|
187
|
+
deuteranopia, tritanopia and achromatopsia at an adjustable severity, using the
|
|
188
|
+
Machado et al. (2009) model. Unlike the other overlays it is opaque, because
|
|
189
|
+
showing what someone else sees means repainting the screen rather than tinting
|
|
190
|
+
it.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
169
194
|
## Community content
|
|
170
195
|
|
|
171
196
|
- **Steam Workshop** — subscribed items are picked up from Steam's own
|
|
@@ -142,6 +142,31 @@ that caps every overlay's refresh rate and drops its render resolution.
|
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
|
|
145
|
+
## Screen time, clipboard and layouts
|
|
146
|
+
|
|
147
|
+
Three things that remember, all of them **off until you switch them on** and
|
|
148
|
+
all of them **local — nothing here is ever sent anywhere**:
|
|
149
|
+
|
|
150
|
+
- **Screen time** (Settings → Screen time) — which apps had focus and for how
|
|
151
|
+
long, with a daily breakdown and a seven-day summary. It pauses while you are
|
|
152
|
+
away from the keyboard, keeps 60 days at most, and **clearing deletes the
|
|
153
|
+
file itself**. While it is off, no file is created at all.
|
|
154
|
+
- **Clipboard history** (Settings → Clipboard history) — search what you copied
|
|
155
|
+
and pin the phrases you reuse. Clipboards routinely hold passwords, so this
|
|
156
|
+
is kept **in memory only** unless you separately tick "keep between
|
|
157
|
+
sessions".
|
|
158
|
+
- **Window layouts** (Tools tab) — save where every window sits and put them
|
|
159
|
+
back later. Windows are matched by title, and one that is not on screen is
|
|
160
|
+
skipped rather than guessed at. Windows only.
|
|
161
|
+
|
|
162
|
+
The **Screen care** tab also gains **colour-vision simulation** — protanopia,
|
|
163
|
+
deuteranopia, tritanopia and achromatopsia at an adjustable severity, using the
|
|
164
|
+
Machado et al. (2009) model. Unlike the other overlays it is opaque, because
|
|
165
|
+
showing what someone else sees means repainting the screen rather than tinting
|
|
166
|
+
it.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
145
170
|
## Community content
|
|
146
171
|
|
|
147
172
|
- **Steam Workshop** — subscribed items are picked up from Steam's own
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
色覺模擬覆蓋層:抓一張畫面、套上色覺缺陷的轉換、再整片蓋回去。
|
|
3
|
+
|
|
4
|
+
這一層和其他覆蓋層不同:它必須是不透明的,因為「模擬看不清楚的顏色」沒辦法
|
|
5
|
+
用半透明疊色做到——得真的把底下的畫面重畫一次。畫面只在記憶體裡轉換,不存檔。
|
|
6
|
+
|
|
7
|
+
A colour-vision overlay: grab the screen, apply the deficiency transform, and
|
|
8
|
+
paint the result back over everything.
|
|
9
|
+
|
|
10
|
+
It is opaque, unlike the other overlays: simulating a colour deficiency cannot
|
|
11
|
+
be done by tinting, it needs the picture underneath redrawn. The grab is
|
|
12
|
+
transformed in memory and never saved.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
import numpy
|
|
19
|
+
from PySide6.QtCore import Qt, QTimer
|
|
20
|
+
from PySide6.QtGui import QGuiApplication, QImage, QPainter, QPixmap
|
|
21
|
+
|
|
22
|
+
from frontengine.show.base_widget import BaseWidget
|
|
23
|
+
from frontengine.utils.color_vision.color_vision import (
|
|
24
|
+
KIND_DEUTERANOPIA, clamp_severity, normalize_kind, simulate_array,
|
|
25
|
+
)
|
|
26
|
+
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
27
|
+
from frontengine.utils.power_mode.power_mode import tier_interval
|
|
28
|
+
|
|
29
|
+
# 色覺模擬不需要很高的更新率,五分之一秒足夠,也省下大量重繪
|
|
30
|
+
# The simulation does not need a high refresh rate; five per second is plenty
|
|
31
|
+
# and saves a great deal of redrawing.
|
|
32
|
+
REFRESH_INTERVAL_MS = 200
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def image_to_array(image: QImage) -> Optional[numpy.ndarray]:
|
|
36
|
+
"""
|
|
37
|
+
QImage -> H x W x 3 的 uint8 陣列。每列可能有補齊位元組,所以要照
|
|
38
|
+
bytesPerLine 切,不能直接 reshape。
|
|
39
|
+
QImage to an H x W x 3 uint8 array. Rows can be padded, so slice by
|
|
40
|
+
bytesPerLine rather than reshaping the raw buffer.
|
|
41
|
+
"""
|
|
42
|
+
if image is None or image.isNull():
|
|
43
|
+
return None
|
|
44
|
+
converted = image.convertToFormat(QImage.Format.Format_RGB888)
|
|
45
|
+
width, height, stride = converted.width(), converted.height(), converted.bytesPerLine()
|
|
46
|
+
if width <= 0 or height <= 0:
|
|
47
|
+
return None
|
|
48
|
+
buffer = numpy.frombuffer(converted.constBits(), dtype=numpy.uint8, count=stride * height)
|
|
49
|
+
return buffer.reshape((height, stride))[:, : width * 3].reshape(
|
|
50
|
+
(height, width, 3)).copy()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def array_to_image(pixels: numpy.ndarray) -> Optional[QImage]:
|
|
54
|
+
"""H x W x 3 的 uint8 陣列 -> QImage(複製一份,不依賴呼叫端的緩衝區)。"""
|
|
55
|
+
data = numpy.ascontiguousarray(pixels, dtype=numpy.uint8)
|
|
56
|
+
if data.ndim != 3 or data.shape[2] != 3:
|
|
57
|
+
return None
|
|
58
|
+
height, width, _channels = data.shape
|
|
59
|
+
image = QImage(data.tobytes(), width, height, width * 3, QImage.Format.Format_RGB888)
|
|
60
|
+
return image.copy()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ColorVisionWidget(BaseWidget):
|
|
64
|
+
"""
|
|
65
|
+
色覺模擬層。擷取函式可注入,所以不需要真的螢幕也測得到轉換流程。
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, kind: str = KIND_DEUTERANOPIA, severity: float = 1.0) -> None:
|
|
69
|
+
front_engine_logger.info(f"[ColorVisionWidget] Init | kind={kind}")
|
|
70
|
+
super().__init__()
|
|
71
|
+
self.kind = normalize_kind(kind)
|
|
72
|
+
self.severity = clamp_severity(severity)
|
|
73
|
+
self.opacity = 1.0
|
|
74
|
+
self.simulated: Optional[QPixmap] = None
|
|
75
|
+
self._grabber = self._default_grabber
|
|
76
|
+
self._timer = QTimer(self)
|
|
77
|
+
self._timer.timeout.connect(self.refresh)
|
|
78
|
+
# 這一層是不透明的:它要蓋掉底下的畫面,而不是疊在上面
|
|
79
|
+
# This layer is opaque - it replaces what is underneath rather than
|
|
80
|
+
# tinting it.
|
|
81
|
+
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False)
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _default_grabber(rect): # pragma: no cover - needs a real screen
|
|
85
|
+
screen = QGuiApplication.primaryScreen()
|
|
86
|
+
if screen is None:
|
|
87
|
+
return None
|
|
88
|
+
return screen.grabWindow(0, rect.x(), rect.y(), rect.width(), rect.height())
|
|
89
|
+
|
|
90
|
+
def set_grabber(self, grabber) -> None:
|
|
91
|
+
"""設定螢幕擷取函式(測試會換成假的)。"""
|
|
92
|
+
self._grabber = grabber or self._default_grabber
|
|
93
|
+
|
|
94
|
+
def set_simulation(self, kind: Optional[str] = None,
|
|
95
|
+
severity: Optional[float] = None) -> None:
|
|
96
|
+
"""換一種色覺類型或嚴重程度,立刻重畫。"""
|
|
97
|
+
if kind is not None:
|
|
98
|
+
self.kind = normalize_kind(kind)
|
|
99
|
+
if severity is not None:
|
|
100
|
+
self.severity = clamp_severity(severity)
|
|
101
|
+
self.refresh()
|
|
102
|
+
|
|
103
|
+
def start(self) -> None:
|
|
104
|
+
self.refresh()
|
|
105
|
+
self._timer.start(tier_interval(REFRESH_INTERVAL_MS, self.quality_tier))
|
|
106
|
+
|
|
107
|
+
def stop(self) -> None:
|
|
108
|
+
self._timer.stop()
|
|
109
|
+
|
|
110
|
+
def apply_quality_tier(self) -> None:
|
|
111
|
+
if self._timer.isActive():
|
|
112
|
+
self._timer.start(tier_interval(REFRESH_INTERVAL_MS, self.quality_tier))
|
|
113
|
+
|
|
114
|
+
def refresh(self) -> None:
|
|
115
|
+
"""抓一次畫面、轉換、準備重畫。"""
|
|
116
|
+
geometry = self.geometry()
|
|
117
|
+
try:
|
|
118
|
+
# 擷取時先把自己藏起來,否則會拍到上一次的模擬結果,越疊越糊
|
|
119
|
+
# Hide first, or the grab catches the previous simulation and the
|
|
120
|
+
# picture degrades with every pass.
|
|
121
|
+
was_visible = self.isVisible()
|
|
122
|
+
if was_visible:
|
|
123
|
+
self.setVisible(False)
|
|
124
|
+
pixmap = self._grabber(geometry)
|
|
125
|
+
if was_visible:
|
|
126
|
+
self.setVisible(True)
|
|
127
|
+
except Exception as error: # pragma: no cover - screen grab boundary
|
|
128
|
+
front_engine_logger.warning(f"[ColorVisionWidget] grab failed: {error!r}")
|
|
129
|
+
return
|
|
130
|
+
if pixmap is None or pixmap.isNull():
|
|
131
|
+
return
|
|
132
|
+
pixels = image_to_array(pixmap.toImage())
|
|
133
|
+
if pixels is None:
|
|
134
|
+
return
|
|
135
|
+
image = array_to_image(simulate_array(pixels, self.kind, self.severity))
|
|
136
|
+
if image is not None:
|
|
137
|
+
self.simulated = QPixmap.fromImage(image)
|
|
138
|
+
self.update()
|
|
139
|
+
|
|
140
|
+
def draw_content(self, painter: QPainter) -> None:
|
|
141
|
+
if self.simulated is None or self.simulated.isNull():
|
|
142
|
+
return
|
|
143
|
+
painter.drawPixmap(self.rect(), self.simulated)
|
|
144
|
+
|
|
145
|
+
def closeEvent(self, event) -> None:
|
|
146
|
+
self.stop()
|
|
147
|
+
super().closeEvent(event)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
剪貼簿歷史視窗:搜尋、釘選、貼回去。
|
|
3
|
+
|
|
4
|
+
預設不記錄;要記錄得自己在這裡打開。除非再額外勾選「保存到檔案」,否則
|
|
5
|
+
內容只留在記憶體,關掉程式就沒了——剪貼簿裡常有密碼,這是刻意的預設。
|
|
6
|
+
|
|
7
|
+
The clipboard history window: search, pin, put back.
|
|
8
|
+
|
|
9
|
+
Recording is off until switched on here, and unless "keep between sessions" is
|
|
10
|
+
also ticked the history lives in memory only and is gone when the app closes.
|
|
11
|
+
Clipboards routinely hold passwords; that default is deliberate.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from PySide6.QtCore import Qt
|
|
18
|
+
from PySide6.QtGui import QGuiApplication
|
|
19
|
+
from PySide6.QtWidgets import (
|
|
20
|
+
QAbstractItemView, QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QLabel, QLineEdit,
|
|
21
|
+
QListWidget, QListWidgetItem, QPushButton, QWidget,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from frontengine.user_setting.user_setting_file import user_setting_dict, write_user_setting
|
|
25
|
+
from frontengine.utils.clipboard.clipboard_history import ClipboardHistory, preview
|
|
26
|
+
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
27
|
+
from frontengine.utils.multi_language.language_wrapper import language_wrapper
|
|
28
|
+
|
|
29
|
+
_PIN_MARK = "📌 "
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _t(key: str, fallback: str) -> str:
|
|
33
|
+
return language_wrapper.language_word_dict.get(key, fallback)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ClipboardDialog(QDialog):
|
|
37
|
+
"""瀏覽剪貼簿歷史並貼回去。"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, history: ClipboardHistory, parent: Optional[QWidget] = None) -> None:
|
|
40
|
+
front_engine_logger.info("[ClipboardDialog] Init")
|
|
41
|
+
super().__init__(parent)
|
|
42
|
+
self.setWindowTitle(_t("clipboard_title", "Clipboard history"))
|
|
43
|
+
self.history = history
|
|
44
|
+
|
|
45
|
+
self.search_edit = QLineEdit()
|
|
46
|
+
self.search_edit.setPlaceholderText(_t("clipboard_search", "Search..."))
|
|
47
|
+
self.search_edit.textChanged.connect(self.reload_entries)
|
|
48
|
+
|
|
49
|
+
self.entry_list = QListWidget()
|
|
50
|
+
self.entry_list.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
|
51
|
+
self.entry_list.itemDoubleClicked.connect(lambda _item: self.copy_selected())
|
|
52
|
+
|
|
53
|
+
self.copy_button = QPushButton(_t("clipboard_copy", "Copy"))
|
|
54
|
+
self.copy_button.clicked.connect(self.copy_selected)
|
|
55
|
+
self.pin_button = QPushButton(_t("clipboard_pin", "Pin / unpin"))
|
|
56
|
+
self.pin_button.clicked.connect(self.toggle_pin_selected)
|
|
57
|
+
self.remove_button = QPushButton(_t("clipboard_remove", "Remove"))
|
|
58
|
+
self.remove_button.clicked.connect(self.remove_selected)
|
|
59
|
+
self.clear_button = QPushButton(_t("clipboard_clear", "Clear (keep pinned)"))
|
|
60
|
+
self.clear_button.clicked.connect(self.clear_history)
|
|
61
|
+
|
|
62
|
+
self.persist_checkbox = QCheckBox(
|
|
63
|
+
_t("clipboard_persist", "Keep this history between sessions"))
|
|
64
|
+
self.persist_checkbox.setChecked(bool(user_setting_dict.get("clipboard_persist")))
|
|
65
|
+
self.persist_checkbox.toggled.connect(self._store_persist)
|
|
66
|
+
self.hint_label = QLabel(
|
|
67
|
+
_t("clipboard_hint",
|
|
68
|
+
"History stays on this machine and is never sent anywhere. Unless you tick "
|
|
69
|
+
"the box above it is kept in memory only, because clipboards often hold "
|
|
70
|
+
"passwords."))
|
|
71
|
+
self.hint_label.setWordWrap(True)
|
|
72
|
+
|
|
73
|
+
self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
|
|
74
|
+
self.button_box.rejected.connect(self.reject)
|
|
75
|
+
|
|
76
|
+
layout = QGridLayout(self)
|
|
77
|
+
layout.addWidget(self.search_edit, 0, 0, 1, 4)
|
|
78
|
+
layout.addWidget(self.entry_list, 1, 0, 1, 4)
|
|
79
|
+
layout.addWidget(self.copy_button, 2, 0)
|
|
80
|
+
layout.addWidget(self.pin_button, 2, 1)
|
|
81
|
+
layout.addWidget(self.remove_button, 2, 2)
|
|
82
|
+
layout.addWidget(self.clear_button, 2, 3)
|
|
83
|
+
layout.addWidget(self.persist_checkbox, 3, 0, 1, 4)
|
|
84
|
+
layout.addWidget(self.hint_label, 4, 0, 1, 4)
|
|
85
|
+
layout.addWidget(self.button_box, 5, 0, 1, 4)
|
|
86
|
+
|
|
87
|
+
self.reload_entries()
|
|
88
|
+
|
|
89
|
+
def reload_entries(self) -> int:
|
|
90
|
+
"""依搜尋字串重畫清單,回傳顯示的筆數。"""
|
|
91
|
+
self.entry_list.clear()
|
|
92
|
+
for entry in self.history.search(self.search_edit.text()):
|
|
93
|
+
label = (_PIN_MARK if entry["pinned"] else "") + preview(entry["text"])
|
|
94
|
+
item = QListWidgetItem(label)
|
|
95
|
+
item.setData(Qt.ItemDataRole.UserRole, entry["text"])
|
|
96
|
+
item.setToolTip(entry["text"])
|
|
97
|
+
self.entry_list.addItem(item)
|
|
98
|
+
return self.entry_list.count()
|
|
99
|
+
|
|
100
|
+
def selected_text(self) -> Optional[str]:
|
|
101
|
+
item = self.entry_list.currentItem()
|
|
102
|
+
if item is None:
|
|
103
|
+
return None
|
|
104
|
+
return str(item.data(Qt.ItemDataRole.UserRole))
|
|
105
|
+
|
|
106
|
+
def copy_selected(self) -> bool:
|
|
107
|
+
"""把選到的內容放回剪貼簿。"""
|
|
108
|
+
text = self.selected_text()
|
|
109
|
+
if text is None:
|
|
110
|
+
return False
|
|
111
|
+
clipboard = QGuiApplication.clipboard()
|
|
112
|
+
if clipboard is None: # pragma: no cover - no clipboard at all
|
|
113
|
+
return False
|
|
114
|
+
clipboard.setText(text)
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
def toggle_pin_selected(self) -> bool:
|
|
118
|
+
text = self.selected_text()
|
|
119
|
+
if text is None:
|
|
120
|
+
return False
|
|
121
|
+
current = next((entry for entry in self.history.entries if entry["text"] == text), None)
|
|
122
|
+
if current is None:
|
|
123
|
+
return False
|
|
124
|
+
self.history.set_pinned(text, not current["pinned"])
|
|
125
|
+
self.reload_entries()
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
def remove_selected(self) -> bool:
|
|
129
|
+
text = self.selected_text()
|
|
130
|
+
if text is None:
|
|
131
|
+
return False
|
|
132
|
+
removed = self.history.remove(text)
|
|
133
|
+
self.reload_entries()
|
|
134
|
+
return removed
|
|
135
|
+
|
|
136
|
+
def clear_history(self) -> None:
|
|
137
|
+
"""清空未釘選的紀錄。"""
|
|
138
|
+
self.history.clear(keep_pinned=True)
|
|
139
|
+
self.reload_entries()
|
|
140
|
+
|
|
141
|
+
def _store_persist(self, enabled: bool) -> None:
|
|
142
|
+
user_setting_dict["clipboard_persist"] = bool(enabled)
|
|
143
|
+
if not enabled:
|
|
144
|
+
# 取消保存時把已經寫下去的內容一併移除,不留殘影
|
|
145
|
+
# Turning saving off also clears what was already written down.
|
|
146
|
+
user_setting_dict["clipboard_entries"] = []
|
|
147
|
+
write_user_setting()
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
使用時間報告:今天各程式用了多久,以及最近七天的總量。
|
|
3
|
+
|
|
4
|
+
這份資料只存在本機的 frontengine_usage.json,不會傳到任何地方;統計預設關閉,
|
|
5
|
+
而且這裡就有「清除」按鈕,按下去連檔案一起刪掉。
|
|
6
|
+
|
|
7
|
+
The usage report: how long each app had focus today, plus the last seven days.
|
|
8
|
+
|
|
9
|
+
The data lives only in the local frontengine_usage.json and goes nowhere else.
|
|
10
|
+
Tracking is off by default, and the clear button here deletes the file itself.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from PySide6.QtWidgets import (
|
|
17
|
+
QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QHeaderView, QLabel, QPushButton,
|
|
18
|
+
QTableWidget, QTableWidgetItem, QWidget,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
22
|
+
from frontengine.utils.multi_language.language_wrapper import language_wrapper
|
|
23
|
+
from frontengine.utils.usage_tracking.usage_tracker import UsageTracker, format_duration
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _t(key: str, fallback: str) -> str:
|
|
27
|
+
return language_wrapper.language_word_dict.get(key, fallback)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def share_percent(seconds: float, total: float) -> int:
|
|
31
|
+
"""某個程式佔今天的百分比;今天還沒有紀錄就是 0。"""
|
|
32
|
+
if total <= 0:
|
|
33
|
+
return 0
|
|
34
|
+
return int(round(max(0.0, min(1.0, seconds / total)) * 100))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class UsageReportDialog(QDialog):
|
|
38
|
+
"""顯示今天的使用時間,並提供開關與清除。"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, tracker: UsageTracker, parent: Optional[QWidget] = None,
|
|
41
|
+
enabled: bool = False, on_toggle=None, on_clear=None) -> None:
|
|
42
|
+
front_engine_logger.info("[UsageReportDialog] Init")
|
|
43
|
+
super().__init__(parent)
|
|
44
|
+
self.setWindowTitle(_t("usage_title", "Screen time"))
|
|
45
|
+
self.tracker = tracker
|
|
46
|
+
self._on_toggle = on_toggle
|
|
47
|
+
self._on_clear = on_clear
|
|
48
|
+
|
|
49
|
+
self.enable_checkbox = QCheckBox(_t("usage_enable", "Track which apps I use"))
|
|
50
|
+
self.enable_checkbox.setChecked(bool(enabled))
|
|
51
|
+
self.enable_checkbox.toggled.connect(self._toggle)
|
|
52
|
+
|
|
53
|
+
self.total_label = QLabel()
|
|
54
|
+
self.table = QTableWidget(0, 3)
|
|
55
|
+
self.table.setHorizontalHeaderLabels([
|
|
56
|
+
_t("usage_app_column", "Application"),
|
|
57
|
+
_t("usage_time_column", "Time"),
|
|
58
|
+
_t("usage_share_column", "Share"),
|
|
59
|
+
])
|
|
60
|
+
self.table.horizontalHeader().setSectionResizeMode(
|
|
61
|
+
0, QHeaderView.ResizeMode.Stretch)
|
|
62
|
+
self.table.verticalHeader().setVisible(False)
|
|
63
|
+
|
|
64
|
+
self.week_label = QLabel()
|
|
65
|
+
self.week_label.setWordWrap(True)
|
|
66
|
+
self.refresh_button = QPushButton(_t("usage_refresh", "Refresh"))
|
|
67
|
+
self.refresh_button.clicked.connect(self.reload_report)
|
|
68
|
+
self.clear_button = QPushButton(_t("usage_clear", "Clear history"))
|
|
69
|
+
self.clear_button.clicked.connect(self.clear_history)
|
|
70
|
+
self.hint_label = QLabel(
|
|
71
|
+
_t("usage_hint",
|
|
72
|
+
"This is stored on this machine only and is never sent anywhere. "
|
|
73
|
+
"Clearing deletes the file itself."))
|
|
74
|
+
self.hint_label.setWordWrap(True)
|
|
75
|
+
|
|
76
|
+
self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
|
|
77
|
+
self.button_box.rejected.connect(self.reject)
|
|
78
|
+
|
|
79
|
+
layout = QGridLayout(self)
|
|
80
|
+
layout.addWidget(self.enable_checkbox, 0, 0, 1, 2)
|
|
81
|
+
layout.addWidget(self.total_label, 1, 0, 1, 2)
|
|
82
|
+
layout.addWidget(self.table, 2, 0, 1, 2)
|
|
83
|
+
layout.addWidget(self.week_label, 3, 0, 1, 2)
|
|
84
|
+
layout.addWidget(self.refresh_button, 4, 0)
|
|
85
|
+
layout.addWidget(self.clear_button, 4, 1)
|
|
86
|
+
layout.addWidget(self.hint_label, 5, 0, 1, 2)
|
|
87
|
+
layout.addWidget(self.button_box, 6, 0, 1, 2)
|
|
88
|
+
|
|
89
|
+
self.reload_report()
|
|
90
|
+
|
|
91
|
+
def reload_report(self) -> int:
|
|
92
|
+
"""重畫今天的排行,回傳列數。"""
|
|
93
|
+
total = self.tracker.total_seconds()
|
|
94
|
+
self.total_label.setText(
|
|
95
|
+
_t("usage_total", "Today: {total}").format(total=format_duration(total)))
|
|
96
|
+
rows = self.tracker.top_apps(limit=12)
|
|
97
|
+
self.table.setRowCount(len(rows))
|
|
98
|
+
for index, (app, seconds) in enumerate(rows):
|
|
99
|
+
self.table.setItem(index, 0, QTableWidgetItem(app))
|
|
100
|
+
self.table.setItem(index, 1, QTableWidgetItem(format_duration(seconds)))
|
|
101
|
+
self.table.setItem(index, 2, QTableWidgetItem(f"{share_percent(seconds, total)}%"))
|
|
102
|
+
self.week_label.setText(self.week_summary())
|
|
103
|
+
return len(rows)
|
|
104
|
+
|
|
105
|
+
def week_summary(self) -> str:
|
|
106
|
+
"""最近七天的一行摘要。"""
|
|
107
|
+
parts = [f"{day[5:]} {format_duration(seconds)}"
|
|
108
|
+
for day, seconds in self.tracker.recent_days(7)]
|
|
109
|
+
return " · ".join(parts)
|
|
110
|
+
|
|
111
|
+
def clear_history(self) -> None:
|
|
112
|
+
"""清掉所有統計(含磁碟上的檔案)。"""
|
|
113
|
+
self.tracker.clear()
|
|
114
|
+
if self._on_clear is not None:
|
|
115
|
+
self._on_clear()
|
|
116
|
+
self.reload_report()
|
|
117
|
+
|
|
118
|
+
def _toggle(self, enabled: bool) -> None:
|
|
119
|
+
if self._on_toggle is not None:
|
|
120
|
+
self._on_toggle(bool(enabled))
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
+
from os import getcwd
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Dict, Optional, Type
|
|
5
6
|
|
|
@@ -49,6 +50,11 @@ from frontengine.utils.plugins.plugin_loader import load_plugins
|
|
|
49
50
|
from frontengine.utils.preset_schedule.preset_schedule_service import PresetScheduleService
|
|
50
51
|
from frontengine.ui.dialog.smart_pause_dialog import current_rules
|
|
51
52
|
from frontengine.utils.app_profile.app_profile_service import AppProfileService
|
|
53
|
+
from frontengine.utils.clipboard.clipboard_history import DEFAULT_LIMIT, ClipboardHistory
|
|
54
|
+
from frontengine.utils.clipboard.clipboard_watcher import ClipboardWatcher
|
|
55
|
+
from frontengine.utils.json.json_repository import JsonRepository
|
|
56
|
+
from frontengine.utils.usage_tracking.usage_service import UsageTrackingService
|
|
57
|
+
from frontengine.utils.usage_tracking.usage_tracker import USAGE_FILE, UsageTracker
|
|
52
58
|
from frontengine.utils.reminder.reminder_service import ReminderService
|
|
53
59
|
from frontengine.utils.smart_pause.smart_pause_service import SmartPauseService
|
|
54
60
|
from frontengine.utils.theme_schedule.theme_schedule_service import ThemeScheduleService
|
|
@@ -201,6 +207,23 @@ class FrontEngineMainUI(QMainWindow):
|
|
|
201
207
|
)
|
|
202
208
|
self.app_profile_service.start()
|
|
203
209
|
|
|
210
|
+
# 使用時間統計:預設關閉,只寫本機檔案,使用者可隨時清除
|
|
211
|
+
# Usage tracking: off by default, local file only, clearable at any time.
|
|
212
|
+
self.usage_tracker = UsageTracker(JsonRepository(Path(getcwd()) / USAGE_FILE))
|
|
213
|
+
self.usage_service = UsageTrackingService(self.usage_tracker)
|
|
214
|
+
if user_setting_dict.get("usage_tracking"):
|
|
215
|
+
self.usage_service.start()
|
|
216
|
+
|
|
217
|
+
# 剪貼簿歷史:預設關閉;未勾選保存時只留在記憶體
|
|
218
|
+
# Clipboard history: off by default, memory only unless saving is on.
|
|
219
|
+
self.clipboard_history = ClipboardHistory(
|
|
220
|
+
limit=user_setting_dict.get("clipboard_limit", DEFAULT_LIMIT))
|
|
221
|
+
if user_setting_dict.get("clipboard_persist"):
|
|
222
|
+
self.clipboard_history.load(user_setting_dict.get("clipboard_entries"))
|
|
223
|
+
self.clipboard_watcher = ClipboardWatcher(self.clipboard_history)
|
|
224
|
+
if user_setting_dict.get("clipboard_history"):
|
|
225
|
+
self.clipboard_watcher.start()
|
|
226
|
+
|
|
204
227
|
# 自訂提醒(喝水、久坐、每日鬧鐘),到期時以提示卡顯示
|
|
205
228
|
# Custom reminders - water, posture, a daily alarm - shown as a toast.
|
|
206
229
|
self.reminder_service = ReminderService(
|
|
@@ -436,6 +459,36 @@ class FrontEngineMainUI(QMainWindow):
|
|
|
436
459
|
front_engine_logger.info(f"[MainUI] reminder | {label}")
|
|
437
460
|
self._last_toast = show_toast(label)
|
|
438
461
|
|
|
462
|
+
def _persist_clipboard(self) -> None:
|
|
463
|
+
"""
|
|
464
|
+
只有使用者勾選「保存到檔案」時才把剪貼簿歷史寫下來;沒勾就確保
|
|
465
|
+
設定裡不留任何內容。
|
|
466
|
+
Write the clipboard history down only when the user asked for it; when
|
|
467
|
+
they did not, make sure nothing is left behind in the settings.
|
|
468
|
+
"""
|
|
469
|
+
if user_setting_dict.get("clipboard_persist"):
|
|
470
|
+
user_setting_dict["clipboard_entries"] = self.clipboard_history.to_list()
|
|
471
|
+
else:
|
|
472
|
+
user_setting_dict["clipboard_entries"] = []
|
|
473
|
+
|
|
474
|
+
def set_usage_tracking(self, enabled: bool) -> None:
|
|
475
|
+
"""開關使用時間統計並記住選擇。"""
|
|
476
|
+
user_setting_dict["usage_tracking"] = bool(enabled)
|
|
477
|
+
write_user_setting()
|
|
478
|
+
if enabled:
|
|
479
|
+
self.usage_service.start()
|
|
480
|
+
else:
|
|
481
|
+
self.usage_service.stop()
|
|
482
|
+
|
|
483
|
+
def set_clipboard_history(self, enabled: bool) -> None:
|
|
484
|
+
"""開關剪貼簿歷史紀錄並記住選擇。"""
|
|
485
|
+
user_setting_dict["clipboard_history"] = bool(enabled)
|
|
486
|
+
write_user_setting()
|
|
487
|
+
if enabled:
|
|
488
|
+
self.clipboard_watcher.start()
|
|
489
|
+
else:
|
|
490
|
+
self.clipboard_watcher.stop()
|
|
491
|
+
|
|
439
492
|
def refresh_rule_services(self) -> None:
|
|
440
493
|
"""
|
|
441
494
|
設定對話框按下確定後呼叫:規則與對照表都是每次輪詢重讀,所以只要把
|
|
@@ -454,6 +507,11 @@ class FrontEngineMainUI(QMainWindow):
|
|
|
454
507
|
self.preset_schedule_service.stop()
|
|
455
508
|
if getattr(self, "theme_schedule_service", None) is not None:
|
|
456
509
|
self.theme_schedule_service.stop()
|
|
510
|
+
if getattr(self, "usage_service", None) is not None:
|
|
511
|
+
self.usage_service.stop()
|
|
512
|
+
if getattr(self, "clipboard_watcher", None) is not None:
|
|
513
|
+
self.clipboard_watcher.stop()
|
|
514
|
+
self._persist_clipboard()
|
|
457
515
|
if getattr(self, "widgets_setting_ui", None) is not None:
|
|
458
516
|
self.widgets_setting_ui.save_notes()
|
|
459
517
|
self.widgets_setting_ui.stop_spectrum()
|
|
@@ -7,9 +7,11 @@ from PySide6.QtGui import QAction
|
|
|
7
7
|
from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox
|
|
8
8
|
|
|
9
9
|
from frontengine.ui.dialog.app_profile_dialog import AppProfileDialog
|
|
10
|
+
from frontengine.ui.dialog.clipboard_dialog import ClipboardDialog
|
|
10
11
|
from frontengine.ui.dialog.hotkey_settings_dialog import HotkeySettingsDialog
|
|
11
12
|
from frontengine.ui.dialog.reminder_dialog import ReminderDialog
|
|
12
13
|
from frontengine.ui.dialog.smart_pause_dialog import SmartPauseDialog
|
|
14
|
+
from frontengine.ui.dialog.usage_report_dialog import UsageReportDialog
|
|
13
15
|
from frontengine.user_setting.user_setting_file import (
|
|
14
16
|
export_user_setting,
|
|
15
17
|
import_user_setting,
|
|
@@ -85,6 +87,23 @@ def build_settings_menu(ui: "FrontEngineMainUI") -> None:
|
|
|
85
87
|
menu.addAction(reminder_action)
|
|
86
88
|
ui.reminder_action = reminder_action
|
|
87
89
|
|
|
90
|
+
usage_action = QAction(_t("settings_menu_usage", "Screen time..."), menu)
|
|
91
|
+
usage_action.triggered.connect(lambda: _open_usage_dialog(ui))
|
|
92
|
+
menu.addAction(usage_action)
|
|
93
|
+
ui.usage_action = usage_action
|
|
94
|
+
|
|
95
|
+
clipboard_action = QAction(_t("settings_menu_clipboard", "Clipboard history..."), menu)
|
|
96
|
+
clipboard_action.triggered.connect(lambda: _open_clipboard_dialog(ui))
|
|
97
|
+
menu.addAction(clipboard_action)
|
|
98
|
+
ui.clipboard_action = clipboard_action
|
|
99
|
+
|
|
100
|
+
clipboard_toggle = QAction(_t("settings_menu_clipboard_record", "Record clipboard"), menu)
|
|
101
|
+
clipboard_toggle.setCheckable(True)
|
|
102
|
+
clipboard_toggle.setChecked(bool(user_setting_dict.get("clipboard_history")))
|
|
103
|
+
clipboard_toggle.toggled.connect(lambda checked: ui.set_clipboard_history(checked))
|
|
104
|
+
menu.addAction(clipboard_toggle)
|
|
105
|
+
ui.clipboard_toggle_action = clipboard_toggle
|
|
106
|
+
|
|
88
107
|
menu.addSeparator()
|
|
89
108
|
export_action = QAction(_t("settings_menu_export", "Export settings..."), menu)
|
|
90
109
|
export_action.triggered.connect(lambda: _export_settings(ui))
|
|
@@ -95,6 +114,22 @@ def build_settings_menu(ui: "FrontEngineMainUI") -> None:
|
|
|
95
114
|
menu.addAction(import_action)
|
|
96
115
|
|
|
97
116
|
|
|
117
|
+
def _open_usage_dialog(ui: "FrontEngineMainUI") -> None:
|
|
118
|
+
"""開使用時間報告;開關與清除都由對話框直接作用在主視窗的服務上。"""
|
|
119
|
+
front_engine_logger.info("[SettingsMenu] open UsageReportDialog")
|
|
120
|
+
dialog = UsageReportDialog(
|
|
121
|
+
ui.usage_tracker, ui,
|
|
122
|
+
enabled=bool(user_setting_dict.get("usage_tracking")),
|
|
123
|
+
on_toggle=ui.set_usage_tracking)
|
|
124
|
+
dialog.exec()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _open_clipboard_dialog(ui: "FrontEngineMainUI") -> None:
|
|
128
|
+
"""開剪貼簿歷史。"""
|
|
129
|
+
front_engine_logger.info("[SettingsMenu] open ClipboardDialog")
|
|
130
|
+
ClipboardDialog(ui.clipboard_history, ui).exec()
|
|
131
|
+
|
|
132
|
+
|
|
98
133
|
def _open_dialog(ui: "FrontEngineMainUI", dialog_class) -> bool:
|
|
99
134
|
"""
|
|
100
135
|
開一個設定對話框並回傳使用者是否按下確定。三個規則類對話框都自己負責寫回
|