1const std = @import("std");
2const log = std.log;
3const Mutex = std.Thread.Mutex;
4
5const wl = @import("wayland").client.wl;
6
7const Bar = @import("Bar.zig");
8const Monitor = @import("Monitor.zig");
9const render = @import("render.zig");
10const zriver = @import("wayland").client.zriver;
11const state = &@import("root").state;
12
13pub const Seat = @This();
14
15seat_status: *zriver.SeatStatusV1,
16current_output: ?*wl.Output,
17window_title: ?[:0]u8,
18status_buffer: [4096]u8 = undefined,
19status_text: std.io.FixedBufferStream([]u8),
20mtx: Mutex,
21
22pub fn create() !*Seat {
23 const self = try state.gpa.create(Seat);
24 const manager = state.wayland.status_manager.?;
25 const seat = state.wayland.seat.?;
26
27 self.mtx = Mutex{};
28 self.current_output = null;
29 self.window_title = null;
30 self.seat_status = try manager.getRiverSeatStatus(seat);
31 self.seat_status.setListener(*Seat, seatListener, self);
32
33 self.status_text = std.io.fixedBufferStream(&self.status_buffer);
34 return self;
35}
36
37pub fn destroy(self: *Seat) void {
38 self.mtx.lock();
39 if (self.window_title) |w| {
40 state.gpa.free(w);
41 }
42 self.mtx.unlock();
43
44 self.seat_status.destroy();
45 state.gpa.destroy(self);
46}
47
48pub fn focusedMonitor(self: *Seat) ?*Monitor {
49 // If there is no current monitor, e.g. on startup use the first one.
50 //
51 // TODO: Find a better way to do this.
52 if (self.current_output == null) {
53 const items = state.wayland.monitors.items;
54 if (items.len > 0) {
55 return items[0];
56 }
57 }
58
59 for (state.wayland.monitors.items) |monitor| {
60 if (monitor.output == self.current_output) {
61 return monitor;
62 }
63 }
64
65 return null;
66}
67
68pub fn focusedBar(self: *Seat) ?*Bar {
69 if (self.focusedMonitor()) |m| {
70 return m.confBar();
71 }
72
73 return null;
74}
75
76fn updateTitle(self: *Seat, data: [*:0]const u8) void {
77 const title = std.mem.sliceTo(data, 0);
78
79 self.mtx.lock();
80 defer self.mtx.unlock();
81
82 if (self.window_title) |t| {
83 state.gpa.free(t);
84 }
85 if (title.len == 0) {
86 self.window_title = null;
87 } else {
88 const vz = state.gpa.allocSentinel(u8, title.len, 0) catch |err| {
89 log.err("allocSentinel failed for window title: {s}\n", .{@errorName(err)});
90 return;
91 };
92 @memcpy(vz[0..vz.len], title);
93 self.window_title = vz;
94 }
95}
96
97fn focusedOutput(self: *Seat, output: *wl.Output) void {
98 var monitor: ?*Monitor = null;
99 for (state.wayland.monitors.items) |m| {
100 if (m.output == output) {
101 monitor = m;
102 break;
103 }
104 }
105
106 if (monitor) |m| {
107 if (m.confBar()) |bar| {
108 self.current_output = m.output;
109 render.renderText(bar, self.status_text.getWritten()) catch |err| {
110 log.err("renderText failed on focus for monitor {}: {s}",
111 .{m.globalName, @errorName(err)});
112 return;
113 };
114
115 bar.text.surface.commit();
116 bar.background.surface.commit();
117 }
118 } else {
119 log.err("seatListener: couldn't find focused output", .{});
120 }
121}
122
123fn unfocusedOutput(self: *Seat, output: *wl.Output) void {
124 var monitor: ?*Monitor = null;
125 for (state.wayland.monitors.items) |m| {
126 if (m.output == output) {
127 monitor = m;
128 break;
129 }
130 }
131
132 if (monitor) |m| {
133 if (m.confBar()) |bar| {
134 render.resetText(bar) catch |err| {
135 log.err("resetText failed for monitor {}: {s}",
136 .{bar.monitor.globalName, @errorName(err)});
137 };
138 bar.text.surface.commit();
139
140 render.renderTitle(bar, null) catch |err| {
141 log.err("renderTitle failed on unfocus for monitor {}: {s}",
142 .{bar.monitor.globalName, @errorName(err)});
143 return;
144 };
145
146 bar.title.surface.commit();
147 bar.background.surface.commit();
148 }
149 } else {
150 log.err("seatListener: couldn't find unfocused output", .{});
151 }
152
153 self.current_output = null;
154}
155
156fn focusedView(self: *Seat, title: [*:0]const u8) void {
157 self.updateTitle(title);
158 if (self.focusedBar()) |bar| {
159 render.renderTitle(bar, self.window_title) catch |err| {
160 log.err("renderTitle failed on focused view for monitor {}: {s}",
161 .{bar.monitor.globalName, @errorName(err)});
162 return;
163 };
164
165 bar.title.surface.commit();
166 bar.background.surface.commit();
167 }
168}
169
170fn seatListener(
171 _: *zriver.SeatStatusV1,
172 event: zriver.SeatStatusV1.Event,
173 seat: *Seat,
174) void {
175 switch (event) {
176 .focused_output => |data| seat.focusedOutput(data.output.?),
177 .unfocused_output => |data| seat.unfocusedOutput(data.output.?),
178 .focused_view => |data| seat.focusedView(data.title),
179 }
180}