meltygui 0.1.0__py3-none-any.whl
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.
- meltygui/__init__.py +107 -0
- meltygui/accounts/__init__.py +0 -0
- meltygui/accounts/internet_accounts.py +1355 -0
- meltygui/chat/__init__.py +91 -0
- meltygui/chat/activity.py +75 -0
- meltygui/chat/backends.py +36 -0
- meltygui/chat/chat_interface.py +732 -0
- meltygui/chat/chat_proxy.py +352 -0
- meltygui/chat/codex_proxy.py +592 -0
- meltygui/chat/codex_settings.py +100 -0
- meltygui/chat/codex_transport.py +60 -0
- meltygui/chat/command_parser.py +204 -0
- meltygui/chat/images.py +227 -0
- meltygui/chat/messages.py +417 -0
- meltygui/chat/metadata.py +139 -0
- meltygui/chat/writer_locks.py +64 -0
- meltygui/code/__init__.py +0 -0
- meltygui/code/basic_converters.py +533 -0
- meltygui/code/chain_converters.py +2111 -0
- meltygui/code/code_checks.py +2209 -0
- meltygui/code/core_syntax.py +1430 -0
- meltygui/code/file_converters.py +1933 -0
- meltygui/code/fileref.py +702 -0
- meltygui/code/hotswap_guard.py +144 -0
- meltygui/code/libcst_conversion.py +9724 -0
- meltygui/code/live_instrument.py +392 -0
- meltygui/code/live_view.py +2490 -0
- meltygui/code/melty_scan.py +2684 -0
- meltygui/code/new_codecs.py +1255 -0
- meltygui/code/new_converters.py +3017 -0
- meltygui/code/project_code.py +278 -0
- meltygui/code/source_context.py +63 -0
- meltygui/code/symbol_roster.py +1588 -0
- meltygui/code/syntax_check.py +34 -0
- meltygui/code/syntax_check_worker.py +114 -0
- meltygui/completion/__init__.py +0 -0
- meltygui/completion/fim.py +1232 -0
- meltygui/completion/fim_context.py +481 -0
- meltygui/completion/providers/__init__.py +0 -0
- meltygui/completion/providers/anthropic_oauth.py +446 -0
- meltygui/completion/providers/anthropic_requests.py +69 -0
- meltygui/completion/providers/claude.py +203 -0
- meltygui/completion/providers/claude_usage.py +499 -0
- meltygui/completion/providers/codex_accounts.py +180 -0
- meltygui/completion/providers/copilot.py +617 -0
- meltygui/completion/providers/oauth_popup.py +220 -0
- meltygui/completion/providers/ollama.py +320 -0
- meltygui/completion/providers/profiles.py +23 -0
- meltygui/core/README.md +88 -0
- meltygui/core/__init__.py +1 -0
- meltygui/core/automation/__init__.py +1 -0
- meltygui/core/automation/action_core.py +153 -0
- meltygui/core/automation/collection_action.py +45 -0
- meltygui/core/automation/mcp_eval.py +167 -0
- meltygui/core/automation/mcp_hotswap.py +107 -0
- meltygui/core/automation/mcp_query.py +552 -0
- meltygui/core/automation/mcp_server.py +657 -0
- meltygui/core/automation/orchestration_core.py +2636 -0
- meltygui/core/automation/query_core.py +124 -0
- meltygui/core/automation/search_core.py +26 -0
- meltygui/core/automation/selector_core.py +340 -0
- meltygui/core/automation/value_core.py +1752 -0
- meltygui/core/cache/__init__.py +1 -0
- meltygui/core/cache/cache_diagnostics.py +0 -0
- meltygui/core/cache/invalidation_decoration.py +153 -0
- meltygui/core/cache/invalidation_tracker.py +43 -0
- meltygui/core/cache/tile_cache.py +5968 -0
- meltygui/core/conversion/__init__.py +1 -0
- meltygui/core/conversion/bubbling.py +599 -0
- meltygui/core/conversion/cache_tree.py +188 -0
- meltygui/core/conversion/chain.py +113 -0
- meltygui/core/conversion/converter_register.py +145 -0
- meltygui/core/conversion/data_decoration.py +67 -0
- meltygui/core/conversion/dict_conversion.py +1815 -0
- meltygui/core/conversion/dict_conversion_util.py +177 -0
- meltygui/core/conversion/dynamic_obj.py +89 -0
- meltygui/core/conversion/graph_compare.py +183 -0
- meltygui/core/conversion/load_save_v2.py +1032 -0
- meltygui/core/conversion/missing_saved_class.py +39 -0
- meltygui/core/conversion/path_finder.py +606 -0
- meltygui/core/conversion/render_host.py +1083 -0
- meltygui/core/core_render.py +6537 -0
- meltygui/core/definition_hotswap.py +231 -0
- meltygui/core/diagnostics/__init__.py +1 -0
- meltygui/core/diagnostics/attribute_churn.py +38 -0
- meltygui/core/diagnostics/fps_counter.py +39 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
- meltygui/core/diagnostics/inspection_core.py +169 -0
- meltygui/core/diagnostics/monitor_core.py +105 -0
- meltygui/core/diagnostics/notifications.py +706 -0
- meltygui/core/diagnostics/perf_trace.py +281 -0
- meltygui/core/diagnostics/profile_decoration.py +88 -0
- meltygui/core/diagnostics/resize_trace.py +62 -0
- meltygui/core/diagnostics/screenshot_core.py +247 -0
- meltygui/core/diagnostics/session_status.py +98 -0
- meltygui/core/diagnostics/trace_core.py +445 -0
- meltygui/core/files/__init__.py +1 -0
- meltygui/core/files/file_core.py +208 -0
- meltygui/core/files/file_explorer_core.py +104 -0
- meltygui/core/files/file_tree_core.py +198 -0
- meltygui/core/files/file_watch_core.py +43 -0
- meltygui/core/files/import_graph_core.py +43 -0
- meltygui/core/files/metadata_core.py +51 -0
- meltygui/core/graphics/__init__.py +1 -0
- meltygui/core/graphics/cuda_context_core.py +166 -0
- meltygui/core/graphics/cuda_interop_core.py +136 -0
- meltygui/core/graphics/cuda_kernel_core.py +91 -0
- meltygui/core/graphics/framebuffer_recorder.py +337 -0
- meltygui/core/graphics/gl_state.py +658 -0
- meltygui/core/graphics/lut_core.py +52 -0
- meltygui/core/graphics/overlay_renderer.py +984 -0
- meltygui/core/graphics/scene_target.py +180 -0
- meltygui/core/graphics/screenshot.py +439 -0
- meltygui/core/graphics/shader_func.py +478 -0
- meltygui/core/graphics/tensor_core.py +45 -0
- meltygui/core/graphics/text_texture.py +329 -0
- meltygui/core/graphics/wayland_color.py +635 -0
- meltygui/core/input/__init__.py +1 -0
- meltygui/core/input/collision.py +165 -0
- meltygui/core/input/drag_drop_core.py +1525 -0
- meltygui/core/input/hypr_left_drag.py +323 -0
- meltygui/core/input/input_core.py +245 -0
- meltygui/core/input/input_handler.py +1101 -0
- meltygui/core/input/mouse_cursor.py +355 -0
- meltygui/core/input/pynput_backend.py +1054 -0
- meltygui/core/input/space_mouse.py +338 -0
- meltygui/core/input/touchpad_backend.py +393 -0
- meltygui/core/input/view_selection.py +177 -0
- meltygui/core/layout/__init__.py +1 -0
- meltygui/core/layout/column_core.py +2153 -0
- meltygui/core/layout/cursor_core.py +161 -0
- meltygui/core/layout/dropdown_core.py +278 -0
- meltygui/core/layout/edge_constraints.py +155 -0
- meltygui/core/layout/grid_core.py +117 -0
- meltygui/core/layout/header_core.py +23 -0
- meltygui/core/layout/header_runtime.py +67 -0
- meltygui/core/layout/layout_core.py +87 -0
- meltygui/core/layout/tile_manager_core.py +591 -0
- meltygui/core/melty.py +6726 -0
- meltygui/core/module_map.json +896 -0
- meltygui/core/module_names.py +19 -0
- meltygui/core/rendering/__init__.py +1 -0
- meltygui/core/rendering/core_decoration.py +431 -0
- meltygui/core/rendering/core_render_helpers.py +328 -0
- meltygui/core/rendering/func_metadata.py +398 -0
- meltygui/core/rendering/mode.py +818 -0
- meltygui/core/rendering/mode_defaults.py +41 -0
- meltygui/core/rendering/modes.py +136 -0
- meltygui/core/rendering/parameter_core.py +1665 -0
- meltygui/core/rendering/render_dispatch.py +1891 -0
- meltygui/core/rendering/render_funcs.py +273 -0
- meltygui/core/rendering/shaped.py +312 -0
- meltygui/core/rendering/window_decoration.py +25 -0
- meltygui/core/runtime/__init__.py +1 -0
- meltygui/core/runtime/app.py +735 -0
- meltygui/core/runtime/app_session.py +140 -0
- meltygui/core/runtime/background.py +564 -0
- meltygui/core/runtime/extensions.py +53 -0
- meltygui/core/runtime/gc_manager.py +1163 -0
- meltygui/core/runtime/lifecycle.py +21 -0
- meltygui/core/runtime/paths.py +27 -0
- meltygui/core/runtime/settings.py +14 -0
- meltygui/core/runtime/singleton.py +16 -0
- meltygui/core/runtime/thread_safe_bool.py +24 -0
- meltygui/core/runtime/thread_signal.py +30 -0
- meltygui/core/runtime/toggles.py +3115 -0
- meltygui/core/services/__init__.py +1 -0
- meltygui/core/services/account_core.py +10 -0
- meltygui/core/services/chat_core.py +19 -0
- meltygui/core/services/claude_terminal_core.py +346 -0
- meltygui/core/services/terminal_core.py +458 -0
- meltygui/core/services/terminal_runtime.py +78 -0
- meltygui/core/styling/__init__.py +1 -0
- meltygui/core/styling/color_core.py +46 -0
- meltygui/core/styling/fonts.py +639 -0
- meltygui/core/styling/global_style.py +338 -0
- meltygui/core/styling/style.py +198 -0
- meltygui/core/styling/style_core.py +522 -0
- meltygui/core/styling/warm_start.py +149 -0
- meltygui/core/windowing/__init__.py +1 -0
- meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
- meltygui/core/windowing/backends/__init__.py +1 -0
- meltygui/core/windowing/backends/imgui_renderer.py +138 -0
- meltygui/core/windowing/backends/native_wayland.py +850 -0
- meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
- meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
- meltygui/core/windowing/backends/wayland_protocol.py +101 -0
- meltygui/core/windowing/dock_core.py +182 -0
- meltygui/core/windowing/frame_geometry.py +42 -0
- meltygui/core/windowing/geometry_feed.py +864 -0
- meltygui/core/windowing/glfw_utils.py +1343 -0
- meltygui/core/windowing/os_frame.py +1552 -0
- meltygui/core/windowing/surface.py +643 -0
- meltygui/core/windowing/titlebar.py +1560 -0
- meltygui/core/windowing/titlebar_buttons.py +281 -0
- meltygui/core/windowing/wayland_move.py +932 -0
- meltygui/core/windowing/window_api.py +62 -0
- meltygui/core/windowing/window_constants.py +339 -0
- meltygui/core/windowing/window_visibility.py +162 -0
- meltygui/debug/__init__.py +0 -0
- meltygui/debug/app_view_utils.py +9 -0
- meltygui/editor/__init__.py +0 -0
- meltygui/editor/bash_syntax.py +30 -0
- meltygui/editor/code_line_fast.py +152 -0
- meltygui/editor/diff.py +139 -0
- meltygui/editor/external_changes.py +159 -0
- meltygui/editor/file_header.py +41 -0
- meltygui/editor/live_usage.py +169 -0
- meltygui/editor/live_view_views.py +1803 -0
- meltygui/editor/pending_save.py +1351 -0
- meltygui/editor/roster_tints.py +547 -0
- meltygui/editor/source_preview.py +16 -0
- meltygui/editor/source_tools.py +10 -0
- meltygui/editor/source_ui.py +70 -0
- meltygui/editor/spell_check.py +77 -0
- meltygui/editor/text_editor.py +9076 -0
- meltygui/editor/usage_picker.py +538 -0
- meltygui/events/__init__.py +0 -0
- meltygui/events/example.py +118 -0
- meltygui/examples/__init__.py +0 -0
- meltygui/examples/columns_demo.py +33 -0
- meltygui/examples/columns_window_demo.py +60 -0
- meltygui/examples/context_menu_demo.py +24 -0
- meltygui/examples/context_menu_window_demo.py +49 -0
- meltygui/examples/gui_playground.py +127 -0
- meltygui/examples/live_view_playground.py +256 -0
- meltygui/examples/lora.py +43 -0
- meltygui/examples/lora_data.py +59 -0
- meltygui/examples/lora_policies.py +67 -0
- meltygui/examples/mode_demo.py +49 -0
- meltygui/examples/modifies_demo.py +119 -0
- meltygui/examples/scalar_policies.py +60 -0
- meltygui/examples/style_layouts.py +149 -0
- meltygui/examples/tile_manager_demo.py +74 -0
- meltygui/examples/tint_demo.py +160 -0
- meltygui/examples/tint_functions.py +56 -0
- meltygui/examples/trace_demo.py +88 -0
- meltygui/examples/two_windows.py +36 -0
- meltygui/files/__init__.py +0 -0
- meltygui/files/fast_file_explorer.py +439 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
- meltygui/graphics/__init__.py +6 -0
- meltygui/graphics/base.py +85 -0
- meltygui/graphics/examples.py +507 -0
- meltygui/graphics/executor.py +520 -0
- meltygui/graphics/filter.py +667 -0
- meltygui/graphics/filter.pyi +856 -0
- meltygui/graphics/generate_stubs.py +22 -0
- meltygui/graphics/registry.py +203 -0
- meltygui/graphics/shader_compiler.py +155 -0
- meltygui/graphics/shaders.py +1302 -0
- meltygui/graphics/stub_generator.py +250 -0
- meltygui/graphics/texture_manager.py +170 -0
- meltygui/graphics/texture_min_max.py +295 -0
- meltygui/hdr_color.py +757 -0
- meltygui/image_load.py +308 -0
- meltygui/model/__init__.py +1 -0
- meltygui/model/account_model.py +142 -0
- meltygui/model/camera_model.py +159 -0
- meltygui/model/chat_model.py +86 -0
- meltygui/model/code_model.py +62 -0
- meltygui/model/code_proxy_model.py +876 -0
- meltygui/model/collection_model.py +41 -0
- meltygui/model/color_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +36 -0
- meltygui/model/cuda_texture_model.py +149 -0
- meltygui/model/dropdown_model.py +137 -0
- meltygui/model/file_metadata_model.py +73 -0
- meltygui/model/file_model.py +289 -0
- meltygui/model/format_model.py +390 -0
- meltygui/model/graph_model.py +98 -0
- meltygui/model/icon_model.py +1024 -0
- meltygui/model/import_graph_model.py +468 -0
- meltygui/model/layout_model.py +15 -0
- meltygui/model/lut_model.py +266 -0
- meltygui/model/search_model.py +173 -0
- meltygui/model/tensor_model.py +402 -0
- meltygui/model/terminal_model.py +329 -0
- meltygui/model/texture_model.py +146 -0
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +198 -0
- meltygui/model/trace_report_model.py +146 -0
- meltygui/models/__init__.py +0 -0
- meltygui/models/file_meta.py +613 -0
- meltygui/models/function_console.py +184 -0
- meltygui/models/orchestration.py +48 -0
- meltygui/pbr.py +1576 -0
- meltygui/png_unfilter.c +49 -0
- meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
- meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
- meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
- meltygui/resources/dejavu/LICENSE.txt +78 -0
- meltygui/resources/fontawesome-LICENSE.txt +121 -0
- meltygui/resources/fontawesome-webfont.ttf +0 -0
- meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
- meltygui/resources/jetbrains-weights/OFL.txt +93 -0
- meltygui/resources/jetbrains-weights/README.md +2 -0
- meltygui/state/__init__.py +0 -0
- meltygui/state/account_state.py +22 -0
- meltygui/state/animation_state.py +97 -0
- meltygui/state/annotation_state.py +22 -0
- meltygui/state/chat_state.py +36 -0
- meltygui/state/code_state.py +10 -0
- meltygui/state/core_enums.py +59 -0
- meltygui/state/core_markers.py +115 -0
- meltygui/state/core_undo.py +1075 -0
- meltygui/state/file_state.py +75 -0
- meltygui/state/graph_state.py +26 -0
- meltygui/state/inspection_state.py +47 -0
- meltygui/state/menu_state.py +10 -0
- meltygui/state/model_enums.py +11 -0
- meltygui/state/new_core_model.py +2394 -0
- meltygui/state/orchestration_state.py +14 -0
- meltygui/state/query_state.py +24 -0
- meltygui/state/tensor_state.py +10 -0
- meltygui/state/terminal_state.py +23 -0
- meltygui/state/trace_state.py +32 -0
- meltygui/state/voxel_state.py +12 -0
- meltygui/text_index.py +816 -0
- meltygui/utils/__init__.py +0 -0
- meltygui/utils/jump_to_code.py +344 -0
- meltygui/utils/pkl_inspect.py +90 -0
- meltygui/utils/render_utils.py +1419 -0
- meltygui/view/__init__.py +1 -0
- meltygui/view/account_view.py +524 -0
- meltygui/view/action_view.py +73 -0
- meltygui/view/chat_decoration_view.py +112 -0
- meltygui/view/chat_view.py +1703 -0
- meltygui/view/code_view.py +2677 -0
- meltygui/view/collection_view.py +1185 -0
- meltygui/view/color_view.py +824 -0
- meltygui/view/control_view.py +559 -0
- meltygui/view/decoration_view.py +439 -0
- meltygui/view/diagnostic_view.py +177 -0
- meltygui/view/dropdown_view.py +1082 -0
- meltygui/view/file_view.py +1599 -0
- meltygui/view/graph_cuda_view.py +105 -0
- meltygui/view/graph_view.py +882 -0
- meltygui/view/header_view.py +848 -0
- meltygui/view/input_view.py +238 -0
- meltygui/view/inspection_view.py +1783 -0
- meltygui/view/layout_view.py +596 -0
- meltygui/view/lut_view.py +45 -0
- meltygui/view/menu_view.py +212 -0
- meltygui/view/orchestration_view.py +658 -0
- meltygui/view/query_view.py +118 -0
- meltygui/view/search_view.py +342 -0
- meltygui/view/tab_view.py +258 -0
- meltygui/view/tensor_view.py +430 -0
- meltygui/view/terminal_view.py +355 -0
- meltygui/view/text_view.py +8031 -0
- meltygui/view/texture_view.py +480 -0
- meltygui/view/tile_view.py +38 -0
- meltygui/view/trace_view.py +923 -0
- meltygui/view/voxel_cuda_view.py +824 -0
- meltygui/view/voxel_view.py +1860 -0
- meltygui/view/window_view.py +111 -0
- meltygui-0.1.0.dist-info/METADATA +143 -0
- meltygui-0.1.0.dist-info/RECORD +372 -0
- meltygui-0.1.0.dist-info/WHEEL +4 -0
- meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Which window controls the desktop puts in a title bar, and on which side.
|
|
2
|
+
|
|
3
|
+
Every desktop lets the user choose the title-bar buttons (drop the maximize
|
|
4
|
+
button, put the controls on the left, ...), and GTK / Qt apps that draw
|
|
5
|
+
their own header bars follow that setting. meltygui's frameless windows draw
|
|
6
|
+
their own controls too (titlebar.py), so they read the same setting:
|
|
7
|
+
|
|
8
|
+
GNOME, Budgie, Hyprland / sway / ... with a dconf profile:
|
|
9
|
+
gsettings get org.gnome.desktop.wm.preferences button-layout
|
|
10
|
+
-> 'appmenu:minimize,maximize,close' (what GTK's header bars read)
|
|
11
|
+
Cinnamon: org.cinnamon.desktop.wm.preferences button-layout
|
|
12
|
+
MATE: org.mate.Marco.general button-layout
|
|
13
|
+
KDE: ~/.config/kwinrc [org.kde.kdecoration2] ButtonsOnLeft / ButtonsOnRight
|
|
14
|
+
(letters: I minimize, A maximize, X close; the rest are ignored)
|
|
15
|
+
XFCE: ~/.config/xfce4/xfconf/xfce-perchannel-xml/xfwm4.xml button_layout
|
|
16
|
+
("O|SHMC": H hide = minimize, M maximize, C close, | splits the sides)
|
|
17
|
+
fallback: the settings portal (org.freedesktop.portal.Settings.Read of
|
|
18
|
+
the GNOME key, what a sandboxed GTK app gets), then DEFAULT.
|
|
19
|
+
|
|
20
|
+
The GNOME syntax is the canonical form here — `parse_gnome` is also what
|
|
21
|
+
Toggles.Melty.titlebar_button_layout takes to pin a layout: "left:right",
|
|
22
|
+
comma-separated `minimize` / `maximize` / `close`; a string without a colon
|
|
23
|
+
is all LEFT buttons (mutter's and GTK's rule); other tokens (appmenu, menu,
|
|
24
|
+
spacer, icon) are dropped.
|
|
25
|
+
|
|
26
|
+
Stdlib only, so app.py's boot can start the probe (`start_probe`) before
|
|
27
|
+
meltygui's heavy imports land: the reads run on a daemon thread (a gsettings
|
|
28
|
+
call is a ~10 ms subprocess), `system_layout()` answers DEFAULT until the
|
|
29
|
+
first probe lands, and a layout change asks for a frame. `refresh()` re-reads
|
|
30
|
+
after a focus gain, `refresh_if_stale()` per frame at
|
|
31
|
+
Toggles.Melty.titlebar_button_refresh_s, so a setting changed while the app
|
|
32
|
+
runs is picked up without a restart.
|
|
33
|
+
|
|
34
|
+
Subprocesses go through posix_spawn (absolute executable, close_fds=False,
|
|
35
|
+
no cwd / preexec_fn) — the studio must never fork (CLAUDE.md).
|
|
36
|
+
"""
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import configparser
|
|
40
|
+
import os
|
|
41
|
+
import re
|
|
42
|
+
import shutil
|
|
43
|
+
import subprocess
|
|
44
|
+
import threading
|
|
45
|
+
import time
|
|
46
|
+
from collections import namedtuple
|
|
47
|
+
|
|
48
|
+
ButtonLayout = namedtuple("ButtonLayout", "left right")
|
|
49
|
+
KINDS = ("minimize", "maximize", "close")
|
|
50
|
+
DEFAULT = ButtonLayout((), ("minimize", "maximize", "close"))
|
|
51
|
+
|
|
52
|
+
_GSETTINGS = shutil.which("gsettings") or "/usr/bin/gsettings"
|
|
53
|
+
_GDBUS = shutil.which("gdbus") or "/usr/bin/gdbus"
|
|
54
|
+
|
|
55
|
+
_GNOME_SCHEMA = "org.gnome.desktop.wm.preferences"
|
|
56
|
+
_CINNAMON_SCHEMA = "org.cinnamon.desktop.wm.preferences"
|
|
57
|
+
_MATE_SCHEMA = "org.mate.Marco.general"
|
|
58
|
+
|
|
59
|
+
# Survives hotswap re-exec (module globals are reused): the last layout read,
|
|
60
|
+
# where it came from, when, and the probe thread.
|
|
61
|
+
_state = globals().get("_state") or {"layout": None, "source": None, "probed_at": 0.0, "thread": None}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Parsers
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
def _kinds(tokens, seen):
|
|
69
|
+
out = []
|
|
70
|
+
for token in tokens:
|
|
71
|
+
token = token.strip().lower()
|
|
72
|
+
if token in KINDS and token not in seen:
|
|
73
|
+
seen.add(token)
|
|
74
|
+
out.append(token)
|
|
75
|
+
return tuple(out)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def parse_gnome(text):
|
|
79
|
+
"""'appmenu:minimize,maximize,close' → ButtonLayout. No colon = all on
|
|
80
|
+
the left (mutter / GTK); unknown tokens dropped; a kind appears once."""
|
|
81
|
+
text = (text or "").strip().strip("'\"").strip()
|
|
82
|
+
if ":" in text:
|
|
83
|
+
left_text, right_text = text.split(":", 1)
|
|
84
|
+
else:
|
|
85
|
+
left_text, right_text = text, ""
|
|
86
|
+
seen = set()
|
|
87
|
+
left = _kinds(left_text.split(","), seen)
|
|
88
|
+
right = _kinds(right_text.split(","), seen)
|
|
89
|
+
return ButtonLayout(left, right)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
_KDE_LETTERS = {"I": "minimize", "A": "maximize", "X": "close"}
|
|
93
|
+
_XFWM_LETTERS = {"H": "minimize", "M": "maximize", "C": "close"}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _letters(text, table, seen):
|
|
97
|
+
out = []
|
|
98
|
+
for letter in (text or "").strip():
|
|
99
|
+
kind = table.get(letter.upper())
|
|
100
|
+
if kind is not None and kind not in seen:
|
|
101
|
+
seen.add(kind)
|
|
102
|
+
out.append(kind)
|
|
103
|
+
return tuple(out)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def parse_kde(buttons_on_left, buttons_on_right):
|
|
107
|
+
"""kwinrc's ButtonsOnLeft / ButtonsOnRight letter strings."""
|
|
108
|
+
seen = set()
|
|
109
|
+
return ButtonLayout(_letters(buttons_on_left, _KDE_LETTERS, seen),
|
|
110
|
+
_letters(buttons_on_right, _KDE_LETTERS, seen))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def parse_xfwm(text):
|
|
114
|
+
"""xfwm4's button_layout ("O|SHMC"): the bar splits the sides; without
|
|
115
|
+
one everything is on the left, like GNOME."""
|
|
116
|
+
text = (text or "").strip()
|
|
117
|
+
left_text, right_text = (text.split("|", 1) + [""])[:2] if "|" in text else (text, "")
|
|
118
|
+
seen = set()
|
|
119
|
+
return ButtonLayout(_letters(left_text, _XFWM_LETTERS, seen),
|
|
120
|
+
_letters(right_text, _XFWM_LETTERS, seen))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# Readers
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
def _run(args, timeout=3.0):
|
|
128
|
+
"""stdout of a command, or None when it can't run / fails. Absolute
|
|
129
|
+
executable + close_fds=False → posix_spawn, never fork."""
|
|
130
|
+
if not args or not os.path.exists(args[0]):
|
|
131
|
+
return None
|
|
132
|
+
try:
|
|
133
|
+
proc = subprocess.run(args, capture_output=True, text=True, timeout=timeout, close_fds=False)
|
|
134
|
+
except (OSError, subprocess.SubprocessError):
|
|
135
|
+
return None
|
|
136
|
+
if proc.returncode != 0:
|
|
137
|
+
return None
|
|
138
|
+
return proc.stdout
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _config_home(environ):
|
|
142
|
+
return environ.get("XDG_CONFIG_HOME") or os.path.join(os.path.expanduser("~"), ".config")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _read_gsettings(schema, run=_run):
|
|
146
|
+
out = run([_GSETTINGS, "get", schema, "button-layout"])
|
|
147
|
+
if out is None or not out.strip():
|
|
148
|
+
return None
|
|
149
|
+
return parse_gnome(out), f"gsettings {schema}"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _read_portal(run=_run):
|
|
153
|
+
"""org.freedesktop.portal.Settings.Read — the answer prints as
|
|
154
|
+
`(<<'appmenu:minimize,maximize,close'>>,)`."""
|
|
155
|
+
out = run([_GDBUS, "call", "--session", "--dest", "org.freedesktop.portal.Desktop",
|
|
156
|
+
"--object-path", "/org/freedesktop/portal/desktop",
|
|
157
|
+
"--method", "org.freedesktop.portal.Settings.Read", _GNOME_SCHEMA, "button-layout"])
|
|
158
|
+
if out is None:
|
|
159
|
+
return None
|
|
160
|
+
match = re.search(r"'([^']*)'", out)
|
|
161
|
+
if match is None:
|
|
162
|
+
return None
|
|
163
|
+
return parse_gnome(match.group(1)), "settings portal"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _read_kde(environ):
|
|
167
|
+
path = os.path.join(_config_home(environ), "kwinrc")
|
|
168
|
+
if not os.path.isfile(path):
|
|
169
|
+
return None
|
|
170
|
+
parser = configparser.ConfigParser(strict=False, interpolation=None, delimiters=("=",))
|
|
171
|
+
parser.optionxform = str
|
|
172
|
+
try:
|
|
173
|
+
parser.read(path, encoding="utf-8")
|
|
174
|
+
except (configparser.Error, OSError, UnicodeDecodeError):
|
|
175
|
+
return None
|
|
176
|
+
section = "org.kde.kdecoration2"
|
|
177
|
+
# KWin's own defaults when the keys are absent: menu + on-all-desktops
|
|
178
|
+
# left, help / minimize / maximize / close right.
|
|
179
|
+
left = parser.get(section, "ButtonsOnLeft", fallback="MS") if parser.has_section(section) else "MS"
|
|
180
|
+
right = parser.get(section, "ButtonsOnRight", fallback="HIAX") if parser.has_section(section) else "HIAX"
|
|
181
|
+
return parse_kde(left, right), "kwinrc"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _read_xfwm(environ):
|
|
185
|
+
path = os.path.join(_config_home(environ), "xfce4", "xfconf", "xfce-perchannel-xml", "xfwm4.xml")
|
|
186
|
+
if not os.path.isfile(path):
|
|
187
|
+
return None
|
|
188
|
+
try:
|
|
189
|
+
import xml.etree.ElementTree as ElementTree
|
|
190
|
+
root = ElementTree.parse(path).getroot()
|
|
191
|
+
except Exception:
|
|
192
|
+
return None
|
|
193
|
+
for node in root.iter("property"):
|
|
194
|
+
if node.get("name") == "button_layout":
|
|
195
|
+
return parse_xfwm(node.get("value", "")), "xfwm4"
|
|
196
|
+
return parse_xfwm("O|SHMC"), "xfwm4 default"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def detect(environ=None, run=_run):
|
|
200
|
+
"""(ButtonLayout, source) for this desktop: the desktop's own store
|
|
201
|
+
first (XDG_CURRENT_DESKTOP), then the GNOME key via gsettings (most
|
|
202
|
+
desktops carry a dconf profile), the settings portal, and DEFAULT."""
|
|
203
|
+
environ = os.environ if environ is None else environ
|
|
204
|
+
desktops = (environ.get("XDG_CURRENT_DESKTOP") or "").lower().split(":")
|
|
205
|
+
probes = []
|
|
206
|
+
if "kde" in desktops:
|
|
207
|
+
probes.append(lambda: _read_kde(environ))
|
|
208
|
+
if "xfce" in desktops:
|
|
209
|
+
probes.append(lambda: _read_xfwm(environ))
|
|
210
|
+
if "x-cinnamon" in desktops or "cinnamon" in desktops:
|
|
211
|
+
probes.append(lambda: _read_gsettings(_CINNAMON_SCHEMA, run))
|
|
212
|
+
if "mate" in desktops:
|
|
213
|
+
probes.append(lambda: _read_gsettings(_MATE_SCHEMA, run))
|
|
214
|
+
probes.append(lambda: _read_gsettings(_GNOME_SCHEMA, run))
|
|
215
|
+
probes.append(lambda: _read_portal(run))
|
|
216
|
+
for probe in probes:
|
|
217
|
+
found = probe()
|
|
218
|
+
if found is not None:
|
|
219
|
+
return found
|
|
220
|
+
return DEFAULT, "default"
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
# A probe thread + the cached answer
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
def _probe():
|
|
228
|
+
try:
|
|
229
|
+
layout, source = detect()
|
|
230
|
+
except Exception:
|
|
231
|
+
layout, source = DEFAULT, "error"
|
|
232
|
+
changed = layout != _state["layout"]
|
|
233
|
+
_state.update(layout=layout, source=source, probed_at=time.monotonic())
|
|
234
|
+
if changed:
|
|
235
|
+
try:
|
|
236
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
237
|
+
request_render()
|
|
238
|
+
except Exception:
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def start_probe(force=False):
|
|
243
|
+
"""Read the layout on a daemon thread (idempotent while one runs; a
|
|
244
|
+
known layout is kept unless ``force``). Returns the thread, or None."""
|
|
245
|
+
thread = _state["thread"]
|
|
246
|
+
if thread is not None and thread.is_alive():
|
|
247
|
+
return thread
|
|
248
|
+
if _state["layout"] is not None and not force:
|
|
249
|
+
return None
|
|
250
|
+
thread = threading.Thread(target=_probe, name="titlebar-buttons", daemon=True)
|
|
251
|
+
thread.start()
|
|
252
|
+
_state["thread"] = thread
|
|
253
|
+
return thread
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def system_layout():
|
|
257
|
+
"""The desktop's layout, DEFAULT until the first probe lands (kicked
|
|
258
|
+
here if nobody started it)."""
|
|
259
|
+
layout = _state["layout"]
|
|
260
|
+
if layout is None:
|
|
261
|
+
start_probe()
|
|
262
|
+
return DEFAULT
|
|
263
|
+
return layout
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def source():
|
|
267
|
+
"""Where the current layout came from ("gsettings …", "kwinrc", …)."""
|
|
268
|
+
return _state["source"]
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def refresh(min_interval=2.0):
|
|
272
|
+
"""Re-read (a focus gain: the user may have changed the setting), at
|
|
273
|
+
most once per ``min_interval`` seconds."""
|
|
274
|
+
if time.monotonic() - _state["probed_at"] >= min_interval:
|
|
275
|
+
start_probe(force=True)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def refresh_if_stale(interval):
|
|
279
|
+
"""Per-frame: re-read once the last probe is ``interval`` seconds old."""
|
|
280
|
+
if interval and interval > 0:
|
|
281
|
+
refresh(min_interval=float(interval))
|