main shar/altaica / compositor / src / viewporter.zig
  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}