Something went wrong. Try again.
window manager for the river wayland compositor
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145// SPDX-FileCopyrightText: © 2026 mtmn//// SPDX-License-Identifier: BSD-3-Clause// Analyzer does not resolve method-call syntax to free functions.// Module type aliases use PascalCase per Zig convention.
const Output = @This();
const std = @import("std");const wl = @import("wayland").client.wl;const river = @import("wayland").client.river;
const Layout = @import("Layout.zig");const Osd = @import("Osd.zig");
const gpa = std.heap.c_allocator;const log = std.log.scoped(.laguna);
object: *river.OutputV1,registry: *wl.Registry,layer_output: ?*river.LayerShellOutputV1 = null,/// Bound only to learn the connector name, which focus-output and/// send-to-output accept as a target.wl_output: ?*wl.Output = null,connector: ?[]const u8 = null,/// Integer wl_output scale. The tag OSD redraws when it changes.scale: i32 = 1,/// Set by WindowManager so the wl_output scale event can reach the OSD.osd: ?*Osd = null,x: i32 = 0,y: i32 = 0,width: i32 = 1,height: i32 = 1,removed: bool = false,tags: u32 = 1,previous_tags: u32 = 1,attach_mode: ?Layout.Attach = null,usable: Layout.Rect = .{ .x = 0, .y = 0, .width = 1, .height = 1 },usable_valid: bool = false,main_count: u32 = 1,main_ratio: f32 = 0.6,main_location: Layout.Location = .left,/// Last presentation mode sent. river starts every output in vsync mode.presentation_sent: river.OutputV1.PresentationMode = .vsync,
pub fn init(output: *Output, object: *river.OutputV1, registry: *wl.Registry) void { output.* = .{ .object = object, .registry = registry }; object.setListener(*Output, handleEvent, output);}
pub fn bindLayerShell(output: *Output, layer_shell: *river.LayerShellV1) void { if (output.layer_output != null) return; const layer_output = layer_shell.getOutput(output.object) catch return; output.layer_output = layer_output; layer_output.setListener(*Output, handleLayerEvent, output);}
pub fn deinit(output: *Output) void { if (output.layer_output) |layer_output| { layer_output.destroy(); output.layer_output = null; } if (output.wl_output) |wl_output| { wl_output.release(); output.wl_output = null; } if (output.connector) |connector| gpa.free(connector); output.connector = null; output.object.destroy();}
/// river sends the matching wl_output global name exactly once, after the/// global was announced. river's wlroots advertises wl_output version 4,/// the first version with the name event.fn bindWlOutput(output: *Output, global_name: u32) void { if (output.wl_output != null) return; const wl_output = output.registry.bind(global_name, wl.Output, 4) catch { log.err("cannot bind wl_output", .{}); return; }; output.wl_output = wl_output; wl_output.setListener(*Output, handleWlOutputEvent, output);}
fn handleWlOutputEvent(_: *wl.Output, event: wl.Output.Event, output: *Output) void { switch (event) { .name => |named| { if (output.connector) |old| gpa.free(old); output.connector = gpa.dupe(u8, std.mem.sliceTo(named.name, 0)) catch |err| blk: { log.warn("cannot store output connector name: {s}", .{@errorName(err)}); break :blk null; }; }, .scale => |scale| { if (scale.factor == output.scale) return; output.scale = scale.factor; if (output.osd) |osd| osd.rescale(output); }, .geometry, .mode, .done, .description => {}, }}
fn handleLayerEvent( _: *river.LayerShellOutputV1, event: river.LayerShellOutputV1.Event, output: *Output,) void { switch (event) { .non_exclusive_area => |area| { output.usable = .{ .x = area.x, .y = area.y, .width = @max(area.width, 0), .height = @max(area.height, 0), }; output.usable_valid = true; }, }}
/// Send the presentation mode unless the output already has it. Rendering/// state: call only inside a manage or render sequence.pub fn setPresentationMode(output: *Output, mode: river.OutputV1.PresentationMode) void { if (output.presentation_sent == mode) return; if (output.object.getVersion() < river.OutputV1.set_presentation_mode_since_version) return; output.object.setPresentationMode(mode); output.presentation_sent = mode;}
fn handleEvent(_: *river.OutputV1, event: river.OutputV1.Event, output: *Output) void { switch (event) { .removed => output.removed = true, .wl_output => |named| output.bindWlOutput(named.name), .position => |position| { output.x = position.x; output.y = position.y; }, .dimensions => |dimensions| { output.width = @max(dimensions.width, 1); output.height = @max(dimensions.height, 1); }, .capture_sessions => {}, }}