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,635 @@
|
|
|
1
|
+
"""wp_color_management_v1 for the studio's Wayland surface.
|
|
2
|
+
|
|
3
|
+
Tags the GLFW window's wl_surface with an IMAGE DESCRIPTION so the
|
|
4
|
+
compositor knows what the pixels mean. Without a tag the surface is plain
|
|
5
|
+
sRGB and the compositor maps 1.0 to its SDR reference white; with a PQ tag
|
|
6
|
+
the presentation pass (scene_target.present, Toggles.HDR.output = "pq")
|
|
7
|
+
encodes the linear scRGB scene as BT.2020 + ST 2084 and colours above
|
|
8
|
+
white(1) / outside sRGB reach the panel.
|
|
9
|
+
|
|
10
|
+
Marshalled by hand over libwayland-client's public API exactly like
|
|
11
|
+
wayland_move.py (whose ctypes mirrors and helpers this reuses): the
|
|
12
|
+
interfaces are built as `wl_interface` structs from the protocol tables
|
|
13
|
+
below, opcodes are the message indices, a second wl_registry on GLFW's
|
|
14
|
+
connection binds `wp_color_manager_v1`. A compositor without the global
|
|
15
|
+
(GNOME, an SDR Hyprland session) leaves `available()` False and the studio
|
|
16
|
+
stays an untagged sRGB window.
|
|
17
|
+
|
|
18
|
+
Flow (`sync(window)` once per frame from Melty.post_frame; "auto", the
|
|
19
|
+
default, resolves to "pq" whenever the manager is bound — resolved_output):
|
|
20
|
+
Toggles.HDR.output "pq" → create_parametric_creator → set_primaries_named
|
|
21
|
+
(bt2020) + set_tf_named (st2084_pq) +
|
|
22
|
+
set_luminances (desktop min, desktop max, reference) → create →
|
|
23
|
+
ready → get_surface → set_image_description
|
|
24
|
+
Toggles.HDR.output "srgb" → unset_image_description
|
|
25
|
+
The next commit (GLFW's swap) applies it. Hotswap-safe module state.
|
|
26
|
+
|
|
27
|
+
REFERENCE WHITE follows the desktop: `attach` also takes a
|
|
28
|
+
wp_color_management_surface_feedback_v1 and `query_preferred` reads the
|
|
29
|
+
compositor's PREFERRED parametric description for the surface
|
|
30
|
+
(get_preferred_parametric → get_information → the `luminances` event) — its
|
|
31
|
+
reference luminance is what the compositor shows an untagged sRGB window's
|
|
32
|
+
white as (Hyprland: the monitor's sdr_max_luminance, see Monitor.cpp
|
|
33
|
+
`applyCMType`), so `desired_reference()` hands it to set_luminances AND the
|
|
34
|
+
PQ encode and a colour of 1.0 lands at the same nits as every SDR window.
|
|
35
|
+
A `preferred_changed` event (the user moved the desktop's SDR white, or
|
|
36
|
+
the window changed monitor) re-queries and re-tags on the next sync.
|
|
37
|
+
Without the feature (no feedback, no luminances event) the fallback is
|
|
38
|
+
`Toggles.HDR.pq_reference_nits`; `Toggles.HDR.follow_desktop_white = False`
|
|
39
|
+
pins the toggle's value regardless.
|
|
40
|
+
"""
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import ctypes
|
|
44
|
+
|
|
45
|
+
import meltygui.core.windowing.window_api as glfw
|
|
46
|
+
|
|
47
|
+
from meltygui.core.windowing.wayland_move import _c
|
|
48
|
+
from meltygui.core.windowing.wayland_move import _iface_addr
|
|
49
|
+
from meltygui.core.windowing.wayland_move import _wl_interface
|
|
50
|
+
from meltygui.core.windowing.wayland_move import _wl_message
|
|
51
|
+
from meltygui.core.windowing.wayland_move import _wl_message_array
|
|
52
|
+
|
|
53
|
+
# Two scopes (see wayland_move for the same split): _STATE is the SURFACE's
|
|
54
|
+
# - its wp_color_management_surface, feedback, description, and mode -
|
|
55
|
+
# swapped per Surface by surface.swap; _CONN is the CONNECTION's - the
|
|
56
|
+
# registry, the bound manager and what it supports, the interface tables,
|
|
57
|
+
# the listener tables and their ctypes callbacks (`keep`) - bound once per
|
|
58
|
+
# wl_display and shared by every window (never swapped, never collected
|
|
59
|
+
# with the window).
|
|
60
|
+
_STATE = globals().get("_STATE") or {
|
|
61
|
+
"display": None, "surface": None,
|
|
62
|
+
"cm_surface": None, "description": None, "applied": None, "wanted": None,
|
|
63
|
+
"ready": None, "failed": None, "identity": None,
|
|
64
|
+
"reference_nits": None, "error": None, "attached_to": None,
|
|
65
|
+
}
|
|
66
|
+
_CONN = globals().get("_CONN") or {
|
|
67
|
+
"display": None, "registry": None, "manager": None, "manager_version": 0,
|
|
68
|
+
"supported_tf": set(), "supported_primaries": set(), "supported_features": set(),
|
|
69
|
+
"done": False, "keep": [], "ifaces": {}, "error": None,
|
|
70
|
+
}
|
|
71
|
+
# Fields added after the first release (a hotswapped module keeps the old dict).
|
|
72
|
+
for _key, _default in (("feedback", None), ("preferred_reference", None), ("preferred_luminances", None),
|
|
73
|
+
("preferred_dirty", False),
|
|
74
|
+
("preferred_query_failed", None), ("info_luminances", None), ("info_done", False),
|
|
75
|
+
("synced_reference", None)):
|
|
76
|
+
_STATE.setdefault(_key, _default)
|
|
77
|
+
|
|
78
|
+
# wp_color_manager_v1 enums (color-management-v1.xml)
|
|
79
|
+
TF_EXT_LINEAR, TF_SRGB, TF_EXT_SRGB, TF_ST2084_PQ, TF_HLG = 5, 9, 10, 11, 13
|
|
80
|
+
PRIMARIES_SRGB, PRIMARIES_BT2020, PRIMARIES_DISPLAY_P3 = 1, 6, 9
|
|
81
|
+
FEATURE_PARAMETRIC, FEATURE_SET_LUMINANCES, FEATURE_WINDOWS_SCRGB = 1, 4, 7
|
|
82
|
+
INTENT_PERCEPTUAL = 0
|
|
83
|
+
_BIND_VERSION = 1 # v1: enough for a parametric PQ / scRGB description
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
# Interfaces (message tables lifted from the XML: opcode = index)
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def _build_interfaces(wl, keep):
|
|
91
|
+
"""The five wl_interface structs this module speaks, kept alive for the
|
|
92
|
+
proxies' lifetime. A message we never send may carry None types."""
|
|
93
|
+
manager, surface, feedback = _wl_interface(), _wl_interface(), _wl_interface()
|
|
94
|
+
creator, desc, info = _wl_interface(), _wl_interface(), _wl_interface()
|
|
95
|
+
keep.extend([manager, surface, feedback, creator, desc, info])
|
|
96
|
+
|
|
97
|
+
def fill(iface, name, version, methods, events):
|
|
98
|
+
iface.name = name
|
|
99
|
+
iface.version = version
|
|
100
|
+
iface.method_count = len(methods)
|
|
101
|
+
iface.methods = ctypes.cast(_wl_message_array(methods, keep), ctypes.POINTER(_wl_message))
|
|
102
|
+
iface.event_count = len(events)
|
|
103
|
+
iface.events = ctypes.cast(_wl_message_array(events, keep), ctypes.POINTER(_wl_message))
|
|
104
|
+
|
|
105
|
+
a_desc, a_creator, a_surface, a_feedback, a_info = (ctypes.addressof(x) for x in
|
|
106
|
+
(desc, creator, surface, feedback, info))
|
|
107
|
+
wl_surface = _iface_addr(wl, "wl_surface_interface")
|
|
108
|
+
wl_output = _iface_addr(wl, "wl_output_interface")
|
|
109
|
+
|
|
110
|
+
fill(info, b"wp_image_description_info_v1", 1, [], [
|
|
111
|
+
(b"done", b"", []), (b"icc_file", b"hu", [None, None]),
|
|
112
|
+
(b"primaries", b"iiiiiiii", [None] * 8), (b"primaries_named", b"u", [None]),
|
|
113
|
+
(b"tf_power", b"u", [None]), (b"tf_named", b"u", [None]),
|
|
114
|
+
(b"luminances", b"uuu", [None] * 3), (b"target_primaries", b"iiiiiiii", [None] * 8),
|
|
115
|
+
(b"target_luminance", b"uu", [None, None]), (b"target_max_cll", b"u", [None]),
|
|
116
|
+
(b"target_max_fall", b"u", [None]),
|
|
117
|
+
])
|
|
118
|
+
fill(desc, b"wp_image_description_v1", 1, [
|
|
119
|
+
(b"destroy", b"", []), (b"get_information", b"n", [a_info]),
|
|
120
|
+
], [
|
|
121
|
+
(b"failed", b"us", [None, None]), (b"ready", b"u", [None]), (b"ready2", b"2uu", [None, None]),
|
|
122
|
+
])
|
|
123
|
+
fill(creator, b"wp_image_description_creator_params_v1", 1, [
|
|
124
|
+
(b"create", b"n", [a_desc]), (b"set_tf_named", b"u", [None]), (b"set_tf_power", b"u", [None]),
|
|
125
|
+
(b"set_primaries_named", b"u", [None]), (b"set_primaries", b"iiiiiiii", [None] * 8),
|
|
126
|
+
(b"set_luminances", b"uuu", [None] * 3),
|
|
127
|
+
(b"set_mastering_display_primaries", b"iiiiiiii", [None] * 8),
|
|
128
|
+
(b"set_mastering_luminance", b"uu", [None, None]),
|
|
129
|
+
(b"set_max_cll", b"u", [None]), (b"set_max_fall", b"u", [None]),
|
|
130
|
+
], [])
|
|
131
|
+
fill(surface, b"wp_color_management_surface_v1", 1, [
|
|
132
|
+
(b"destroy", b"", []), (b"set_image_description", b"ou", [a_desc, None]),
|
|
133
|
+
(b"unset_image_description", b"", []),
|
|
134
|
+
], [])
|
|
135
|
+
fill(feedback, b"wp_color_management_surface_feedback_v1", 1, [
|
|
136
|
+
(b"destroy", b"", []), (b"get_preferred", b"n", [a_desc]), (b"get_preferred_parametric", b"n", [a_desc]),
|
|
137
|
+
], [
|
|
138
|
+
(b"preferred_changed", b"u", [None]), (b"preferred_changed2", b"2uu", [None, None]),
|
|
139
|
+
])
|
|
140
|
+
fill(manager, b"wp_color_manager_v1", 1, [
|
|
141
|
+
(b"destroy", b"", []), (b"get_output", b"no", [None, wl_output]),
|
|
142
|
+
(b"get_surface", b"no", [a_surface, wl_surface]),
|
|
143
|
+
(b"get_surface_feedback", b"no", [a_feedback, wl_surface]),
|
|
144
|
+
(b"create_icc_creator", b"n", [None]), (b"create_parametric_creator", b"n", [a_creator]),
|
|
145
|
+
(b"create_windows_scrgb", b"n", [a_desc]), (b"get_image_description", b"2no", [a_desc, None]),
|
|
146
|
+
(b"create_windows_bt2100", b"3n", [a_desc]),
|
|
147
|
+
], [
|
|
148
|
+
(b"supported_intent", b"u", [None]), (b"supported_feature", b"u", [None]),
|
|
149
|
+
(b"supported_tf_named", b"u", [None]), (b"supported_primaries_named", b"u", [None]),
|
|
150
|
+
(b"done", b"", []),
|
|
151
|
+
])
|
|
152
|
+
return {"manager": manager, "surface": surface, "feedback": feedback,
|
|
153
|
+
"creator": creator, "desc": desc, "info": info}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
# Listeners
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
_GLOBAL_CB = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32,
|
|
161
|
+
ctypes.c_char_p, ctypes.c_uint32)
|
|
162
|
+
_GLOBAL_REMOVE_CB = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32)
|
|
163
|
+
_UINT_CB = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32)
|
|
164
|
+
_VOID_CB = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p)
|
|
165
|
+
_FAILED_CB = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32, ctypes.c_char_p)
|
|
166
|
+
_UINT2_CB = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32)
|
|
167
|
+
_UINT3_CB = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32,
|
|
168
|
+
ctypes.c_uint32)
|
|
169
|
+
_ICC_CB = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int32, ctypes.c_uint32)
|
|
170
|
+
_INT8_CB = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, *([ctypes.c_int32] * 8))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _on_global(data, registry, name, interface, version):
|
|
174
|
+
if interface == b"wp_color_manager_v1" and _CONN["manager"] is None:
|
|
175
|
+
_, wl = _c()
|
|
176
|
+
ver = min(int(version), _BIND_VERSION)
|
|
177
|
+
manager = wl.wl_proxy_marshal_flags(
|
|
178
|
+
registry, 0, ctypes.addressof(_CONN["ifaces"]["manager"]), ver, 0,
|
|
179
|
+
ctypes.c_uint32(name), ctypes.c_char_p(b"wp_color_manager_v1"),
|
|
180
|
+
ctypes.c_uint32(ver), ctypes.c_void_p(None))
|
|
181
|
+
if manager:
|
|
182
|
+
_CONN["manager"] = manager
|
|
183
|
+
_CONN["manager_version"] = ver
|
|
184
|
+
wl.wl_proxy_add_listener(manager, _CONN["manager_listener"], None)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _on_global_remove(data, registry, name):
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _on_intent(data, proxy, value):
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _on_feature(data, proxy, value):
|
|
196
|
+
_CONN["supported_features"].add(int(value))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _on_tf(data, proxy, value):
|
|
200
|
+
_CONN["supported_tf"].add(int(value))
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _on_primaries(data, proxy, value):
|
|
204
|
+
_CONN["supported_primaries"].add(int(value))
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _on_done(data, proxy):
|
|
208
|
+
_CONN["done"] = True
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _on_failed(data, proxy, cause, msg):
|
|
212
|
+
_STATE["failed"] = (int(cause), (msg or b"").decode(errors="replace"))
|
|
213
|
+
_STATE["ready"] = False
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _on_ready(data, proxy, identity):
|
|
217
|
+
_STATE["identity"] = int(identity)
|
|
218
|
+
_STATE["ready"] = True
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _on_ready2(data, proxy, hi, lo):
|
|
222
|
+
_STATE["identity"] = (int(hi) << 32) | int(lo)
|
|
223
|
+
_STATE["ready"] = True
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _on_preferred_changed(data, proxy, *identity):
|
|
227
|
+
# The compositor's preferred description for the surface changed (SDR
|
|
228
|
+
# white moved, monitor changed): re-read it on the next sync, which
|
|
229
|
+
# runs in post_frame — so ask for a frame (an idle app otherwise keeps
|
|
230
|
+
# the stale tag until the next input).
|
|
231
|
+
_STATE["preferred_dirty"] = True
|
|
232
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
233
|
+
request_render()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _on_info_luminances(data, proxy, min_lum, max_lum, reference):
|
|
237
|
+
# min is in 0.0001 cd/m², max and reference in cd/m²
|
|
238
|
+
_STATE["info_luminances"] = (int(min_lum) / 10000.0, float(max_lum), float(reference))
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _on_info_done(data, proxy):
|
|
242
|
+
_STATE["info_done"] = True
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _on_info_icc(data, proxy, fd, size):
|
|
246
|
+
import os
|
|
247
|
+
try:
|
|
248
|
+
os.close(int(fd)) # never sent for a parametric info; a leak otherwise
|
|
249
|
+
except OSError:
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _on_info_ignore(data, proxy, *args):
|
|
254
|
+
pass
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _table(callbacks):
|
|
258
|
+
cbs = [ctor(fn) for ctor, fn in callbacks]
|
|
259
|
+
table = (ctypes.c_void_p * len(cbs))(*[ctypes.cast(cb, ctypes.c_void_p).value for cb in cbs])
|
|
260
|
+
_CONN["keep"].extend(cbs + [table])
|
|
261
|
+
return ctypes.addressof(table)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _build_listeners():
|
|
265
|
+
_CONN["registry_listener"] = _table([(_GLOBAL_CB, _on_global), (_GLOBAL_REMOVE_CB, _on_global_remove)])
|
|
266
|
+
_CONN["manager_listener"] = _table([(_UINT_CB, _on_intent), (_UINT_CB, _on_feature),
|
|
267
|
+
(_UINT_CB, _on_tf), (_UINT_CB, _on_primaries),
|
|
268
|
+
(_VOID_CB, _on_done)])
|
|
269
|
+
_CONN["desc_listener"] = _table([(_FAILED_CB, _on_failed), (_UINT_CB, _on_ready),
|
|
270
|
+
(_UINT2_CB, _on_ready2)])
|
|
271
|
+
_CONN["feedback_listener"] = _table([(_UINT_CB, _on_preferred_changed),
|
|
272
|
+
(_UINT2_CB, _on_preferred_changed)])
|
|
273
|
+
# wp_image_description_info_v1 events, in table order (see _build_interfaces)
|
|
274
|
+
_CONN["info_listener"] = _table([
|
|
275
|
+
(_VOID_CB, _on_info_done), (_ICC_CB, _on_info_icc), (_INT8_CB, _on_info_ignore),
|
|
276
|
+
(_UINT_CB, _on_info_ignore), (_UINT_CB, _on_info_ignore), (_UINT_CB, _on_info_ignore),
|
|
277
|
+
(_UINT3_CB, _on_info_luminances), (_INT8_CB, _on_info_ignore), (_UINT2_CB, _on_info_ignore),
|
|
278
|
+
(_UINT_CB, _on_info_ignore), (_UINT_CB, _on_info_ignore),
|
|
279
|
+
])
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# ---------------------------------------------------------------------------
|
|
283
|
+
# Attach / describe / apply
|
|
284
|
+
# ---------------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
def attach(display, surface) -> bool:
|
|
287
|
+
"""Bind the colour manager on `display` (a wl_display*) for `surface`
|
|
288
|
+
(a wl_surface*). Returns True when the compositor offers it; the reason
|
|
289
|
+
for a False sits in last_error(). Idempotent per (display, surface)."""
|
|
290
|
+
if _STATE["attached_to"] == (display, surface):
|
|
291
|
+
return available()
|
|
292
|
+
_STATE["attached_to"] = (display, surface)
|
|
293
|
+
try:
|
|
294
|
+
_, wl = _c()
|
|
295
|
+
if not display or not surface:
|
|
296
|
+
_STATE["error"] = "no Wayland display/surface"
|
|
297
|
+
return False
|
|
298
|
+
if _STATE["display"] != display or _STATE["surface"] != surface:
|
|
299
|
+
_STATE.update(cm_surface=None, description=None, applied=None, wanted=None,
|
|
300
|
+
feedback=None, preferred_reference=None, preferred_luminances=None,
|
|
301
|
+
preferred_dirty=False, preferred_query_failed=None, synced_reference=None)
|
|
302
|
+
_STATE.update(display=display, surface=surface)
|
|
303
|
+
if not hasattr(wl, "_melty_color_bound"):
|
|
304
|
+
wl.wl_proxy_destroy.argtypes = [ctypes.c_void_p]
|
|
305
|
+
wl.wl_proxy_destroy.restype = None
|
|
306
|
+
wl._melty_color_bound = True
|
|
307
|
+
if not _bind_connection(display):
|
|
308
|
+
_STATE["error"] = _CONN["error"]
|
|
309
|
+
return False
|
|
310
|
+
_STATE["error"] = None
|
|
311
|
+
query_preferred() # the desktop's SDR white; a failure here only means the fallback
|
|
312
|
+
return True
|
|
313
|
+
except Exception as e: # never take the studio down over a colour tag
|
|
314
|
+
_STATE["error"] = f"{type(e).__name__}: {e}"
|
|
315
|
+
return False
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _bind_connection(display) -> bool:
|
|
319
|
+
"""Once per wl_display: the registry, the colour manager and its
|
|
320
|
+
supported_* tables. False with the reason in _CONN["error"]."""
|
|
321
|
+
_, wl = _c()
|
|
322
|
+
if _CONN["display"] != display:
|
|
323
|
+
# A new connection (fresh GLFW instance): the old proxies are dangling.
|
|
324
|
+
_CONN.update(registry=None, manager=None, manager_version=0, done=False, supported_tf=set(),
|
|
325
|
+
supported_primaries=set(), supported_features=set(), error=None)
|
|
326
|
+
_CONN["display"] = display
|
|
327
|
+
if not _CONN["ifaces"]:
|
|
328
|
+
_CONN["ifaces"] = _build_interfaces(wl, _CONN["keep"])
|
|
329
|
+
_build_listeners()
|
|
330
|
+
if _CONN["registry"] is None:
|
|
331
|
+
reg_iface = _iface_addr(wl, "wl_registry_interface")
|
|
332
|
+
registry = wl.wl_proxy_marshal_flags(display, 1, reg_iface, wl.wl_proxy_get_version(display), 0,
|
|
333
|
+
ctypes.c_void_p(None))
|
|
334
|
+
if not registry:
|
|
335
|
+
_CONN["error"] = "wl_display.get_registry failed"
|
|
336
|
+
return False
|
|
337
|
+
_CONN["registry"] = registry
|
|
338
|
+
wl.wl_proxy_add_listener(registry, _CONN["registry_listener"], None)
|
|
339
|
+
wl.wl_display_roundtrip(display) # globals → manager bound
|
|
340
|
+
wl.wl_display_roundtrip(display) # manager's supported_* + done
|
|
341
|
+
if _CONN["manager"] is None:
|
|
342
|
+
_CONN["error"] = "compositor offers no wp_color_manager_v1"
|
|
343
|
+
return False
|
|
344
|
+
_CONN["error"] = None
|
|
345
|
+
return True
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def detach(surface=None):
|
|
349
|
+
"""The surface is going away (Surface.destroy, before glfw.destroy_window):
|
|
350
|
+
destroy ITS colour-management objects — feedback, surface tag,
|
|
351
|
+
description — and forget them. The connection's manager stays bound
|
|
352
|
+
for the other windows. ``surface`` None: whatever _STATE holds."""
|
|
353
|
+
if surface is not None and _STATE["surface"] != surface:
|
|
354
|
+
return
|
|
355
|
+
for key in ("feedback", "cm_surface", "description"):
|
|
356
|
+
try:
|
|
357
|
+
_destroy(key)
|
|
358
|
+
except Exception:
|
|
359
|
+
_STATE[key] = None
|
|
360
|
+
_STATE.update(surface=None, attached_to=None, applied=None, wanted=None, synced_reference=None,
|
|
361
|
+
preferred_reference=None, preferred_luminances=None, preferred_dirty=False)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def available() -> bool:
|
|
365
|
+
return bool(_CONN["manager"] and _STATE["error"] is None)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def last_error():
|
|
369
|
+
return _STATE["error"]
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def supported() -> dict:
|
|
373
|
+
return {"tf": sorted(_CONN["supported_tf"]), "primaries": sorted(_CONN["supported_primaries"]),
|
|
374
|
+
"features": sorted(_CONN["supported_features"]), "version": _CONN["manager_version"]}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _destroy(key):
|
|
378
|
+
proxy = _STATE.get(key)
|
|
379
|
+
if proxy:
|
|
380
|
+
_, wl = _c()
|
|
381
|
+
wl.wl_proxy_marshal_flags(proxy, 0, ctypes.c_void_p(None), wl.wl_proxy_get_version(proxy),
|
|
382
|
+
1) # WL_MARSHAL_FLAG_DESTROY: request 0 is destroy on all of these
|
|
383
|
+
_STATE[key] = None
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def create_description(primaries: int, tf: int, reference_nits: float | None = None,
|
|
387
|
+
max_nits: float = 10000.0, min_nits: float = 0.0) -> bool:
|
|
388
|
+
"""A parametric image description; blocks for the compositor's ready /
|
|
389
|
+
failed. On success it replaces `_STATE["description"]`."""
|
|
390
|
+
_, wl = _c()
|
|
391
|
+
manager, display = _CONN["manager"], _STATE["display"]
|
|
392
|
+
if not manager:
|
|
393
|
+
return False
|
|
394
|
+
if tf not in _CONN["supported_tf"] or primaries not in _CONN["supported_primaries"]:
|
|
395
|
+
_STATE["error"] = f"compositor lacks tf {tf} / primaries {primaries}: {supported()}"
|
|
396
|
+
return False
|
|
397
|
+
ifaces = _CONN["ifaces"]
|
|
398
|
+
ver = _CONN["manager_version"]
|
|
399
|
+
creator = wl.wl_proxy_marshal_flags(manager, 5, ctypes.addressof(ifaces["creator"]), ver, 0,
|
|
400
|
+
ctypes.c_void_p(None))
|
|
401
|
+
if not creator:
|
|
402
|
+
_STATE["error"] = "create_parametric_creator failed"
|
|
403
|
+
return False
|
|
404
|
+
wl.wl_proxy_marshal_flags(creator, 3, ctypes.c_void_p(None), ver, 0, ctypes.c_uint32(primaries))
|
|
405
|
+
wl.wl_proxy_marshal_flags(creator, 1, ctypes.c_void_p(None), ver, 0, ctypes.c_uint32(tf))
|
|
406
|
+
used_reference = None
|
|
407
|
+
if reference_nits and FEATURE_SET_LUMINANCES in _CONN["supported_features"]:
|
|
408
|
+
# min_lum is in 0.0001 cd/m², the others in cd/m²
|
|
409
|
+
wl.wl_proxy_marshal_flags(creator, 5, ctypes.c_void_p(None), ver, 0,
|
|
410
|
+
ctypes.c_uint32(int(round(min_nits * 10000))), ctypes.c_uint32(int(max_nits)),
|
|
411
|
+
ctypes.c_uint32(int(round(reference_nits))))
|
|
412
|
+
used_reference = float(int(round(reference_nits)))
|
|
413
|
+
_STATE.update(ready=None, failed=None, identity=None)
|
|
414
|
+
# create(new_id) consumes the creator (the protocol destroys it); we
|
|
415
|
+
# still free the client proxy.
|
|
416
|
+
desc = wl.wl_proxy_marshal_flags(creator, 0, ctypes.addressof(ifaces["desc"]), ver, 0,
|
|
417
|
+
ctypes.c_void_p(None))
|
|
418
|
+
wl.wl_proxy_destroy(creator)
|
|
419
|
+
if not desc:
|
|
420
|
+
_STATE["error"] = "image description create failed"
|
|
421
|
+
return False
|
|
422
|
+
wl.wl_proxy_add_listener(desc, _CONN["desc_listener"], None)
|
|
423
|
+
for _ in range(4):
|
|
424
|
+
wl.wl_display_roundtrip(display)
|
|
425
|
+
if _STATE["ready"] is not None:
|
|
426
|
+
break
|
|
427
|
+
if not _STATE["ready"]:
|
|
428
|
+
_STATE["error"] = f"image description failed: {_STATE['failed']}"
|
|
429
|
+
wl.wl_proxy_marshal_flags(desc, 0, ctypes.c_void_p(None), ver, 1)
|
|
430
|
+
return False
|
|
431
|
+
_destroy("description")
|
|
432
|
+
_STATE["description"] = desc
|
|
433
|
+
_STATE["reference_nits"] = used_reference
|
|
434
|
+
return True
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def set_surface_description(intent: int = INTENT_PERCEPTUAL) -> bool:
|
|
438
|
+
"""Tag the surface with the current description (create_description first)."""
|
|
439
|
+
_, wl = _c()
|
|
440
|
+
manager, surface, desc = _CONN["manager"], _STATE["surface"], _STATE["description"]
|
|
441
|
+
if not (manager and surface and desc):
|
|
442
|
+
return False
|
|
443
|
+
ver = _CONN["manager_version"]
|
|
444
|
+
if _STATE["cm_surface"] is None:
|
|
445
|
+
cm = wl.wl_proxy_marshal_flags(manager, 2, ctypes.addressof(_CONN["ifaces"]["surface"]), ver, 0,
|
|
446
|
+
ctypes.c_void_p(None), ctypes.c_void_p(surface))
|
|
447
|
+
if not cm:
|
|
448
|
+
_STATE["error"] = "get_surface failed"
|
|
449
|
+
return False
|
|
450
|
+
_STATE["cm_surface"] = cm
|
|
451
|
+
wl.wl_proxy_marshal_flags(_STATE["cm_surface"], 1, ctypes.c_void_p(None), ver, 0,
|
|
452
|
+
ctypes.c_void_p(desc), ctypes.c_uint32(intent))
|
|
453
|
+
wl.wl_display_flush(_STATE["display"])
|
|
454
|
+
return True
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def unset_surface_description() -> bool:
|
|
458
|
+
_, wl = _c()
|
|
459
|
+
cm = _STATE["cm_surface"]
|
|
460
|
+
if not cm:
|
|
461
|
+
return True
|
|
462
|
+
wl.wl_proxy_marshal_flags(cm, 2, ctypes.c_void_p(None), _CONN["manager_version"], 0)
|
|
463
|
+
wl.wl_display_flush(_STATE["display"])
|
|
464
|
+
return True
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def reference_nits():
|
|
468
|
+
"""The reference white the compositor was told for the applied PQ tag,
|
|
469
|
+
or None (untagged / luminances unsupported → the compositor's default)."""
|
|
470
|
+
return _STATE["reference_nits"] if _STATE["applied"] == "pq" else None
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _ensure_feedback() -> bool:
|
|
474
|
+
_, wl = _c()
|
|
475
|
+
manager, surface = _CONN["manager"], _STATE["surface"]
|
|
476
|
+
if not (manager and surface):
|
|
477
|
+
return False
|
|
478
|
+
if _STATE["feedback"] is None:
|
|
479
|
+
fb = wl.wl_proxy_marshal_flags(manager, 3, ctypes.addressof(_CONN["ifaces"]["feedback"]),
|
|
480
|
+
_CONN["manager_version"], 0, ctypes.c_void_p(None),
|
|
481
|
+
ctypes.c_void_p(surface))
|
|
482
|
+
if not fb:
|
|
483
|
+
_STATE["preferred_query_failed"] = "get_surface_feedback failed"
|
|
484
|
+
return False
|
|
485
|
+
_STATE["feedback"] = fb
|
|
486
|
+
wl.wl_proxy_add_listener(fb, _CONN["feedback_listener"], None)
|
|
487
|
+
return True
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def query_preferred() -> float | None:
|
|
491
|
+
"""Ask the compositor for the surface's PREFERRED parametric image
|
|
492
|
+
description and read its reference luminance (nits) — the desktop's SDR
|
|
493
|
+
white. Blocks for a few roundtrips. Returns the reference (also kept in
|
|
494
|
+
`_STATE["preferred_reference"]`), or None with the reason in
|
|
495
|
+
`preferred_query_failed()`."""
|
|
496
|
+
_, wl = _c()
|
|
497
|
+
_STATE["preferred_dirty"] = False
|
|
498
|
+
if not _ensure_feedback():
|
|
499
|
+
return None
|
|
500
|
+
ver, display = _CONN["manager_version"], _STATE["display"]
|
|
501
|
+
ifaces = _CONN["ifaces"]
|
|
502
|
+
_STATE.update(ready=None, failed=None, identity=None, info_luminances=None, info_done=False)
|
|
503
|
+
desc = wl.wl_proxy_marshal_flags(_STATE["feedback"], 2, ctypes.addressof(ifaces["desc"]), ver, 0,
|
|
504
|
+
ctypes.c_void_p(None))
|
|
505
|
+
if not desc:
|
|
506
|
+
_STATE["preferred_query_failed"] = "get_preferred_parametric failed"
|
|
507
|
+
return None
|
|
508
|
+
wl.wl_proxy_add_listener(desc, _CONN["desc_listener"], None)
|
|
509
|
+
info = None
|
|
510
|
+
try:
|
|
511
|
+
for _ in range(4):
|
|
512
|
+
wl.wl_display_roundtrip(display)
|
|
513
|
+
if _STATE["ready"] is not None:
|
|
514
|
+
break
|
|
515
|
+
if not _STATE["ready"]:
|
|
516
|
+
_STATE["preferred_query_failed"] = f"preferred description failed: {_STATE['failed']}"
|
|
517
|
+
return None
|
|
518
|
+
info = wl.wl_proxy_marshal_flags(desc, 1, ctypes.addressof(ifaces["info"]), ver, 0,
|
|
519
|
+
ctypes.c_void_p(None))
|
|
520
|
+
if not info:
|
|
521
|
+
_STATE["preferred_query_failed"] = "get_information failed"
|
|
522
|
+
return None
|
|
523
|
+
wl.wl_proxy_add_listener(info, _CONN["info_listener"], None)
|
|
524
|
+
for _ in range(4):
|
|
525
|
+
wl.wl_display_roundtrip(display)
|
|
526
|
+
if _STATE["info_done"]:
|
|
527
|
+
break
|
|
528
|
+
lums = _STATE["info_luminances"]
|
|
529
|
+
if not lums or lums[2] <= 0:
|
|
530
|
+
_STATE["preferred_query_failed"] = "preferred description carries no luminances"
|
|
531
|
+
return None
|
|
532
|
+
_STATE["preferred_reference"] = float(lums[2])
|
|
533
|
+
_STATE["preferred_luminances"] = (float(lums[0]), float(lums[1]), float(lums[2]))
|
|
534
|
+
_STATE["preferred_query_failed"] = None
|
|
535
|
+
return _STATE["preferred_reference"]
|
|
536
|
+
finally:
|
|
537
|
+
# The info object has no destroy request (it ends with `done`); the
|
|
538
|
+
# description is destroyed through special request 0.
|
|
539
|
+
if info:
|
|
540
|
+
wl.wl_proxy_destroy(info)
|
|
541
|
+
wl.wl_proxy_marshal_flags(desc, 0, ctypes.c_void_p(None), ver, 1)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def preferred_reference():
|
|
545
|
+
"""The compositor's reference white for this surface in nits, or None."""
|
|
546
|
+
return _STATE["preferred_reference"]
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def preferred_query_failed():
|
|
550
|
+
return _STATE["preferred_query_failed"]
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def desired_reference() -> float:
|
|
554
|
+
"""Reference white (nits) for the PQ tag + encode: the desktop's, when it
|
|
555
|
+
told us and `Toggles.HDR.follow_desktop_white`; else the toggle."""
|
|
556
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
557
|
+
if _STATE["preferred_dirty"] and available():
|
|
558
|
+
try:
|
|
559
|
+
query_preferred()
|
|
560
|
+
except Exception as e:
|
|
561
|
+
_STATE["preferred_query_failed"] = f"{type(e).__name__}: {e}"
|
|
562
|
+
if Toggles.HDR.follow_desktop_white and _STATE["preferred_reference"]:
|
|
563
|
+
return float(_STATE["preferred_reference"])
|
|
564
|
+
return float(Toggles.HDR.pq_reference_nits)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def desired_luminances() -> tuple[float, float, float]:
|
|
568
|
+
"""(min, max, reference) nits for the PQ tag. The range is the
|
|
569
|
+
DESKTOP's (its preferred description: the panel's peak, 1241 here), not
|
|
570
|
+
PQ's 0..10000: Hyprland tone-maps a surface whose declared max exceeds
|
|
571
|
+
the output's (getCMSettings needsTonemap, max >= dst * 1.01) — the
|
|
572
|
+
10000-nit frog came out compressed under the panel's peak while
|
|
573
|
+
Chromium, which echoes the preferred range, showed it clipped at the
|
|
574
|
+
peak as it is (Lukas 09-11). Declaring the desktop's range makes the
|
|
575
|
+
compositor pass our nits through and clip at the panel, like Chromium."""
|
|
576
|
+
reference = desired_reference()
|
|
577
|
+
lums = _STATE["preferred_luminances"]
|
|
578
|
+
if lums and lums[1] > reference:
|
|
579
|
+
return (lums[0], lums[1], reference)
|
|
580
|
+
return (0.0, 10000.0, reference)
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def applied():
|
|
584
|
+
return _STATE["applied"]
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def sync(window=None) -> str | None:
|
|
588
|
+
"""Once per frame: make the surface tag follow Toggles.HDR.output.
|
|
589
|
+
Cheap when nothing changed. Returns the applied mode."""
|
|
590
|
+
if not available():
|
|
591
|
+
return _STATE["applied"]
|
|
592
|
+
wanted = resolved_output()
|
|
593
|
+
lums = desired_luminances() if wanted == "pq" else None
|
|
594
|
+
if wanted == _STATE["wanted"] and lums == _STATE["synced_reference"]:
|
|
595
|
+
return _STATE["applied"]
|
|
596
|
+
_STATE["wanted"] = wanted
|
|
597
|
+
_STATE["synced_reference"] = lums
|
|
598
|
+
try:
|
|
599
|
+
if wanted == "pq":
|
|
600
|
+
if create_description(PRIMARIES_BT2020, TF_ST2084_PQ, reference_nits=lums[2],
|
|
601
|
+
max_nits=lums[1], min_nits=lums[0]) \
|
|
602
|
+
and set_surface_description():
|
|
603
|
+
_STATE["applied"] = "pq"
|
|
604
|
+
else:
|
|
605
|
+
print(f"wayland_color: PQ tag not applied: {last_error()}")
|
|
606
|
+
_STATE["wanted"] = None # retry next frame's toggle edge
|
|
607
|
+
else:
|
|
608
|
+
unset_surface_description()
|
|
609
|
+
_STATE["applied"] = "srgb"
|
|
610
|
+
except Exception as e:
|
|
611
|
+
_STATE["error"] = f"{type(e).__name__}: {e}"
|
|
612
|
+
print(f"wayland_color: {e}")
|
|
613
|
+
return _STATE["applied"]
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def resolved_output() -> str:
|
|
617
|
+
"""Toggles.HDR.output with "auto" resolved: PQ when the compositor offers
|
|
618
|
+
colour management, sRGB otherwise."""
|
|
619
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
620
|
+
mode = Toggles.HDR.output
|
|
621
|
+
if mode == "auto":
|
|
622
|
+
return "pq" if available() else "srgb"
|
|
623
|
+
return "pq" if mode == "pq" else "srgb"
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def attach_window(window) -> bool:
|
|
627
|
+
"""attach() for the GLFW studio window."""
|
|
628
|
+
try:
|
|
629
|
+
if glfw.get_platform() != glfw.PLATFORM_WAYLAND:
|
|
630
|
+
_STATE["error"] = "not a Wayland session"
|
|
631
|
+
return False
|
|
632
|
+
return attach(glfw.get_wayland_display(), glfw.get_wayland_window(window))
|
|
633
|
+
except Exception as e:
|
|
634
|
+
_STATE["error"] = f"{type(e).__name__}: {e}"
|
|
635
|
+
return False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Shared framework input machinery."""
|