12b4f1c8
Bind wl_output globals into ScaleTracker
a73x 2026-04-09 10:15
Commit message
build.zig
| Old | New | ||
|---|---|---|---|
| @@ -27,6 +27,7 @@ pub fn build(b: *std.Build) void { | |||
| 27 | scanner.generate("wl_compositor", 6); | 27 | scanner.generate("wl_compositor", 6); |
| 28 | scanner.generate("wl_seat", 9); | 28 | scanner.generate("wl_seat", 9); |
| 29 | scanner.generate("wl_data_device_manager", 3); | 29 | scanner.generate("wl_data_device_manager", 3); |
| 30 | scanner.generate("wl_output", 4); | ||
| 30 | scanner.generate("xdg_wm_base", 6); | 31 | scanner.generate("xdg_wm_base", 6); |
| 31 | 32 | ||
| 32 | // wayland module — generated bindings + our Connection wrapper | 33 | // wayland module — generated bindings + our Connection wrapper |
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -85,7 +85,7 @@ fn runTerminal(alloc: std.mem.Allocator) !void { | |||
| 85 | const initial_h: u32 = @as(u32, rows) * cell_h; | 85 | const initial_h: u32 = @as(u32, rows) * cell_h; |
| 86 | 86 | ||
| 87 | // === wayland === | 87 | // === wayland === |
| 88 | var conn = try wayland_client.Connection.init(); | 88 | const conn = try wayland_client.Connection.init(alloc); |
| 89 | defer conn.deinit(); | 89 | defer conn.deinit(); |
| 90 | 90 | ||
| 91 | const window = try conn.createWindow(alloc, "waystty"); | 91 | const window = try conn.createWindow(alloc, "waystty"); |
| @@ -1757,7 +1757,7 @@ fn makeTestInstances( | |||
| 1757 | } | 1757 | } |
| 1758 | 1758 | ||
| 1759 | fn runTextCoverageCompare(alloc: std.mem.Allocator) !void { | 1759 | fn runTextCoverageCompare(alloc: std.mem.Allocator) !void { |
| 1760 | var conn = try wayland_client.Connection.init(); | 1760 | const conn = try wayland_client.Connection.init(alloc); |
| 1761 | defer conn.deinit(); | 1761 | defer conn.deinit(); |
| 1762 | 1762 | ||
| 1763 | const window = try conn.createWindow(alloc, "waystty-text-compare"); | 1763 | const window = try conn.createWindow(alloc, "waystty-text-compare"); |
| @@ -1847,7 +1847,7 @@ fn runTextCoverageCompare(alloc: std.mem.Allocator) !void { | |||
| 1847 | } | 1847 | } |
| 1848 | 1848 | ||
| 1849 | fn runDrawSmokeTest(alloc: std.mem.Allocator) !void { | 1849 | fn runDrawSmokeTest(alloc: std.mem.Allocator) !void { |
| 1850 | var conn = try wayland_client.Connection.init(); | 1850 | const conn = try wayland_client.Connection.init(alloc); |
| 1851 | defer conn.deinit(); | 1851 | defer conn.deinit(); |
| 1852 | std.debug.print("wayland connected\n", .{}); | 1852 | std.debug.print("wayland connected\n", .{}); |
| 1853 | 1853 | ||
| @@ -2179,7 +2179,7 @@ test "buildTextCoverageCompareScene repeats the same specimen in four panels" { | |||
| 2179 | } | 2179 | } |
| 2180 | 2180 | ||
| 2181 | fn runRenderSmokeTest(alloc: std.mem.Allocator) !void { | 2181 | fn runRenderSmokeTest(alloc: std.mem.Allocator) !void { |
| 2182 | var conn = try wayland_client.Connection.init(); | 2182 | const conn = try wayland_client.Connection.init(alloc); |
| 2183 | defer conn.deinit(); | 2183 | defer conn.deinit(); |
| 2184 | std.debug.print("wayland connected\n", .{}); | 2184 | std.debug.print("wayland connected\n", .{}); |
| 2185 | 2185 | ||
| @@ -2210,7 +2210,7 @@ fn runRenderSmokeTest(alloc: std.mem.Allocator) !void { | |||
| 2210 | } | 2210 | } |
| 2211 | 2211 | ||
| 2212 | fn runVulkanSmokeTest(alloc: std.mem.Allocator) !void { | 2212 | fn runVulkanSmokeTest(alloc: std.mem.Allocator) !void { |
| 2213 | var conn = try wayland_client.Connection.init(); | 2213 | const conn = try wayland_client.Connection.init(alloc); |
| 2214 | defer conn.deinit(); | 2214 | defer conn.deinit(); |
| 2215 | std.debug.print("wayland connected\n", .{}); | 2215 | std.debug.print("wayland connected\n", .{}); |
| 2216 | 2216 | ||
| @@ -2237,7 +2237,7 @@ fn runVulkanSmokeTest(alloc: std.mem.Allocator) !void { | |||
| 2237 | } | 2237 | } |
| 2238 | 2238 | ||
| 2239 | fn runWaylandSmokeTest(alloc: std.mem.Allocator) !void { | 2239 | fn runWaylandSmokeTest(alloc: std.mem.Allocator) !void { |
| 2240 | var conn = try wayland_client.Connection.init(); | 2240 | const conn = try wayland_client.Connection.init(alloc); |
| 2241 | defer conn.deinit(); | 2241 | defer conn.deinit(); |
| 2242 | std.debug.print("connected\n", .{}); | 2242 | std.debug.print("connected\n", .{}); |
| 2243 | 2243 | ||
src/wayland.zig
| Old | New | ||
|---|---|---|---|
| @@ -3,6 +3,7 @@ const posix = std.posix; | |||
| 3 | const wayland = @import("wayland"); | 3 | const wayland = @import("wayland"); |
| 4 | const wl = wayland.client.wl; | 4 | const wl = wayland.client.wl; |
| 5 | const xdg = wayland.client.xdg; | 5 | const xdg = wayland.client.xdg; |
| 6 | const ScaleTracker = @import("scale_tracker").ScaleTracker; | ||
| 6 | 7 | ||
| 7 | const c = @cImport({ | 8 | const c = @cImport({ |
| 8 | @cInclude("xkbcommon/xkbcommon.h"); | 9 | @cInclude("xkbcommon/xkbcommon.h"); |
| @@ -10,6 +11,13 @@ const c = @cImport({ | |||
| 10 | @cInclude("unistd.h"); | 11 | @cInclude("unistd.h"); |
| 11 | }); | 12 | }); |
| 12 | 13 | ||
| 14 | pub const Output = struct { | ||
| 15 | wl_output: *wl.Output, | ||
| 16 | name: u32, | ||
| 17 | tracker: *ScaleTracker, | ||
| 18 | pending_scale: i32 = 1, | ||
| 19 | }; | ||
| 20 | |||
| 13 | pub const KeyboardEvent = struct { | 21 | pub const KeyboardEvent = struct { |
| 14 | keysym: u32, | 22 | keysym: u32, |
| 15 | modifiers: Modifiers, | 23 | modifiers: Modifiers, |
| @@ -254,32 +262,65 @@ pub const Connection = struct { | |||
| 254 | display: *wl.Display, | 262 | display: *wl.Display, |
| 255 | registry: *wl.Registry, | 263 | registry: *wl.Registry, |
| 256 | globals: Globals, | 264 | globals: Globals, |
| 257 | 265 | alloc: std.mem.Allocator, | |
| 258 | pub fn init() !Connection { | 266 | scale_tracker: ScaleTracker, |
| 267 | outputs: std.ArrayListUnmanaged(*Output), | ||
| 268 | |||
| 269 | // Heap-allocated so the pointer passed to `registry.setListener` remains stable | ||
| 270 | // across the init call boundary. With wl_output hotplug support the listener | ||
| 271 | // can fire after init returns, so we need a stable address — a stack-local | ||
| 272 | // Connection would dangle. | ||
| 273 | pub fn init(alloc: std.mem.Allocator) !*Connection { | ||
| 259 | const display = try wl.Display.connect(null); | 274 | const display = try wl.Display.connect(null); |
| 260 | errdefer display.disconnect(); | 275 | errdefer display.disconnect(); |
| 261 | 276 | ||
| 262 | const registry = try display.getRegistry(); | 277 | const registry = try display.getRegistry(); |
| 263 | errdefer registry.destroy(); | 278 | errdefer registry.destroy(); |
| 264 | 279 | ||
| 265 | var globals = Globals{}; | 280 | const conn = try alloc.create(Connection); |
| 266 | registry.setListener(*Globals, registryListener, &globals); | 281 | errdefer alloc.destroy(conn); |
| 267 | |||
| 268 | if (display.roundtrip() != .SUCCESS) return error.RoundtripFailed; | ||
| 269 | |||
| 270 | if (globals.compositor == null) return error.NoCompositor; | ||
| 271 | if (globals.wm_base == null) return error.NoXdgWmBase; | ||
| 272 | if (globals.seat == null) return error.NoSeat; | ||
| 273 | 282 | ||
| 274 | return .{ | 283 | conn.* = .{ |
| 275 | .display = display, | 284 | .display = display, |
| 276 | .registry = registry, | 285 | .registry = registry, |
| 277 | .globals = globals, | 286 | .globals = Globals{}, |
| 287 | .alloc = alloc, | ||
| 288 | .scale_tracker = ScaleTracker.init(alloc), | ||
| 289 | .outputs = .empty, | ||
| 278 | }; | 290 | }; |
| 291 | errdefer { | ||
| 292 | for (conn.outputs.items) |out| { | ||
| 293 | out.wl_output.release(); | ||
| 294 | alloc.destroy(out); | ||
| 295 | } | ||
| 296 | conn.outputs.deinit(alloc); | ||
| 297 | conn.scale_tracker.deinit(); | ||
| 298 | } | ||
| 299 | |||
| 300 | registry.setListener(*Connection, registryListener, conn); | ||
| 301 | |||
| 302 | if (display.roundtrip() != .SUCCESS) return error.RoundtripFailed; | ||
| 303 | // Second roundtrip so each wl_output's initial scale/done events are | ||
| 304 | // received after the output's own listener is attached in the first pass. | ||
| 305 | if (display.roundtrip() != .SUCCESS) return error.RoundtripFailed; | ||
| 306 | |||
| 307 | if (conn.globals.compositor == null) return error.NoCompositor; | ||
| 308 | if (conn.globals.wm_base == null) return error.NoXdgWmBase; | ||
| 309 | if (conn.globals.seat == null) return error.NoSeat; | ||
| 310 | |||
| 311 | return conn; | ||
| 279 | } | 312 | } |
| 280 | 313 | ||
| 281 | pub fn deinit(self: *Connection) void { | 314 | pub fn deinit(self: *Connection) void { |
| 315 | const alloc = self.alloc; | ||
| 316 | for (self.outputs.items) |out| { | ||
| 317 | out.wl_output.release(); | ||
| 318 | alloc.destroy(out); | ||
| 319 | } | ||
| 320 | self.outputs.deinit(alloc); | ||
| 321 | self.scale_tracker.deinit(); | ||
| 282 | self.display.disconnect(); | 322 | self.display.disconnect(); |
| 323 | alloc.destroy(self); | ||
| 283 | } | 324 | } |
| 284 | 325 | ||
| 285 | pub fn createWindow(self: *Connection, alloc: std.mem.Allocator, title: [*:0]const u8) !*Window { | 326 | pub fn createWindow(self: *Connection, alloc: std.mem.Allocator, title: [*:0]const u8) !*Window { |
| @@ -493,22 +534,68 @@ fn xdgToplevelListener(_: *xdg.Toplevel, event: xdg.Toplevel.Event, window: *Win | |||
| 493 | fn registryListener( | 534 | fn registryListener( |
| 494 | registry: *wl.Registry, | 535 | registry: *wl.Registry, |
| 495 | event: wl.Registry.Event, | 536 | event: wl.Registry.Event, |
| 496 | globals: *Globals, | 537 | conn: *Connection, |
| 497 | ) void { | 538 | ) void { |
| 498 | switch (event) { | 539 | switch (event) { |
| 499 | .global => |g| { | 540 | .global => |g| { |
| 500 | const iface = std.mem.span(g.interface); | 541 | const iface = std.mem.span(g.interface); |
| 501 | if (std.mem.eql(u8, iface, std.mem.span(wl.Compositor.interface.name))) { | 542 | if (std.mem.eql(u8, iface, std.mem.span(wl.Compositor.interface.name))) { |
| 502 | globals.compositor = registry.bind(g.name, wl.Compositor, 6) catch return; | 543 | conn.globals.compositor = registry.bind(g.name, wl.Compositor, 6) catch return; |
| 503 | } else if (std.mem.eql(u8, iface, std.mem.span(wl.DataDeviceManager.interface.name))) { | 544 | } else if (std.mem.eql(u8, iface, std.mem.span(wl.DataDeviceManager.interface.name))) { |
| 504 | globals.data_device_manager = registry.bind(g.name, wl.DataDeviceManager, 3) catch return; | 545 | conn.globals.data_device_manager = registry.bind(g.name, wl.DataDeviceManager, 3) catch return; |
| 505 | } else if (std.mem.eql(u8, iface, std.mem.span(xdg.WmBase.interface.name))) { | 546 | } else if (std.mem.eql(u8, iface, std.mem.span(xdg.WmBase.interface.name))) { |
| 506 | globals.wm_base = registry.bind(g.name, xdg.WmBase, 5) catch return; | 547 | conn.globals.wm_base = registry.bind(g.name, xdg.WmBase, 5) catch return; |
| 507 | } else if (std.mem.eql(u8, iface, std.mem.span(wl.Seat.interface.name))) { | 548 | } else if (std.mem.eql(u8, iface, std.mem.span(wl.Seat.interface.name))) { |
| 508 | globals.seat = registry.bind(g.name, wl.Seat, 9) catch return; | 549 | conn.globals.seat = registry.bind(g.name, wl.Seat, 9) catch return; |
| 550 | } else if (std.mem.eql(u8, iface, std.mem.span(wl.Output.interface.name))) { | ||
| 551 | const wl_out = registry.bind(g.name, wl.Output, 4) catch return; | ||
| 552 | const out = conn.alloc.create(Output) catch { | ||
| 553 | wl_out.release(); | ||
| 554 | return; | ||
| 555 | }; | ||
| 556 | out.* = .{ | ||
| 557 | .wl_output = wl_out, | ||
| 558 | .name = g.name, | ||
| 559 | .tracker = &conn.scale_tracker, | ||
| 560 | }; | ||
| 561 | conn.outputs.append(conn.alloc, out) catch { | ||
| 562 | wl_out.release(); | ||
| 563 | conn.alloc.destroy(out); | ||
| 564 | return; | ||
| 565 | }; | ||
| 566 | conn.scale_tracker.addOutput(g.name) catch {}; | ||
| 567 | wl_out.setListener(*Output, outputListener, out); | ||
| 568 | } | ||
| 569 | }, | ||
| 570 | .global_remove => |g| { | ||
| 571 | var i: usize = 0; | ||
| 572 | while (i < conn.outputs.items.len) : (i += 1) { | ||
| 573 | const out = conn.outputs.items[i]; | ||
| 574 | if (out.name == g.name) { | ||
| 575 | conn.scale_tracker.removeOutput(out.name); | ||
| 576 | out.wl_output.release(); | ||
| 577 | conn.alloc.destroy(out); | ||
| 578 | _ = conn.outputs.swapRemove(i); | ||
| 579 | return; | ||
| 580 | } | ||
| 509 | } | 581 | } |
| 510 | }, | 582 | }, |
| 511 | .global_remove => {}, | 583 | } |
| 584 | } | ||
| 585 | |||
| 586 | fn outputListener( | ||
| 587 | _: *wl.Output, | ||
| 588 | event: wl.Output.Event, | ||
| 589 | out: *Output, | ||
| 590 | ) void { | ||
| 591 | switch (event) { | ||
| 592 | .scale => |s| { | ||
| 593 | out.pending_scale = s.factor; | ||
| 594 | }, | ||
| 595 | .done => { | ||
| 596 | out.tracker.setOutputScale(out.name, out.pending_scale); | ||
| 597 | }, | ||
| 598 | .geometry, .mode, .name, .description => {}, | ||
| 512 | } | 599 | } |
| 513 | } | 600 | } |
| 514 | 601 | ||