frontengine 1.0.33__tar.gz → 1.0.34__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.33 → frontengine-1.0.34}/PKG-INFO +45 -1
- {frontengine-1.0.33 → frontengine-1.0.34}/README.md +44 -0
- frontengine-1.0.34/frontengine/show/focus_shield/focus_shield_widget.py +137 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/pet/desktop_pet.py +0 -1
- frontengine-1.0.34/frontengine/show/wallpaper/wallpaper_widget.py +155 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/main_ui.py +15 -1
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/menu/preset_menu.py +64 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/menu/settings_menu.py +24 -0
- frontengine-1.0.34/frontengine/ui/page/focus/focus_setting_ui.py +211 -0
- frontengine-1.0.34/frontengine/ui/page/wallpaper/wallpaper_setting_ui.py +247 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/user_setting/user_setting_file.py +3 -0
- frontengine-1.0.34/frontengine/utils/focus_shield/focus_shield.py +93 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/english.py +34 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/traditional_chinese.py +34 -0
- frontengine-1.0.34/frontengine/utils/playlist/playlist.py +135 -0
- frontengine-1.0.34/frontengine/utils/plugins/plugin_loader.py +138 -0
- frontengine-1.0.34/frontengine/utils/redirect_manager/__init__.py +0 -0
- frontengine-1.0.34/frontengine/utils/smart_pause/__init__.py +0 -0
- frontengine-1.0.34/frontengine/utils/system_stats/__init__.py +0 -0
- frontengine-1.0.34/frontengine/utils/text_source/__init__.py +0 -0
- frontengine-1.0.34/frontengine/utils/theme_schedule/__init__.py +0 -0
- frontengine-1.0.34/frontengine/utils/weather/__init__.py +0 -0
- frontengine-1.0.34/frontengine/utils/workshop/__init__.py +0 -0
- frontengine-1.0.34/frontengine/utils/workshop/workshop_content.py +149 -0
- frontengine-1.0.34/frontengine/worker/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/PKG-INFO +45 -1
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/SOURCES.txt +16 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/pyproject.toml +1 -1
- {frontengine-1.0.33 → frontengine-1.0.34}/LICENSE +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/__main__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/base_widget.py +0 -0
- {frontengine-1.0.33/frontengine/show/gif → frontengine-1.0.34/frontengine/show/focus_shield}/__init__.py +0 -0
- {frontengine-1.0.33/frontengine/show/image → frontengine-1.0.34/frontengine/show/gif}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/gif/paint_gif.py +0 -0
- {frontengine-1.0.33/frontengine/show/load → frontengine-1.0.34/frontengine/show/image}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/image/paint_image.py +0 -0
- {frontengine-1.0.33/frontengine/show/particle → frontengine-1.0.34/frontengine/show/load}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/load/load_someone_make_ui.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/overlay_factory.py +0 -0
- {frontengine-1.0.33/frontengine/show/pet → frontengine-1.0.34/frontengine/show/particle}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/particle/particle_ui.py +0 -0
- {frontengine-1.0.33/frontengine/show/presentation → frontengine-1.0.34/frontengine/show/pet}/__init__.py +0 -0
- {frontengine-1.0.33/frontengine/show/scene → frontengine-1.0.34/frontengine/show/presentation}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/presentation/annotation_overlay.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/presentation/cursor_effects.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/presentation/keystroke_display.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/presentation/magnifier.py +0 -0
- {frontengine-1.0.33/frontengine/show/screen_care → frontengine-1.0.34/frontengine/show/scene}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/scene/extend_graphic_scene.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/scene/extend_graphic_view.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/scene/scene.py +0 -0
- {frontengine-1.0.33/frontengine/show/sound_player → frontengine-1.0.34/frontengine/show/screen_care}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/screen_care/screen_filter.py +0 -0
- {frontengine-1.0.33/frontengine/show/text → frontengine-1.0.34/frontengine/show/sound_player}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/sound_player/sound_effect.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/sound_player/sound_player.py +0 -0
- {frontengine-1.0.33/frontengine/show/video → frontengine-1.0.34/frontengine/show/text}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/text/draw_text.py +0 -0
- {frontengine-1.0.33/frontengine/show/web → frontengine-1.0.34/frontengine/show/video}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/video/video_player.py +0 -0
- {frontengine-1.0.33/frontengine/system_tray → frontengine-1.0.34/frontengine/show/wallpaper}/__init__.py +0 -0
- {frontengine-1.0.33/frontengine/ui → frontengine-1.0.34/frontengine/show/web}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/web/webview.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/show/window_helpers.py +0 -0
- {frontengine-1.0.33/frontengine/ui/color → frontengine-1.0.34/frontengine/system_tray}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/system_tray/extend_system_tray.py +0 -0
- {frontengine-1.0.33/frontengine/ui/dialog → frontengine-1.0.34/frontengine/ui}/__init__.py +0 -0
- {frontengine-1.0.33/frontengine/ui/menu → frontengine-1.0.34/frontengine/ui/color}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/color/global_color.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page → frontengine-1.0.34/frontengine/ui/dialog}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/dialog/choose_file_dialog.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/dialog/hotkey_settings_dialog.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/dialog/save_file_dialog.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/control_center → frontengine-1.0.34/frontengine/ui/menu}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/menu/help_menu.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/menu/how_to_menu.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/menu/language_menu.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/gif → frontengine-1.0.34/frontengine/ui/page}/__init__.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/image → frontengine-1.0.34/frontengine/ui/page/control_center}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/control_center/control_center_ui.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/particle → frontengine-1.0.34/frontengine/ui/page/focus}/__init__.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/pet → frontengine-1.0.34/frontengine/ui/page/gif}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/gif/gif_setting_ui.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/presentation → frontengine-1.0.34/frontengine/ui/page/image}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/image/image_setting_ui.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/scene_setting → frontengine-1.0.34/frontengine/ui/page/particle}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/particle/particle_setting_ui.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/scene_setting/scene_page → frontengine-1.0.34/frontengine/ui/page/pet}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/pet/pet_setting_ui.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/screen_care → frontengine-1.0.34/frontengine/ui/page/presentation}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/presentation/presentation_setting_ui.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/sound_player → frontengine-1.0.34/frontengine/ui/page/scene_setting}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_manager.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/text → frontengine-1.0.34/frontengine/ui/page/scene_setting/scene_page}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/base_scene_page.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/gif.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/image.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/registry.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/sound.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/text.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/video.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_page/web.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/scene_setting/scene_setting_ui.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/video → frontengine-1.0.34/frontengine/ui/page/screen_care}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/screen_care/screen_care_setting_ui.py +0 -0
- {frontengine-1.0.33/frontengine/ui/page/web → frontengine-1.0.34/frontengine/ui/page/sound_player}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/sound_player/sound_player_setting_ui.py +0 -0
- {frontengine-1.0.33/frontengine/user_setting → frontengine-1.0.34/frontengine/ui/page/text}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/text/text_setting_ui.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/utils.py +0 -0
- {frontengine-1.0.33/frontengine/utils → frontengine-1.0.34/frontengine/ui/page/video}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/video/video_setting_ui.py +0 -0
- {frontengine-1.0.33/frontengine/utils/audio_meter → frontengine-1.0.34/frontengine/ui/page/wallpaper}/__init__.py +0 -0
- {frontengine-1.0.33/frontengine/utils/autostart → frontengine-1.0.34/frontengine/ui/page/web}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/ui/page/web/web_setting_ui.py +0 -0
- {frontengine-1.0.33/frontengine/utils/break_reminder → frontengine-1.0.34/frontengine/user_setting}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/user_setting/preset_repository.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/user_setting/scene_setting.py +0 -0
- {frontengine-1.0.33/frontengine/utils/browser → frontengine-1.0.34/frontengine/utils}/__init__.py +0 -0
- {frontengine-1.0.33/frontengine/utils/critical_exit → frontengine-1.0.34/frontengine/utils/audio_meter}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/audio_meter/audio_envelope.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/audio_meter/screen_audio.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/audio_meter/system_audio_meter.py +0 -0
- {frontengine-1.0.33/frontengine/utils/file → frontengine-1.0.34/frontengine/utils/autostart}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/autostart/autostart_service.py +0 -0
- {frontengine-1.0.33/frontengine/utils/focus_timer → frontengine-1.0.34/frontengine/utils/break_reminder}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/break_reminder/break_reminder.py +0 -0
- {frontengine-1.0.33/frontengine/utils/hotkey → frontengine-1.0.34/frontengine/utils/browser}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/browser/browser.py +0 -0
- {frontengine-1.0.33/frontengine/utils/input_watch → frontengine-1.0.34/frontengine/utils/critical_exit}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/critical_exit/check_key_is_press.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/critical_exit/critical_exit.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/critical_exit/win32_vk.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/exception/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/exception/exception_tags.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/exception/exceptions.py +0 -0
- {frontengine-1.0.33/frontengine/utils/json → frontengine-1.0.34/frontengine/utils/file}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/file/open/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/file/open/open_file.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/file/save/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/file/save/save_file.py +0 -0
- {frontengine-1.0.33/frontengine/utils/logging → frontengine-1.0.34/frontengine/utils/focus_shield}/__init__.py +0 -0
- {frontengine-1.0.33/frontengine/utils/multi_language → frontengine-1.0.34/frontengine/utils/focus_timer}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/focus_timer/focus_timer.py +0 -0
- {frontengine-1.0.33/frontengine/utils/pet_chat → frontengine-1.0.34/frontengine/utils/hotkey}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/hotkey/hotkey_service.py +0 -0
- {frontengine-1.0.33/frontengine/utils/platform_info → frontengine-1.0.34/frontengine/utils/input_watch}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/input_watch/input_watch_service.py +0 -0
- {frontengine-1.0.33/frontengine/utils/power_mode → frontengine-1.0.34/frontengine/utils/json}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/json/json_file.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/json/json_repository.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/json_format/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/json_format/json_process.py +0 -0
- {frontengine-1.0.33/frontengine/utils/preset_schedule → frontengine-1.0.34/frontengine/utils/logging}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/logging/loggin_instance.py +0 -0
- {frontengine-1.0.33/frontengine/utils/redirect_manager → frontengine-1.0.34/frontengine/utils/multi_language}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/france.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/germany.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/italy.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/language_wrapper.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/russian.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/multi_language/simplified_chinese.py +0 -0
- {frontengine-1.0.33/frontengine/utils/smart_pause → frontengine-1.0.34/frontengine/utils/pet_chat}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/pet_chat/pet_chat_service.py +0 -0
- {frontengine-1.0.33/frontengine/utils/system_stats → frontengine-1.0.34/frontengine/utils/platform_info}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/platform_info/platform_info.py +0 -0
- {frontengine-1.0.33/frontengine/utils/text_source → frontengine-1.0.34/frontengine/utils/playlist}/__init__.py +0 -0
- {frontengine-1.0.33/frontengine/utils/theme_schedule → frontengine-1.0.34/frontengine/utils/plugins}/__init__.py +0 -0
- {frontengine-1.0.33/frontengine/utils/weather → frontengine-1.0.34/frontengine/utils/power_mode}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/power_mode/power_mode.py +0 -0
- {frontengine-1.0.33/frontengine/worker → frontengine-1.0.34/frontengine/utils/preset_schedule}/__init__.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/preset_schedule/preset_schedule_service.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/redirect_manager/redirect_manager_class.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/smart_pause/smart_pause_service.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/system_stats/system_stats.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/text_source/text_source.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/theme_schedule/theme_schedule_service.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/weather/weather_service.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/utils/web_url.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine/worker/qthread_worker.py +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/dependency_links.txt +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/entry_points.txt +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/requires.txt +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/frontengine.egg-info/top_level.txt +0 -0
- {frontengine-1.0.33 → frontengine-1.0.34}/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.34
|
|
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
|
|
@@ -85,6 +85,50 @@ Audio features are Windows-only and degrade to "no pulse" elsewhere.
|
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
88
|
+
## Focus
|
|
89
|
+
|
|
90
|
+
Two overlays for when the screen is competing with your work, both on the
|
|
91
|
+
**Focus** tab:
|
|
92
|
+
|
|
93
|
+
- **Dim background windows** — everything except the window you are working in
|
|
94
|
+
is shaded, at an adjustable strength. Off Windows, where the active window
|
|
95
|
+
cannot be identified without a native binding, it shades the whole screen.
|
|
96
|
+
- **Cover a distraction** — mask a strip of the screen: the taskbar, a
|
|
97
|
+
notification corner, an edge, or all of it.
|
|
98
|
+
|
|
99
|
+
Both pass clicks through, so what they cover still works — it just stops pulling
|
|
100
|
+
your eye.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Wallpaper
|
|
105
|
+
|
|
106
|
+
Play a folder of images and animations *beneath* every window, from the
|
|
107
|
+
**Wallpaper** tab:
|
|
108
|
+
|
|
109
|
+
- **A playlist per monitor** — each screen points at its own folder, changes on
|
|
110
|
+
its own timer, and can be shuffled or read recursively.
|
|
111
|
+
- **React to audio** — the wallpaper can pulse with your speakers' output level,
|
|
112
|
+
using the same meter-only reading as the pet (no audio is captured).
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Community content
|
|
117
|
+
|
|
118
|
+
- **Steam Workshop** — subscribed items are picked up from Steam's own
|
|
119
|
+
`steamapps/workshop/content` folder: presets are imported and pet packs are
|
|
120
|
+
listed with their paths, from **Presets → Import Workshop content**.
|
|
121
|
+
Publishing to the Workshop needs the Steamworks SDK and is not built in.
|
|
122
|
+
- **Plugins** — a `plugins/` folder can add its own tabs, either through a
|
|
123
|
+
`FRONTENGINE_TABS` mapping or a `register(registry)` hook.
|
|
124
|
+
|
|
125
|
+
> A plugin is Python and runs with the same privileges as FrontEngine — it
|
|
126
|
+
> cannot be sandboxed. Loading is **off by default** (Settings → Load plugins),
|
|
127
|
+
> every load is logged, and one broken plugin is skipped rather than stopping
|
|
128
|
+
> the app. Only install plugins you trust.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
88
132
|
## Install
|
|
89
133
|
|
|
90
134
|
- **System Requirements**
|
|
@@ -61,6 +61,50 @@ Audio features are Windows-only and degrade to "no pulse" elsewhere.
|
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
64
|
+
## Focus
|
|
65
|
+
|
|
66
|
+
Two overlays for when the screen is competing with your work, both on the
|
|
67
|
+
**Focus** tab:
|
|
68
|
+
|
|
69
|
+
- **Dim background windows** — everything except the window you are working in
|
|
70
|
+
is shaded, at an adjustable strength. Off Windows, where the active window
|
|
71
|
+
cannot be identified without a native binding, it shades the whole screen.
|
|
72
|
+
- **Cover a distraction** — mask a strip of the screen: the taskbar, a
|
|
73
|
+
notification corner, an edge, or all of it.
|
|
74
|
+
|
|
75
|
+
Both pass clicks through, so what they cover still works — it just stops pulling
|
|
76
|
+
your eye.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Wallpaper
|
|
81
|
+
|
|
82
|
+
Play a folder of images and animations *beneath* every window, from the
|
|
83
|
+
**Wallpaper** tab:
|
|
84
|
+
|
|
85
|
+
- **A playlist per monitor** — each screen points at its own folder, changes on
|
|
86
|
+
its own timer, and can be shuffled or read recursively.
|
|
87
|
+
- **React to audio** — the wallpaper can pulse with your speakers' output level,
|
|
88
|
+
using the same meter-only reading as the pet (no audio is captured).
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Community content
|
|
93
|
+
|
|
94
|
+
- **Steam Workshop** — subscribed items are picked up from Steam's own
|
|
95
|
+
`steamapps/workshop/content` folder: presets are imported and pet packs are
|
|
96
|
+
listed with their paths, from **Presets → Import Workshop content**.
|
|
97
|
+
Publishing to the Workshop needs the Steamworks SDK and is not built in.
|
|
98
|
+
- **Plugins** — a `plugins/` folder can add its own tabs, either through a
|
|
99
|
+
`FRONTENGINE_TABS` mapping or a `register(registry)` hook.
|
|
100
|
+
|
|
101
|
+
> A plugin is Python and runs with the same privileges as FrontEngine — it
|
|
102
|
+
> cannot be sandboxed. Loading is **off by default** (Settings → Load plugins),
|
|
103
|
+
> every load is logged, and one broken plugin is skipped rather than stopping
|
|
104
|
+
> the app. Only install plugins you trust.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
64
108
|
## Install
|
|
65
109
|
|
|
66
110
|
- **System Requirements**
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
專注用的兩種遮罩:
|
|
3
|
+
- DimBackgroundWidget:把作用中視窗以外的地方壓暗,視線自然留在手上的工作
|
|
4
|
+
- DistractionMaskWidget:蓋住指定的螢幕區域(工作列、通知角落)
|
|
5
|
+
兩者都是點擊穿透的,所以被遮住的地方仍然可以正常點擊。
|
|
6
|
+
|
|
7
|
+
Two focus masks: one dims everything except the active window so your eye stays
|
|
8
|
+
on the work, the other covers a chosen strip of the screen (the taskbar, a
|
|
9
|
+
notification corner). Both pass clicks through, so what is covered still works.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
from typing import List, Optional, Tuple
|
|
15
|
+
|
|
16
|
+
from PySide6.QtCore import QRect, QTimer
|
|
17
|
+
from PySide6.QtGui import QColor, QPainter
|
|
18
|
+
|
|
19
|
+
from frontengine.show.base_widget import BaseWidget
|
|
20
|
+
from frontengine.utils.focus_shield.focus_shield import (
|
|
21
|
+
REGION_BOTTOM, clamp_percent, mask_rect, surrounding_rects,
|
|
22
|
+
)
|
|
23
|
+
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
24
|
+
|
|
25
|
+
DEFAULT_DIM = 45
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def active_window_rect() -> Optional[Tuple[int, int, int, int]]:
|
|
29
|
+
"""
|
|
30
|
+
回傳目前作用中視窗的 (x, y, w, h);取不到或非 Windows 回傳 None
|
|
31
|
+
(其他平台會退化成「整片壓暗」,仍然可用)。
|
|
32
|
+
The active window's rectangle, or None off Windows — where the dimmer simply
|
|
33
|
+
shades the whole screen instead.
|
|
34
|
+
"""
|
|
35
|
+
if sys.platform != "win32":
|
|
36
|
+
return None
|
|
37
|
+
try:
|
|
38
|
+
import ctypes
|
|
39
|
+
from ctypes import wintypes
|
|
40
|
+
|
|
41
|
+
user32 = ctypes.windll.user32
|
|
42
|
+
hwnd = user32.GetForegroundWindow()
|
|
43
|
+
if not hwnd:
|
|
44
|
+
return None
|
|
45
|
+
rect = wintypes.RECT()
|
|
46
|
+
if not user32.GetWindowRect(hwnd, ctypes.byref(rect)):
|
|
47
|
+
return None
|
|
48
|
+
return (rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top)
|
|
49
|
+
except Exception as error: # pragma: no cover - Win32 boundary
|
|
50
|
+
front_engine_logger.debug(f"[focus_shield] active window unavailable: {error!r}")
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DimBackgroundWidget(BaseWidget):
|
|
55
|
+
"""
|
|
56
|
+
背景變暗:只有作用中視窗保持原樣,其餘壓暗;視窗換了會自動跟上。
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
REFRESH_MS = 250
|
|
60
|
+
|
|
61
|
+
def __init__(self, dim: int = DEFAULT_DIM) -> None:
|
|
62
|
+
front_engine_logger.info(f"[DimBackgroundWidget] Init | dim={dim}")
|
|
63
|
+
super().__init__()
|
|
64
|
+
self.opacity = 1.0
|
|
65
|
+
self.dim = clamp_percent(dim, DEFAULT_DIM)
|
|
66
|
+
self.shaded: List[Tuple[int, int, int, int]] = []
|
|
67
|
+
self._window_provider = active_window_rect
|
|
68
|
+
self._timer = QTimer(self)
|
|
69
|
+
self._timer.timeout.connect(self.refresh)
|
|
70
|
+
|
|
71
|
+
def set_window_provider(self, provider) -> None:
|
|
72
|
+
"""注入作用中視窗來源(測試用)。"""
|
|
73
|
+
self._window_provider = provider
|
|
74
|
+
|
|
75
|
+
def set_dim(self, dim: int) -> None:
|
|
76
|
+
self.dim = clamp_percent(dim, DEFAULT_DIM)
|
|
77
|
+
self.update()
|
|
78
|
+
|
|
79
|
+
def start_following(self, interval_ms: int = REFRESH_MS) -> None:
|
|
80
|
+
self.refresh()
|
|
81
|
+
self._timer.start(max(50, int(interval_ms)))
|
|
82
|
+
|
|
83
|
+
def screen_rect(self) -> Tuple[int, int, int, int]:
|
|
84
|
+
geometry = self.geometry()
|
|
85
|
+
return (geometry.x(), geometry.y(), geometry.width(), geometry.height())
|
|
86
|
+
|
|
87
|
+
def refresh(self) -> None:
|
|
88
|
+
"""重新計算要壓暗的區塊。"""
|
|
89
|
+
try:
|
|
90
|
+
active = self._window_provider()
|
|
91
|
+
except Exception: # pragma: no cover - defensive around providers
|
|
92
|
+
active = None
|
|
93
|
+
rects = surrounding_rects(self.screen_rect(), active)
|
|
94
|
+
origin_x, origin_y = self.x(), self.y()
|
|
95
|
+
# 換算成本視窗的座標系 / Convert to this widget's own coordinates
|
|
96
|
+
self.shaded = [(x - origin_x, y - origin_y, w, h) for x, y, w, h in rects]
|
|
97
|
+
self.update()
|
|
98
|
+
|
|
99
|
+
def draw_content(self, painter: QPainter) -> None:
|
|
100
|
+
shade = QColor(0, 0, 0, int(255 * self.dim / 100.0))
|
|
101
|
+
for x, y, width, height in self.shaded:
|
|
102
|
+
painter.fillRect(QRect(x, y, width, height), shade)
|
|
103
|
+
|
|
104
|
+
def closeEvent(self, event) -> None:
|
|
105
|
+
if self._timer.isActive():
|
|
106
|
+
self._timer.stop()
|
|
107
|
+
super().closeEvent(event)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class DistractionMaskWidget(BaseWidget):
|
|
111
|
+
"""
|
|
112
|
+
干擾遮罩:把螢幕的某個區域(預設底部,正好蓋住工作列)以不透明色蓋掉。
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(self, region: str = REGION_BOTTOM, percent: int = 6,
|
|
116
|
+
color: str = "#101010") -> None:
|
|
117
|
+
front_engine_logger.info(
|
|
118
|
+
f"[DistractionMaskWidget] Init | region={region}, percent={percent}")
|
|
119
|
+
super().__init__()
|
|
120
|
+
self.opacity = 1.0
|
|
121
|
+
self.region = region
|
|
122
|
+
self.percent = clamp_percent(percent)
|
|
123
|
+
self.mask_color = QColor(color) if QColor(color).isValid() else QColor("#101010")
|
|
124
|
+
|
|
125
|
+
def geometry_for(self, screen_rect: Tuple[int, int, int, int]) -> QRect:
|
|
126
|
+
"""算出這個遮罩該放在哪裡、多大。"""
|
|
127
|
+
x, y, width, height = mask_rect(screen_rect, self.region, self.percent)
|
|
128
|
+
return QRect(x, y, width, height)
|
|
129
|
+
|
|
130
|
+
def set_region(self, region: Optional[str] = None, percent: Optional[int] = None) -> None:
|
|
131
|
+
if region is not None:
|
|
132
|
+
self.region = region
|
|
133
|
+
if percent is not None:
|
|
134
|
+
self.percent = clamp_percent(percent)
|
|
135
|
+
|
|
136
|
+
def draw_content(self, painter: QPainter) -> None:
|
|
137
|
+
painter.fillRect(self.rect(), self.mask_color)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
桌布覆蓋層:在所有視窗底下播放圖片或動圖,可依系統音量脈動(音訊反應桌布),
|
|
3
|
+
並由播放清單定時換張。音量來源沿用寵物已經在用的系統峰值電表,只讀電表、
|
|
4
|
+
不擷取音訊內容。
|
|
5
|
+
|
|
6
|
+
A wallpaper layer: plays an image or animation beneath every window, optionally
|
|
7
|
+
pulsing with the system output level, with a playlist swapping it over time.
|
|
8
|
+
The level comes from the same meter the pet uses — a meter reading only, never
|
|
9
|
+
captured audio.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from PySide6.QtCore import QRect, QTimer
|
|
17
|
+
from PySide6.QtGui import QMovie, QPainter, QPixmap
|
|
18
|
+
|
|
19
|
+
from frontengine.show.base_widget import BaseWidget
|
|
20
|
+
from frontengine.utils.audio_meter.audio_envelope import AudioEnvelope
|
|
21
|
+
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
22
|
+
|
|
23
|
+
_ANIMATED_SUFFIXES = (".gif", ".webp")
|
|
24
|
+
MIN_REACT_SCALE = 1.0
|
|
25
|
+
MAX_REACT_SCALE = 1.25
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def react_scale(level, strength: int = 100) -> float:
|
|
29
|
+
"""
|
|
30
|
+
把音量 (0~1) 換成桌布的縮放比例:安靜時 1.0,最大聲時最多放大 25%。
|
|
31
|
+
strength 是效果強度百分比,0 代表完全不反應。
|
|
32
|
+
Map an audio level to a wallpaper scale: 1.0 when quiet, up to +25% loud.
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
clamped = max(0.0, min(1.0, float(level)))
|
|
36
|
+
except (TypeError, ValueError):
|
|
37
|
+
return MIN_REACT_SCALE
|
|
38
|
+
try:
|
|
39
|
+
share = max(0, min(100, int(strength))) / 100.0
|
|
40
|
+
except (TypeError, ValueError):
|
|
41
|
+
share = 1.0
|
|
42
|
+
return MIN_REACT_SCALE + (MAX_REACT_SCALE - MIN_REACT_SCALE) * clamped * share
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class WallpaperWidget(BaseWidget):
|
|
46
|
+
"""
|
|
47
|
+
桌布:顯示一張圖或一段動圖,位於所有視窗底下;可選擇隨系統音量脈動。
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
REACT_INTERVAL_MS = 33
|
|
51
|
+
|
|
52
|
+
def __init__(self, media_path: str = "", audio_react: bool = False,
|
|
53
|
+
react_strength: int = 100) -> None:
|
|
54
|
+
front_engine_logger.info(
|
|
55
|
+
f"[WallpaperWidget] Init | path={media_path}, audio_react={audio_react}")
|
|
56
|
+
super().__init__()
|
|
57
|
+
self.opacity = 1.0
|
|
58
|
+
self.media_path: Optional[Path] = None
|
|
59
|
+
self._movie: Optional[QMovie] = None
|
|
60
|
+
self._pixmap: Optional[QPixmap] = None
|
|
61
|
+
self.audio_react = bool(audio_react)
|
|
62
|
+
self.react_strength = max(0, min(100, int(react_strength)))
|
|
63
|
+
self.scale = MIN_REACT_SCALE
|
|
64
|
+
self._level_provider = None
|
|
65
|
+
self._envelope = AudioEnvelope()
|
|
66
|
+
self._react_timer = QTimer(self)
|
|
67
|
+
self._react_timer.timeout.connect(self._update_scale)
|
|
68
|
+
if media_path:
|
|
69
|
+
self.set_media(media_path)
|
|
70
|
+
|
|
71
|
+
def set_media(self, media_path: str) -> bool:
|
|
72
|
+
"""換一張桌布;載不起來時保留原本的並回傳 False。"""
|
|
73
|
+
path = Path(media_path)
|
|
74
|
+
if not path.is_file():
|
|
75
|
+
front_engine_logger.warning(f"[WallpaperWidget] missing media: {path}")
|
|
76
|
+
return False
|
|
77
|
+
if path.suffix.lower() in _ANIMATED_SUFFIXES:
|
|
78
|
+
movie = QMovie(str(path))
|
|
79
|
+
if not movie.isValid():
|
|
80
|
+
return False
|
|
81
|
+
self._stop_movie()
|
|
82
|
+
self._movie = movie
|
|
83
|
+
self._pixmap = None
|
|
84
|
+
movie.frameChanged.connect(self.update)
|
|
85
|
+
movie.start()
|
|
86
|
+
else:
|
|
87
|
+
pixmap = QPixmap(str(path))
|
|
88
|
+
if pixmap.isNull():
|
|
89
|
+
return False
|
|
90
|
+
self._stop_movie()
|
|
91
|
+
self._pixmap = pixmap
|
|
92
|
+
self._movie = None
|
|
93
|
+
self.media_path = path
|
|
94
|
+
self.update()
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
def set_audio_level_provider(self, provider) -> None:
|
|
98
|
+
"""設定音量來源(回傳 0~1 或 None)。"""
|
|
99
|
+
self._level_provider = provider
|
|
100
|
+
self._envelope.reset()
|
|
101
|
+
|
|
102
|
+
def set_audio_react(self, enabled: bool, strength: Optional[int] = None) -> None:
|
|
103
|
+
"""開關音訊反應並視需要調整強度。"""
|
|
104
|
+
self.audio_react = bool(enabled)
|
|
105
|
+
if strength is not None:
|
|
106
|
+
self.react_strength = max(0, min(100, int(strength)))
|
|
107
|
+
if self.audio_react:
|
|
108
|
+
self._react_timer.start(self.REACT_INTERVAL_MS)
|
|
109
|
+
else:
|
|
110
|
+
self._react_timer.stop()
|
|
111
|
+
self.scale = MIN_REACT_SCALE
|
|
112
|
+
self.update()
|
|
113
|
+
|
|
114
|
+
def _update_scale(self) -> None:
|
|
115
|
+
"""取一次音量、平滑後換成縮放比例。"""
|
|
116
|
+
if not self.audio_react or self._level_provider is None:
|
|
117
|
+
self.scale = MIN_REACT_SCALE
|
|
118
|
+
return
|
|
119
|
+
try:
|
|
120
|
+
level = self._level_provider()
|
|
121
|
+
except Exception: # pragma: no cover - defensive around providers
|
|
122
|
+
level = None
|
|
123
|
+
if level is None:
|
|
124
|
+
self.scale = MIN_REACT_SCALE
|
|
125
|
+
self._envelope.reset()
|
|
126
|
+
else:
|
|
127
|
+
self.scale = react_scale(self._envelope.push(level), self.react_strength)
|
|
128
|
+
self.update()
|
|
129
|
+
|
|
130
|
+
def _stop_movie(self) -> None:
|
|
131
|
+
if self._movie is not None:
|
|
132
|
+
self._movie.stop()
|
|
133
|
+
self._movie = None
|
|
134
|
+
|
|
135
|
+
def current_pixmap(self) -> Optional[QPixmap]:
|
|
136
|
+
if self._movie is not None:
|
|
137
|
+
return self._movie.currentPixmap()
|
|
138
|
+
return self._pixmap
|
|
139
|
+
|
|
140
|
+
def draw_content(self, painter: QPainter) -> None:
|
|
141
|
+
pixmap = self.current_pixmap()
|
|
142
|
+
if pixmap is None or pixmap.isNull():
|
|
143
|
+
return
|
|
144
|
+
width = int(self.width() * self.scale)
|
|
145
|
+
height = int(self.height() * self.scale)
|
|
146
|
+
# 放大時往外溢出,維持置中,畫面才不會出現黑邊
|
|
147
|
+
# Overflow outward and stay centred so no bars appear while pulsing.
|
|
148
|
+
painter.drawPixmap(
|
|
149
|
+
QRect((self.width() - width) // 2, (self.height() - height) // 2, width, height), pixmap)
|
|
150
|
+
|
|
151
|
+
def closeEvent(self, event) -> None:
|
|
152
|
+
if self._react_timer.isActive():
|
|
153
|
+
self._react_timer.stop()
|
|
154
|
+
self._stop_movie()
|
|
155
|
+
super().closeEvent(event)
|
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Dict, Optional, Type
|
|
5
5
|
|
|
6
6
|
from PySide6.QtCore import QByteArray, QTimer, QCoreApplication
|
|
7
|
-
from PySide6.QtGui import QIcon, Qt
|
|
7
|
+
from PySide6.QtGui import QIcon, Qt
|
|
8
8
|
from PySide6.QtWidgets import QMainWindow, QApplication, QGridLayout, QTabWidget, QMenuBar, QWidget
|
|
9
9
|
from qt_material import apply_stylesheet
|
|
10
10
|
|
|
@@ -18,6 +18,7 @@ from frontengine.ui.menu.preset_menu import (
|
|
|
18
18
|
)
|
|
19
19
|
from frontengine.ui.menu.settings_menu import build_settings_menu
|
|
20
20
|
from frontengine.ui.page.control_center.control_center_ui import ControlCenterUI
|
|
21
|
+
from frontengine.ui.page.focus.focus_setting_ui import FocusSettingUI
|
|
21
22
|
from frontengine.ui.page.gif.gif_setting_ui import GIFSettingUI
|
|
22
23
|
from frontengine.ui.page.image.image_setting_ui import ImageSettingUI
|
|
23
24
|
from frontengine.ui.page.particle.particle_setting_ui import ParticleSettingUI
|
|
@@ -28,6 +29,7 @@ from frontengine.ui.page.screen_care.screen_care_setting_ui import ScreenCareSet
|
|
|
28
29
|
from frontengine.ui.page.sound_player.sound_player_setting_ui import SoundPlayerSettingUI
|
|
29
30
|
from frontengine.ui.page.text.text_setting_ui import TextSettingUI
|
|
30
31
|
from frontengine.ui.page.video.video_setting_ui import VideoSettingUI
|
|
32
|
+
from frontengine.ui.page.wallpaper.wallpaper_setting_ui import WallpaperSettingUI
|
|
31
33
|
from frontengine.ui.page.web.web_setting_ui import WEBSettingUI
|
|
32
34
|
from frontengine.user_setting.user_setting_file import (
|
|
33
35
|
get_hotkey_bindings,
|
|
@@ -40,6 +42,7 @@ from frontengine.utils.critical_exit.win32_vk import keyboard_keys_table
|
|
|
40
42
|
from frontengine.utils.hotkey.hotkey_service import HotkeyService
|
|
41
43
|
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
42
44
|
from frontengine.utils.multi_language.language_wrapper import language_wrapper
|
|
45
|
+
from frontengine.utils.plugins.plugin_loader import load_plugins
|
|
43
46
|
from frontengine.utils.preset_schedule.preset_schedule_service import PresetScheduleService
|
|
44
47
|
from frontengine.utils.smart_pause.smart_pause_service import SmartPauseService
|
|
45
48
|
from frontengine.utils.theme_schedule.theme_schedule_service import ThemeScheduleService
|
|
@@ -107,6 +110,8 @@ class FrontEngineMainUI(QMainWindow):
|
|
|
107
110
|
self.pet_setting_ui = PetSettingUI()
|
|
108
111
|
self.screen_care_setting_ui = ScreenCareSettingUI()
|
|
109
112
|
self.presentation_setting_ui = PresentationSettingUI()
|
|
113
|
+
self.wallpaper_setting_ui = WallpaperSettingUI()
|
|
114
|
+
self.focus_setting_ui = FocusSettingUI()
|
|
110
115
|
|
|
111
116
|
# 控制中心
|
|
112
117
|
# Control Center
|
|
@@ -127,6 +132,13 @@ class FrontEngineMainUI(QMainWindow):
|
|
|
127
132
|
self.menu_bar = QMenuBar()
|
|
128
133
|
self.setMenuBar(self.menu_bar)
|
|
129
134
|
|
|
135
|
+
# 使用者明確開啟時才載入外掛(外掛與本程式同權限,無法沙箱化)
|
|
136
|
+
# Plugins load only when explicitly enabled; they run with our privileges.
|
|
137
|
+
loaded_plugins = load_plugins(
|
|
138
|
+
FrontEngine_EXTEND_TAB, enabled=bool(user_setting_dict.get("load_plugins")))
|
|
139
|
+
if loaded_plugins:
|
|
140
|
+
front_engine_logger.info(f"[FrontEngineMainUI] plugin tabs: {loaded_plugins}")
|
|
141
|
+
|
|
130
142
|
# 加入各 Tab
|
|
131
143
|
# Add tabs
|
|
132
144
|
self._add_tabs()
|
|
@@ -215,6 +227,8 @@ class FrontEngineMainUI(QMainWindow):
|
|
|
215
227
|
(self.pet_setting_ui, "tab_pet_text"),
|
|
216
228
|
(self.screen_care_setting_ui, "tab_screen_care_text"),
|
|
217
229
|
(self.presentation_setting_ui, "tab_presentation_text"),
|
|
230
|
+
(self.wallpaper_setting_ui, "tab_wallpaper_text"),
|
|
231
|
+
(self.focus_setting_ui, "tab_focus_text"),
|
|
218
232
|
(self.control_center_ui, "tab_control_center_text"),
|
|
219
233
|
]
|
|
220
234
|
|
|
@@ -7,6 +7,9 @@ from PySide6.QtGui import QAction
|
|
|
7
7
|
from PySide6.QtWidgets import QFileDialog, QInputDialog, QMessageBox
|
|
8
8
|
|
|
9
9
|
from frontengine.user_setting.preset_repository import PresetRepository
|
|
10
|
+
from frontengine.utils.workshop.workshop_content import (
|
|
11
|
+
KIND_PET_PACK, KIND_PRESET, find_workshop_dir, preset_files, scan_workshop_items,
|
|
12
|
+
)
|
|
10
13
|
from frontengine.user_setting.user_setting_file import user_setting_dict, write_user_setting
|
|
11
14
|
from frontengine.utils.logging.loggin_instance import front_engine_logger
|
|
12
15
|
from frontengine.utils.multi_language.language_wrapper import language_wrapper
|
|
@@ -296,6 +299,66 @@ def _import_package_action(ui: "FrontEngineMainUI") -> Callable[[], None]:
|
|
|
296
299
|
return handler
|
|
297
300
|
|
|
298
301
|
|
|
302
|
+
def _workshop_action(ui: "FrontEngineMainUI") -> Callable[[], None]:
|
|
303
|
+
"""
|
|
304
|
+
匯入 Steam 創意工坊已訂閱的內容:掃描 Steam 下載的資料夾,把預設集複製進來,
|
|
305
|
+
動作包則列出路徑供寵物頁選用。找不到 Steam 時請使用者自己指定路徑。
|
|
306
|
+
Import subscribed Steam Workshop content: copy presets in, and list pet packs
|
|
307
|
+
with their paths so the pet page can point at them. If Steam is not where we
|
|
308
|
+
expect, ask the user where it is.
|
|
309
|
+
"""
|
|
310
|
+
def handler() -> None:
|
|
311
|
+
front_engine_logger.info("[PresetMenu] import workshop content")
|
|
312
|
+
title = _t("workshop_menu_import", "Import Workshop content...")
|
|
313
|
+
content_dir = find_workshop_dir()
|
|
314
|
+
if content_dir is None:
|
|
315
|
+
chosen = QFileDialog.getExistingDirectory(
|
|
316
|
+
ui, _t("workshop_pick_steam", "Where is Steam installed?"))
|
|
317
|
+
if not chosen:
|
|
318
|
+
return
|
|
319
|
+
content_dir = find_workshop_dir(extra_paths=[chosen])
|
|
320
|
+
if content_dir is None:
|
|
321
|
+
QMessageBox.information(
|
|
322
|
+
ui, title, _t("workshop_not_found", "No Workshop content found for FrontEngine."))
|
|
323
|
+
return
|
|
324
|
+
items = scan_workshop_items(content_dir)
|
|
325
|
+
if not items:
|
|
326
|
+
QMessageBox.information(
|
|
327
|
+
ui, title,
|
|
328
|
+
_t("workshop_no_items", "Nothing subscribed yet - subscribe in Steam first."))
|
|
329
|
+
return
|
|
330
|
+
imported, packs = _install_workshop_items(items)
|
|
331
|
+
lines = [
|
|
332
|
+
_t("workshop_imported_presets", "Presets imported: {count}").format(count=imported),
|
|
333
|
+
_t("workshop_found_packs", "Pet packs available: {count}").format(count=len(packs)),
|
|
334
|
+
]
|
|
335
|
+
lines.extend(f"- {title_}: {path}" for title_, path in packs[:8])
|
|
336
|
+
QMessageBox.information(ui, title, chr(10).join(lines))
|
|
337
|
+
|
|
338
|
+
return handler
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _install_workshop_items(items) -> tuple:
|
|
342
|
+
"""把預設集複製進本機的 presets/,並回傳可用的動作包 (名稱, 路徑)。"""
|
|
343
|
+
repository = PresetRepository()
|
|
344
|
+
imported = 0
|
|
345
|
+
packs = []
|
|
346
|
+
for item in items:
|
|
347
|
+
if item["kind"] == KIND_PET_PACK:
|
|
348
|
+
packs.append((item["title"], item["path"]))
|
|
349
|
+
continue
|
|
350
|
+
if item["kind"] != KIND_PRESET:
|
|
351
|
+
continue
|
|
352
|
+
for preset_path in preset_files(item["path"]):
|
|
353
|
+
try:
|
|
354
|
+
repository.import_preset(Path(preset_path))
|
|
355
|
+
imported += 1
|
|
356
|
+
except Exception as error: # pragma: no cover - third-party content
|
|
357
|
+
front_engine_logger.warning(
|
|
358
|
+
f"[PresetMenu] workshop preset failed: {preset_path} ({error!r})")
|
|
359
|
+
return (imported, packs)
|
|
360
|
+
|
|
361
|
+
|
|
299
362
|
def build_preset_menu(ui: "FrontEngineMainUI") -> None:
|
|
300
363
|
"""
|
|
301
364
|
Build the Presets menu with Save / Load / Delete / Export / Import actions.
|
|
@@ -314,6 +377,7 @@ def build_preset_menu(ui: "FrontEngineMainUI") -> None:
|
|
|
314
377
|
("preset_menu_import_package", "Import package (+media)...", _import_package_action),
|
|
315
378
|
("preset_menu_set_startup", "Set as startup preset...", _set_startup_action),
|
|
316
379
|
("preset_menu_clear_startup", "Clear startup preset", _clear_startup_action),
|
|
380
|
+
("workshop_menu_import", "Import Workshop content...", _workshop_action),
|
|
317
381
|
):
|
|
318
382
|
action = QAction(_t(label_key, fallback), menu)
|
|
319
383
|
action.triggered.connect(callback_factory(ui))
|
|
@@ -58,6 +58,12 @@ def build_settings_menu(ui: "FrontEngineMainUI") -> None:
|
|
|
58
58
|
restore_action.toggled.connect(lambda checked: _toggle_restore_session(checked))
|
|
59
59
|
menu.addAction(restore_action)
|
|
60
60
|
ui.restore_session_action = restore_action
|
|
61
|
+
plugins_action = QAction(_t("settings_menu_plugins", "Load plugins (advanced)"), menu)
|
|
62
|
+
plugins_action.setCheckable(True)
|
|
63
|
+
plugins_action.setChecked(bool(user_setting_dict.get("load_plugins")))
|
|
64
|
+
plugins_action.toggled.connect(lambda checked: _toggle_plugins(ui, checked))
|
|
65
|
+
menu.addAction(plugins_action)
|
|
66
|
+
ui.plugins_action = plugins_action
|
|
61
67
|
|
|
62
68
|
menu.addSeparator()
|
|
63
69
|
export_action = QAction(_t("settings_menu_export", "Export settings..."), menu)
|
|
@@ -89,6 +95,24 @@ def _toggle_restore_session(enabled: bool) -> None:
|
|
|
89
95
|
"""開關「還原上次工作階段」。"""
|
|
90
96
|
user_setting_dict["restore_last_session"] = bool(enabled)
|
|
91
97
|
write_user_setting()
|
|
98
|
+
def _toggle_plugins(ui: "FrontEngineMainUI", enabled: bool) -> None:
|
|
99
|
+
"""
|
|
100
|
+
開關外掛載入。外掛是 Python 程式、與本程式同權限,無法沙箱化,
|
|
101
|
+
因此開啟時明確告知風險,並在下次啟動才生效。
|
|
102
|
+
Toggle plugin loading. Plugins are Python and run with this application's
|
|
103
|
+
own privileges — they cannot be sandboxed — so say so plainly when it is
|
|
104
|
+
switched on. It takes effect on the next launch.
|
|
105
|
+
"""
|
|
106
|
+
user_setting_dict["load_plugins"] = bool(enabled)
|
|
107
|
+
write_user_setting()
|
|
108
|
+
if enabled:
|
|
109
|
+
QMessageBox.warning(
|
|
110
|
+
ui,
|
|
111
|
+
_t("settings_menu_plugins", "Load plugins (advanced)"),
|
|
112
|
+
_t("settings_plugins_warning",
|
|
113
|
+
"Plugins are Python code and run with the same privileges as FrontEngine. "
|
|
114
|
+
"Only install plugins you trust. Takes effect on the next launch."),
|
|
115
|
+
)
|
|
92
116
|
|
|
93
117
|
|
|
94
118
|
def _toggle_theme_schedule(ui: "FrontEngineMainUI", enabled: bool) -> None:
|