1const wire = @import("wire.zig");
2const proto = @import("protocol.zig");
3const objects = @import("objects.zig");
4
5const MAX_VIEWPORTS = 16;
6
7pub const ViewportState = struct {
8 source_set: bool,
9 src_x: i32,
10 src_y: i32,
11 src_w: i32,
12 src_h: i32,
13 dest_set: bool,
14 dst_w: i32,
15 dst_h: i32,
16};
17
18const ViewportData = struct {
19 obj_id: u32,
20 client_id: u32,
21 surf_id: u32,
22 surf_client: u32,
23 state: ViewportState,
24};
25
26var viewport_data: [MAX_VIEWPORTS]?ViewportData = [_]?ViewportData{null} ** MAX_VIEWPORTS;
27
28pub fn init() void {
29 for (&viewport_data) |*v| v.* = null;
30}
31
32fn findFreeSlot() ?*ViewportData {
33 for (&viewport_data) |*maybe| {
34 if (maybe.* == null) {
35 maybe.* = ViewportData{
36 .obj_id = 0,
37 .client_id = 0,
38 .surf_id = 0,
39 .surf_client = 0,
40 .state = undefined,
41 };
42 return &(maybe.* orelse unreachable);
43 }
44 }
45 return null;
46}
47
48fn findViewport(client_id: u32, obj_id: u32) ?*ViewportData {
49 for (&viewport_data) |*maybe| {
50 const v = &(maybe.* orelse continue);
51 if (v.client_id == client_id and v.obj_id == obj_id) return v;
52 }
53 return null;
54}
55
56pub fn findViewportForSurface(surf_client: u32, surf_id: u32) ?*ViewportState {
57 for (&viewport_data) |*maybe| {
58 const v = &(maybe.* orelse continue);
59 if (v.surf_client == surf_client and v.surf_id == surf_id) return &v.state;
60 }
61 return null;
62}
63
64pub fn createViewport(client_id: u32, obj_id: u32, surf_client: u32, surf_id: u32) bool {
65 const slot = findFreeSlot() orelse return false;
66 slot.* = .{
67 .obj_id = obj_id,
68 .client_id = client_id,
69 .surf_id = surf_id,
70 .surf_client = surf_client,
71 .state = .{
72 .source_set = false,
73 .src_x = 0, .src_y = 0, .src_w = 0, .src_h = 0,
74 .dest_set = false,
75 .dst_w = 0, .dst_h = 0,
76 },
77 };
78 return true;
79}
80
81pub fn destroyViewport(client_id: u32, obj_id: u32) void {
82 for (&viewport_data) |*maybe| {
83 const v = &(maybe.* orelse continue);
84 if (v.client_id == client_id and v.obj_id == obj_id) {
85 maybe.* = null;
86 return;
87 }
88 }
89}
90
91pub fn destroyForSurface(surf_client: u32, surf_id: u32) void {
92 for (&viewport_data) |*maybe| {
93 const v = &(maybe.* orelse continue);
94 if (v.surf_client == surf_client and v.surf_id == surf_id) {
95 maybe.* = null;
96 return;
97 }
98 }
99}
100
101pub fn handleViewporterRequest(client_id: u32, obj_id: u32, opcode: u16, args: []const wire.Arg) void {
102 switch (opcode) {
103 0 => { // destroy
104 objects.remove(client_id, obj_id);
105 },
106 1 => { // get_viewport
107 const new_id = args[0].new_id;
108 const surf_id = args[1].object;
109 if (findViewportForSurface(client_id, surf_id) != null) return;
110 if (createViewport(client_id, new_id, client_id, surf_id)) {
111 _ = objects.register(client_id, new_id, .wp_viewport);
112 }
113 },
114 else => {},
115 }
116}
117
118pub fn handleViewportRequest(client_id: u32, obj_id: u32, opcode: u16, args: []const wire.Arg) void {
119 const vp = findViewport(client_id, obj_id) orelse return;
120
121 const surf_valid = objects.lookup(vp.surf_client, vp.surf_id) != null;
122
123 switch (opcode) {
124 0 => { // destroy
125 objects.remove(client_id, obj_id);
126 destroyViewport(client_id, obj_id);
127 },
128 1 => { // set_source
129 if (!surf_valid) return;
130 const x = args[0].@"fixed";
131 const y = args[1].@"fixed";
132 const w = args[2].@"fixed";
133 const h = args[3].@"fixed";
134
135 if (x == -256 and y == -256 and w == -256 and h == -256) {
136 vp.state.source_set = false;
137 return;
138 }
139 if (x < 0 or y < 0 or w <= 0 or h <= 0) return;
140
141 vp.state.source_set = true;
142 vp.state.src_x = x;
143 vp.state.src_y = y;
144 vp.state.src_w = w;
145 vp.state.src_h = h;
146 },
147 2 => { // set_destination
148 if (!surf_valid) return;
149 const dw = args[0].int;
150 const dh = args[1].int;
151
152 if (dw == -1 and dh == -1) {
153 vp.state.dest_set = false;
154 return;
155 }
156 if (dw <= 0 or dh <= 0) return;
157
158 vp.state.dest_set = true;
159 vp.state.dst_w = dw;
160 vp.state.dst_h = dh;
161 },
162 else => {},
163 }
164}
165
166pub const ViewportRect = struct {
167 src_x: u32, src_y: u32, src_w: u32, src_h: u32,
168 dst_w: u32, dst_h: u32,
169};
170
171/// Compute effective source rect (in buffer pixels) and dest size for a surface.
172/// Returns null if no viewport is active or if constraints are violated.
173pub fn computeViewport(surf_client: u32, surf_id: u32, buf_w: u32, buf_h: u32) ?ViewportRect {
174 const vs = findViewportForSurface(surf_client, surf_id) orelse return null;
175 if (!vs.source_set and !vs.dest_set) return null;
176
177 // If source is set without destination, src_w/src_h must be integers
178 if (vs.source_set and !vs.dest_set) {
179 if ((vs.src_w & 0xFF) != 0 or (vs.src_h & 0xFF) != 0) return null;
180 }
181
182 var sx: u32 = 0;
183 var sy: u32 = 0;
184 var sw: u32 = buf_w;
185 var sh: u32 = buf_h;
186
187 if (vs.source_set) {
188 sx = @as(u32, @intCast(@divTrunc(@max(0, vs.src_x), 256)));
189 sy = @as(u32, @intCast(@divTrunc(@max(0, vs.src_y), 256)));
190 sw = @as(u32, @intCast(@divTrunc(@max(0, vs.src_w), 256)));
191 sh = @as(u32, @intCast(@divTrunc(@max(0, vs.src_h), 256)));
192 if (sx > buf_w) sx = buf_w;
193 if (sy > buf_h) sy = buf_h;
194 if (sx + sw > buf_w) sw = buf_w - sx;
195 if (sy + sh > buf_h) sh = buf_h - sy;
196 }
197
198 const dw: u32 = if (vs.dest_set) @as(u32, @intCast(@max(0, vs.dst_w))) else sw;
199 const dh: u32 = if (vs.dest_set) @as(u32, @intCast(@max(0, vs.dst_h))) else sh;
200
201 return .{
202 .src_x = sx, .src_y = sy,
203 .src_w = sw, .src_h = sh,
204 .dst_w = dw, .dst_h = dh,
205 };
206}