| wayland-bar-compositor: a wlroots compositor in jolt, for panels on X11 1d16366 nandi 8d ago | 1 | #!/usr/bin/env jolt |
| 2 | ;; A Wayland compositor for wayland-bar, written in jolt on raw wlroots. |
| 3 | ;; |
| 4 | ;; It replaces the nested sway: one wlroots X11 output of the requested size, |
| 5 | ;; layer-shell and xdg-shell surfaces drawn by the wlroots scene graph, pointer |
| 6 | ;; and keyboard forwarded to them. Because it owns the scene, it knows where |
| 7 | ;; every popup is, so it shapes its own X11 window (strip + popups) through |
| 8 | ;; XShape directly — no screenshots, no polling. |
| 9 | ;; |
| 10 | ;; usage: wayland-bar-compositor --size WxH --strip X,Y,W,H --title TITLE -- CMD [ARGS...] |
| 11 | ;; |
| 12 | ;; Struct offsets come from offsets.edn, generated from the installed wlroots |
| 13 | ;; headers by ./build. Single-threaded: every wlroots callback arrives on this |
| 14 | ;; thread from inside wl_event_loop_dispatch. |
| 15 | |
| 16 | (require '[jolt.ffi :as ffi] |
| 17 | '[babashka.fs :as fs] |
| 18 | '[babashka.process :as p] |
| 19 | '[clojure.edn :as edn] |
| 20 | '[clojure.string :as str]) |
| 21 | |
| 22 | (def here (str (fs/parent (fs/canonicalize *file*)))) |
| 23 | |
| 24 | (def debug? (some? (System/getenv "WBC_DEBUG"))) |
| 25 | |
| 26 | (defn debug [& parts] |
| 27 | (when debug? |
| 28 | (binding [*out* *err*] (apply println "wbc:" parts)))) |
| 29 | |
| 30 | (defn die [& msg] |
| 31 | (binding [*out* *err*] (apply println "wayland-bar-compositor:" msg)) |
| 32 | (System/exit 1)) |
| 33 | |
| 34 | ;; --- arguments --------------------------------------------------------------- |
| 35 | |
| 36 | (defn parse-args [args] |
| 37 | (loop [[a b & more :as args] args opts {}] |
| 38 | (cond |
| 39 | (empty? args) opts |
| 40 | (= a "--") (assoc opts :command (vec (rest args))) |
| 41 | (= a "--size") (let [[w h] (map parse-long (str/split b #"x"))] (recur more (assoc opts :width w :height h))) |
| 42 | (= a "--strip") (recur more (assoc opts :strip (mapv parse-long (str/split b #",")))) |
| 43 | (= a "--title") (recur more (assoc opts :title b)) |
| 44 | (str/starts-with? a "--") (die "unknown argument" a) |
| 45 | ;; jolt may consume a "--" itself, so the first non-option starts the command. |
| 46 | :else (assoc opts :command (vec args))))) |
| 47 | |
| 48 | (def opts (parse-args *command-line-args*)) |
| 49 | (let [{:keys [width height strip title command]} opts] |
| 50 | (when-not (and width height strip title (seq command)) |
| 51 | (die "usage: --size WxH --strip X,Y,W,H --title TITLE -- CMD [ARGS...]"))) |
| 52 | |
| 53 | ;; --- layout facts -------------------------------------------------------------- |
| 54 | |
| 55 | (def layout |
| 56 | (let [f (str here "/offsets.edn")] |
| 57 | (when-not (fs/exists? f) (die "missing" f "- run" (str here "/build"))) |
| 58 | (edn/read-string (slurp f)))) |
| 59 | |
| 60 | (defn off [k] |
| 61 | (or (get layout k) (die "offsets.edn has no" k))) |
| 62 | |
| 63 | ;; --- libraries ----------------------------------------------------------------- |
| 64 | |
| 65 | (ffi/load-library "libc.so.6") |
| 66 | (ffi/load-library "libwayland-server.so.0") |
| 67 | (ffi/load-library "libxkbcommon.so.0") |
| 68 | (ffi/load-library "libwlroots-0.20.so") |
| 69 | (ffi/load-library "libX11.so.6") |
| 70 | (ffi/load-library "libXext.so.6") |
| 71 | |
| 72 | (ffi/defcfn clock-gettime "clock_gettime" [:int :pointer] :int) |
| 73 | |
| 74 | (ffi/defcfn wl-display-create "wl_display_create" [] :pointer) |
| 75 | (ffi/defcfn wl-display-get-event-loop "wl_display_get_event_loop" [:pointer] :pointer) |
| 76 | (ffi/defcfn wl-display-add-socket-auto "wl_display_add_socket_auto" [:pointer] :string) |
| 77 | (ffi/defcfn wl-display-flush-clients "wl_display_flush_clients" [:pointer] :void) |
| 78 | (ffi/defcfn wl-display-destroy-clients "wl_display_destroy_clients" [:pointer] :void) |
| 79 | (ffi/defcfn wl-event-loop-dispatch "wl_event_loop_dispatch" [:pointer :int] :int) |
| 80 | (ffi/defcfn wl-list-insert "wl_list_insert" [:pointer :pointer] :void) |
| 81 | (ffi/defcfn wl-list-remove "wl_list_remove" [:pointer] :void) |
| 82 | |
| 83 | (ffi/defcfn xkb-context-new "xkb_context_new" [:int] :pointer) |
| 84 | (ffi/defcfn xkb-keymap-new-from-names "xkb_keymap_new_from_names" [:pointer :pointer :int] :pointer) |
| 85 | (ffi/defcfn xkb-keymap-unref "xkb_keymap_unref" [:pointer] :void) |
| 86 | (ffi/defcfn xkb-context-unref "xkb_context_unref" [:pointer] :void) |
| 87 | |
| 88 | (ffi/defcfn backend-autocreate "wlr_backend_autocreate" [:pointer :pointer] :pointer) |
| 89 | (ffi/defcfn backend-start "wlr_backend_start" [:pointer] :bool) |
| 90 | (ffi/defcfn renderer-autocreate "wlr_renderer_autocreate" [:pointer] :pointer) |
| 91 | (ffi/defcfn renderer-init-wl-display "wlr_renderer_init_wl_display" [:pointer :pointer] :bool) |
| 92 | (ffi/defcfn allocator-autocreate "wlr_allocator_autocreate" [:pointer :pointer] :pointer) |
| 93 | |
| 94 | (ffi/defcfn compositor-create "wlr_compositor_create" [:pointer :uint32 :pointer] :pointer) |
| 95 | (ffi/defcfn subcompositor-create "wlr_subcompositor_create" [:pointer] :pointer) |
| 96 | (ffi/defcfn data-device-manager-create "wlr_data_device_manager_create" [:pointer] :pointer) |
| 97 | (ffi/defcfn primary-selection-manager-create "wlr_primary_selection_v1_device_manager_create" [:pointer] :pointer) |
| 98 | (ffi/defcfn data-control-manager-create "wlr_data_control_manager_v1_create" [:pointer] :pointer) |
| 99 | (ffi/defcfn xdg-output-manager-create "wlr_xdg_output_manager_v1_create" [:pointer :pointer] :pointer) |
| 100 | (ffi/defcfn screencopy-manager-create "wlr_screencopy_manager_v1_create" [:pointer] :pointer) |
| 101 | (ffi/defcfn viewporter-create "wlr_viewporter_create" [:pointer] :pointer) |
| 102 | (ffi/defcfn fractional-scale-manager-create "wlr_fractional_scale_manager_v1_create" [:pointer :uint32] :pointer) |
| 103 | (ffi/defcfn single-pixel-buffer-manager-create "wlr_single_pixel_buffer_manager_v1_create" [:pointer] :pointer) |
| 104 | (ffi/defcfn presentation-create "wlr_presentation_create" [:pointer :pointer :uint32] :pointer) |
| 105 | (ffi/defcfn xdg-activation-create "wlr_xdg_activation_v1_create" [:pointer] :pointer) |
| 106 | |
| 107 | (ffi/defcfn output-layout-create "wlr_output_layout_create" [:pointer] :pointer) |
| 108 | (ffi/defcfn output-layout-add-auto "wlr_output_layout_add_auto" [:pointer :pointer] :pointer) |
| 109 | (ffi/defcfn output-init-render "wlr_output_init_render" [:pointer :pointer :pointer] :bool) |
| 110 | (ffi/defcfn output-state-init "wlr_output_state_init" [:pointer] :void) |
| 111 | (ffi/defcfn output-state-finish "wlr_output_state_finish" [:pointer] :void) |
| 112 | (ffi/defcfn output-state-set-enabled "wlr_output_state_set_enabled" [:pointer :bool] :void) |
| 113 | (ffi/defcfn output-state-set-custom-mode "wlr_output_state_set_custom_mode" [:pointer :int32 :int32 :int32] :void) |
| 114 | (ffi/defcfn output-commit-state "wlr_output_commit_state" [:pointer :pointer] :bool) |
| 115 | (ffi/defcfn x11-output-set-title "wlr_x11_output_set_title" [:pointer :string] :void) |
| 116 | (ffi/defcfn output-is-x11 "wlr_output_is_x11" [:pointer] :bool) |
| 117 | |
| 118 | (ffi/defcfn scene-create "wlr_scene_create" [] :pointer) |
| 119 | (ffi/defcfn scene-attach-output-layout "wlr_scene_attach_output_layout" [:pointer :pointer] :pointer) |
| 120 | (ffi/defcfn scene-output-create "wlr_scene_output_create" [:pointer :pointer] :pointer) |
| 121 | (ffi/defcfn scene-output-layout-add-output "wlr_scene_output_layout_add_output" [:pointer :pointer :pointer] :void) |
| 122 | (ffi/defcfn scene-output-needs-frame "wlr_scene_output_needs_frame" [:pointer] :bool) |
| 123 | (ffi/defcfn scene-output-build-state "wlr_scene_output_build_state" [:pointer :pointer :pointer] :bool) |
| 124 | (ffi/defcfn buffer-begin-data-ptr-access "wlr_buffer_begin_data_ptr_access" [:pointer :uint32 :pointer :pointer :pointer] :bool) |
| 125 | (ffi/defcfn buffer-end-data-ptr-access "wlr_buffer_end_data_ptr_access" [:pointer] :void) |
| 126 | (ffi/defcfn scene-output-send-frame-done "wlr_scene_output_send_frame_done" [:pointer :pointer] :void) |
| 127 | (ffi/defcfn scene-tree-create "wlr_scene_tree_create" [:pointer] :pointer) |
| 128 | (ffi/defcfn scene-node-at "wlr_scene_node_at" [:pointer :double :double :pointer :pointer] :pointer) |
| 129 | (ffi/defcfn scene-node-coords "wlr_scene_node_coords" [:pointer :pointer :pointer] :bool) |
| 130 | (ffi/defcfn scene-buffer-from-node "wlr_scene_buffer_from_node" [:pointer] :pointer) |
| 131 | (ffi/defcfn scene-surface-try-from-buffer "wlr_scene_surface_try_from_buffer" [:pointer] :pointer) |
| 132 | (ffi/defcfn scene-xdg-surface-create "wlr_scene_xdg_surface_create" [:pointer :pointer] :pointer) |
| 133 | (ffi/defcfn scene-layer-surface-create "wlr_scene_layer_surface_v1_create" [:pointer :pointer] :pointer) |
| 134 | (ffi/defcfn scene-layer-surface-configure "wlr_scene_layer_surface_v1_configure" [:pointer :pointer :pointer] :void) |
| 135 | |
| 136 | (ffi/defcfn xdg-shell-create "wlr_xdg_shell_create" [:pointer :uint32] :pointer) |
| 137 | (ffi/defcfn xdg-surface-try-from-wlr-surface "wlr_xdg_surface_try_from_wlr_surface" [:pointer] :pointer) |
| 138 | (ffi/defcfn xdg-surface-schedule-configure "wlr_xdg_surface_schedule_configure" [:pointer] :uint32) |
| 139 | (ffi/defcfn xdg-toplevel-set-size "wlr_xdg_toplevel_set_size" [:pointer :int32 :int32] :uint32) |
| 140 | (ffi/defcfn xdg-popup-unconstrain-from-box "wlr_xdg_popup_unconstrain_from_box" [:pointer :pointer] :void) |
| 141 | (ffi/defcfn layer-shell-create "wlr_layer_shell_v1_create" [:pointer :uint32] :pointer) |
| 142 | (ffi/defcfn layer-surface-try-from-wlr-surface "wlr_layer_surface_v1_try_from_wlr_surface" [:pointer] :pointer) |
| 143 | |
| 144 | (ffi/defcfn cursor-create "wlr_cursor_create" [] :pointer) |
| 145 | (ffi/defcfn cursor-attach-output-layout "wlr_cursor_attach_output_layout" [:pointer :pointer] :void) |
| 146 | (ffi/defcfn cursor-attach-input-device "wlr_cursor_attach_input_device" [:pointer :pointer] :void) |
| 147 | (ffi/defcfn cursor-move "wlr_cursor_move" [:pointer :pointer :double :double] :void) |
| 148 | (ffi/defcfn cursor-warp-absolute "wlr_cursor_warp_absolute" [:pointer :pointer :double :double] :void) |
| 149 | (ffi/defcfn cursor-set-xcursor "wlr_cursor_set_xcursor" [:pointer :pointer :string] :void) |
| 150 | (ffi/defcfn cursor-set-surface "wlr_cursor_set_surface" [:pointer :pointer :int32 :int32] :void) |
| 151 | (ffi/defcfn xcursor-manager-create "wlr_xcursor_manager_create" [:pointer :uint32] :pointer) |
| 152 | |
| 153 | (ffi/defcfn seat-create "wlr_seat_create" [:pointer :string] :pointer) |
| 154 | (ffi/defcfn seat-set-capabilities "wlr_seat_set_capabilities" [:pointer :uint32] :void) |
| 155 | (ffi/defcfn seat-set-keyboard "wlr_seat_set_keyboard" [:pointer :pointer] :void) |
| 156 | (ffi/defcfn seat-set-selection "wlr_seat_set_selection" [:pointer :pointer :uint32] :void) |
| 157 | (ffi/defcfn seat-set-primary-selection "wlr_seat_set_primary_selection" [:pointer :pointer :uint32] :void) |
| 158 | (ffi/defcfn seat-pointer-notify-enter "wlr_seat_pointer_notify_enter" [:pointer :pointer :double :double] :void) |
| 159 | (ffi/defcfn seat-pointer-notify-motion "wlr_seat_pointer_notify_motion" [:pointer :uint32 :double :double] :void) |
| 160 | (ffi/defcfn seat-pointer-notify-button "wlr_seat_pointer_notify_button" [:pointer :uint32 :uint32 :int] :uint32) |
| 161 | (ffi/defcfn seat-pointer-notify-axis "wlr_seat_pointer_notify_axis" [:pointer :uint32 :int :double :int32 :int :int] :void) |
| 162 | (ffi/defcfn seat-pointer-notify-frame "wlr_seat_pointer_notify_frame" [:pointer] :void) |
| 163 | (ffi/defcfn seat-pointer-clear-focus "wlr_seat_pointer_clear_focus" [:pointer] :void) |
| 164 | (ffi/defcfn seat-keyboard-notify-enter "wlr_seat_keyboard_notify_enter" [:pointer :pointer :pointer :size_t :pointer] :void) |
| 165 | (ffi/defcfn seat-keyboard-notify-key "wlr_seat_keyboard_notify_key" [:pointer :uint32 :uint32 :uint32] :void) |
| 166 | (ffi/defcfn seat-keyboard-notify-modifiers "wlr_seat_keyboard_notify_modifiers" [:pointer :pointer] :void) |
| 167 | (ffi/defcfn keyboard-from-input-device "wlr_keyboard_from_input_device" [:pointer] :pointer) |
| 168 | (ffi/defcfn keyboard-set-keymap "wlr_keyboard_set_keymap" [:pointer :pointer] :bool) |
| 169 | (ffi/defcfn keyboard-set-repeat-info "wlr_keyboard_set_repeat_info" [:pointer :int32 :int32] :void) |
| 170 | |
| 171 | (ffi/load-library "libXi.so.6") |
| 172 | (ffi/defcfn wl-event-loop-add-fd "wl_event_loop_add_fd" [:pointer :int :uint32 :pointer :pointer] :pointer) |
| 173 | (ffi/defcfn xdg-popup-destroy "wlr_xdg_popup_destroy" [:pointer] :void) |
| 174 | (ffi/defcfn x-query-extension "XQueryExtension" [:pointer :string :pointer :pointer :pointer] :int) |
| 175 | (ffi/defcfn xi-query-version "XIQueryVersion" [:pointer :pointer :pointer] :int) |
| 176 | (ffi/defcfn xi-select-events "XISelectEvents" [:pointer :long :pointer :int] :int) |
| 177 | (ffi/defcfn x-pending "XPending" [:pointer] :int) |
| 178 | (ffi/defcfn x-next-event "XNextEvent" [:pointer :pointer] :int) |
| 179 | (ffi/defcfn x-get-event-data "XGetEventData" [:pointer :pointer] :int) |
| 180 | (ffi/defcfn x-free-event-data "XFreeEventData" [:pointer :pointer] :void) |
| 181 | (ffi/defcfn x-query-pointer "XQueryPointer" |
| 182 | [:pointer :long :pointer :pointer :pointer :pointer :pointer :pointer :pointer] :int) |
| 183 | (ffi/defcfn x-default-root-window "XDefaultRootWindow" [:pointer] :long) |
| 184 | (ffi/defcfn x-connection-number "XConnectionNumber" [:pointer] :int) |
| 185 | (ffi/defcfn x-open-display "XOpenDisplay" [:pointer] :pointer) |
| 186 | (ffi/defcfn x-flush "XFlush" [:pointer] :int) |
| 187 | (ffi/defcfn x-shape-combine-rectangles "XShapeCombineRectangles" |
| 188 | [:pointer :long :int :int :int :pointer :int :int :int] :void) |
| 189 | |
| 190 | ;; --- memory helpers --------------------------------------------------------------- |
| 191 | |
| 192 | (def arena (ffi/global-arena)) |
| 193 | (defn ptr [p k] (ffi/read p :pointer (off k))) |
| 194 | (defn i32 [p k] (ffi/read p :int (off k))) |
| 195 | (defn u32 [p k] (ffi/read p :uint32 (off k))) |
| 196 | (defn f64 [p k] (ffi/read p :double (off k))) |
| 197 | (defn flag [p k] (not (zero? (ffi/read p :uint8 (off k))))) |
| 198 | (defn field [p k] (+ p (off k))) |
| 199 | |
| 200 | ;; Scratch buffers reused by every callback; this compositor is single-threaded. |
| 201 | (def out-a (ffi/alloc arena 8)) |
| 202 | (def out-b (ffi/alloc arena 8)) |
| 203 | (def box-buf (ffi/alloc arena (off :wlr_box/sizeof))) |
| 204 | (def usable-buf (ffi/alloc arena (off :wlr_box/sizeof))) |
| 205 | (def timespec (ffi/alloc arena (off :timespec/sizeof))) |
| 206 | |
| 207 | (defn write-box! [buf x y w h] |
| 208 | (ffi/write buf :int x (off :wlr_box/x)) |
| 209 | (ffi/write buf :int y (off :wlr_box/y)) |
| 210 | (ffi/write buf :int w (off :wlr_box/width)) |
| 211 | (ffi/write buf :int h (off :wlr_box/height)) |
| 212 | buf) |
| 213 | |
| 214 | ;; --- signals ---------------------------------------------------------------------- |
| 215 | ;; wl_signal_add is a static inline in C. Every listener here shares ONE native |
| 216 | ;; callback, which looks the listener's address up in `handlers`; listeners come |
| 217 | ;; from a free list, so surfaces that come and go don't leak callbacks. |
| 218 | |
| 219 | (def handlers (atom {})) |
| 220 | (def free-listeners (atom ())) |
| 221 | |
| 222 | (defn- dispatch [listener data] |
| 223 | (when-let [f (get @handlers listener)] |
| 224 | (try (f data) |
| 225 | (catch Throwable e |
| 226 | (binding [*out* *err*] (println "wayland-bar-compositor: handler failed:" e)))))) |
| 227 | |
| 228 | (def notify (ffi/callback arena dispatch [:pointer :pointer] :void)) |
| 229 | |
| 230 | (defn listen! |
| 231 | "Hook f onto the wl_signal at address `signal`. Answers the listener." |
| 232 | [signal f] |
| 233 | (let [listener (or (let [[l] @free-listeners] (when l (swap! free-listeners rest) l)) |
| 234 | (ffi/alloc arena (off :wl_listener/sizeof)))] |
| 235 | (ffi/write listener :pointer notify (off :wl_listener/notify)) |
| 236 | (swap! handlers assoc listener f) |
| 237 | ;; wl_list_insert(signal->listener_list.prev, &listener->link) |
| 238 | (wl-list-insert (ffi/read signal :pointer 0) (field listener :wl_listener/link)) |
| 239 | listener)) |
| 240 | |
| 241 | (defn unlisten! [& listeners] |
| 242 | (doseq [l listeners] |
| 243 | (wl-list-remove (field l :wl_listener/link)) |
| 244 | (swap! handlers dissoc l) |
| 245 | (swap! free-listeners conj l))) |
| 246 | |
| 247 | ;; --- the server ----------------------------------------------------------------- |
| 248 | |
| 249 | (def width (:width opts)) |
| 250 | (def height (:height opts)) |
| 251 | (def display (wl-display-create)) |
| 252 | (def event-loop (wl-display-get-event-loop display)) |
| 253 | (def backend (backend-autocreate event-loop 0)) |
| 254 | (when (zero? backend) (die "no wlroots backend; is DISPLAY set and WLR_BACKENDS=x11?")) |
| 255 | (def renderer (renderer-autocreate backend)) |
| 256 | (when (zero? renderer) (die "no renderer")) |
| 257 | (renderer-init-wl-display renderer display) |
| 258 | (def allocator (allocator-autocreate backend renderer)) |
| 259 | (when (zero? allocator) (die "no allocator")) |
| 260 | |
| 261 | (compositor-create display 5 renderer) |
| 262 | (subcompositor-create display) |
| 263 | (data-device-manager-create display) |
| 264 | (primary-selection-manager-create display) |
| 265 | (data-control-manager-create display) |
| 266 | (viewporter-create display) |
| 267 | (fractional-scale-manager-create display 1) |
| 268 | (single-pixel-buffer-manager-create display) |
| 269 | (screencopy-manager-create display) |
| 270 | (xdg-activation-create display) |
| 271 | (presentation-create display backend 2) |
| 272 | |
| 273 | (def output-layout (output-layout-create display)) |
| 274 | (xdg-output-manager-create display output-layout) |
| 275 | (def scene (scene-create)) |
| 276 | (def scene-layout (scene-attach-output-layout scene output-layout)) |
| 277 | (def root (field scene :wlr_scene/tree)) |
| 278 | |
| 279 | ;; Layer-shell layers, bottom to top; popups float above everything. |
| 280 | (def layer-trees (vec (repeatedly 4 #(scene-tree-create root)))) |
| 281 | (def toplevel-tree (scene-tree-create root)) |
| 282 | (def popup-tree (scene-tree-create root)) |
| 283 | |
| 284 | (def output (atom nil)) ; the one wlr_output |
| 285 | (def layer-surfaces (atom {})) ; wlr_layer_surface_v1 -> scene layer surface |
| 286 | (def popups (atom {})) ; wlr_xdg_popup -> scene tree |
| 287 | |
| 288 | ;; --- X11 window shape -------------------------------------------------------------- |
| 289 | |
| 290 | (def x-display (x-open-display 0)) |
| 291 | (when (zero? x-display) (die "cannot open the X display")) |
| 292 | (def x-window (atom nil)) |
| 293 | (def shown-rects (atom nil)) |
| 294 | (def max-rects 1024) |
| 295 | (def rect-buf (ffi/alloc arena (* 8 max-rects))) ; XRectangle { short x, y; ushort w, h } |
| 296 | |
| 297 | (defn find-x-window |
| 298 | "wlroots owns the X11 window; we gave it a unique title, so look it up." |
| 299 | [] |
| 300 | (let [out (:out (p/sh "xwininfo" "-name" (:title opts)))] |
| 301 | (some->> (re-find #"Window id: (0x[0-9a-f]+)" out) second (#(Long/parseLong (subs % 2) 16))))) |
| 302 | |
| 303 | (defn popup-rects |
| 304 | "Layout-space boxes of every mapped popup: its geometry, without shadows." |
| 305 | [] |
| 306 | (for [[popup tree] @popups |
| 307 | :let [base (ptr popup :wlr_xdg_popup/base) |
| 308 | surface (ptr base :wlr_xdg_surface/surface)] |
| 309 | :when (flag surface :wlr_surface/mapped) |
| 310 | :let [_ (scene-node-coords (field tree :wlr_scene_tree/node) out-a out-b) |
| 311 | geo (field base :wlr_xdg_surface/geometry) |
| 312 | w (i32 geo :wlr_box/width) |
| 313 | h (i32 geo :wlr_box/height)] |
| 314 | :when (and (pos? w) (pos? h))] |
| 315 | [(ffi/read out-a :int) (ffi/read out-b :int) w h])) |
| 316 | |
| 317 | ;; Popups have rounded corners; their geometry box would show the black output |
| 318 | ;; behind the corners. So read the frame about to be shown and trace each |
| 319 | ;; corner row inward to the first painted pixel. Pixman frames are plain |
| 320 | ;; memory, and only corner rows are scanned, only when the popups change. |
| 321 | |
| 322 | (def data-out (ffi/alloc arena 8)) |
| 323 | (def format-out (ffi/alloc arena 4)) |
| 324 | (def stride-out (ffi/alloc arena 8)) |
| 325 | (def rgb32-formats #{0x34325258 0x34325241 0x34324258 0x34324241}) ; XRGB/ARGB/XBGR/ABGR8888 |
| 326 | |
| 327 | ;; Transparent margins and faint shadows composite onto the black output as |
| 328 | ;; black or near-black; a popup's own background is well above this. |
| 329 | (def paint-threshold 10) |
| 330 | |
| 331 | (defn- painted? [px] |
| 332 | (or (> (bit-and (bit-shift-right px 16) 0xff) paint-threshold) |
| 333 | (> (bit-and (bit-shift-right px 8) 0xff) paint-threshold) |
| 334 | (> (bit-and px 0xff) paint-threshold))) |
| 335 | |
| 336 | (defn- row-span |
| 337 | "[left right) of the painted part of row y within [x, x+w); nil if none. |
| 338 | The row is copied out in one call rather than read pixel by pixel." |
| 339 | [data stride x y w] |
| 340 | (let [row (ffi/read-array (+ data (* y stride)) :uint32 w (* 4 x)) |
| 341 | left (loop [i 0] (cond (= i w) nil (painted? (aget row i)) i :else (recur (inc i))))] |
| 342 | (when left |
| 343 | (let [right (loop [i (dec w)] (if (painted? (aget row i)) (inc i) (recur (dec i))))] |
| 344 | [(+ x left) (+ x right)])))) |
| 345 | |
| 346 | (defn outline-rects |
| 347 | "XShape rects for popup box [x y w h], following what the popup actually |
| 348 | painted: rounded corners, and any transparent margin its geometry includes. |
| 349 | Consecutive rows with the same span merge into one rect." |
| 350 | [data stride [x y w h]] |
| 351 | (let [rows (for [row (range y (+ y h)) |
| 352 | :let [[l r] (row-span data stride x row w)] |
| 353 | :when l] |
| 354 | [l row (- r l) 1])] |
| 355 | (->> rows |
| 356 | (reduce (fn [acc [rx ry rw rh :as rect]] |
| 357 | (let [[px py pw ph] (peek acc)] |
| 358 | (if (and px (= px rx) (= pw rw) (= (+ py ph) ry)) |
| 359 | (conj (pop acc) [px py pw (+ ph rh)]) |
| 360 | (conj acc rect)))) |
| 361 | [])))) |
| 362 | |
| 363 | (defn- frame-outlines |
| 364 | "Outline rects for every popup box, read from `buffer`; nil if unreadable." |
| 365 | [buffer boxes] |
| 366 | (when (and (pos? buffer) |
| 367 | (buffer-begin-data-ptr-access buffer (off :enum/buffer-data-ptr-access-read) |
| 368 | data-out format-out stride-out)) |
| 369 | (try |
| 370 | (when (rgb32-formats (ffi/read format-out :uint32)) |
| 371 | (let [data (ffi/read data-out :pointer) |
| 372 | stride (ffi/read stride-out :size_t)] |
| 373 | (vec (mapcat (fn [[x y w h]] |
| 374 | ;; clamp to the output so a scan never leaves the buffer |
| 375 | (let [x (max 0 x) y (max 0 y) |
| 376 | w (min w (- width x)) h (min h (- height y))] |
| 377 | (when (and (pos? w) (pos? h)) |
| 378 | (outline-rects data stride [x y w h])))) |
| 379 | boxes)))) |
| 380 | (finally (buffer-end-data-ptr-access buffer))))) |
| 381 | |
| 382 | (def shown-boxes (atom nil)) |
| 383 | (def popups-dirty (atom false)) ; a popup committed since its outline was traced |
| 384 | |
| 385 | (defn update-shape! |
| 386 | "Shape the window to the strip plus the popups' outlines. `buffer` is the |
| 387 | frame about to be shown, or nil when nothing was rendered." |
| 388 | [buffer] |
| 389 | (when-not @x-window |
| 390 | (reset! x-window (find-x-window))) |
| 391 | (when-let [win @x-window] |
| 392 | (let [boxes (vec (popup-rects)) |
| 393 | retrace? (and (seq boxes) (or (not= boxes @shown-boxes) (and buffer @popups-dirty))) |
| 394 | outlines (when retrace? |
| 395 | ;; tracing needs a rendered frame; without one, wait for it |
| 396 | (when buffer |
| 397 | (reset! popups-dirty false) |
| 398 | (or (frame-outlines buffer boxes) boxes))) |
| 399 | rects (cond |
| 400 | (empty? boxes) [(:strip opts)] |
| 401 | outlines (vec (take max-rects (cons (:strip opts) outlines))) |
| 402 | :else nil)] |
| 403 | (when (and rects (not= rects @shown-rects)) |
| 404 | (reset! shown-boxes boxes) |
| 405 | (debug "shape" (count rects) "rects" (take 4 rects)) |
| 406 | (doseq [[i [x y w h]] (map-indexed vector rects)] |
| 407 | (ffi/write rect-buf :short (short x) (* 8 i)) |
| 408 | (ffi/write rect-buf :short (short y) (+ 2 (* 8 i))) |
| 409 | (ffi/write rect-buf :short (unchecked-short w) (+ 4 (* 8 i))) |
| 410 | (ffi/write rect-buf :short (unchecked-short h) (+ 6 (* 8 i)))) |
| 411 | ;; ShapeBounding = 0, ShapeInput = 2; ShapeSet = 0; Unsorted = 0 |
| 412 | (x-shape-combine-rectangles x-display win 0 0 0 rect-buf (count rects) 0 0) |
| 413 | (x-shape-combine-rectangles x-display win 2 0 0 rect-buf (count rects) 0 0) |
| 414 | (x-flush x-display) |
| 415 | (reset! shown-rects rects))))) |
| 416 | |
| 417 | ;; --- outputs ------------------------------------------------------------------------ |
| 418 | |
| 419 | (defn with-output-state [f] |
| 420 | (let [state (ffi/alloc arena (off :wlr_output_state/sizeof))] |
| 421 | (output-state-init state) |
| 422 | (try (f state) (finally (output-state-finish state))))) |
| 423 | |
| 424 | (def child (atom nil)) |
| 425 | (def frame-state (ffi/alloc arena (off :wlr_output_state/sizeof))) ; reused every frame |
| 426 | |
| 427 | (defn arrange-layers! [] |
| 428 | (write-box! box-buf 0 0 width height) |
| 429 | (write-box! usable-buf 0 0 width height) |
| 430 | (doseq [[_ scene-ls] @layer-surfaces] |
| 431 | (scene-layer-surface-configure scene-ls box-buf usable-buf))) |
| 432 | |
| 433 | (listen! (field backend :wlr_backend/events.new_output) |
| 434 | (fn [wlr-output] |
| 435 | (if @output |
| 436 | (println "wayland-bar-compositor: ignoring extra output") |
| 437 | (do |
| 438 | (reset! output wlr-output) |
| 439 | (output-init-render wlr-output allocator renderer) |
| 440 | (with-output-state |
| 441 | (fn [state] |
| 442 | (output-state-set-enabled state true) |
| 443 | (output-state-set-custom-mode state width height 0) |
| 444 | (output-commit-state wlr-output state))) |
| 445 | (when (output-is-x11 wlr-output) |
| 446 | (x11-output-set-title wlr-output (:title opts))) |
| 447 | (let [scene-output (scene-output-create scene wlr-output)] |
| 448 | (scene-output-layout-add-output scene-layout (output-layout-add-auto output-layout wlr-output) scene-output) |
| 449 | (listen! (field wlr-output :wlr_output/events.frame) |
| 450 | (fn [_] |
| 451 | ;; wlr_scene_output_commit, split open so the frame can be read |
| 452 | ;; for popup outlines between rendering and showing it. |
| 453 | (if (scene-output-needs-frame scene-output) |
| 454 | (do |
| 455 | (output-state-init frame-state) |
| 456 | (try |
| 457 | (when (scene-output-build-state scene-output frame-state 0) |
| 458 | (update-shape! (when (pos? (bit-and (u32 frame-state :wlr_output_state/committed) |
| 459 | (off :enum/output-state-buffer))) |
| 460 | (ptr frame-state :wlr_output_state/buffer))) |
| 461 | (output-commit-state wlr-output frame-state)) |
| 462 | (finally (output-state-finish frame-state)))) |
| 463 | (update-shape! nil)) |
| 464 | (clock-gettime 1 timespec) ; CLOCK_MONOTONIC |
| 465 | (scene-output-send-frame-done scene-output timespec))) |
| 466 | (listen! (field wlr-output :wlr_output/events.request_state) |
| 467 | (fn [event] |
| 468 | (output-commit-state wlr-output (ptr event :wlr_output_event_request_state/state))))))))) |
| 469 | |
| 470 | ;; --- layer shell ----------------------------------------------------------------- |
| 471 | |
| 472 | (def layer-shell (layer-shell-create display 4)) |
| 473 | (declare add-popup!) |
| 474 | |
| 475 | (listen! (field layer-shell :wlr_layer_shell_v1/events.new_surface) |
| 476 | (fn [ls] |
| 477 | (when (zero? (ptr ls :wlr_layer_surface_v1/output)) |
| 478 | (ffi/write ls :pointer (or @output 0) (off :wlr_layer_surface_v1/output))) |
| 479 | (let [layer (min 3 (max 0 (i32 ls :wlr_layer_surface_v1/current.layer))) |
| 480 | scene-ls (scene-layer-surface-create (nth layer-trees layer) ls) |
| 481 | surface (ptr ls :wlr_layer_surface_v1/surface)] |
| 482 | ;; popups find their parent's scene tree through data |
| 483 | (ffi/write ls :pointer (ptr scene-ls :wlr_scene_layer_surface_v1/tree) (off :wlr_layer_surface_v1/data)) |
| 484 | (swap! layer-surfaces assoc ls scene-ls) |
| 485 | (let [commit (listen! (field surface :wlr_surface/events.commit) |
| 486 | (fn [_] (when (flag ls :wlr_layer_surface_v1/initialized) (arrange-layers!)))) |
| 487 | ;; xdg-shell announces a layer surface's popup before it has a |
| 488 | ;; parent; this signal comes once the parent is set. |
| 489 | new-popup (listen! (field ls :wlr_layer_surface_v1/events.new_popup) add-popup!) |
| 490 | destroy (atom nil)] |
| 491 | (reset! destroy |
| 492 | (listen! (field ls :wlr_layer_surface_v1/events.destroy) |
| 493 | (fn [_] |
| 494 | (swap! layer-surfaces dissoc ls) |
| 495 | (unlisten! commit new-popup @destroy)))))))) |
| 496 | |
| 497 | ;; --- xdg shell ------------------------------------------------------------------- |
| 498 | |
| 499 | (def xdg-shell (xdg-shell-create display 6)) |
| 500 | |
| 501 | (listen! (field xdg-shell :wlr_xdg_shell/events.new_toplevel) |
| 502 | (fn [toplevel] |
| 503 | (let [base (ptr toplevel :wlr_xdg_toplevel/base) |
| 504 | tree (scene-xdg-surface-create toplevel-tree base) |
| 505 | surface (ptr base :wlr_xdg_surface/surface)] |
| 506 | (ffi/write base :pointer tree (off :wlr_xdg_surface/data)) |
| 507 | (let [commit (listen! (field surface :wlr_surface/events.commit) |
| 508 | (fn [_] (when (flag base :wlr_xdg_surface/initial_commit) |
| 509 | (xdg-toplevel-set-size toplevel 0 0)))) |
| 510 | destroy (atom nil)] |
| 511 | (reset! destroy |
| 512 | (listen! (field toplevel :wlr_xdg_toplevel/events.destroy) |
| 513 | (fn [_] (unlisten! commit @destroy)))))))) |
| 514 | |
| 515 | (defn parent-tree |
| 516 | "The scene tree a popup's parent surface draws into: an xdg surface's or a |
| 517 | layer surface's, both kept in their data field." |
| 518 | [parent-surface] |
| 519 | (let [xdg (xdg-surface-try-from-wlr-surface parent-surface)] |
| 520 | (if (pos? xdg) |
| 521 | (ptr xdg :wlr_xdg_surface/data) |
| 522 | (let [ls (layer-surface-try-from-wlr-surface parent-surface)] |
| 523 | (when (pos? ls) (ptr ls :wlr_layer_surface_v1/data)))))) |
| 524 | |
| 525 | (defn add-popup! |
| 526 | "Put a popup whose parent is set into the scene, under its parent's tree." |
| 527 | [popup] |
| 528 | (let [base (ptr popup :wlr_xdg_popup/base) |
| 529 | surface (ptr base :wlr_xdg_surface/surface) |
| 530 | parent (parent-tree (ptr popup :wlr_xdg_popup/parent))] |
| 531 | (debug "add popup" popup "parent tree" parent) |
| 532 | (if-not parent |
| 533 | (println "wayland-bar-compositor: popup with no known parent") |
| 534 | (let [tree (scene-xdg-surface-create parent base)] |
| 535 | (ffi/write base :pointer tree (off :wlr_xdg_surface/data)) |
| 536 | (swap! popups assoc popup tree) |
| 537 | (let [commit (listen! (field surface :wlr_surface/events.commit) |
| 538 | (fn [_] |
| 539 | ;; new content may change the outline (a client's |
| 540 | ;; first buffer is often blank), so retrace it |
| 541 | (reset! popups-dirty true) |
| 542 | (when (flag base :wlr_xdg_surface/initial_commit) |
| 543 | ;; keep the popup inside the output, in parent-relative space |
| 544 | (scene-node-coords (field parent :wlr_scene_tree/node) out-a out-b) |
| 545 | (xdg-popup-unconstrain-from-box |
| 546 | popup (write-box! box-buf (- (ffi/read out-a :int)) (- (ffi/read out-b :int)) width height)) |
| 547 | (xdg-surface-schedule-configure base)))) |
| 548 | destroy (atom nil)] |
| 549 | (reset! destroy |
| 550 | (listen! (field popup :wlr_xdg_popup/events.destroy) |
| 551 | (fn [_] |
| 552 | (swap! popups dissoc popup) |
| 553 | (unlisten! commit @destroy))))))))) |
| 554 | |
| 555 | ;; A layer surface's popup arrives here with no parent yet and again, parented, |
| 556 | ;; on the layer surface's own new_popup; only take the parented ones here. |
| 557 | (listen! (field xdg-shell :wlr_xdg_shell/events.new_popup) |
| 558 | (fn [popup] |
| 559 | (when (pos? (ptr popup :wlr_xdg_popup/parent)) |
| 560 | (add-popup! popup)))) |
| 561 | |
| 562 | ;; --- clicks on X11 apps close popups -------------------------------------------- |
| 563 | ;; A click on an X11 window never reaches Wayland, so popups' own grabs can't |
| 564 | ;; see it. XInput2 raw button presses on the root window report every click |
| 565 | ;; without grabbing anything; if the pointer is outside what our window shows, |
| 566 | ;; close the open popups. Layouts below checked against the X11/XI2 headers. |
| 567 | |
| 568 | (def generic-event 35) |
| 569 | (def xi-raw-button-press 15) |
| 570 | |
| 571 | (def xi-opcode |
| 572 | (let [opcode (ffi/alloc arena 4) event (ffi/alloc arena 4) error (ffi/alloc arena 4)] |
| 573 | (when (pos? (x-query-extension x-display "XInputExtension" opcode event error)) |
| 574 | (ffi/read opcode :int)))) |
| 575 | |
| 576 | (if-not xi-opcode |
| 577 | (println "wayland-bar-compositor: no XInput2; clicks on X11 apps won't close popups") |
| 578 | (let [major (ffi/alloc arena 4) minor (ffi/alloc arena 4) |
| 579 | mask (ffi/alloc arena 5) ; XIMaskLen(XI_LASTEVENT) |
| 580 | event-mask (ffi/alloc arena 16)] ; XIEventMask { deviceid; mask_len; mask* } |
| 581 | (ffi/write major :int 2) |
| 582 | (ffi/write minor :int 0) |
| 583 | (xi-query-version x-display major minor) ; required before XI2 selection |
| 584 | (ffi/write mask :uint8 (bit-shift-left 1 (mod xi-raw-button-press 8)) (quot xi-raw-button-press 8)) |
| 585 | (ffi/write event-mask :int 1 0) ; XIAllMasterDevices |
| 586 | (ffi/write event-mask :int 5 4) |
| 587 | (ffi/write event-mask :pointer mask 8) |
| 588 | (xi-select-events x-display (x-default-root-window x-display) event-mask 1) |
| 589 | (x-flush x-display))) |
| 590 | |
| 591 | (def x-event (ffi/alloc arena 192)) ; sizeof(XEvent) |
| 592 | (def query-bufs (vec (repeatedly 7 #(ffi/alloc arena 8)))) |
| 593 | |
| 594 | (defn pointer-in-window? |
| 595 | "Whether the pointer is over a part of our window that is shown." |
| 596 | [] |
| 597 | (let [[root child root-x root-y win-x win-y buttons] query-bufs] |
| 598 | (x-query-pointer x-display @x-window root child root-x root-y win-x win-y buttons) |
| 599 | (let [x (ffi/read win-x :int) y (ffi/read win-y :int)] |
| 600 | (some (fn [[rx ry rw rh]] (and (<= rx x) (< x (+ rx rw)) (<= ry y) (< y (+ ry rh)))) |
| 601 | @shown-rects)))) |
| 602 | |
| 603 | (defn dismiss-popups! |
| 604 | "Destroy the top-level popups; wlroots takes their child popups with them." |
| 605 | [] |
| 606 | (let [roots (doall (for [[popup _] @popups |
| 607 | :when (zero? (xdg-surface-try-from-wlr-surface (ptr popup :wlr_xdg_popup/parent)))] |
| 608 | popup))] |
| 609 | (debug "click outside on X11: closing" (count roots) "popup(s)") |
| 610 | (doseq [popup roots] (xdg-popup-destroy popup)))) |
| 611 | |
| 612 | (defn drain-x-events! [] |
| 613 | (while (pos? (x-pending x-display)) |
| 614 | (x-next-event x-display x-event) |
| 615 | (when (and xi-opcode |
| 616 | (= (ffi/read x-event :int 0) generic-event) ; cookie.type |
| 617 | (= (ffi/read x-event :int 32) xi-opcode) ; cookie.extension |
| 618 | (pos? (x-get-event-data x-display x-event))) |
| 619 | (let [evtype (ffi/read x-event :int 36)] ; cookie.evtype |
| 620 | (x-free-event-data x-display x-event) |
| 621 | (when (and (= evtype xi-raw-button-press) (seq @popups) @x-window (not (pointer-in-window?))) |
| 622 | (dismiss-popups!)))))) |
| 623 | |
| 624 | (def x-fd-ready |
| 625 | (ffi/callback arena |
| 626 | (fn [_fd _mask _data] |
| 627 | (try (drain-x-events!) |
| 628 | (catch Throwable e |
| 629 | (binding [*out* *err*] (println "wayland-bar-compositor: X event handling failed:" e)))) |
| 630 | 0) |
| 631 | [:int :uint32 :pointer] :int)) |
| 632 | |
| 633 | (when xi-opcode |
| 634 | (wl-event-loop-add-fd event-loop (x-connection-number x-display) 1 x-fd-ready 0)) ; WL_EVENT_READABLE |
| 635 | |
| 636 | ;; --- seat and input ---------------------------------------------------------------- |
| 637 | |
| 638 | (def seat (seat-create display "seat0")) |
| 639 | (def cursor (cursor-create)) |
| 640 | (cursor-attach-output-layout cursor output-layout) |
| 641 | (def cursor-manager (xcursor-manager-create 0 24)) |
| 642 | (def keyboards (atom #{})) |
| 643 | |
| 644 | (defn surface-at |
| 645 | "[surface sx sy] under layout point (lx, ly), or nil." |
| 646 | [lx ly] |
| 647 | (let [node (scene-node-at (field root :wlr_scene_tree/node) lx ly out-a out-b)] |
| 648 | (when (and (pos? node) (= (i32 node :wlr_scene_node/type) (off :enum/scene-node-buffer))) |
| 649 | (let [scene-surface (scene-surface-try-from-buffer (scene-buffer-from-node node))] |
| 650 | (when (pos? scene-surface) |
| 651 | [(ptr scene-surface :wlr_scene_surface/surface) (ffi/read out-a :double) (ffi/read out-b :double)]))))) |
| 652 | |
| 653 | (defn focus-keyboard! [surface] |
| 654 | (when-let [kb (first @keyboards)] |
| 655 | (seat-keyboard-notify-enter seat surface (field kb :wlr_keyboard/keycodes) |
| 656 | (ffi/read kb :size_t (off :wlr_keyboard/num_keycodes)) |
| 657 | (field kb :wlr_keyboard/modifiers)))) |
| 658 | |
| 659 | (defn pointer-motion! [time] |
| 660 | (debug "motion" (f64 cursor :wlr_cursor/x) (f64 cursor :wlr_cursor/y)) |
| 661 | (if-let [[surface sx sy] (surface-at (f64 cursor :wlr_cursor/x) (f64 cursor :wlr_cursor/y))] |
| 662 | (do (seat-pointer-notify-enter seat surface sx sy) |
| 663 | (seat-pointer-notify-motion seat time sx sy)) |
| 664 | (do (cursor-set-xcursor cursor cursor-manager "default") |
| 665 | (seat-pointer-clear-focus seat)))) |
| 666 | |
| 667 | (listen! (field cursor :wlr_cursor/events.motion) |
| 668 | (fn [e] |
| 669 | (cursor-move cursor (field (ptr e :wlr_pointer_motion_event/pointer) :wlr_pointer/base) |
| 670 | (f64 e :wlr_pointer_motion_event/delta_x) (f64 e :wlr_pointer_motion_event/delta_y)) |
| 671 | (pointer-motion! (u32 e :wlr_pointer_motion_event/time_msec)))) |
| 672 | |
| 673 | (listen! (field cursor :wlr_cursor/events.motion_absolute) |
| 674 | (fn [e] |
| 675 | (cursor-warp-absolute cursor (field (ptr e :wlr_pointer_motion_absolute_event/pointer) :wlr_pointer/base) |
| 676 | (f64 e :wlr_pointer_motion_absolute_event/x) (f64 e :wlr_pointer_motion_absolute_event/y)) |
| 677 | (pointer-motion! (u32 e :wlr_pointer_motion_absolute_event/time_msec)))) |
| 678 | |
| 679 | (listen! (field cursor :wlr_cursor/events.button) |
| 680 | (fn [e] |
| 681 | (seat-pointer-notify-button seat (u32 e :wlr_pointer_button_event/time_msec) |
| 682 | (u32 e :wlr_pointer_button_event/button) (i32 e :wlr_pointer_button_event/state)) |
| 683 | (debug "button" (u32 e :wlr_pointer_button_event/button) "state" (i32 e :wlr_pointer_button_event/state) |
| 684 | "at" (f64 cursor :wlr_cursor/x) (f64 cursor :wlr_cursor/y) |
| 685 | "surface" (first (surface-at (f64 cursor :wlr_cursor/x) (f64 cursor :wlr_cursor/y)))) |
| 686 | (when (= (i32 e :wlr_pointer_button_event/state) (off :enum/button-pressed)) |
| 687 | (when-let [[surface] (surface-at (f64 cursor :wlr_cursor/x) (f64 cursor :wlr_cursor/y))] |
| 688 | (focus-keyboard! surface))))) |
| 689 | |
| 690 | (listen! (field cursor :wlr_cursor/events.axis) |
| 691 | (fn [e] |
| 692 | (seat-pointer-notify-axis seat (u32 e :wlr_pointer_axis_event/time_msec) |
| 693 | (i32 e :wlr_pointer_axis_event/orientation) (f64 e :wlr_pointer_axis_event/delta) |
| 694 | (i32 e :wlr_pointer_axis_event/delta_discrete) (i32 e :wlr_pointer_axis_event/source) |
| 695 | (i32 e :wlr_pointer_axis_event/relative_direction)))) |
| 696 | |
| 697 | (listen! (field cursor :wlr_cursor/events.frame) |
| 698 | (fn [_] (seat-pointer-notify-frame seat))) |
| 699 | |
| 700 | (listen! (field seat :wlr_seat/events.request_set_cursor) |
| 701 | (fn [e] |
| 702 | (when (= (ptr e :wlr_seat_pointer_request_set_cursor_event/seat_client) |
| 703 | (ptr seat :wlr_seat/pointer_state.focused_client)) |
| 704 | (cursor-set-surface cursor (ptr e :wlr_seat_pointer_request_set_cursor_event/surface) |
| 705 | (i32 e :wlr_seat_pointer_request_set_cursor_event/hotspot_x) |
| 706 | (i32 e :wlr_seat_pointer_request_set_cursor_event/hotspot_y))))) |
| 707 | |
| 708 | (listen! (field seat :wlr_seat/events.request_set_selection) |
| 709 | (fn [e] (seat-set-selection seat (ptr e :wlr_seat_request_set_selection_event/source) |
| 710 | (u32 e :wlr_seat_request_set_selection_event/serial)))) |
| 711 | |
| 712 | (listen! (field seat :wlr_seat/events.request_set_primary_selection) |
| 713 | (fn [e] (seat-set-primary-selection seat (ptr e :wlr_seat_request_set_primary_selection_event/source) |
| 714 | (u32 e :wlr_seat_request_set_primary_selection_event/serial)))) |
| 715 | |
| 716 | (defn update-capabilities! [] |
| 717 | (seat-set-capabilities seat (bit-or (off :enum/seat-capability-pointer) |
| 718 | (if (seq @keyboards) (off :enum/seat-capability-keyboard) 0)))) |
| 719 | |
| 720 | (defn add-keyboard! [device] |
| 721 | (let [kb (keyboard-from-input-device device) |
| 722 | context (xkb-context-new 0) |
| 723 | keymap (xkb-keymap-new-from-names context 0 0)] |
| 724 | (keyboard-set-keymap kb keymap) |
| 725 | (xkb-keymap-unref keymap) |
| 726 | (xkb-context-unref context) |
| 727 | (keyboard-set-repeat-info kb 25 600) |
| 728 | (let [modifiers (listen! (field kb :wlr_keyboard/events.modifiers) |
| 729 | (fn [_] |
| 730 | (seat-set-keyboard seat kb) |
| 731 | (seat-keyboard-notify-modifiers seat (field kb :wlr_keyboard/modifiers)))) |
| 732 | key (listen! (field kb :wlr_keyboard/events.key) |
| 733 | (fn [e] |
| 734 | (seat-set-keyboard seat kb) |
| 735 | (seat-keyboard-notify-key seat (u32 e :wlr_keyboard_key_event/time_msec) |
| 736 | (u32 e :wlr_keyboard_key_event/keycode) |
| 737 | (u32 e :wlr_keyboard_key_event/state)))) |
| 738 | destroy (atom nil)] |
| 739 | (reset! destroy |
| 740 | (listen! (field device :wlr_input_device/events.destroy) |
| 741 | (fn [_] |
| 742 | (swap! keyboards disj kb) |
| 743 | (unlisten! modifiers key @destroy) |
| 744 | (update-capabilities!))))) |
| 745 | (seat-set-keyboard seat kb) |
| 746 | (swap! keyboards conj kb))) |
| 747 | |
| 748 | (listen! (field backend :wlr_backend/events.new_input) |
| 749 | (fn [device] |
| 750 | (let [type (i32 device :wlr_input_device/type)] |
| 751 | (debug "new input device type" type) |
| 752 | (cond |
| 753 | (= type (off :enum/input-device-keyboard)) (add-keyboard! device) |
| 754 | (= type (off :enum/input-device-pointer)) (cursor-attach-input-device cursor device))) |
| 755 | (update-capabilities!))) |
| 756 | |
| 757 | ;; --- run ----------------------------------------------------------------------------- |
| 758 | |
| 759 | (def socket (wl-display-add-socket-auto display)) |
| 760 | (when-not socket (die "cannot create a wayland socket")) |
| 761 | (when-not (backend-start backend) (die "backend failed to start")) |
| 762 | (println "wayland-bar-compositor: WAYLAND_DISPLAY" socket) |
| 763 | |
| 764 | (reset! child (p/process (:command opts) {:extra-env {"WAYLAND_DISPLAY" socket} |
| 765 | :out :inherit :err :inherit})) |
| 766 | (.addShutdownHook (Runtime/getRuntime) |
| 767 | (Thread. (fn [] (when @child (p/destroy @child))))) |
| 768 | |
| 769 | (while (p/alive? @child) |
| 770 | (wl-event-loop-dispatch event-loop 250) |
| 771 | ;; Xlib may have read events into its queue during our own X calls, where |
| 772 | ;; the fd never becomes readable again; drain them here too. |
| 773 | (drain-x-events!) |
| 774 | (wl-display-flush-clients display)) |
| 775 | |
| 776 | (println "wayland-bar-compositor:" (first (:command opts)) "exited") |
| 777 | (wl-display-destroy-clients display) |
| 778 | (System/exit 0) |