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.
Files changed (372) hide show
  1. meltygui/__init__.py +107 -0
  2. meltygui/accounts/__init__.py +0 -0
  3. meltygui/accounts/internet_accounts.py +1355 -0
  4. meltygui/chat/__init__.py +91 -0
  5. meltygui/chat/activity.py +75 -0
  6. meltygui/chat/backends.py +36 -0
  7. meltygui/chat/chat_interface.py +732 -0
  8. meltygui/chat/chat_proxy.py +352 -0
  9. meltygui/chat/codex_proxy.py +592 -0
  10. meltygui/chat/codex_settings.py +100 -0
  11. meltygui/chat/codex_transport.py +60 -0
  12. meltygui/chat/command_parser.py +204 -0
  13. meltygui/chat/images.py +227 -0
  14. meltygui/chat/messages.py +417 -0
  15. meltygui/chat/metadata.py +139 -0
  16. meltygui/chat/writer_locks.py +64 -0
  17. meltygui/code/__init__.py +0 -0
  18. meltygui/code/basic_converters.py +533 -0
  19. meltygui/code/chain_converters.py +2111 -0
  20. meltygui/code/code_checks.py +2209 -0
  21. meltygui/code/core_syntax.py +1430 -0
  22. meltygui/code/file_converters.py +1933 -0
  23. meltygui/code/fileref.py +702 -0
  24. meltygui/code/hotswap_guard.py +144 -0
  25. meltygui/code/libcst_conversion.py +9724 -0
  26. meltygui/code/live_instrument.py +392 -0
  27. meltygui/code/live_view.py +2490 -0
  28. meltygui/code/melty_scan.py +2684 -0
  29. meltygui/code/new_codecs.py +1255 -0
  30. meltygui/code/new_converters.py +3017 -0
  31. meltygui/code/project_code.py +278 -0
  32. meltygui/code/source_context.py +63 -0
  33. meltygui/code/symbol_roster.py +1588 -0
  34. meltygui/code/syntax_check.py +34 -0
  35. meltygui/code/syntax_check_worker.py +114 -0
  36. meltygui/completion/__init__.py +0 -0
  37. meltygui/completion/fim.py +1232 -0
  38. meltygui/completion/fim_context.py +481 -0
  39. meltygui/completion/providers/__init__.py +0 -0
  40. meltygui/completion/providers/anthropic_oauth.py +446 -0
  41. meltygui/completion/providers/anthropic_requests.py +69 -0
  42. meltygui/completion/providers/claude.py +203 -0
  43. meltygui/completion/providers/claude_usage.py +499 -0
  44. meltygui/completion/providers/codex_accounts.py +180 -0
  45. meltygui/completion/providers/copilot.py +617 -0
  46. meltygui/completion/providers/oauth_popup.py +220 -0
  47. meltygui/completion/providers/ollama.py +320 -0
  48. meltygui/completion/providers/profiles.py +23 -0
  49. meltygui/core/README.md +88 -0
  50. meltygui/core/__init__.py +1 -0
  51. meltygui/core/automation/__init__.py +1 -0
  52. meltygui/core/automation/action_core.py +153 -0
  53. meltygui/core/automation/collection_action.py +45 -0
  54. meltygui/core/automation/mcp_eval.py +167 -0
  55. meltygui/core/automation/mcp_hotswap.py +107 -0
  56. meltygui/core/automation/mcp_query.py +552 -0
  57. meltygui/core/automation/mcp_server.py +657 -0
  58. meltygui/core/automation/orchestration_core.py +2636 -0
  59. meltygui/core/automation/query_core.py +124 -0
  60. meltygui/core/automation/search_core.py +26 -0
  61. meltygui/core/automation/selector_core.py +340 -0
  62. meltygui/core/automation/value_core.py +1752 -0
  63. meltygui/core/cache/__init__.py +1 -0
  64. meltygui/core/cache/cache_diagnostics.py +0 -0
  65. meltygui/core/cache/invalidation_decoration.py +153 -0
  66. meltygui/core/cache/invalidation_tracker.py +43 -0
  67. meltygui/core/cache/tile_cache.py +5968 -0
  68. meltygui/core/conversion/__init__.py +1 -0
  69. meltygui/core/conversion/bubbling.py +599 -0
  70. meltygui/core/conversion/cache_tree.py +188 -0
  71. meltygui/core/conversion/chain.py +113 -0
  72. meltygui/core/conversion/converter_register.py +145 -0
  73. meltygui/core/conversion/data_decoration.py +67 -0
  74. meltygui/core/conversion/dict_conversion.py +1815 -0
  75. meltygui/core/conversion/dict_conversion_util.py +177 -0
  76. meltygui/core/conversion/dynamic_obj.py +89 -0
  77. meltygui/core/conversion/graph_compare.py +183 -0
  78. meltygui/core/conversion/load_save_v2.py +1032 -0
  79. meltygui/core/conversion/missing_saved_class.py +39 -0
  80. meltygui/core/conversion/path_finder.py +606 -0
  81. meltygui/core/conversion/render_host.py +1083 -0
  82. meltygui/core/core_render.py +6537 -0
  83. meltygui/core/definition_hotswap.py +231 -0
  84. meltygui/core/diagnostics/__init__.py +1 -0
  85. meltygui/core/diagnostics/attribute_churn.py +38 -0
  86. meltygui/core/diagnostics/fps_counter.py +39 -0
  87. meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
  88. meltygui/core/diagnostics/inspection_core.py +169 -0
  89. meltygui/core/diagnostics/monitor_core.py +105 -0
  90. meltygui/core/diagnostics/notifications.py +706 -0
  91. meltygui/core/diagnostics/perf_trace.py +281 -0
  92. meltygui/core/diagnostics/profile_decoration.py +88 -0
  93. meltygui/core/diagnostics/resize_trace.py +62 -0
  94. meltygui/core/diagnostics/screenshot_core.py +247 -0
  95. meltygui/core/diagnostics/session_status.py +98 -0
  96. meltygui/core/diagnostics/trace_core.py +445 -0
  97. meltygui/core/files/__init__.py +1 -0
  98. meltygui/core/files/file_core.py +208 -0
  99. meltygui/core/files/file_explorer_core.py +104 -0
  100. meltygui/core/files/file_tree_core.py +198 -0
  101. meltygui/core/files/file_watch_core.py +43 -0
  102. meltygui/core/files/import_graph_core.py +43 -0
  103. meltygui/core/files/metadata_core.py +51 -0
  104. meltygui/core/graphics/__init__.py +1 -0
  105. meltygui/core/graphics/cuda_context_core.py +166 -0
  106. meltygui/core/graphics/cuda_interop_core.py +136 -0
  107. meltygui/core/graphics/cuda_kernel_core.py +91 -0
  108. meltygui/core/graphics/framebuffer_recorder.py +337 -0
  109. meltygui/core/graphics/gl_state.py +658 -0
  110. meltygui/core/graphics/lut_core.py +52 -0
  111. meltygui/core/graphics/overlay_renderer.py +984 -0
  112. meltygui/core/graphics/scene_target.py +180 -0
  113. meltygui/core/graphics/screenshot.py +439 -0
  114. meltygui/core/graphics/shader_func.py +478 -0
  115. meltygui/core/graphics/tensor_core.py +45 -0
  116. meltygui/core/graphics/text_texture.py +329 -0
  117. meltygui/core/graphics/wayland_color.py +635 -0
  118. meltygui/core/input/__init__.py +1 -0
  119. meltygui/core/input/collision.py +165 -0
  120. meltygui/core/input/drag_drop_core.py +1525 -0
  121. meltygui/core/input/hypr_left_drag.py +323 -0
  122. meltygui/core/input/input_core.py +245 -0
  123. meltygui/core/input/input_handler.py +1101 -0
  124. meltygui/core/input/mouse_cursor.py +355 -0
  125. meltygui/core/input/pynput_backend.py +1054 -0
  126. meltygui/core/input/space_mouse.py +338 -0
  127. meltygui/core/input/touchpad_backend.py +393 -0
  128. meltygui/core/input/view_selection.py +177 -0
  129. meltygui/core/layout/__init__.py +1 -0
  130. meltygui/core/layout/column_core.py +2153 -0
  131. meltygui/core/layout/cursor_core.py +161 -0
  132. meltygui/core/layout/dropdown_core.py +278 -0
  133. meltygui/core/layout/edge_constraints.py +155 -0
  134. meltygui/core/layout/grid_core.py +117 -0
  135. meltygui/core/layout/header_core.py +23 -0
  136. meltygui/core/layout/header_runtime.py +67 -0
  137. meltygui/core/layout/layout_core.py +87 -0
  138. meltygui/core/layout/tile_manager_core.py +591 -0
  139. meltygui/core/melty.py +6726 -0
  140. meltygui/core/module_map.json +896 -0
  141. meltygui/core/module_names.py +19 -0
  142. meltygui/core/rendering/__init__.py +1 -0
  143. meltygui/core/rendering/core_decoration.py +431 -0
  144. meltygui/core/rendering/core_render_helpers.py +328 -0
  145. meltygui/core/rendering/func_metadata.py +398 -0
  146. meltygui/core/rendering/mode.py +818 -0
  147. meltygui/core/rendering/mode_defaults.py +41 -0
  148. meltygui/core/rendering/modes.py +136 -0
  149. meltygui/core/rendering/parameter_core.py +1665 -0
  150. meltygui/core/rendering/render_dispatch.py +1891 -0
  151. meltygui/core/rendering/render_funcs.py +273 -0
  152. meltygui/core/rendering/shaped.py +312 -0
  153. meltygui/core/rendering/window_decoration.py +25 -0
  154. meltygui/core/runtime/__init__.py +1 -0
  155. meltygui/core/runtime/app.py +735 -0
  156. meltygui/core/runtime/app_session.py +140 -0
  157. meltygui/core/runtime/background.py +564 -0
  158. meltygui/core/runtime/extensions.py +53 -0
  159. meltygui/core/runtime/gc_manager.py +1163 -0
  160. meltygui/core/runtime/lifecycle.py +21 -0
  161. meltygui/core/runtime/paths.py +27 -0
  162. meltygui/core/runtime/settings.py +14 -0
  163. meltygui/core/runtime/singleton.py +16 -0
  164. meltygui/core/runtime/thread_safe_bool.py +24 -0
  165. meltygui/core/runtime/thread_signal.py +30 -0
  166. meltygui/core/runtime/toggles.py +3115 -0
  167. meltygui/core/services/__init__.py +1 -0
  168. meltygui/core/services/account_core.py +10 -0
  169. meltygui/core/services/chat_core.py +19 -0
  170. meltygui/core/services/claude_terminal_core.py +346 -0
  171. meltygui/core/services/terminal_core.py +458 -0
  172. meltygui/core/services/terminal_runtime.py +78 -0
  173. meltygui/core/styling/__init__.py +1 -0
  174. meltygui/core/styling/color_core.py +46 -0
  175. meltygui/core/styling/fonts.py +639 -0
  176. meltygui/core/styling/global_style.py +338 -0
  177. meltygui/core/styling/style.py +198 -0
  178. meltygui/core/styling/style_core.py +522 -0
  179. meltygui/core/styling/warm_start.py +149 -0
  180. meltygui/core/windowing/__init__.py +1 -0
  181. meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
  182. meltygui/core/windowing/backends/__init__.py +1 -0
  183. meltygui/core/windowing/backends/imgui_renderer.py +138 -0
  184. meltygui/core/windowing/backends/native_wayland.py +850 -0
  185. meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
  186. meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
  187. meltygui/core/windowing/backends/wayland_protocol.py +101 -0
  188. meltygui/core/windowing/dock_core.py +182 -0
  189. meltygui/core/windowing/frame_geometry.py +42 -0
  190. meltygui/core/windowing/geometry_feed.py +864 -0
  191. meltygui/core/windowing/glfw_utils.py +1343 -0
  192. meltygui/core/windowing/os_frame.py +1552 -0
  193. meltygui/core/windowing/surface.py +643 -0
  194. meltygui/core/windowing/titlebar.py +1560 -0
  195. meltygui/core/windowing/titlebar_buttons.py +281 -0
  196. meltygui/core/windowing/wayland_move.py +932 -0
  197. meltygui/core/windowing/window_api.py +62 -0
  198. meltygui/core/windowing/window_constants.py +339 -0
  199. meltygui/core/windowing/window_visibility.py +162 -0
  200. meltygui/debug/__init__.py +0 -0
  201. meltygui/debug/app_view_utils.py +9 -0
  202. meltygui/editor/__init__.py +0 -0
  203. meltygui/editor/bash_syntax.py +30 -0
  204. meltygui/editor/code_line_fast.py +152 -0
  205. meltygui/editor/diff.py +139 -0
  206. meltygui/editor/external_changes.py +159 -0
  207. meltygui/editor/file_header.py +41 -0
  208. meltygui/editor/live_usage.py +169 -0
  209. meltygui/editor/live_view_views.py +1803 -0
  210. meltygui/editor/pending_save.py +1351 -0
  211. meltygui/editor/roster_tints.py +547 -0
  212. meltygui/editor/source_preview.py +16 -0
  213. meltygui/editor/source_tools.py +10 -0
  214. meltygui/editor/source_ui.py +70 -0
  215. meltygui/editor/spell_check.py +77 -0
  216. meltygui/editor/text_editor.py +9076 -0
  217. meltygui/editor/usage_picker.py +538 -0
  218. meltygui/events/__init__.py +0 -0
  219. meltygui/events/example.py +118 -0
  220. meltygui/examples/__init__.py +0 -0
  221. meltygui/examples/columns_demo.py +33 -0
  222. meltygui/examples/columns_window_demo.py +60 -0
  223. meltygui/examples/context_menu_demo.py +24 -0
  224. meltygui/examples/context_menu_window_demo.py +49 -0
  225. meltygui/examples/gui_playground.py +127 -0
  226. meltygui/examples/live_view_playground.py +256 -0
  227. meltygui/examples/lora.py +43 -0
  228. meltygui/examples/lora_data.py +59 -0
  229. meltygui/examples/lora_policies.py +67 -0
  230. meltygui/examples/mode_demo.py +49 -0
  231. meltygui/examples/modifies_demo.py +119 -0
  232. meltygui/examples/scalar_policies.py +60 -0
  233. meltygui/examples/style_layouts.py +149 -0
  234. meltygui/examples/tile_manager_demo.py +74 -0
  235. meltygui/examples/tint_demo.py +160 -0
  236. meltygui/examples/tint_functions.py +56 -0
  237. meltygui/examples/trace_demo.py +88 -0
  238. meltygui/examples/two_windows.py +36 -0
  239. meltygui/files/__init__.py +0 -0
  240. meltygui/files/fast_file_explorer.py +439 -0
  241. meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
  242. meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
  243. meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
  244. meltygui/graphics/__init__.py +6 -0
  245. meltygui/graphics/base.py +85 -0
  246. meltygui/graphics/examples.py +507 -0
  247. meltygui/graphics/executor.py +520 -0
  248. meltygui/graphics/filter.py +667 -0
  249. meltygui/graphics/filter.pyi +856 -0
  250. meltygui/graphics/generate_stubs.py +22 -0
  251. meltygui/graphics/registry.py +203 -0
  252. meltygui/graphics/shader_compiler.py +155 -0
  253. meltygui/graphics/shaders.py +1302 -0
  254. meltygui/graphics/stub_generator.py +250 -0
  255. meltygui/graphics/texture_manager.py +170 -0
  256. meltygui/graphics/texture_min_max.py +295 -0
  257. meltygui/hdr_color.py +757 -0
  258. meltygui/image_load.py +308 -0
  259. meltygui/model/__init__.py +1 -0
  260. meltygui/model/account_model.py +142 -0
  261. meltygui/model/camera_model.py +159 -0
  262. meltygui/model/chat_model.py +86 -0
  263. meltygui/model/code_model.py +62 -0
  264. meltygui/model/code_proxy_model.py +876 -0
  265. meltygui/model/collection_model.py +41 -0
  266. meltygui/model/color_model.py +127 -0
  267. meltygui/model/cuda_tensor_model.py +36 -0
  268. meltygui/model/cuda_texture_model.py +149 -0
  269. meltygui/model/dropdown_model.py +137 -0
  270. meltygui/model/file_metadata_model.py +73 -0
  271. meltygui/model/file_model.py +289 -0
  272. meltygui/model/format_model.py +390 -0
  273. meltygui/model/graph_model.py +98 -0
  274. meltygui/model/icon_model.py +1024 -0
  275. meltygui/model/import_graph_model.py +468 -0
  276. meltygui/model/layout_model.py +15 -0
  277. meltygui/model/lut_model.py +266 -0
  278. meltygui/model/search_model.py +173 -0
  279. meltygui/model/tensor_model.py +402 -0
  280. meltygui/model/terminal_model.py +329 -0
  281. meltygui/model/texture_model.py +146 -0
  282. meltygui/model/tile_model.py +24 -0
  283. meltygui/model/trace_model.py +198 -0
  284. meltygui/model/trace_report_model.py +146 -0
  285. meltygui/models/__init__.py +0 -0
  286. meltygui/models/file_meta.py +613 -0
  287. meltygui/models/function_console.py +184 -0
  288. meltygui/models/orchestration.py +48 -0
  289. meltygui/pbr.py +1576 -0
  290. meltygui/png_unfilter.c +49 -0
  291. meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
  292. meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
  293. meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
  294. meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  295. meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
  296. meltygui/resources/dejavu/LICENSE.txt +78 -0
  297. meltygui/resources/fontawesome-LICENSE.txt +121 -0
  298. meltygui/resources/fontawesome-webfont.ttf +0 -0
  299. meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
  300. meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
  301. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
  302. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
  303. meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
  304. meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
  305. meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
  306. meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
  307. meltygui/resources/jetbrains-weights/OFL.txt +93 -0
  308. meltygui/resources/jetbrains-weights/README.md +2 -0
  309. meltygui/state/__init__.py +0 -0
  310. meltygui/state/account_state.py +22 -0
  311. meltygui/state/animation_state.py +97 -0
  312. meltygui/state/annotation_state.py +22 -0
  313. meltygui/state/chat_state.py +36 -0
  314. meltygui/state/code_state.py +10 -0
  315. meltygui/state/core_enums.py +59 -0
  316. meltygui/state/core_markers.py +115 -0
  317. meltygui/state/core_undo.py +1075 -0
  318. meltygui/state/file_state.py +75 -0
  319. meltygui/state/graph_state.py +26 -0
  320. meltygui/state/inspection_state.py +47 -0
  321. meltygui/state/menu_state.py +10 -0
  322. meltygui/state/model_enums.py +11 -0
  323. meltygui/state/new_core_model.py +2394 -0
  324. meltygui/state/orchestration_state.py +14 -0
  325. meltygui/state/query_state.py +24 -0
  326. meltygui/state/tensor_state.py +10 -0
  327. meltygui/state/terminal_state.py +23 -0
  328. meltygui/state/trace_state.py +32 -0
  329. meltygui/state/voxel_state.py +12 -0
  330. meltygui/text_index.py +816 -0
  331. meltygui/utils/__init__.py +0 -0
  332. meltygui/utils/jump_to_code.py +344 -0
  333. meltygui/utils/pkl_inspect.py +90 -0
  334. meltygui/utils/render_utils.py +1419 -0
  335. meltygui/view/__init__.py +1 -0
  336. meltygui/view/account_view.py +524 -0
  337. meltygui/view/action_view.py +73 -0
  338. meltygui/view/chat_decoration_view.py +112 -0
  339. meltygui/view/chat_view.py +1703 -0
  340. meltygui/view/code_view.py +2677 -0
  341. meltygui/view/collection_view.py +1185 -0
  342. meltygui/view/color_view.py +824 -0
  343. meltygui/view/control_view.py +559 -0
  344. meltygui/view/decoration_view.py +439 -0
  345. meltygui/view/diagnostic_view.py +177 -0
  346. meltygui/view/dropdown_view.py +1082 -0
  347. meltygui/view/file_view.py +1599 -0
  348. meltygui/view/graph_cuda_view.py +105 -0
  349. meltygui/view/graph_view.py +882 -0
  350. meltygui/view/header_view.py +848 -0
  351. meltygui/view/input_view.py +238 -0
  352. meltygui/view/inspection_view.py +1783 -0
  353. meltygui/view/layout_view.py +596 -0
  354. meltygui/view/lut_view.py +45 -0
  355. meltygui/view/menu_view.py +212 -0
  356. meltygui/view/orchestration_view.py +658 -0
  357. meltygui/view/query_view.py +118 -0
  358. meltygui/view/search_view.py +342 -0
  359. meltygui/view/tab_view.py +258 -0
  360. meltygui/view/tensor_view.py +430 -0
  361. meltygui/view/terminal_view.py +355 -0
  362. meltygui/view/text_view.py +8031 -0
  363. meltygui/view/texture_view.py +480 -0
  364. meltygui/view/tile_view.py +38 -0
  365. meltygui/view/trace_view.py +923 -0
  366. meltygui/view/voxel_cuda_view.py +824 -0
  367. meltygui/view/voxel_view.py +1860 -0
  368. meltygui/view/window_view.py +111 -0
  369. meltygui-0.1.0.dist-info/METADATA +143 -0
  370. meltygui-0.1.0.dist-info/RECORD +372 -0
  371. meltygui-0.1.0.dist-info/WHEEL +4 -0
  372. meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,850 @@
1
+ """Native Wayland/EGL window backend for meltygui's Surface share group.
2
+
3
+ GLFW-compatible constants/callbacks let existing input/chrome code share this
4
+ backend without loading GLFW. All protocol dispatch and graphics calls run on
5
+ the render thread.
6
+ """
7
+ import ctypes
8
+ import errno
9
+ import mmap
10
+ import os
11
+ import select
12
+ import socket
13
+ import threading
14
+ import time
15
+ from types import SimpleNamespace
16
+
17
+ import meltygui.core.windowing.window_constants as codes
18
+
19
+ from meltygui.core.windowing.backends.wayland_protocol import Native
20
+ from meltygui.core.windowing.backends.wayland_protocol import function
21
+ from meltygui.core.windowing.backends.wayland_protocol import P
22
+ from meltygui.core.windowing.backends.wayland_protocol import U
23
+ from meltygui.core.windowing.backends.wayland_protocol import I
24
+
25
+
26
+ class NativeWindow:
27
+ native_wayland = True
28
+ cursor_motion_generation = 0
29
+
30
+ def __init__(self, backend, width, height, title, hints):
31
+ self.backend = backend
32
+ self.width, self.height, self.title = width, height, title
33
+ self.hints = hints.copy()
34
+ self.surface = self.xdg_surface = self.toplevel = None
35
+ self.decoration = None
36
+ self.decoration_mode = 0
37
+ self.egl_window = self.egl_surface = self.context = None
38
+ self.configured = self.closed = self.destroyed = False
39
+ self.visible = bool(hints.get(codes.VISIBLE, True))
40
+ self.focused = self.hovered = self.maximized = self.iconified = False
41
+ self.scale = 1
42
+ self.outputs = set()
43
+ self.cursor_pos = (0., 0.)
44
+ self.keys, self.buttons, self.callbacks = {}, {}, {}
45
+ self.saved_size = (width, height)
46
+
47
+ @property
48
+ def _as_parameter_(self):
49
+ # Existing GLState keys use ctypes.cast(window, c_void_p).
50
+ return ctypes.c_void_p(self.context)
51
+
52
+ def emit(self, event, *args):
53
+ callback = self.callbacks.get(event)
54
+ if callback is not None:
55
+ callback(self, *args)
56
+
57
+
58
+ class Backend:
59
+ def __init__(self):
60
+ self.native = None
61
+ self.decoration_manager = None
62
+ self.display = self.egl_display = None
63
+ self.windows = []
64
+ self.by_surface = {}
65
+ self.globals, self.outputs = {}, {}
66
+ self.hints, self.string_hints = {}, {}
67
+ self.current = threading.local()
68
+ self.error = None
69
+ self.pointer_window = self.keyboard_window = None
70
+ self.pointer = self.keyboard = None
71
+ self.serial = self.enter_serial = 0
72
+ self.modifiers = 0
73
+ self.repeat_key = None
74
+ self.repeat_rate, self.repeat_delay = 25, 0.5
75
+ self.next_repeat = 0.
76
+ self.xkb_context = self.xkb_keymap = self.xkb_state = None
77
+ self.clipboard_text = None
78
+ self.selection = self.data_source = None
79
+ self.offers = {}
80
+ self.cursor_theme = self.cursor_surface = None
81
+ self.custom_cursor_buffers = []
82
+ self.cursor_scale = 1
83
+ self.wake_reader, self.wake_writer = socket.socketpair()
84
+ self.wake_reader.setblocking(False)
85
+ self.wake_writer.setblocking(False)
86
+
87
+ def init(self):
88
+ if self.display:
89
+ return True
90
+ self.native = Native()
91
+ native = self.native
92
+ inherited = os.environ.pop('WAYLAND_SOCKET', None)
93
+ self.display = native.connect_fd(int(inherited)) if inherited else native.connect(None)
94
+ if not self.display:
95
+ raise RuntimeError('Native Wayland: cannot connect to compositor')
96
+ self.registry = native.request(self.display, 1, P(), interface='wl_registry', version=1)
97
+ self.listen(self.registry, [([U, ctypes.c_char_p, U], self.global_added), ([U], self.global_removed)])
98
+ self.check(native.roundtrip(self.display))
99
+ self.compositor = self.bind('wl_compositor', 4)
100
+ self.shm = self.bind('wl_shm')
101
+ self.wm = self.bind('xdg_wm_base')
102
+ if 'zxdg_decoration_manager_v1' in self.globals:
103
+ self.decoration_manager = self.bind('zxdg_decoration_manager_v1')
104
+ self.listen(self.wm, [([U], lambda serial: native.request(self.wm, 3, U(serial)))])
105
+ self.seat = self.bind('wl_seat', 5)
106
+ self.listen(self.seat, [([U], self.capabilities), ([ctypes.c_char_p], lambda name: None)])
107
+ if 'wl_data_device_manager' in self.globals:
108
+ self.data_manager = self.bind('wl_data_device_manager')
109
+ self.data_device = native.request(self.data_manager, 1, P(), P(self.seat), interface='wl_data_device')
110
+ self.listen(self.data_device, [([P], self.data_offer),
111
+ ([U, P, I, I, P], lambda *args: None), ([], lambda: None),
112
+ ([U, I, I], lambda *args: None), ([], lambda: None), ([P], self.select_offer)])
113
+ self.check(native.roundtrip(self.display))
114
+ self.init_egl()
115
+ return True
116
+
117
+ def check(self, result):
118
+ if self.error:
119
+ error, self.error = self.error, None
120
+ raise error
121
+ if result < 0:
122
+ raise RuntimeError('Native Wayland connection failed')
123
+
124
+ def listen(self, proxy, callbacks):
125
+ self.native.listener(proxy, callbacks, self.fail)
126
+
127
+ def fail(self, error):
128
+ self.error = error
129
+
130
+ def global_added(self, name, interface, version):
131
+ interface = interface.decode()
132
+ self.globals.setdefault(interface, (name, version))
133
+ if interface == 'wl_output':
134
+ output = self.native.request(self.registry, 0, U(name), ctypes.c_char_p(b'wl_output'),
135
+ U(min(version, 2)), P(), interface='wl_output', version=min(version, 2))
136
+ record = SimpleNamespace(proxy=output, name=name, x=0, y=0, width=1280, height=800,
137
+ scale=1, refresh=60000)
138
+ self.outputs[output] = record
139
+ def geometry(x, y, physical_width, physical_height, subpixel, make, model, transform):
140
+ record.x, record.y = x, y
141
+ def mode(flags, width, height, refresh):
142
+ if flags & 1:
143
+ record.width, record.height, record.refresh = width, height, refresh
144
+ def scale(value):
145
+ record.scale = max(1, value)
146
+ for window in self.windows:
147
+ self.update_scale(window)
148
+ self.listen(output, [([I, I, I, I, I, ctypes.c_char_p, ctypes.c_char_p, I], geometry),
149
+ ([U, I, I, I], mode), ([], lambda: None), ([I], scale)])
150
+
151
+ def global_removed(self, name):
152
+ for proxy, output in list(self.outputs.items()):
153
+ if output.name == name:
154
+ del self.outputs[proxy]
155
+ self.native.destroy(proxy)
156
+ for window in self.windows:
157
+ self.update_scale(window)
158
+
159
+ def bind(self, interface, version=1):
160
+ if interface not in self.globals:
161
+ raise RuntimeError(f'Native Wayland requires {interface}')
162
+ name, advertised = self.globals[interface]
163
+ version = min(version, advertised)
164
+ return self.native.request(self.registry, 0, U(name), ctypes.c_char_p(interface.encode()), U(version),
165
+ P(), interface=interface, version=version)
166
+
167
+ def init_egl(self):
168
+ library = ctypes.CDLL('libEGL.so.1')
169
+ signatures = {
170
+ 'eglGetError': (U,), 'eglGetPlatformDisplay': (P, U, P, P),
171
+ 'eglInitialize': (U, P, ctypes.POINTER(I), ctypes.POINTER(I)), 'eglBindAPI': (U, U),
172
+ 'eglChooseConfig': (U, P, ctypes.POINTER(I), ctypes.POINTER(P), I, ctypes.POINTER(I)),
173
+ 'eglCreateContext': (P, P, P, P, ctypes.POINTER(I)),
174
+ 'eglCreateWindowSurface': (P, P, P, P, P),
175
+ 'eglCreatePbufferSurface': (P, P, P, ctypes.POINTER(I)),
176
+ 'eglMakeCurrent': (U, P, P, P, P), 'eglSwapBuffers': (U, P, P), 'eglSwapInterval': (U, P, I),
177
+ 'eglDestroySurface': (U, P, P), 'eglDestroyContext': (U, P, P), 'eglTerminate': (U, P)}
178
+ self.egl = SimpleNamespace(**{name: function(library, name, *signature)
179
+ for name, signature in signatures.items()})
180
+ self.egl_display = self.egl.eglGetPlatformDisplay(0x31D8, self.display, None)
181
+ self.egl_check(self.egl_display, 'eglGetPlatformDisplay')
182
+ major, minor = I(), I()
183
+ self.egl_check(self.egl.eglInitialize(self.egl_display, ctypes.byref(major), ctypes.byref(minor)), 'eglInitialize')
184
+ self.egl_check(self.egl.eglBindAPI(0x30A2), 'eglBindAPI')
185
+ # Window and pbuffer config: the hidden atlas owner needs a framebuffer.
186
+ attributes = (I * 15)(0x3033, 5, 0x3040, 8, 0x3024, 8, 0x3023, 8,
187
+ 0x3022, 8, 0x3021, 8, 0x3026, 8, 0x3038)
188
+ self.config, count = P(), I()
189
+ self.egl_check(self.egl.eglChooseConfig(self.egl_display, attributes, ctypes.byref(self.config),
190
+ 1, ctypes.byref(count)), 'eglChooseConfig')
191
+ if not count.value:
192
+ raise RuntimeError('No native EGL OpenGL RGBA8/stencil8 config')
193
+ library = ctypes.CDLL('libwayland-egl.so.1')
194
+ self.egl_window_create = function(library, 'wl_egl_window_create', P, P, I, I)
195
+ self.egl_window_resize = function(library, 'wl_egl_window_resize', None, P, I, I, I, I)
196
+ self.egl_window_destroy = function(library, 'wl_egl_window_destroy', None, P)
197
+
198
+ def egl_check(self, result, operation):
199
+ if not result:
200
+ raise RuntimeError(f'{operation} failed: EGL 0x{self.egl.eglGetError():04x}')
201
+
202
+ def create_window(self, width, height, title, monitor=None, share=None):
203
+ window = NativeWindow(self, int(width), int(height), title, self.hints)
204
+ self.windows.append(window)
205
+ try:
206
+ if window.visible:
207
+ native = self.native
208
+ window.surface = native.request(self.compositor, 0, P(), interface='wl_surface')
209
+ self.by_surface[window.surface] = window
210
+ self.listen(window.surface, [([P], lambda output: self.surface_output(window, output, True)),
211
+ ([P], lambda output: self.surface_output(window, output, False))])
212
+ window.xdg_surface = native.request(self.wm, 2, P(), P(window.surface), interface='xdg_surface')
213
+ self.listen(window.xdg_surface, [([U], lambda serial: self.configure(window, serial))])
214
+ window.toplevel = native.request(window.xdg_surface, 1, P(), interface='xdg_toplevel')
215
+ self.listen(window.toplevel, [([I, I, P], lambda w, h, states: self.toplevel_size(window, w, h, states)),
216
+ ([], lambda: self.request_close(window))])
217
+ native.request(window.toplevel, 2, ctypes.c_char_p(title.encode()))
218
+ app_id = self.string_hints.get(codes.WAYLAND_APP_ID, 'meltygui')
219
+ native.request(window.toplevel, 3, ctypes.c_char_p(app_id.encode()))
220
+ self.sync_decoration(window)
221
+ native.request(window.surface, 6)
222
+ while not window.configured:
223
+ self.check(native.dispatch(self.display))
224
+ window.egl_window = self.egl_window_create(window.surface, *self.get_framebuffer_size(window))
225
+ if not window.egl_window:
226
+ raise RuntimeError('wl_egl_window_create failed')
227
+ window.egl_surface = self.egl.eglCreateWindowSurface(self.egl_display, self.config, window.egl_window, None)
228
+ else:
229
+ attributes = (I * 5)(0x3057, width, 0x3056, height, 0x3038)
230
+ window.egl_surface = self.egl.eglCreatePbufferSurface(self.egl_display, self.config, attributes)
231
+ self.egl_check(window.egl_surface, 'create EGL surface')
232
+ attributes = (I * 7)(0x3098, 4, 0x30FB, 3, 0x30FD, 1, 0x3038)
233
+ window.context = self.egl.eglCreateContext(self.egl_display, self.config,
234
+ share.context if share else None, attributes)
235
+ self.egl_check(window.context, 'eglCreateContext')
236
+ return window
237
+ except BaseException:
238
+ self.destroy_window(window)
239
+ raise
240
+
241
+ def toplevel_size(self, window, width, height, states):
242
+ class Array(ctypes.Structure):
243
+ _fields_ = [('size', ctypes.c_size_t), ('alloc', ctypes.c_size_t), ('data', P)]
244
+ values = ctypes.cast(states, ctypes.POINTER(Array)).contents
245
+ states = list((U * (values.size // 4)).from_address(values.data)) if values.size else []
246
+ window.maximized = 1 in states
247
+ window.width, window.height = width or window.width, height or window.height
248
+
249
+ def configure(self, window, serial):
250
+ self.native.request(window.xdg_surface, 4, U(serial))
251
+ window.configured = True
252
+ if window.egl_window:
253
+ self.egl_window_resize(window.egl_window, *self.get_framebuffer_size(window), 0, 0)
254
+ window.emit('window_size', window.width, window.height)
255
+ window.emit('framebuffer_size', *self.get_framebuffer_size(window))
256
+
257
+ def surface_output(self, window, output, entered):
258
+ if entered:
259
+ window.outputs.add(output)
260
+ else:
261
+ window.outputs.discard(output)
262
+ self.update_scale(window)
263
+
264
+ def update_scale(self, window):
265
+ scale = max((self.outputs[o].scale for o in window.outputs if o in self.outputs), default=1)
266
+ if scale != window.scale:
267
+ window.scale = scale
268
+ self.native.request(window.surface, 8, I(scale))
269
+ if window.egl_window:
270
+ self.egl_window_resize(window.egl_window, *self.get_framebuffer_size(window), 0, 0)
271
+ window.emit('framebuffer_size', *self.get_framebuffer_size(window))
272
+
273
+ def make_context_current(self, window):
274
+ if self.get_current_context() is window:
275
+ return
276
+ surface, context = (window.egl_surface, window.context) if window else (None, None)
277
+ self.egl_check(self.egl.eglMakeCurrent(self.egl_display, surface, surface, context), 'eglMakeCurrent')
278
+ self.current.window = window
279
+
280
+ def get_current_context(self):
281
+ return getattr(self.current, 'window', None)
282
+
283
+ def swap_buffers(self, window):
284
+ self.egl_check(self.egl.eglSwapBuffers(self.egl_display, window.egl_surface), 'eglSwapBuffers')
285
+
286
+ def swap_interval(self, interval):
287
+ self.egl_check(self.egl.eglSwapInterval(self.egl_display, interval), 'eglSwapInterval')
288
+
289
+ def destroy_window(self, window):
290
+ if window.destroyed:
291
+ return
292
+ window.destroyed = True
293
+ if self.get_current_context() is window:
294
+ self.make_context_current(None)
295
+ if window.egl_surface:
296
+ self.egl.eglDestroySurface(self.egl_display, window.egl_surface)
297
+ if window.context:
298
+ self.egl.eglDestroyContext(self.egl_display, window.context)
299
+ if window.egl_window:
300
+ self.egl_window_destroy(window.egl_window)
301
+ for proxy in (window.decoration, window.toplevel, window.xdg_surface, window.surface):
302
+ if proxy:
303
+ self.native.request(proxy, 0, destroy=True)
304
+ self.by_surface.pop(window.surface, None)
305
+ if window in self.windows:
306
+ self.windows.remove(window)
307
+ if self.keyboard_window is window:
308
+ self.keyboard_window, self.repeat_key = None, None
309
+ if self.pointer_window is window:
310
+ self.pointer_window = None
311
+ window.callbacks.clear()
312
+
313
+ def terminate(self):
314
+ for window in list(reversed(self.windows)):
315
+ self.destroy_window(window)
316
+ if self.egl_display:
317
+ self.egl.eglTerminate(self.egl_display)
318
+ if self.cursor_theme:
319
+ self.cursor_theme_destroy(self.cursor_theme)
320
+ for attr, release in [('xkb_state', 'xkb_state_unref'), ('xkb_keymap', 'xkb_keymap_unref'),
321
+ ('xkb_context', 'xkb_context_unref')]:
322
+ value = getattr(self, attr)
323
+ if value:
324
+ getattr(self.xkb, release)(value)
325
+ if self.display:
326
+ self.native.disconnect(self.display)
327
+ self.display = self.egl_display = None
328
+ self.wake_reader.close()
329
+ self.wake_writer.close()
330
+
331
+ def capabilities(self, capabilities):
332
+ native = self.native
333
+ if capabilities & 2 and not self.keyboard:
334
+ self.keyboard = native.request(self.seat, 1, P(), interface='wl_keyboard')
335
+ self.listen(self.keyboard, [([U, I, U], self.keymap), ([U, P, P], self.keyboard_enter),
336
+ ([U, P], self.keyboard_leave), ([U, U, U, U], self.key),
337
+ ([U, U, U, U, U], self.keyboard_modifiers), ([I, I], self.repeat_info)])
338
+ if capabilities & 1 and not self.pointer:
339
+ self.pointer = native.request(self.seat, 0, P(), interface='wl_pointer')
340
+ self.listen(self.pointer, [([U, P, I, I], self.pointer_enter), ([U, P], self.pointer_leave),
341
+ ([U, I, I], self.pointer_motion), ([U, U, U, U], self.pointer_button),
342
+ ([U, U, I], self.pointer_axis), ([], lambda: None), ([U], lambda source: None),
343
+ ([U, U], lambda timestamp, axis: None), ([U, I], lambda axis, steps: None)])
344
+
345
+ def keymap(self, format, descriptor, size):
346
+ try:
347
+ if format != 1:
348
+ return
349
+ with mmap.mmap(descriptor, size, access=mmap.ACCESS_READ) as source:
350
+ text = source[:]
351
+ finally:
352
+ os.close(descriptor)
353
+ if not self.xkb_context:
354
+ library = ctypes.CDLL('libxkbcommon.so.0')
355
+ signatures = {'xkb_context_new': (P, U), 'xkb_context_unref': (None, P),
356
+ 'xkb_keymap_new_from_string': (P, P, ctypes.c_char_p, U, U), 'xkb_keymap_unref': (None, P),
357
+ 'xkb_state_new': (P, P), 'xkb_state_unref': (None, P),
358
+ 'xkb_state_update_mask': (U, P, U, U, U, U, U, U),
359
+ 'xkb_state_key_get_utf32': (U, P, U), 'xkb_state_key_get_one_sym': (U, P, U),
360
+ 'xkb_state_mod_name_is_active': (I, P, ctypes.c_char_p, U),
361
+ 'xkb_keymap_key_repeats': (I, P, U)}
362
+ self.xkb = SimpleNamespace(**{name: function(library, name, *signature)
363
+ for name, signature in signatures.items()})
364
+ self.xkb_context = self.xkb.xkb_context_new(0)
365
+ if self.xkb_state:
366
+ self.xkb.xkb_state_unref(self.xkb_state)
367
+ self.xkb.xkb_keymap_unref(self.xkb_keymap)
368
+ self.xkb_keymap = self.xkb.xkb_keymap_new_from_string(self.xkb_context, text, 1, 0)
369
+ if not self.xkb_keymap:
370
+ raise RuntimeError('Cannot compile Wayland keyboard keymap')
371
+ self.xkb_state = self.xkb.xkb_state_new(self.xkb_keymap)
372
+
373
+ def keyboard_modifiers(self, serial, depressed, latched, locked, group):
374
+ self.serial = serial
375
+ if not self.xkb_state:
376
+ return
377
+ self.xkb.xkb_state_update_mask(self.xkb_state, depressed, latched, locked, 0, 0, group)
378
+ self.modifiers = 0
379
+ for name, mask in [('Shift', codes.MOD_SHIFT), ('Control', codes.MOD_CONTROL),
380
+ ('Mod1', codes.MOD_ALT), ('Mod4', codes.MOD_SUPER)]:
381
+ if self.xkb.xkb_state_mod_name_is_active(self.xkb_state, name.encode(), 1 << 3):
382
+ self.modifiers |= mask
383
+
384
+ def keyboard_enter(self, serial, surface, keys):
385
+ self.serial = serial
386
+ window = self.by_surface.get(surface)
387
+ self.keyboard_window = window
388
+ if window:
389
+ window.focused, window.iconified = True, False
390
+ if keys:
391
+ class Array(ctypes.Structure):
392
+ _fields_ = [('size', ctypes.c_size_t), ('alloc', ctypes.c_size_t), ('data', P)]
393
+ held = ctypes.cast(keys, ctypes.POINTER(Array)).contents
394
+ scans = (U * (held.size // ctypes.sizeof(U))).from_address(held.data) if held.size else ()
395
+ for scan in scans:
396
+ key = self.key_code(scan)
397
+ window.keys[key] = codes.PRESS
398
+ window.emit('key', key, scan, codes.PRESS, self.modifiers)
399
+ window.emit('window_focus', True)
400
+
401
+ def keyboard_leave(self, serial, surface):
402
+ self.serial = serial
403
+ window = self.by_surface.get(surface)
404
+ if window:
405
+ for key in list(window.keys):
406
+ if window.keys[key]:
407
+ window.keys[key] = codes.RELEASE
408
+ window.emit('key', key, 0, codes.RELEASE, self.modifiers)
409
+ window.focused = False
410
+ window.emit('window_focus', False)
411
+ self.keyboard_window, self.repeat_key = None, None
412
+
413
+ def key_code(self, scan):
414
+ # GLFW key tokens use keyboard physical positions, independent of text layout.
415
+ rows = [(2, '1234567890'), (16, 'QWERTYUIOP'), (30, 'ASDFGHJKL'), (44, 'ZXCVBNM')]
416
+ for start, letters in rows:
417
+ if start <= scan < start + len(letters):
418
+ return ord(letters[scan - start])
419
+ special = {1: 'ESCAPE', 12: 'MINUS', 13: 'EQUAL', 14: 'BACKSPACE', 15: 'TAB',
420
+ 26: 'LEFT_BRACKET', 27: 'RIGHT_BRACKET', 28: 'ENTER', 29: 'LEFT_CONTROL',
421
+ 39: 'SEMICOLON', 40: 'APOSTROPHE', 41: 'GRAVE_ACCENT', 42: 'LEFT_SHIFT',
422
+ 43: 'BACKSLASH', 51: 'COMMA', 52: 'PERIOD', 53: 'SLASH', 54: 'RIGHT_SHIFT',
423
+ 55: 'KP_MULTIPLY', 56: 'LEFT_ALT', 57: 'SPACE', 58: 'CAPS_LOCK', 69: 'NUM_LOCK',
424
+ 70: 'SCROLL_LOCK', 71: 'KP_7', 72: 'KP_8', 73: 'KP_9', 74: 'KP_SUBTRACT',
425
+ 75: 'KP_4', 76: 'KP_5', 77: 'KP_6', 78: 'KP_ADD', 79: 'KP_1', 80: 'KP_2',
426
+ 81: 'KP_3', 82: 'KP_0', 83: 'KP_DECIMAL', 87: 'F11', 88: 'F12', 96: 'KP_ENTER',
427
+ 97: 'RIGHT_CONTROL', 98: 'KP_DIVIDE', 100: 'RIGHT_ALT', 102: 'HOME', 103: 'UP',
428
+ 104: 'PAGE_UP', 105: 'LEFT', 106: 'RIGHT', 107: 'END', 108: 'DOWN',
429
+ 109: 'PAGE_DOWN', 110: 'INSERT', 111: 'DELETE', 125: 'LEFT_SUPER', 126: 'RIGHT_SUPER', 127: 'MENU'}
430
+ if 59 <= scan <= 68:
431
+ return codes.KEY_F1 + scan - 59
432
+ name = special.get(scan)
433
+ return getattr(codes, 'KEY_' + name) if name else codes.KEY_UNKNOWN
434
+
435
+ def key(self, serial, timestamp, scan, state):
436
+ self.serial = serial
437
+ window = self.keyboard_window
438
+ if window is None:
439
+ return
440
+ key = self.key_code(scan)
441
+ action = codes.PRESS if state else codes.RELEASE
442
+ window.keys[key] = action
443
+ window.emit('key', key, scan, action, self.modifiers)
444
+ if state:
445
+ self.character(window, scan)
446
+ if self.xkb_keymap and self.xkb.xkb_keymap_key_repeats(self.xkb_keymap, scan + 8):
447
+ self.repeat_key, self.next_repeat = (window, scan, key), time.monotonic() + self.repeat_delay
448
+ elif self.repeat_key and self.repeat_key[1] == scan:
449
+ self.repeat_key = None
450
+
451
+ def character(self, window, scan):
452
+ if self.xkb_state and not self.modifiers & (codes.MOD_CONTROL | codes.MOD_SUPER):
453
+ character = self.xkb.xkb_state_key_get_utf32(self.xkb_state, scan + 8)
454
+ if character >= 32 and character != 127:
455
+ window.emit('char', character)
456
+
457
+ def repeat_info(self, rate, delay):
458
+ self.repeat_rate, self.repeat_delay = max(0, rate), max(0, delay) / 1000
459
+
460
+ def repeat(self):
461
+ if self.repeat_key and self.repeat_rate and time.monotonic() >= self.next_repeat:
462
+ window, scan, key = self.repeat_key
463
+ window.emit('key', key, scan, codes.REPEAT, self.modifiers)
464
+ self.character(window, scan)
465
+ self.next_repeat = time.monotonic() + 1 / self.repeat_rate
466
+
467
+ def pointer_enter(self, serial, surface, x, y):
468
+ self.enter_serial = self.serial = serial
469
+ self.pointer_window = window = self.by_surface.get(surface)
470
+ if window:
471
+ window.hovered = True
472
+ window.cursor_pos = (x / 256, y / 256)
473
+ window.cursor_motion_generation += 1
474
+ window.emit('cursor_enter', True)
475
+ window.emit('cursor_pos', *window.cursor_pos)
476
+ self.set_cursor(window, getattr(window, 'cursor', None))
477
+
478
+ def pointer_leave(self, serial, surface):
479
+ window = self.by_surface.get(surface)
480
+ if window:
481
+ window.hovered = False
482
+ window.emit('cursor_enter', False)
483
+ self.pointer_window = None
484
+
485
+ def pointer_motion(self, timestamp, x, y):
486
+ if self.pointer_window:
487
+ self.pointer_window.cursor_pos = (x / 256, y / 256)
488
+ self.pointer_window.cursor_motion_generation += 1
489
+ self.pointer_window.emit('cursor_pos', *self.pointer_window.cursor_pos)
490
+
491
+ def pointer_button(self, serial, timestamp, button, state):
492
+ self.serial = serial
493
+ if self.pointer_window:
494
+ button = {272: 0, 273: 1, 274: 2}.get(button, button - 272)
495
+ self.pointer_window.buttons[button] = int(state)
496
+ self.pointer_window.emit('mouse_button', button, int(state), self.modifiers)
497
+
498
+ def pointer_axis(self, timestamp, axis, value):
499
+ if self.pointer_window:
500
+ offset = -value / 2560
501
+ self.pointer_window.emit('scroll', offset if axis else 0., 0. if axis else offset)
502
+
503
+ def poll_events(self):
504
+ self.wait_events_timeout(0)
505
+
506
+ def wait_events(self):
507
+ self.wait_events_timeout(None)
508
+
509
+ def wait_events_timeout(self, timeout):
510
+ native = self.native
511
+ self.check(native.pending(self.display))
512
+ if self.repeat_key and self.repeat_rate:
513
+ delay = max(0., self.next_repeat - time.monotonic())
514
+ timeout = min(timeout, delay) if timeout is not None else delay
515
+ # EGL might also read this connection. Reserve the read before selecting
516
+ # so another reader cannot consume the readiness and leave us blocked.
517
+ while native.prepare_read(self.display) != 0:
518
+ self.check(native.pending(self.display))
519
+ prepared = True
520
+ descriptor = native.get_fd(self.display)
521
+ try:
522
+ flushed = native.flush(self.display)
523
+ blocked = flushed < 0 and ctypes.get_errno() == errno.EAGAIN
524
+ if flushed < 0 and not blocked:
525
+ self.check(flushed)
526
+ ready, writable, _ = select.select([descriptor, self.wake_reader],
527
+ [descriptor] if blocked else [], [], timeout)
528
+ if descriptor in ready:
529
+ prepared = False
530
+ self.check(native.read_events(self.display))
531
+ if writable:
532
+ native.flush(self.display)
533
+ finally:
534
+ if prepared:
535
+ native.cancel_read(self.display)
536
+ self.check(native.pending(self.display))
537
+ if self.wake_reader in ready:
538
+ try:
539
+ while self.wake_reader.recv(4096):
540
+ pass
541
+ except BlockingIOError:
542
+ pass
543
+ self.repeat()
544
+
545
+ def post_empty_event(self):
546
+ try:
547
+ self.wake_writer.send(b'1')
548
+ except (BlockingIOError, OSError):
549
+ pass
550
+
551
+ def data_offer(self, offer):
552
+ self.offers[offer] = []
553
+ self.listen(offer, [([ctypes.c_char_p], lambda mime: self.offers[offer].append(mime))])
554
+
555
+ def select_offer(self, offer):
556
+ previous = self.selection
557
+ self.selection = offer
558
+ if previous and previous != offer:
559
+ self.native.request(previous, 2, destroy=True)
560
+ self.offers.pop(previous, None)
561
+
562
+ def set_clipboard_string(self, window, text):
563
+ if not hasattr(self, 'data_device'):
564
+ raise RuntimeError('Compositor has no clipboard protocol')
565
+ self.clipboard_text = text.encode('utf-8') if isinstance(text, str) else bytes(text)
566
+ if self.data_source:
567
+ self.native.request(self.data_source, 1, destroy=True)
568
+ self.data_source = source = self.native.request(self.data_manager, 0, P(), interface='wl_data_source')
569
+ payload = self.clipboard_text
570
+ def send(mime, descriptor):
571
+ def write():
572
+ try:
573
+ os.set_blocking(descriptor, True)
574
+ remaining = memoryview(payload)
575
+ while remaining:
576
+ remaining = remaining[os.write(descriptor, remaining):]
577
+ except (BrokenPipeError, OSError):
578
+ pass
579
+ finally:
580
+ os.close(descriptor)
581
+ threading.Thread(target=write, daemon=True, name='wayland-clipboard').start()
582
+ def cancelled():
583
+ if self.data_source == source:
584
+ self.data_source = None
585
+ self.clipboard_text = None
586
+ self.native.request(source, 1, destroy=True)
587
+ self.listen(source, [([ctypes.c_char_p], lambda mime: None), ([ctypes.c_char_p, I], send), ([], cancelled)])
588
+ for mime in (b'text/plain;charset=utf-8', b'text/plain', b'UTF8_STRING'):
589
+ self.native.request(source, 0, ctypes.c_char_p(mime))
590
+ self.native.request(self.data_device, 1, P(source), U(self.serial))
591
+ self.native.flush(self.display)
592
+
593
+ def get_clipboard_string(self, window):
594
+ if self.data_source and self.clipboard_text is not None:
595
+ return self.clipboard_text
596
+ if not self.selection:
597
+ return b''
598
+ offered = self.offers.get(self.selection, [])
599
+ mime = next((m for m in (b'text/plain;charset=utf-8', b'UTF8_STRING', b'text/plain') if m in offered), None)
600
+ if mime is None:
601
+ return b''
602
+ reader, writer = os.pipe2(os.O_CLOEXEC)
603
+ os.set_blocking(reader, False)
604
+ data = bytearray()
605
+ try:
606
+ self.native.request(self.selection, 1, ctypes.c_char_p(mime), I(writer))
607
+ self.native.flush(self.display)
608
+ os.close(writer)
609
+ writer = -1
610
+ deadline = time.monotonic() + 2
611
+ while time.monotonic() < deadline:
612
+ if select.select([reader], [], [], 0.01)[0]:
613
+ chunk = os.read(reader, 65536)
614
+ if not chunk:
615
+ return bytes(data)
616
+ data.extend(chunk)
617
+ self.poll_events()
618
+ raise TimeoutError('Wayland clipboard transfer timed out')
619
+ finally:
620
+ os.close(reader)
621
+ if writer >= 0:
622
+ os.close(writer)
623
+
624
+ def create_standard_cursor(self, shape):
625
+ return shape
626
+
627
+ def create_cursor(self, image, xhot, yhot):
628
+ # Custom cursors are represented without GL/Wayland work until selected.
629
+ return SimpleNamespace(image=image, xhot=int(xhot), yhot=int(yhot), buffer=None)
630
+
631
+ def set_cursor(self, window, cursor):
632
+ window.cursor = cursor
633
+ if window is not self.pointer_window or not self.enter_serial:
634
+ return
635
+ self.install_cursor(cursor)
636
+
637
+ def install_cursor(self, cursor):
638
+ if cursor is not None and not isinstance(cursor, int):
639
+ self.install_image_cursor(cursor)
640
+ return
641
+ if not self.cursor_theme:
642
+ library = ctypes.CDLL('libwayland-cursor.so.0')
643
+ load = function(library, 'wl_cursor_theme_load', P, ctypes.c_char_p, I, P)
644
+ self.cursor_theme_destroy = function(library, 'wl_cursor_theme_destroy', None, P)
645
+ self.cursor_get = function(library, 'wl_cursor_theme_get_cursor', P, P, ctypes.c_char_p)
646
+ self.cursor_buffer = function(library, 'wl_cursor_image_get_buffer', P, P)
647
+ self.cursor_theme = load(os.environ.get('XCURSOR_THEME', 'default').encode(),
648
+ int(os.environ.get('XCURSOR_SIZE', '24')), self.shm)
649
+ self.cursor_surface = self.native.request(self.compositor, 0, P(), interface='wl_surface')
650
+ names = {codes.IBEAM_CURSOR: b'text', codes.CROSSHAIR_CURSOR: b'crosshair',
651
+ codes.POINTING_HAND_CURSOR: b'pointer', codes.RESIZE_EW_CURSOR: b'ew-resize',
652
+ codes.RESIZE_NS_CURSOR: b'ns-resize', codes.RESIZE_NWSE_CURSOR: b'nwse-resize',
653
+ codes.RESIZE_NESW_CURSOR: b'nesw-resize', codes.NOT_ALLOWED_CURSOR: b'not-allowed'}
654
+ name = names.get(cursor, b'left_ptr') if isinstance(cursor, int) else b'left_ptr'
655
+ pointer = self.cursor_get(self.cursor_theme, name) if self.cursor_theme else None
656
+ if not pointer:
657
+ return
658
+ class Cursor(ctypes.Structure):
659
+ _fields_ = [('count', U), ('images', ctypes.POINTER(P)), ('name', ctypes.c_char_p)]
660
+ class Image(ctypes.Structure):
661
+ _fields_ = [('width', U), ('height', U), ('hotspot_x', U), ('hotspot_y', U), ('delay', U)]
662
+ image_pointer = ctypes.cast(pointer, ctypes.POINTER(Cursor)).contents.images[0]
663
+ image = ctypes.cast(image_pointer, ctypes.POINTER(Image)).contents
664
+ buffer = self.cursor_buffer(image_pointer)
665
+ self.native.request(self.pointer, 0, U(self.enter_serial), P(self.cursor_surface), I(image.hotspot_x), I(image.hotspot_y))
666
+ self.native.request(self.cursor_surface, 1, P(buffer), I(0), I(0))
667
+ self.native.request(self.cursor_surface, 2, I(0), I(0), I(image.width), I(image.height))
668
+ self.native.request(self.cursor_surface, 6)
669
+
670
+ def install_image_cursor(self, cursor):
671
+ if not self.cursor_surface:
672
+ self.cursor_surface = self.native.request(self.compositor, 0, P(), interface='wl_surface')
673
+ if cursor.buffer is None:
674
+ import numpy
675
+ pixels = numpy.asarray(cursor.image.convert('RGBA'), dtype=numpy.uint8)
676
+ # wl_shm ARGB8888 stores premultiplied BGRA bytes on little-endian Linux.
677
+ pixels = pixels[:, :, [2, 1, 0, 3]].copy()
678
+ pixels[:, :, :3] = ((pixels[:, :, :3].astype(numpy.uint16) *
679
+ pixels[:, :, 3:4].astype(numpy.uint16) + 127) // 255).astype(numpy.uint8)
680
+ payload = pixels.tobytes()
681
+ descriptor = os.memfd_create('meltygui-cursor', os.MFD_CLOEXEC)
682
+ try:
683
+ os.ftruncate(descriptor, len(payload))
684
+ with mmap.mmap(descriptor, len(payload)) as memory:
685
+ memory[:] = payload
686
+ pool = self.native.request(self.shm, 0, P(), I(descriptor), I(len(payload)), interface='wl_shm_pool')
687
+ width, height = cursor.image.size
688
+ cursor.buffer = self.native.request(pool, 0, P(), I(0), I(width), I(height), I(width * 4), U(0),
689
+ interface='wl_buffer')
690
+ self.listen(cursor.buffer, [([], lambda: None)])
691
+ self.native.request(pool, 1, destroy=True)
692
+ self.custom_cursor_buffers.append(cursor.buffer)
693
+ finally:
694
+ os.close(descriptor)
695
+ width, height = cursor.image.size
696
+ self.native.request(self.pointer, 0, U(self.enter_serial), P(self.cursor_surface), I(cursor.xhot), I(cursor.yhot))
697
+ self.native.request(self.cursor_surface, 1, P(cursor.buffer), I(0), I(0))
698
+ self.native.request(self.cursor_surface, 2, I(0), I(0), I(width), I(height))
699
+ self.native.request(self.cursor_surface, 6)
700
+
701
+ def request_close(self, window):
702
+ window.closed = True
703
+ window.emit('window_close')
704
+
705
+ def window_should_close(self, window):
706
+ return window.closed
707
+
708
+ def set_window_should_close(self, window, value):
709
+ window.closed = bool(value)
710
+ self.post_empty_event()
711
+
712
+ def get_window_size(self, window):
713
+ return window.width, window.height
714
+
715
+ def get_framebuffer_size(self, window):
716
+ return window.width * window.scale, window.height * window.scale
717
+
718
+ def set_window_size(self, window, width, height):
719
+ window.width, window.height = max(1, int(width)), max(1, int(height))
720
+ if window.egl_window:
721
+ self.egl_window_resize(window.egl_window, *self.get_framebuffer_size(window), 0, 0)
722
+ window.emit('window_size', window.width, window.height)
723
+ window.emit('framebuffer_size', *self.get_framebuffer_size(window))
724
+
725
+ def get_window_attrib(self, window, attribute):
726
+ values = {codes.FOCUSED: window.focused, codes.HOVERED: window.hovered,
727
+ codes.VISIBLE: window.visible, codes.MAXIMIZED: window.maximized, codes.ICONIFIED: window.iconified,
728
+ codes.TRANSPARENT_FRAMEBUFFER: window.hints.get(codes.TRANSPARENT_FRAMEBUFFER, False)}
729
+ return int(values.get(attribute, window.hints.get(attribute, False)))
730
+
731
+ def sync_decoration(self, window):
732
+ # Undecorated means the app supplies its own chrome. Without this
733
+ # request Hyprbars cannot distinguish it from an unframed client.
734
+ if not self.decoration_manager or not window.toplevel:
735
+ return
736
+ if window.decoration is None:
737
+ window.decoration = self.native.request(
738
+ self.decoration_manager, 1, P(), P(window.toplevel),
739
+ interface='zxdg_toplevel_decoration_v1')
740
+ self.listen(window.decoration, [([U], lambda mode: setattr(window, 'decoration_mode', mode))])
741
+ mode = 2 if window.hints.get(codes.DECORATED, True) else 1
742
+ self.native.request(window.decoration, 1, U(mode))
743
+
744
+ def set_window_attrib(self, window, attribute, value):
745
+ window.hints[attribute] = value
746
+ if attribute == codes.DECORATED:
747
+ self.sync_decoration(window)
748
+
749
+ def get_cursor_pos(self, window):
750
+ return window.cursor_pos
751
+
752
+ def get_mouse_button(self, window, button):
753
+ return window.buttons.get(button, codes.RELEASE)
754
+
755
+ def get_key(self, window, key):
756
+ return window.keys.get(key, codes.RELEASE)
757
+
758
+ def get_time(self):
759
+ return time.monotonic()
760
+
761
+ def get_platform(self):
762
+ return codes.PLATFORM_WAYLAND
763
+
764
+ def get_wayland_display(self):
765
+ return self.display
766
+
767
+ def get_wayland_window(self, window):
768
+ return window.surface
769
+
770
+ def init_hint(self, *args):
771
+ pass
772
+
773
+ def default_window_hints(self):
774
+ self.hints.clear()
775
+ self.string_hints.clear()
776
+
777
+ def window_hint(self, hint, value):
778
+ self.hints[hint] = value
779
+
780
+ def window_hint_string(self, hint, value):
781
+ self.string_hints[hint] = value
782
+
783
+ def set_window_title(self, window, title):
784
+ window.title = title
785
+ if window.toplevel:
786
+ self.native.request(window.toplevel, 2, ctypes.c_char_p(title.encode()))
787
+
788
+ def maximize_window(self, window):
789
+ self.native.request(window.toplevel, 9)
790
+
791
+ def restore_window(self, window):
792
+ self.native.request(window.toplevel, 10)
793
+ window.iconified = False
794
+
795
+ def iconify_window(self, window):
796
+ self.native.request(window.toplevel, 13)
797
+ window.iconified = True
798
+
799
+ def hide_window(self, window):
800
+ if window.surface:
801
+ self.native.request(window.surface, 1, P(), I(0), I(0))
802
+ self.native.request(window.surface, 6)
803
+ window.visible, window.configured = False, False
804
+
805
+ def show_window(self, window):
806
+ if window.surface and not window.visible:
807
+ self.native.request(window.surface, 6)
808
+ while not window.configured:
809
+ self.check(self.native.dispatch(self.display))
810
+ window.visible = True
811
+ self.post_empty_event()
812
+
813
+ def get_window_pos(self, window):
814
+ import meltygui.core.windowing.geometry_feed as geometry_feed
815
+ rect = geometry_feed.surface_rect(window.title)
816
+ return (int(rect[0]), int(rect[1])) if rect else (0, 0)
817
+
818
+ def set_window_pos(self, window, x, y):
819
+ import meltygui.core.windowing.geometry_feed as geometry_feed
820
+ geometry_feed.place_window(window.title, (x, y, window.width, window.height), resize=False)
821
+
822
+ def get_monitors(self):
823
+ return list(self.outputs.values())
824
+
825
+ def get_primary_monitor(self):
826
+ return next(iter(self.outputs.values()), None)
827
+
828
+ def get_window_monitor(self, window):
829
+ return None
830
+
831
+ def get_monitor_pos(self, monitor):
832
+ return monitor.x, monitor.y
833
+
834
+ def get_monitor_workarea(self, monitor):
835
+ return monitor.x, monitor.y, monitor.width // monitor.scale, monitor.height // monitor.scale
836
+
837
+ def get_video_mode(self, monitor):
838
+ return SimpleNamespace(size=SimpleNamespace(width=monitor.width // monitor.scale,
839
+ height=monitor.height // monitor.scale),
840
+ refresh_rate=monitor.refresh // 1000, bits=(8, 8, 8))
841
+
842
+ def __getattr__(self, name):
843
+ if name.startswith('set_') and name.endswith('_callback'):
844
+ event = name[4:-9]
845
+ def setter(window, callback):
846
+ previous = window.callbacks.get(event)
847
+ window.callbacks[event] = callback
848
+ return previous
849
+ return setter
850
+ raise AttributeError(f'Native Wayland has no window operation {name}')