WIP massive refactor
This commit is contained in:
parent
76ed2a72a8
commit
0584fde126
338
src/cursor.zig
Normal file
338
src/cursor.zig
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
const CursorMode = enum {
|
||||||
|
Passthrough,
|
||||||
|
Move,
|
||||||
|
Resize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Cursor = struct {
|
||||||
|
seat: *Seat,
|
||||||
|
|
||||||
|
wlr_cursor: *c.wlr_cursor,
|
||||||
|
wlr_xcursor_manager: *c.wlr_xcursor_manager,
|
||||||
|
|
||||||
|
listen_motion: c.wl_listener,
|
||||||
|
listen_motion_absolute: c.wl_listener,
|
||||||
|
listen_button: c.wl_listener,
|
||||||
|
listen_axis: c.wl_listener,
|
||||||
|
listen_frame: c.wl_listener,
|
||||||
|
|
||||||
|
listen_request_cursor: c.wl_listener,
|
||||||
|
|
||||||
|
cursor_mode: CursorMode,
|
||||||
|
grabbed_view: ?*View,
|
||||||
|
grab_x: f64,
|
||||||
|
grab_y: f64,
|
||||||
|
grab_width: c_int,
|
||||||
|
grab_height: c_int,
|
||||||
|
resize_edges: u32,
|
||||||
|
|
||||||
|
pub fn init(seat: *Seat) !@This() {
|
||||||
|
var cursor = @This(){
|
||||||
|
.server = seat.server,
|
||||||
|
.seat = seat,
|
||||||
|
|
||||||
|
// Creates a wlroots utility for tracking the cursor image shown on screen.
|
||||||
|
.wlr_cursor = c.wlr_cursor_create() orelse
|
||||||
|
return error.CantCreateWlrCursor,
|
||||||
|
|
||||||
|
// Creates an xcursor manager, another wlroots utility which loads up
|
||||||
|
// Xcursor themes to source cursor images from and makes sure that cursor
|
||||||
|
// images are available at all scale factors on the screen (necessary for
|
||||||
|
// HiDPI support). We add a cursor theme at scale factor 1 to begin with.
|
||||||
|
.wlr_xcursor_manager = c.wlr_xcursor_manager_create(null, 24) orelse
|
||||||
|
return error.CantCreateWlrXCursorManager,
|
||||||
|
|
||||||
|
.listen_motion = c.wl_listener{
|
||||||
|
.link = undefined,
|
||||||
|
.notify = @This().handle_motion,
|
||||||
|
},
|
||||||
|
.listen_motion_absolute = c.wl_listener{
|
||||||
|
.link = undefined,
|
||||||
|
.notify = @This().handle_motion_absolute,
|
||||||
|
},
|
||||||
|
.listen_button = c.wl_listener{
|
||||||
|
.link = undefined,
|
||||||
|
.notify = @This().handle_button,
|
||||||
|
},
|
||||||
|
.listen_axis = c.wl_listener{
|
||||||
|
.link = undefined,
|
||||||
|
.notify = @This().handle_axis,
|
||||||
|
},
|
||||||
|
.listen_frame = c.wl_listener{
|
||||||
|
.link = undefined,
|
||||||
|
.notify = @This().handle_frame,
|
||||||
|
},
|
||||||
|
|
||||||
|
.listen_request_set_cursor = c.wl_listener{
|
||||||
|
.link = undefined,
|
||||||
|
.notify = @This().handle_request_set_cursor,
|
||||||
|
},
|
||||||
|
|
||||||
|
.mode = CursorMode.Passthrough,
|
||||||
|
|
||||||
|
.grabbed_view = null,
|
||||||
|
.grab_x = 0.0,
|
||||||
|
.grab_y = 0.0,
|
||||||
|
.grab_width = 0,
|
||||||
|
.grab_height = 0,
|
||||||
|
.resize_edges = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
c.wlr_cursor_attach_output_layout(cursor.wlr_cursor, seat.*.server.*.output_layout);
|
||||||
|
_ = c.wlr_xcursor_manager_load(server.cursor_mgr, 1);
|
||||||
|
|
||||||
|
// wlr_cursor *only* displays an image on screen. It does not move around
|
||||||
|
// when the pointer moves. However, we can attach input devices to it, and
|
||||||
|
// it will generate aggregate events for all of them. In these events, we
|
||||||
|
// can choose how we want to process them, forwarding them to clients and
|
||||||
|
// moving the cursor around. See following post for more detail:
|
||||||
|
// https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html
|
||||||
|
c.wl_signal_add(&cursor.wlr_cursor.*.events.motion, &cursor.listen_motion);
|
||||||
|
c.wl_signal_add(&cursor.wlr_cursor.*.events.motion_absolute, &cursor.listen_motion_absolute);
|
||||||
|
c.wl_signal_add(&cursor.wlr_cursor.*.events.button, &cursor.listen_button);
|
||||||
|
c.wl_signal_add(&cursor.wlr_cursor.*.events.axis, &cursor.listen_axis);
|
||||||
|
c.wl_signal_add(&cursor.wlr_cursor.*.events.frame, &cursor.listen_frame);
|
||||||
|
|
||||||
|
// This listens for clients requesting a specific cursor image
|
||||||
|
c.wl_signal_add(&server.seat.*.events.request_set_cursor, &cursor.listen_request_set_cursor);
|
||||||
|
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_cursor_move(server: *Server, time: u32) void {
|
||||||
|
// Move the grabbed view to the new position.
|
||||||
|
server.*.grabbed_view.?.*.x = @floatToInt(c_int, server.*.cursor.*.x - server.*.grab_x);
|
||||||
|
server.*.grabbed_view.?.*.y = @floatToInt(c_int, server.*.cursor.*.y - server.*.grab_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_cursor_resize(server: *Server, time: u32) void {
|
||||||
|
// Resizing the grabbed view can be a little bit complicated, because we
|
||||||
|
// could be resizing from any corner or edge. This not only resizes the view
|
||||||
|
// on one or two axes, but can also move the view if you resize from the top
|
||||||
|
// or left edges (or top-left corner).
|
||||||
|
//
|
||||||
|
// Note that I took some shortcuts here. In a more fleshed-out compositor,
|
||||||
|
// you'd wait for the client to prepare a buffer at the new size, then
|
||||||
|
// commit any movement that was prepared.
|
||||||
|
var view = server.*.grabbed_view;
|
||||||
|
|
||||||
|
var dx: f64 = (server.*.cursor.*.x - server.*.grab_x);
|
||||||
|
var dy: f64 = (server.*.cursor.*.y - server.*.grab_y);
|
||||||
|
var x: f64 = @intToFloat(f64, view.?.*.x);
|
||||||
|
var y: f64 = @intToFloat(f64, view.?.*.y);
|
||||||
|
|
||||||
|
var width = @intToFloat(f64, server.*.grab_width);
|
||||||
|
var height = @intToFloat(f64, server.*.grab_height);
|
||||||
|
if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_TOP) != 0) {
|
||||||
|
y = server.*.grab_y + dy;
|
||||||
|
height -= dy;
|
||||||
|
if (height < 1) {
|
||||||
|
y += height;
|
||||||
|
}
|
||||||
|
} else if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_BOTTOM) != 0) {
|
||||||
|
height += dy;
|
||||||
|
}
|
||||||
|
if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_LEFT) != 0) {
|
||||||
|
x = server.*.grab_x + dx;
|
||||||
|
width -= dx;
|
||||||
|
if (width < 1) {
|
||||||
|
x += width;
|
||||||
|
}
|
||||||
|
} else if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_RIGHT) != 0) {
|
||||||
|
width += dx;
|
||||||
|
}
|
||||||
|
view.?.*.x = @floatToInt(c_int, x);
|
||||||
|
view.?.*.y = @floatToInt(c_int, y);
|
||||||
|
_ = c.wlr_xdg_toplevel_set_size(
|
||||||
|
view.?.*.xdg_surface,
|
||||||
|
@floatToInt(u32, width),
|
||||||
|
@floatToInt(u32, height),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_cursor_motion(server: *Server, time: u32) void {
|
||||||
|
// If the mode is non-passthrough, delegate to those functions.
|
||||||
|
if (server.*.cursor_mode == CursorMode.Move) {
|
||||||
|
process_cursor_move(server, time);
|
||||||
|
return;
|
||||||
|
} else if (server.*.cursor_mode == CursorMode.Resize) {
|
||||||
|
process_cursor_resize(server, time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, find the view under the pointer and send the event along.
|
||||||
|
var sx: f64 = undefined;
|
||||||
|
var sy: f64 = undefined;
|
||||||
|
var seat = server.*.seat;
|
||||||
|
var opt_surface: ?*c.wlr_surface = null;
|
||||||
|
var view = desktop_view_at(
|
||||||
|
server,
|
||||||
|
server.*.cursor.*.x,
|
||||||
|
server.*.cursor.*.y,
|
||||||
|
&opt_surface,
|
||||||
|
&sx,
|
||||||
|
&sy,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (view == null) {
|
||||||
|
// If there's no view under the cursor, set the cursor image to a
|
||||||
|
// default. This is what makes the cursor image appear when you move it
|
||||||
|
// around the screen, not over any views.
|
||||||
|
c.wlr_xcursor_manager_set_cursor_image(
|
||||||
|
server.*.cursor_mgr,
|
||||||
|
"left_ptr",
|
||||||
|
server.*.cursor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt_surface) |surface| {
|
||||||
|
const focus_changed = seat.*.pointer_state.focused_surface != surface;
|
||||||
|
// "Enter" the surface if necessary. This lets the client know that the
|
||||||
|
// cursor has entered one of its surfaces.
|
||||||
|
//
|
||||||
|
// Note that this gives the surface "pointer focus", which is distinct
|
||||||
|
// from keyboard focus. You get pointer focus by moving the pointer over
|
||||||
|
// a window.
|
||||||
|
c.wlr_seat_pointer_notify_enter(seat, surface, sx, sy);
|
||||||
|
if (!focus_changed) {
|
||||||
|
// The enter event contains coordinates, so we only need to notify
|
||||||
|
// on motion if the focus did not change.
|
||||||
|
c.wlr_seat_pointer_notify_motion(seat, time, sx, sy);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Clear pointer focus so future button events and such are not sent to
|
||||||
|
// the last client to have the cursor over it.
|
||||||
|
c.wlr_seat_pointer_clear_focus(seat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_motion(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This event is forwarded by the cursor when a pointer emits a _relative_
|
||||||
|
// pointer motion event (i.e. a delta)
|
||||||
|
var server = @fieldParentPtr(Server, "cursor_motion", listener);
|
||||||
|
var event = @ptrCast(
|
||||||
|
*c.wlr_event_pointer_motion,
|
||||||
|
@alignCast(@alignOf(*c.wlr_event_pointer_motion), data),
|
||||||
|
);
|
||||||
|
// The cursor doesn't move unless we tell it to. The cursor automatically
|
||||||
|
// handles constraining the motion to the output layout, as well as any
|
||||||
|
// special configuration applied for the specific input device which
|
||||||
|
// generated the event. You can pass NULL for the device if you want to move
|
||||||
|
// the cursor around without any input.
|
||||||
|
c.wlr_cursor_move(server.*.cursor, event.*.device, event.*.delta_x, event.*.delta_y);
|
||||||
|
process_cursor_motion(server, event.*.time_msec);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_motion_absolute(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This event is forwarded by the cursor when a pointer emits an _absolute_
|
||||||
|
// motion event, from 0..1 on each axis. This happens, for example, when
|
||||||
|
// wlroots is running under a Wayland window rather than KMS+DRM, and you
|
||||||
|
// move the mouse over the window. You could enter the window from any edge,
|
||||||
|
// so we have to warp the mouse there. There is also some hardware which
|
||||||
|
// emits these events.
|
||||||
|
var server = @fieldParentPtr(Server, "cursor_motion_absolute", listener);
|
||||||
|
var event = @ptrCast(
|
||||||
|
*c.wlr_event_pointer_motion_absolute,
|
||||||
|
@alignCast(@alignOf(*c.wlr_event_pointer_motion_absolute), data),
|
||||||
|
);
|
||||||
|
c.wlr_cursor_warp_absolute(server.*.cursor, event.*.device, event.*.x, event.*.y);
|
||||||
|
process_cursor_motion(server, event.*.time_msec);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_button(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This event is forwarded by the cursor when a pointer emits a button
|
||||||
|
// event.
|
||||||
|
var server = @fieldParentPtr(Server, "cursor_button", listener);
|
||||||
|
var event = @ptrCast(
|
||||||
|
*c.wlr_event_pointer_button,
|
||||||
|
@alignCast(@alignOf(*c.wlr_event_pointer_button), data),
|
||||||
|
);
|
||||||
|
// Notify the client with pointer focus that a button press has occurred
|
||||||
|
_ = c.wlr_seat_pointer_notify_button(
|
||||||
|
server.*.seat,
|
||||||
|
event.*.time_msec,
|
||||||
|
event.*.button,
|
||||||
|
event.*.state,
|
||||||
|
);
|
||||||
|
|
||||||
|
var sx: f64 = undefined;
|
||||||
|
var sy: f64 = undefined;
|
||||||
|
|
||||||
|
var surface: ?*c.wlr_surface = null;
|
||||||
|
var view = desktop_view_at(
|
||||||
|
server,
|
||||||
|
server.*.cursor.*.x,
|
||||||
|
server.*.cursor.*.y,
|
||||||
|
&surface,
|
||||||
|
&sx,
|
||||||
|
&sy,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event.*.state == c.enum_wlr_button_state.WLR_BUTTON_RELEASED) {
|
||||||
|
// If you released any buttons, we exit interactive move/resize mode.
|
||||||
|
server.*.cursor_mode = CursorMode.Passthrough;
|
||||||
|
} else {
|
||||||
|
// Focus that client if the button was _pressed_
|
||||||
|
if (view) |v| {
|
||||||
|
focus_view(v, surface.?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_axis(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This event is forwarded by the cursor when a pointer emits an axis event,
|
||||||
|
// for example when you move the scroll wheel.
|
||||||
|
var server = @fieldParentPtr(Server, "cursor_axis", listener);
|
||||||
|
var event = @ptrCast(
|
||||||
|
*c.wlr_event_pointer_axis,
|
||||||
|
@alignCast(@alignOf(*c.wlr_event_pointer_axis), data),
|
||||||
|
);
|
||||||
|
// Notify the client with pointer focus of the axis event.
|
||||||
|
c.wlr_seat_pointer_notify_axis(
|
||||||
|
server.*.seat,
|
||||||
|
event.*.time_msec,
|
||||||
|
event.*.orientation,
|
||||||
|
event.*.delta,
|
||||||
|
event.*.delta_discrete,
|
||||||
|
event.*.source,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_frame(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This event is forwarded by the cursor when a pointer emits an frame
|
||||||
|
// event. Frame events are sent after regular pointer events to group
|
||||||
|
// multiple events together. For instance, two axis events may happen at the
|
||||||
|
// same time, in which case a frame event won't be sent in between.
|
||||||
|
var server = @fieldParentPtr(Server, "cursor_frame", listener);
|
||||||
|
// Notify the client with pointer focus of the frame event.
|
||||||
|
c.wlr_seat_pointer_notify_frame(server.*.seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_request_set_cursor(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This event is rasied by the seat when a client provides a cursor image
|
||||||
|
var server = @fieldParentPtr(Server, "request_cursor", listener);
|
||||||
|
var event = @ptrCast(
|
||||||
|
*c.wlr_seat_pointer_request_set_cursor_event,
|
||||||
|
@alignCast(@alignOf(*c.wlr_seat_pointer_request_set_cursor_event), data),
|
||||||
|
);
|
||||||
|
var focused_client = server.*.seat.*.pointer_state.focused_client;
|
||||||
|
|
||||||
|
// This can be sent by any client, so we check to make sure this one is
|
||||||
|
// actually has pointer focus first.
|
||||||
|
if (focused_client == event.*.seat_client) {
|
||||||
|
// Once we've vetted the client, we can tell the cursor to use the
|
||||||
|
// provided surface as the cursor image. It will set the hardware cursor
|
||||||
|
// on the output that it's currently on and continue to do so as the
|
||||||
|
// cursor moves between outputs.
|
||||||
|
c.wlr_cursor_set_surface(
|
||||||
|
server.*.cursor,
|
||||||
|
event.*.surface,
|
||||||
|
event.*.hotspot_x,
|
||||||
|
event.*.hotspot_y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
117
src/keyboard.zig
Normal file
117
src/keyboard.zig
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
const Keyboard = struct {
|
||||||
|
seat: *Seat,
|
||||||
|
device: *c.wlr_input_device,
|
||||||
|
|
||||||
|
listen_modifiers: c.wl_listener,
|
||||||
|
listen_key: c.wl_listener,
|
||||||
|
|
||||||
|
pub fn init(seat: *Seat, device: *c.wlr_input_device) @This() {
|
||||||
|
var keyboard = @This(){
|
||||||
|
.seat = seat,
|
||||||
|
.device = device,
|
||||||
|
|
||||||
|
.listen_modifiers = c.wl_listener{
|
||||||
|
.link = undefined,
|
||||||
|
.notify = handle_modifiers,
|
||||||
|
},
|
||||||
|
.listen_key = c.wl_listener{
|
||||||
|
.link = undefined,
|
||||||
|
.notify = handle_key,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// We need to prepare an XKB keymap and assign it to the keyboard. This
|
||||||
|
// assumes the defaults (e.g. layout = "us").
|
||||||
|
const rules = c.xkb_rule_names{
|
||||||
|
.rules = null,
|
||||||
|
.model = null,
|
||||||
|
.layout = null,
|
||||||
|
.variant = null,
|
||||||
|
.options = null,
|
||||||
|
};
|
||||||
|
const context = c.xkb_context_new(c.enum_xkb_context_flags.XKB_CONTEXT_NO_FLAGS);
|
||||||
|
defer c.xkb_context_unref(context);
|
||||||
|
|
||||||
|
const keymap = man_c.xkb_map_new_from_names(
|
||||||
|
context,
|
||||||
|
&rules,
|
||||||
|
c.enum_xkb_keymap_compile_flags.XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||||
|
);
|
||||||
|
defer c.xkb_keymap_unref(keymap);
|
||||||
|
|
||||||
|
var keyboard_device = device.*.unnamed_37.keyboard;
|
||||||
|
c.wlr_keyboard_set_keymap(keyboard_device, keymap);
|
||||||
|
c.wlr_keyboard_set_repeat_info(keyboard_device, 25, 600);
|
||||||
|
|
||||||
|
// Setup listeners for keyboard events
|
||||||
|
c.wl_signal_add(&keyboard_device.*.events.modifiers, &keyboard.*.listen_modifiers);
|
||||||
|
c.wl_signal_add(&keyboard_device.*.events.key, &keyboard.*.listen_key);
|
||||||
|
|
||||||
|
return keyboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_modifiers(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This event is raised when a modifier key, such as shift or alt, is
|
||||||
|
// pressed. We simply communicate this to the client. */
|
||||||
|
var keyboard = @fieldParentPtr(Keyboard, "listen_modifiers", listener);
|
||||||
|
|
||||||
|
// A seat can only have one keyboard, but this is a limitation of the
|
||||||
|
// Wayland protocol - not wlroots. We assign all connected keyboards to the
|
||||||
|
// same seat. You can swap out the underlying wlr_keyboard like this and
|
||||||
|
// wlr_seat handles this transparently.
|
||||||
|
c.wlr_seat_set_keyboard(keyboard.*.server.*.seat, keyboard.*.device);
|
||||||
|
|
||||||
|
// Send modifiers to the client.
|
||||||
|
c.wlr_seat_keyboard_notify_modifiers(keyboard.*.server.*.seat, &keyboard.*.device.*.unnamed_37.keyboard.*.modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This event is raised when a key is pressed or released.
|
||||||
|
const keyboard = @fieldParentPtr(Keyboard, "listen_key", listener);
|
||||||
|
const event = @ptrCast(
|
||||||
|
*c.wlr_event_keyboard_key,
|
||||||
|
@alignCast(@alignOf(*c.wlr_event_keyboard_key), data),
|
||||||
|
);
|
||||||
|
|
||||||
|
const server = keyboard.*.server;
|
||||||
|
const seat = server.*.seat;
|
||||||
|
const keyboard_device = keyboard.*.device.*.unnamed_37.keyboard;
|
||||||
|
|
||||||
|
// Translate libinput keycode -> xkbcommon
|
||||||
|
const keycode = event.*.keycode + 8;
|
||||||
|
// Get a list of keysyms based on the keymap for this keyboard
|
||||||
|
var syms: *c.xkb_keysym_t = undefined;
|
||||||
|
const nsyms = c.xkb_state_key_get_syms(keyboard_device.*.xkb_state, keycode, &syms);
|
||||||
|
|
||||||
|
var handled = false;
|
||||||
|
const modifiers = c.wlr_keyboard_get_modifiers(keyboard_device);
|
||||||
|
if (modifiers & @intCast(u32, c.WLR_MODIFIER_LOGO) != 0 and
|
||||||
|
event.*.state == c.enum_wlr_key_state.WLR_KEY_PRESSED)
|
||||||
|
{
|
||||||
|
// If mod is held down and this button was _pressed_, we attempt to
|
||||||
|
// process it as a compositor keybinding.
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < nsyms) {
|
||||||
|
handled = keyboard.seat.server.handle_keybinding(syms[i]);
|
||||||
|
if (handled) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handled) {
|
||||||
|
// Otherwise, we pass it along to the client.
|
||||||
|
c.wlr_seat_set_keyboard(seat, keyboard.*.device);
|
||||||
|
c.wlr_seat_keyboard_notify_key(
|
||||||
|
seat,
|
||||||
|
event.*.time_msec,
|
||||||
|
event.*.keycode,
|
||||||
|
@intCast(u32, @enumToInt(event.*.state)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
786
src/main.zig
786
src/main.zig
@ -2,718 +2,6 @@ const std = @import("std");
|
|||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
const man_c = @import("c.zig").manual;
|
const man_c = @import("c.zig").manual;
|
||||||
|
|
||||||
const Server = struct {
|
|
||||||
wl_display: *c.wl_display,
|
|
||||||
backend: *c.wlr_backend,
|
|
||||||
renderer: *c.wlr_renderer,
|
|
||||||
xdg_shell: *c.wlr_xdg_shell,
|
|
||||||
new_xdg_surface: c.wl_listener,
|
|
||||||
views: std.ArrayList(View),
|
|
||||||
|
|
||||||
cursor: *c.wlr_cursor,
|
|
||||||
cursor_mgr: *c.wlr_xcursor_manager,
|
|
||||||
cursor_motion: c.wl_listener,
|
|
||||||
cursor_motion_absolute: c.wl_listener,
|
|
||||||
cursor_button: c.wl_listener,
|
|
||||||
cursor_axis: c.wl_listener,
|
|
||||||
cursor_frame: c.wl_listener,
|
|
||||||
|
|
||||||
seat: *c.wlr_seat,
|
|
||||||
new_input: c.wl_listener,
|
|
||||||
request_cursor: c.wl_listener,
|
|
||||||
keyboards: c.wl_list,
|
|
||||||
cursor_mode: CursorMode,
|
|
||||||
grabbed_view: ?*View,
|
|
||||||
grab_x: f64,
|
|
||||||
grab_y: f64,
|
|
||||||
grab_width: c_int,
|
|
||||||
grab_height: c_int,
|
|
||||||
resize_edges: u32,
|
|
||||||
|
|
||||||
output_layout: *c.wlr_output_layout,
|
|
||||||
outputs: std.ArrayList(Output),
|
|
||||||
new_output: c.wl_listener,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Output = struct {
|
|
||||||
server: *Server,
|
|
||||||
wlr_output: *c.wlr_output,
|
|
||||||
frame: c.wl_listener,
|
|
||||||
};
|
|
||||||
|
|
||||||
const View = struct {
|
|
||||||
server: *Server,
|
|
||||||
xdg_surface: *c.wlr_xdg_surface,
|
|
||||||
map: c.wl_listener,
|
|
||||||
unmap: c.wl_listener,
|
|
||||||
destroy: c.wl_listener,
|
|
||||||
request_move: c.wl_listener,
|
|
||||||
request_resize: c.wl_listener,
|
|
||||||
mapped: bool,
|
|
||||||
x: c_int,
|
|
||||||
y: c_int,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Keyboard = struct {
|
|
||||||
link: c.wl_list,
|
|
||||||
server: *Server,
|
|
||||||
device: *c.wlr_input_device,
|
|
||||||
|
|
||||||
modifiers: c.wl_listener,
|
|
||||||
key: c.wl_listener,
|
|
||||||
};
|
|
||||||
|
|
||||||
const CursorMode = enum {
|
|
||||||
Passthrough,
|
|
||||||
Move,
|
|
||||||
Resize,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn new_list() c.wl_list {
|
|
||||||
return c.wl_list{
|
|
||||||
.prev = null,
|
|
||||||
.next = null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_listener() c.wl_listener {
|
|
||||||
return c.wl_listener{
|
|
||||||
.link = new_list(),
|
|
||||||
.notify = null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const RenderData = struct {
|
|
||||||
output: *c.wlr_output,
|
|
||||||
renderer: *c.wlr_renderer,
|
|
||||||
view: *View,
|
|
||||||
when: *c.struct_timespec,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn render_surface(surface: [*c]c.wlr_surface, sx: c_int, sy: c_int, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This function is called for every surface that needs to be rendered.
|
|
||||||
var rdata = @ptrCast(*RenderData, @alignCast(@alignOf(RenderData), data));
|
|
||||||
var view = rdata.*.view;
|
|
||||||
var output = rdata.*.output;
|
|
||||||
|
|
||||||
// We first obtain a wlr_texture, which is a GPU resource. wlroots
|
|
||||||
// automatically handles negotiating these with the client. The underlying
|
|
||||||
// resource could be an opaque handle passed from the client, or the client
|
|
||||||
// could have sent a pixel buffer which we copied to the GPU, or a few other
|
|
||||||
// means. You don't have to worry about this, wlroots takes care of it.
|
|
||||||
var texture = c.wlr_surface_get_texture(surface);
|
|
||||||
if (texture == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The view has a position in layout coordinates. If you have two displays,
|
|
||||||
// one next to the other, both 1080p, a view on the rightmost display might
|
|
||||||
// have layout coordinates of 2000,100. We need to translate that to
|
|
||||||
// output-local coordinates, or (2000 - 1920).
|
|
||||||
var ox: f64 = 0.0;
|
|
||||||
var oy: f64 = 0.0;
|
|
||||||
c.wlr_output_layout_output_coords(view.*.server.*.output_layout, output, &ox, &oy);
|
|
||||||
ox += @intToFloat(f64, view.*.x + sx);
|
|
||||||
oy += @intToFloat(f64, view.*.y + sy);
|
|
||||||
|
|
||||||
// We also have to apply the scale factor for HiDPI outputs. This is only
|
|
||||||
// part of the puzzle, TinyWL does not fully support HiDPI.
|
|
||||||
var box = c.wlr_box{
|
|
||||||
.x = @floatToInt(c_int, ox * output.*.scale),
|
|
||||||
.y = @floatToInt(c_int, oy * output.*.scale),
|
|
||||||
.width = @floatToInt(c_int, @intToFloat(f32, surface.*.current.width) * output.*.scale),
|
|
||||||
.height = @floatToInt(c_int, @intToFloat(f32, surface.*.current.height) * output.*.scale),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Those familiar with OpenGL are also familiar with the role of matricies
|
|
||||||
// in graphics programming. We need to prepare a matrix to render the view
|
|
||||||
// with. wlr_matrix_project_box is a helper which takes a box with a desired
|
|
||||||
// x, y coordinates, width and height, and an output geometry, then
|
|
||||||
// prepares an orthographic projection and multiplies the necessary
|
|
||||||
// transforms to produce a model-view-projection matrix.
|
|
||||||
//
|
|
||||||
// Naturally you can do this any way you like, for example to make a 3D
|
|
||||||
// compositor.
|
|
||||||
var matrix: [9]f32 = undefined;
|
|
||||||
var transform = c.wlr_output_transform_invert(surface.*.current.transform);
|
|
||||||
c.wlr_matrix_project_box(&matrix, &box, transform, 0.0, &output.*.transform_matrix);
|
|
||||||
|
|
||||||
// This takes our matrix, the texture, and an alpha, and performs the actual
|
|
||||||
// rendering on the GPU.
|
|
||||||
_ = c.wlr_render_texture_with_matrix(rdata.*.renderer, texture, &matrix, 1.0);
|
|
||||||
|
|
||||||
// This lets the client know that we've displayed that frame and it can
|
|
||||||
// prepare another one now if it likes.
|
|
||||||
c.wlr_surface_send_frame_done(surface, rdata.*.when);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn output_frame(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This function is called every time an output is ready to display a frame,
|
|
||||||
// generally at the output's refresh rate (e.g. 60Hz).
|
|
||||||
var output = @fieldParentPtr(Output, "frame", listener);
|
|
||||||
var renderer = output.*.server.*.renderer;
|
|
||||||
|
|
||||||
var now: c.struct_timespec = undefined;
|
|
||||||
_ = c.clock_gettime(c.CLOCK_MONOTONIC, &now);
|
|
||||||
|
|
||||||
// wlr_output_attach_render makes the OpenGL context current.
|
|
||||||
if (!c.wlr_output_attach_render(output.*.wlr_output, null)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// The "effective" resolution can change if you rotate your outputs.
|
|
||||||
var width: c_int = undefined;
|
|
||||||
var height: c_int = undefined;
|
|
||||||
c.wlr_output_effective_resolution(output.*.wlr_output, &width, &height);
|
|
||||||
// Begin the renderer (calls glViewport and some other GL sanity checks)
|
|
||||||
c.wlr_renderer_begin(renderer, width, height);
|
|
||||||
|
|
||||||
const color = [_]f32{ 0.3, 0.3, 0.3, 1.0 };
|
|
||||||
c.wlr_renderer_clear(renderer, &color);
|
|
||||||
|
|
||||||
// Each subsequent window we render is rendered on top of the last. Because
|
|
||||||
// our view list is ordered front-to-back, we iterate over it backwards.
|
|
||||||
for (output.*.server.views.span()) |*view| {
|
|
||||||
if (!view.*.mapped) {
|
|
||||||
// An unmapped view should not be rendered.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var rdata = RenderData{
|
|
||||||
.output = output.*.wlr_output,
|
|
||||||
.view = view,
|
|
||||||
.renderer = renderer,
|
|
||||||
.when = &now,
|
|
||||||
};
|
|
||||||
// This calls our render_surface function for each surface among the
|
|
||||||
// xdg_surface's toplevel and popups.
|
|
||||||
c.wlr_xdg_surface_for_each_surface(view.*.xdg_surface, render_surface, &rdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hardware cursors are rendered by the GPU on a separate plane, and can be
|
|
||||||
// moved around without re-rendering what's beneath them - which is more
|
|
||||||
// efficient. However, not all hardware supports hardware cursors. For this
|
|
||||||
// reason, wlroots provides a software fallback, which we ask it to render
|
|
||||||
// here. wlr_cursor handles configuring hardware vs software cursors for you,
|
|
||||||
// and this function is a no-op when hardware cursors are in use.
|
|
||||||
c.wlr_output_render_software_cursors(output.*.wlr_output, null);
|
|
||||||
|
|
||||||
// Conclude rendering and swap the buffers, showing the final frame
|
|
||||||
// on-screen.
|
|
||||||
c.wlr_renderer_end(renderer);
|
|
||||||
// TODO: handle failure
|
|
||||||
_ = c.wlr_output_commit(output.*.wlr_output);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_new_output(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
var server = @fieldParentPtr(Server, "new_output", listener);
|
|
||||||
var wlr_output = @ptrCast(*c.wlr_output, @alignCast(@alignOf(*c.wlr_output), data));
|
|
||||||
|
|
||||||
// Some backends don't have modes. DRM+KMS does, and we need to set a mode
|
|
||||||
// before we can use the output. The mode is a tuple of (width, height,
|
|
||||||
// refresh rate), and each monitor supports only a specific set of modes. We
|
|
||||||
// just pick the monitor's preferred mode, a more sophisticated compositor
|
|
||||||
// would let the user configure it.
|
|
||||||
|
|
||||||
// if not empty
|
|
||||||
if (c.wl_list_empty(&wlr_output.*.modes) == 0) {
|
|
||||||
var mode = c.wlr_output_preferred_mode(wlr_output);
|
|
||||||
c.wlr_output_set_mode(wlr_output, mode);
|
|
||||||
c.wlr_output_enable(wlr_output, true);
|
|
||||||
if (!c.wlr_output_commit(wlr_output)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocates and configures our state for this output
|
|
||||||
server.*.outputs.append(Output{
|
|
||||||
.server = undefined,
|
|
||||||
.wlr_output = undefined,
|
|
||||||
.frame = undefined,
|
|
||||||
}) catch unreachable;
|
|
||||||
var output = &server.*.outputs.span()[server.*.outputs.span().len - 1];
|
|
||||||
output.*.wlr_output = wlr_output;
|
|
||||||
output.*.server = server;
|
|
||||||
|
|
||||||
// Sets up a listener for the frame notify event.
|
|
||||||
output.*.frame.notify = output_frame;
|
|
||||||
c.wl_signal_add(&wlr_output.*.events.frame, &output.*.frame);
|
|
||||||
|
|
||||||
// Adds this to the output layout. The add_auto function arranges outputs
|
|
||||||
// from left-to-right in the order they appear. A more sophisticated
|
|
||||||
// compositor would let the user configure the arrangement of outputs in the
|
|
||||||
// layout.
|
|
||||||
c.wlr_output_layout_add_auto(server.*.output_layout, wlr_output);
|
|
||||||
|
|
||||||
// Creating the global adds a wl_output global to the display, which Wayland
|
|
||||||
// clients can see to find out information about the output (such as
|
|
||||||
// DPI, scale factor, manufacturer, etc).
|
|
||||||
c.wlr_output_create_global(wlr_output);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_view(view: *View, surface: *c.wlr_surface) void {
|
|
||||||
const server = view.server;
|
|
||||||
const seat = server.*.seat;
|
|
||||||
const prev_surface = seat.*.keyboard_state.focused_surface;
|
|
||||||
|
|
||||||
if (prev_surface == surface) {
|
|
||||||
// Don't re-focus an already focused surface.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prev_surface != null) {
|
|
||||||
// Deactivate the previously focused surface. This lets the client know
|
|
||||||
// it no longer has focus and the client will repaint accordingly, e.g.
|
|
||||||
// stop displaying a caret.
|
|
||||||
var prev_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(prev_surface);
|
|
||||||
_ = c.wlr_xdg_toplevel_set_activated(prev_xdg_surface, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the index
|
|
||||||
const idx = for (server.*.views.span()) |*v, i| {
|
|
||||||
if (v == view) {
|
|
||||||
break i;
|
|
||||||
}
|
|
||||||
} else unreachable;
|
|
||||||
|
|
||||||
// Move the view to the front
|
|
||||||
server.*.views.append(server.*.views.orderedRemove(idx)) catch unreachable;
|
|
||||||
|
|
||||||
var moved_view = &server.*.views.span()[server.*.views.span().len - 1];
|
|
||||||
|
|
||||||
// Activate the new surface
|
|
||||||
_ = c.wlr_xdg_toplevel_set_activated(moved_view.*.xdg_surface, true);
|
|
||||||
|
|
||||||
// Tell the seat to have the keyboard enter this surface. wlroots will keep
|
|
||||||
// track of this and automatically send key events to the appropriate
|
|
||||||
// clients without additional work on your part.
|
|
||||||
var keyboard = c.wlr_seat_get_keyboard(seat);
|
|
||||||
c.wlr_seat_keyboard_notify_enter(seat, moved_view.*.xdg_surface.*.surface, &keyboard.*.keycodes, keyboard.*.num_keycodes, &keyboard.*.modifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xdg_surface_map(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// Called when the surface is mapped, or ready to display on-screen.
|
|
||||||
var view = @fieldParentPtr(View, "map", listener);
|
|
||||||
view.*.mapped = true;
|
|
||||||
focus_view(view, view.*.xdg_surface.*.surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xdg_surface_unmap(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
var view = @fieldParentPtr(View, "unmap", listener);
|
|
||||||
view.*.mapped = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xdg_surface_destroy(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
var view = @fieldParentPtr(View, "destroy", listener);
|
|
||||||
var server = view.*.server;
|
|
||||||
const idx = for (server.*.views.span()) |*v, i| {
|
|
||||||
if (v == view) {
|
|
||||||
break i;
|
|
||||||
}
|
|
||||||
} else return;
|
|
||||||
_ = server.*.views.orderedRemove(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xdg_toplevel_request_move(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// ignore for now
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xdg_toplevel_request_resize(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// ignore for now
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_new_xdg_surface(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This event is raised when wlr_xdg_shell receives a new xdg surface from a
|
|
||||||
// client, either a toplevel (application window) or popup.
|
|
||||||
var server = @fieldParentPtr(Server, "new_xdg_surface", listener);
|
|
||||||
var xdg_surface = @ptrCast(*c.wlr_xdg_surface, @alignCast(@alignOf(*c.wlr_xdg_surface), data));
|
|
||||||
|
|
||||||
if (xdg_surface.*.role != c.enum_wlr_xdg_surface_role.WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate a View for this surface
|
|
||||||
server.*.views.append(undefined) catch unreachable;
|
|
||||||
var view = &server.*.views.span()[server.*.views.span().len - 1];
|
|
||||||
|
|
||||||
view.*.server = server;
|
|
||||||
view.*.xdg_surface = xdg_surface;
|
|
||||||
|
|
||||||
// Listen to the various events it can emit
|
|
||||||
view.*.map.notify = xdg_surface_map;
|
|
||||||
c.wl_signal_add(&xdg_surface.*.events.map, &view.*.map);
|
|
||||||
|
|
||||||
view.*.unmap.notify = xdg_surface_unmap;
|
|
||||||
c.wl_signal_add(&xdg_surface.*.events.unmap, &view.*.unmap);
|
|
||||||
|
|
||||||
view.*.destroy.notify = xdg_surface_destroy;
|
|
||||||
c.wl_signal_add(&xdg_surface.*.events.destroy, &view.*.destroy);
|
|
||||||
|
|
||||||
var toplevel = xdg_surface.*.unnamed_160.toplevel;
|
|
||||||
view.*.request_move.notify = xdg_toplevel_request_move;
|
|
||||||
c.wl_signal_add(&toplevel.*.events.request_move, &view.*.request_move);
|
|
||||||
|
|
||||||
view.*.request_resize.notify = xdg_toplevel_request_resize;
|
|
||||||
c.wl_signal_add(&toplevel.*.events.request_resize, &view.*.request_resize);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keyboard_handle_modifiers(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This event is raised when a modifier key, such as shift or alt, is
|
|
||||||
// pressed. We simply communicate this to the client. */
|
|
||||||
var keyboard = @fieldParentPtr(Keyboard, "modifiers", listener);
|
|
||||||
|
|
||||||
// A seat can only have one keyboard, but this is a limitation of the
|
|
||||||
// Wayland protocol - not wlroots. We assign all connected keyboards to the
|
|
||||||
// same seat. You can swap out the underlying wlr_keyboard like this and
|
|
||||||
// wlr_seat handles this transparently.
|
|
||||||
c.wlr_seat_set_keyboard(keyboard.*.server.*.seat, keyboard.*.device);
|
|
||||||
|
|
||||||
// Send modifiers to the client.
|
|
||||||
c.wlr_seat_keyboard_notify_modifiers(keyboard.*.server.*.seat, &keyboard.*.device.*.unnamed_37.keyboard.*.modifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_keybinding(server: *Server, sym: c.xkb_keysym_t) bool {
|
|
||||||
// Here we handle compositor keybindings. This is when the compositor is
|
|
||||||
// processing keys, rather than passing them on to the client for its own
|
|
||||||
// processing.
|
|
||||||
//
|
|
||||||
// This function assumes the proper modifier is held down.
|
|
||||||
switch (sym) {
|
|
||||||
c.XKB_KEY_Escape => c.wl_display_terminate(server.*.wl_display),
|
|
||||||
c.XKB_KEY_F1 => {
|
|
||||||
// Cycle to the next view
|
|
||||||
//if (c.wl_list_length(&server.*.views) > 1) {
|
|
||||||
// const current_view = @fieldParentPtr(View, "link", server.*.views.next);
|
|
||||||
// const next_view = @fieldParentPtr(View, "link", current_view.*.link.next);
|
|
||||||
// focus_view(next_view, next_view.*.xdg_surface.*.surface);
|
|
||||||
// // Move the previous view to the end of the list
|
|
||||||
// c.wl_list_remove(¤t_view.*.link);
|
|
||||||
// c.wl_list_insert(server.*.views.prev, ¤t_view.*.link);
|
|
||||||
//}
|
|
||||||
},
|
|
||||||
else => return false,
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keyboard_handle_key(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This event is raised when a key is pressed or released.
|
|
||||||
const keyboard = @fieldParentPtr(Keyboard, "key", listener);
|
|
||||||
const event = @ptrCast(*c.wlr_event_keyboard_key, @alignCast(@alignOf(*c.wlr_event_keyboard_key), data));
|
|
||||||
|
|
||||||
const server = keyboard.*.server;
|
|
||||||
const seat = server.*.seat;
|
|
||||||
const keyboard_device = keyboard.*.device.*.unnamed_37.keyboard;
|
|
||||||
|
|
||||||
// Translate libinput keycode -> xkbcommon
|
|
||||||
const keycode = event.*.keycode + 8;
|
|
||||||
// Get a list of keysyms based on the keymap for this keyboard
|
|
||||||
var syms: [*c]c.xkb_keysym_t = undefined;
|
|
||||||
const nsyms = c.xkb_state_key_get_syms(keyboard_device.*.xkb_state, keycode, &syms);
|
|
||||||
|
|
||||||
var handled = false;
|
|
||||||
const modifiers = c.wlr_keyboard_get_modifiers(keyboard_device);
|
|
||||||
if (modifiers & @intCast(u32, c.WLR_MODIFIER_LOGO) != 0 and event.*.state == c.enum_wlr_key_state.WLR_KEY_PRESSED) {
|
|
||||||
// If mod is held down and this button was _pressed_, we attempt to
|
|
||||||
// process it as a compositor keybinding.
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < nsyms) {
|
|
||||||
handled = handle_keybinding(server, syms[i]);
|
|
||||||
if (handled) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!handled) {
|
|
||||||
// Otherwise, we pass it along to the client.
|
|
||||||
c.wlr_seat_set_keyboard(seat, keyboard.*.device);
|
|
||||||
c.wlr_seat_keyboard_notify_key(seat, event.*.time_msec, event.*.keycode, @intCast(u32, @enumToInt(event.*.state)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_new_keyboard(server: *Server, device: *c.wlr_input_device) void {
|
|
||||||
var keyboard = std.heap.c_allocator.create(Keyboard) catch unreachable;
|
|
||||||
keyboard.*.server = server;
|
|
||||||
keyboard.*.device = device;
|
|
||||||
|
|
||||||
// We need to prepare an XKB keymap and assign it to the keyboard. This
|
|
||||||
// assumes the defaults (e.g. layout = "us").
|
|
||||||
const rules = c.xkb_rule_names{
|
|
||||||
.rules = null,
|
|
||||||
.model = null,
|
|
||||||
.layout = null,
|
|
||||||
.variant = null,
|
|
||||||
.options = null,
|
|
||||||
};
|
|
||||||
const context = c.xkb_context_new(c.enum_xkb_context_flags.XKB_CONTEXT_NO_FLAGS);
|
|
||||||
defer c.xkb_context_unref(context);
|
|
||||||
|
|
||||||
const keymap = man_c.xkb_map_new_from_names(context, &rules, c.enum_xkb_keymap_compile_flags.XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
||||||
defer c.xkb_keymap_unref(keymap);
|
|
||||||
|
|
||||||
var keyboard_device = device.*.unnamed_37.keyboard;
|
|
||||||
c.wlr_keyboard_set_keymap(keyboard_device, keymap);
|
|
||||||
c.wlr_keyboard_set_repeat_info(keyboard_device, 25, 600);
|
|
||||||
|
|
||||||
// Setup listeners for keyboard events
|
|
||||||
keyboard.*.modifiers.notify = keyboard_handle_modifiers;
|
|
||||||
c.wl_signal_add(&keyboard_device.*.events.modifiers, &keyboard.*.modifiers);
|
|
||||||
keyboard.*.key.notify = keyboard_handle_key;
|
|
||||||
c.wl_signal_add(&keyboard_device.*.events.key, &keyboard.*.key);
|
|
||||||
|
|
||||||
c.wlr_seat_set_keyboard(server.*.seat, device);
|
|
||||||
|
|
||||||
// And add the keyboard to our list of keyboards
|
|
||||||
c.wl_list_insert(&server.*.keyboards, &keyboard.*.link);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_new_pointer(server: *Server, device: *c.struct_wlr_input_device) void {
|
|
||||||
// We don't do anything special with pointers. All of our pointer handling
|
|
||||||
// is proxied through wlr_cursor. On another compositor, you might take this
|
|
||||||
// opportunity to do libinput configuration on the device to set
|
|
||||||
// acceleration, etc.
|
|
||||||
c.wlr_cursor_attach_input_device(server.*.cursor, device);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_new_input(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This event is raised by the backend when a new input device becomes available.
|
|
||||||
var server = @fieldParentPtr(Server, "new_input", listener);
|
|
||||||
var device = @ptrCast(*c.wlr_input_device, @alignCast(@alignOf(*c.wlr_input_device), data));
|
|
||||||
|
|
||||||
switch (device.*.type) {
|
|
||||||
.WLR_INPUT_DEVICE_KEYBOARD => server_new_keyboard(server, device),
|
|
||||||
.WLR_INPUT_DEVICE_POINTER => server_new_pointer(server, device),
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to let the wlr_seat know what our capabilities are, which is
|
|
||||||
// communiciated to the client. In TinyWL we always have a cursor, even if
|
|
||||||
// there are no pointer devices, so we always include that capability.
|
|
||||||
var caps: u32 = @intCast(u32, c.WL_SEAT_CAPABILITY_POINTER);
|
|
||||||
// if list not empty
|
|
||||||
if (c.wl_list_empty(&server.*.keyboards) == 0) {
|
|
||||||
caps |= @intCast(u32, c.WL_SEAT_CAPABILITY_KEYBOARD);
|
|
||||||
}
|
|
||||||
c.wlr_seat_set_capabilities(server.*.seat, caps);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn seat_request_cursor(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This event is rasied by the seat when a client provides a cursor image
|
|
||||||
var server = @fieldParentPtr(Server, "request_cursor", listener);
|
|
||||||
var event = @ptrCast(*c.wlr_seat_pointer_request_set_cursor_event, @alignCast(@alignOf(*c.wlr_seat_pointer_request_set_cursor_event), data));
|
|
||||||
var focused_client = server.*.seat.*.pointer_state.focused_client;
|
|
||||||
|
|
||||||
// This can be sent by any client, so we check to make sure this one is
|
|
||||||
// actually has pointer focus first.
|
|
||||||
if (focused_client == event.*.seat_client) {
|
|
||||||
// Once we've vetted the client, we can tell the cursor to use the
|
|
||||||
// provided surface as the cursor image. It will set the hardware cursor
|
|
||||||
// on the output that it's currently on and continue to do so as the
|
|
||||||
// cursor moves between outputs.
|
|
||||||
c.wlr_cursor_set_surface(server.*.cursor, event.*.surface, event.*.hotspot_x, event.*.hotspot_y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view_at(view: *View, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) bool {
|
|
||||||
// XDG toplevels may have nested surfaces, such as popup windows for context
|
|
||||||
// menus or tooltips. This function tests if any of those are underneath the
|
|
||||||
// coordinates lx and ly (in output Layout Coordinates). If so, it sets the
|
|
||||||
// surface pointer to that wlr_surface and the sx and sy coordinates to the
|
|
||||||
// coordinates relative to that surface's top-left corner.
|
|
||||||
var view_sx = lx - @intToFloat(f64, view.*.x);
|
|
||||||
var view_sy = ly - @intToFloat(f64, view.*.y);
|
|
||||||
|
|
||||||
// This variable seems to have been unsued in TinyWL
|
|
||||||
// struct wlr_surface_state *state = &view->xdg_surface->surface->current;
|
|
||||||
|
|
||||||
var _sx: f64 = undefined;
|
|
||||||
var _sy: f64 = undefined;
|
|
||||||
var _surface = c.wlr_xdg_surface_surface_at(view.*.xdg_surface, view_sx, view_sy, &_sx, &_sy);
|
|
||||||
|
|
||||||
if (_surface) |surface_at| {
|
|
||||||
sx.* = _sx;
|
|
||||||
sy.* = _sy;
|
|
||||||
surface.* = surface_at;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desktop_view_at(server: *Server, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) ?*View {
|
|
||||||
// This iterates over all of our surfaces and attempts to find one under the
|
|
||||||
// cursor. This relies on server.*.views being ordered from top-to-bottom.
|
|
||||||
for (server.*.views.span()) |*view| {
|
|
||||||
if (view_at(view, lx, ly, surface, sx, sy)) {
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_cursor_move(server: *Server, time: u32) void {
|
|
||||||
// Move the grabbed view to the new position.
|
|
||||||
server.*.grabbed_view.?.*.x = @floatToInt(c_int, server.*.cursor.*.x - server.*.grab_x);
|
|
||||||
server.*.grabbed_view.?.*.y = @floatToInt(c_int, server.*.cursor.*.y - server.*.grab_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_cursor_resize(server: *Server, time: u32) void {
|
|
||||||
// Resizing the grabbed view can be a little bit complicated, because we
|
|
||||||
// could be resizing from any corner or edge. This not only resizes the view
|
|
||||||
// on one or two axes, but can also move the view if you resize from the top
|
|
||||||
// or left edges (or top-left corner).
|
|
||||||
//
|
|
||||||
// Note that I took some shortcuts here. In a more fleshed-out compositor,
|
|
||||||
// you'd wait for the client to prepare a buffer at the new size, then
|
|
||||||
// commit any movement that was prepared.
|
|
||||||
var view = server.*.grabbed_view;
|
|
||||||
|
|
||||||
var dx: f64 = (server.*.cursor.*.x - server.*.grab_x);
|
|
||||||
var dy: f64 = (server.*.cursor.*.y - server.*.grab_y);
|
|
||||||
var x: f64 = @intToFloat(f64, view.?.*.x);
|
|
||||||
var y: f64 = @intToFloat(f64, view.?.*.y);
|
|
||||||
|
|
||||||
var width = @intToFloat(f64, server.*.grab_width);
|
|
||||||
var height = @intToFloat(f64, server.*.grab_height);
|
|
||||||
if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_TOP) != 0) {
|
|
||||||
y = server.*.grab_y + dy;
|
|
||||||
height -= dy;
|
|
||||||
if (height < 1) {
|
|
||||||
y += height;
|
|
||||||
}
|
|
||||||
} else if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_BOTTOM) != 0) {
|
|
||||||
height += dy;
|
|
||||||
}
|
|
||||||
if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_LEFT) != 0) {
|
|
||||||
x = server.*.grab_x + dx;
|
|
||||||
width -= dx;
|
|
||||||
if (width < 1) {
|
|
||||||
x += width;
|
|
||||||
}
|
|
||||||
} else if (server.*.resize_edges & @intCast(u32, c.WLR_EDGE_RIGHT) != 0) {
|
|
||||||
width += dx;
|
|
||||||
}
|
|
||||||
view.?.*.x = @floatToInt(c_int, x);
|
|
||||||
view.?.*.y = @floatToInt(c_int, y);
|
|
||||||
_ = c.wlr_xdg_toplevel_set_size(view.?.*.xdg_surface, @floatToInt(u32, width), @floatToInt(u32, height));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_cursor_motion(server: *Server, time: u32) void {
|
|
||||||
// If the mode is non-passthrough, delegate to those functions.
|
|
||||||
if (server.*.cursor_mode == CursorMode.Move) {
|
|
||||||
process_cursor_move(server, time);
|
|
||||||
return;
|
|
||||||
} else if (server.*.cursor_mode == CursorMode.Resize) {
|
|
||||||
process_cursor_resize(server, time);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, find the view under the pointer and send the event along.
|
|
||||||
var sx: f64 = undefined;
|
|
||||||
var sy: f64 = undefined;
|
|
||||||
var seat = server.*.seat;
|
|
||||||
var opt_surface: ?*c.wlr_surface = null;
|
|
||||||
var view = desktop_view_at(server, server.*.cursor.*.x, server.*.cursor.*.y, &opt_surface, &sx, &sy);
|
|
||||||
|
|
||||||
if (view == null) {
|
|
||||||
// If there's no view under the cursor, set the cursor image to a
|
|
||||||
// default. This is what makes the cursor image appear when you move it
|
|
||||||
// around the screen, not over any views.
|
|
||||||
c.wlr_xcursor_manager_set_cursor_image(server.*.cursor_mgr, "left_ptr", server.*.cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt_surface) |surface| {
|
|
||||||
const focus_changed = seat.*.pointer_state.focused_surface != surface;
|
|
||||||
// "Enter" the surface if necessary. This lets the client know that the
|
|
||||||
// cursor has entered one of its surfaces.
|
|
||||||
//
|
|
||||||
// Note that this gives the surface "pointer focus", which is distinct
|
|
||||||
// from keyboard focus. You get pointer focus by moving the pointer over
|
|
||||||
// a window.
|
|
||||||
c.wlr_seat_pointer_notify_enter(seat, surface, sx, sy);
|
|
||||||
if (!focus_changed) {
|
|
||||||
// The enter event contains coordinates, so we only need to notify
|
|
||||||
// on motion if the focus did not change.
|
|
||||||
c.wlr_seat_pointer_notify_motion(seat, time, sx, sy);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Clear pointer focus so future button events and such are not sent to
|
|
||||||
// the last client to have the cursor over it.
|
|
||||||
c.wlr_seat_pointer_clear_focus(seat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_cursor_motion(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This event is forwarded by the cursor when a pointer emits a _relative_
|
|
||||||
// pointer motion event (i.e. a delta)
|
|
||||||
var server = @fieldParentPtr(Server, "cursor_motion", listener);
|
|
||||||
var event = @ptrCast(*c.wlr_event_pointer_motion, @alignCast(@alignOf(*c.wlr_event_pointer_motion), data));
|
|
||||||
// The cursor doesn't move unless we tell it to. The cursor automatically
|
|
||||||
// handles constraining the motion to the output layout, as well as any
|
|
||||||
// special configuration applied for the specific input device which
|
|
||||||
// generated the event. You can pass NULL for the device if you want to move
|
|
||||||
// the cursor around without any input.
|
|
||||||
c.wlr_cursor_move(server.*.cursor, event.*.device, event.*.delta_x, event.*.delta_y);
|
|
||||||
process_cursor_motion(server, event.*.time_msec);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_cursor_motion_absolute(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This event is forwarded by the cursor when a pointer emits an _absolute_
|
|
||||||
// motion event, from 0..1 on each axis. This happens, for example, when
|
|
||||||
// wlroots is running under a Wayland window rather than KMS+DRM, and you
|
|
||||||
// move the mouse over the window. You could enter the window from any edge,
|
|
||||||
// so we have to warp the mouse there. There is also some hardware which
|
|
||||||
// emits these events.
|
|
||||||
var server = @fieldParentPtr(Server, "cursor_motion_absolute", listener);
|
|
||||||
var event = @ptrCast(*c.wlr_event_pointer_motion_absolute, @alignCast(@alignOf(*c.wlr_event_pointer_motion_absolute), data));
|
|
||||||
c.wlr_cursor_warp_absolute(server.*.cursor, event.*.device, event.*.x, event.*.y);
|
|
||||||
process_cursor_motion(server, event.*.time_msec);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_cursor_button(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This event is forwarded by the cursor when a pointer emits a button
|
|
||||||
// event.
|
|
||||||
var server = @fieldParentPtr(Server, "cursor_button", listener);
|
|
||||||
var event = @ptrCast(*c.wlr_event_pointer_button, @alignCast(@alignOf(*c.wlr_event_pointer_button), data));
|
|
||||||
// Notify the client with pointer focus that a button press has occurred
|
|
||||||
_ = c.wlr_seat_pointer_notify_button(server.*.seat, event.*.time_msec, event.*.button, event.*.state);
|
|
||||||
|
|
||||||
var sx: f64 = undefined;
|
|
||||||
var sy: f64 = undefined;
|
|
||||||
|
|
||||||
var surface: ?*c.wlr_surface = null;
|
|
||||||
var view = desktop_view_at(server, server.*.cursor.*.x, server.*.cursor.*.y, &surface, &sx, &sy);
|
|
||||||
|
|
||||||
if (event.*.state == c.enum_wlr_button_state.WLR_BUTTON_RELEASED) {
|
|
||||||
// If you released any buttons, we exit interactive move/resize mode.
|
|
||||||
server.*.cursor_mode = CursorMode.Passthrough;
|
|
||||||
} else {
|
|
||||||
// Focus that client if the button was _pressed_
|
|
||||||
if (view) |v| {
|
|
||||||
focus_view(v, surface.?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_cursor_axis(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This event is forwarded by the cursor when a pointer emits an axis event,
|
|
||||||
// for example when you move the scroll wheel.
|
|
||||||
var server = @fieldParentPtr(Server, "cursor_axis", listener);
|
|
||||||
var event = @ptrCast(*c.wlr_event_pointer_axis, @alignCast(@alignOf(*c.wlr_event_pointer_axis), data));
|
|
||||||
// Notify the client with pointer focus of the axis event.
|
|
||||||
c.wlr_seat_pointer_notify_axis(server.*.seat, event.*.time_msec, event.*.orientation, event.*.delta, event.*.delta_discrete, event.*.source);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn server_cursor_frame(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
|
||||||
// This event is forwarded by the cursor when a pointer emits an frame
|
|
||||||
// event. Frame events are sent after regular pointer events to group
|
|
||||||
// multiple events together. For instance, two axis events may happen at the
|
|
||||||
// same time, in which case a frame event won't be sent in between.
|
|
||||||
var server = @fieldParentPtr(Server, "cursor_frame", listener);
|
|
||||||
// Notify the client with pointer focus of the frame event.
|
|
||||||
c.wlr_seat_pointer_notify_frame(server.*.seat);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ZagError = error{
|
const ZagError = error{
|
||||||
InitError,
|
InitError,
|
||||||
CantAddSocket,
|
CantAddSocket,
|
||||||
@ -727,32 +15,6 @@ pub fn main() !void {
|
|||||||
c.wlr_log_init(c.enum_wlr_log_importance.WLR_DEBUG, null);
|
c.wlr_log_init(c.enum_wlr_log_importance.WLR_DEBUG, null);
|
||||||
|
|
||||||
var server: Server = undefined;
|
var server: Server = undefined;
|
||||||
// The Wayland display is managed by libwayland. It handles accepting
|
|
||||||
// clients from the Unix socket, manging Wayland globals, and so on.
|
|
||||||
server.wl_display = c.wl_display_create() orelse return ZagError.InitError;
|
|
||||||
|
|
||||||
// The backend is a wlroots feature which abstracts the underlying input and
|
|
||||||
// output hardware. The autocreate option will choose the most suitable
|
|
||||||
// backend based on the current environment, such as opening an X11 window
|
|
||||||
// if an X11 server is running. The NULL argument here optionally allows you
|
|
||||||
// to pass in a custom renderer if wlr_renderer doesn't meet your needs. The
|
|
||||||
// backend uses the renderer, for example, to fall back to software cursors
|
|
||||||
// if the backend does not support hardware cursors (some older GPUs
|
|
||||||
// don't).
|
|
||||||
server.backend = c.zag_wlr_backend_autocreate(server.wl_display) orelse return ZagError.InitError;
|
|
||||||
|
|
||||||
// If we don't provide a renderer, autocreate makes a GLES2 renderer for us.
|
|
||||||
// The renderer is responsible for defining the various pixel formats it
|
|
||||||
// supports for shared memory, this configures that for clients.
|
|
||||||
server.renderer = c.zag_wlr_backend_get_renderer(server.backend);
|
|
||||||
c.wlr_renderer_init_wl_display(server.renderer, server.wl_display);
|
|
||||||
|
|
||||||
// This creates some hands-off wlroots interfaces. The compositor is
|
|
||||||
// necessary for clients to allocate surfaces and the data device manager
|
|
||||||
// handles the clipboard. Each of these wlroots interfaces has room for you
|
|
||||||
// to dig your fingers in and play with their behavior if you want.
|
|
||||||
_ = c.wlr_compositor_create(server.wl_display, server.renderer);
|
|
||||||
_ = c.wlr_data_device_manager_create(server.wl_display);
|
|
||||||
|
|
||||||
// Creates an output layout, which a wlroots utility for working with an
|
// Creates an output layout, which a wlroots utility for working with an
|
||||||
// arrangement of screens in a physical layout.
|
// arrangement of screens in a physical layout.
|
||||||
@ -773,54 +35,6 @@ pub fn main() !void {
|
|||||||
server.new_xdg_surface.notify = server_new_xdg_surface;
|
server.new_xdg_surface.notify = server_new_xdg_surface;
|
||||||
c.wl_signal_add(&server.xdg_shell.*.events.new_surface, &server.new_xdg_surface);
|
c.wl_signal_add(&server.xdg_shell.*.events.new_surface, &server.new_xdg_surface);
|
||||||
|
|
||||||
// Creates a cursor, which is a wlroots utility for tracking the cursor
|
|
||||||
// image shown on screen.
|
|
||||||
server.cursor = c.wlr_cursor_create();
|
|
||||||
c.wlr_cursor_attach_output_layout(server.cursor, server.output_layout);
|
|
||||||
|
|
||||||
// Creates an xcursor manager, another wlroots utility which loads up
|
|
||||||
// Xcursor themes to source cursor images from and makes sure that cursor
|
|
||||||
// images are available at all scale factors on the screen (necessary for
|
|
||||||
// HiDPI support). We add a cursor theme at scale factor 1 to begin with.
|
|
||||||
server.cursor_mgr = c.wlr_xcursor_manager_create(null, 24);
|
|
||||||
_ = c.wlr_xcursor_manager_load(server.cursor_mgr, 1);
|
|
||||||
|
|
||||||
// wlr_cursor *only* displays an image on screen. It does not move around
|
|
||||||
// when the pointer moves. However, we can attach input devices to it, and
|
|
||||||
// it will generate aggregate events for all of them. In these events, we
|
|
||||||
// can choose how we want to process them, forwarding them to clients and
|
|
||||||
// moving the cursor around. More detail on this process is described in my
|
|
||||||
// input handling blog post:
|
|
||||||
//
|
|
||||||
// https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html
|
|
||||||
//
|
|
||||||
// And more comments are sprinkled throughout the notify functions above.
|
|
||||||
server.cursor_motion.notify = server_cursor_motion;
|
|
||||||
c.wl_signal_add(&server.cursor.*.events.motion, &server.cursor_motion);
|
|
||||||
|
|
||||||
server.cursor_motion_absolute.notify = server_cursor_motion_absolute;
|
|
||||||
c.wl_signal_add(&server.cursor.*.events.motion_absolute, &server.cursor_motion_absolute);
|
|
||||||
|
|
||||||
server.cursor_button.notify = server_cursor_button;
|
|
||||||
c.wl_signal_add(&server.cursor.*.events.button, &server.cursor_button);
|
|
||||||
|
|
||||||
server.cursor_axis.notify = server_cursor_axis;
|
|
||||||
c.wl_signal_add(&server.cursor.*.events.axis, &server.cursor_axis);
|
|
||||||
|
|
||||||
server.cursor_frame.notify = server_cursor_frame;
|
|
||||||
c.wl_signal_add(&server.cursor.*.events.frame, &server.cursor_frame);
|
|
||||||
|
|
||||||
// Configures a seat, which is a single "seat" at which a user sits and
|
|
||||||
// operates the computer. This conceptually includes up to one keyboard,
|
|
||||||
// pointer, touch, and drawing tablet device. We also rig up a listener to
|
|
||||||
// let us know when new input devices are available on the backend.
|
|
||||||
c.wl_list_init(&server.keyboards);
|
|
||||||
server.new_input.notify = server_new_input;
|
|
||||||
c.wl_signal_add(&server.backend.*.events.new_input, &server.new_input);
|
|
||||||
server.seat = c.wlr_seat_create(server.wl_display, "seat0");
|
|
||||||
server.request_cursor.notify = seat_request_cursor;
|
|
||||||
c.wl_signal_add(&server.seat.*.events.request_set_cursor, &server.request_cursor);
|
|
||||||
|
|
||||||
// Add a Unix socket to the Wayland display.
|
// Add a Unix socket to the Wayland display.
|
||||||
const socket = c.wl_display_add_socket_auto(server.wl_display);
|
const socket = c.wl_display_add_socket_auto(server.wl_display);
|
||||||
if (socket == null) {
|
if (socket == null) {
|
||||||
|
110
src/output.zig
Normal file
110
src/output.zig
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
const Output = struct {
|
||||||
|
server: *Server,
|
||||||
|
wlr_output: *c.wlr_output,
|
||||||
|
frame: c.wl_listener,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn output_frame(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This function is called every time an output is ready to display a frame,
|
||||||
|
// generally at the output's refresh rate (e.g. 60Hz).
|
||||||
|
var output = @fieldParentPtr(Output, "frame", listener);
|
||||||
|
var renderer = output.*.server.*.renderer;
|
||||||
|
|
||||||
|
var now: c.struct_timespec = undefined;
|
||||||
|
_ = c.clock_gettime(c.CLOCK_MONOTONIC, &now);
|
||||||
|
|
||||||
|
// wlr_output_attach_render makes the OpenGL context current.
|
||||||
|
if (!c.wlr_output_attach_render(output.*.wlr_output, null)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The "effective" resolution can change if you rotate your outputs.
|
||||||
|
var width: c_int = undefined;
|
||||||
|
var height: c_int = undefined;
|
||||||
|
c.wlr_output_effective_resolution(output.*.wlr_output, &width, &height);
|
||||||
|
// Begin the renderer (calls glViewport and some other GL sanity checks)
|
||||||
|
c.wlr_renderer_begin(renderer, width, height);
|
||||||
|
|
||||||
|
const color = [_]f32{ 0.3, 0.3, 0.3, 1.0 };
|
||||||
|
c.wlr_renderer_clear(renderer, &color);
|
||||||
|
|
||||||
|
// Each subsequent window we render is rendered on top of the last. Because
|
||||||
|
// our view list is ordered front-to-back, we iterate over it backwards.
|
||||||
|
for (output.*.server.views.span()) |*view| {
|
||||||
|
if (!view.*.mapped) {
|
||||||
|
// An unmapped view should not be rendered.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var rdata = RenderData{
|
||||||
|
.output = output.*.wlr_output,
|
||||||
|
.view = view,
|
||||||
|
.renderer = renderer,
|
||||||
|
.when = &now,
|
||||||
|
};
|
||||||
|
// This calls our render_surface function for each surface among the
|
||||||
|
// xdg_surface's toplevel and popups.
|
||||||
|
c.wlr_xdg_surface_for_each_surface(view.*.xdg_surface, render_surface, &rdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hardware cursors are rendered by the GPU on a separate plane, and can be
|
||||||
|
// moved around without re-rendering what's beneath them - which is more
|
||||||
|
// efficient. However, not all hardware supports hardware cursors. For this
|
||||||
|
// reason, wlroots provides a software fallback, which we ask it to render
|
||||||
|
// here. wlr_cursor handles configuring hardware vs software cursors for you,
|
||||||
|
// and this function is a no-op when hardware cursors are in use.
|
||||||
|
c.wlr_output_render_software_cursors(output.*.wlr_output, null);
|
||||||
|
|
||||||
|
// Conclude rendering and swap the buffers, showing the final frame
|
||||||
|
// on-screen.
|
||||||
|
c.wlr_renderer_end(renderer);
|
||||||
|
// TODO: handle failure
|
||||||
|
_ = c.wlr_output_commit(output.*.wlr_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn server_new_output(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
var server = @fieldParentPtr(Server, "new_output", listener);
|
||||||
|
var wlr_output = @ptrCast(*c.wlr_output, @alignCast(@alignOf(*c.wlr_output), data));
|
||||||
|
|
||||||
|
// Some backends don't have modes. DRM+KMS does, and we need to set a mode
|
||||||
|
// before we can use the output. The mode is a tuple of (width, height,
|
||||||
|
// refresh rate), and each monitor supports only a specific set of modes. We
|
||||||
|
// just pick the monitor's preferred mode, a more sophisticated compositor
|
||||||
|
// would let the user configure it.
|
||||||
|
|
||||||
|
// if not empty
|
||||||
|
if (c.wl_list_empty(&wlr_output.*.modes) == 0) {
|
||||||
|
var mode = c.wlr_output_preferred_mode(wlr_output);
|
||||||
|
c.wlr_output_set_mode(wlr_output, mode);
|
||||||
|
c.wlr_output_enable(wlr_output, true);
|
||||||
|
if (!c.wlr_output_commit(wlr_output)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocates and configures our state for this output
|
||||||
|
server.*.outputs.append(Output{
|
||||||
|
.server = undefined,
|
||||||
|
.wlr_output = undefined,
|
||||||
|
.frame = undefined,
|
||||||
|
}) catch unreachable;
|
||||||
|
var output = &server.*.outputs.span()[server.*.outputs.span().len - 1];
|
||||||
|
output.*.wlr_output = wlr_output;
|
||||||
|
output.*.server = server;
|
||||||
|
|
||||||
|
// Sets up a listener for the frame notify event.
|
||||||
|
output.*.frame.notify = output_frame;
|
||||||
|
c.wl_signal_add(&wlr_output.*.events.frame, &output.*.frame);
|
||||||
|
|
||||||
|
// Adds this to the output layout. The add_auto function arranges outputs
|
||||||
|
// from left-to-right in the order they appear. A more sophisticated
|
||||||
|
// compositor would let the user configure the arrangement of outputs in the
|
||||||
|
// layout.
|
||||||
|
c.wlr_output_layout_add_auto(server.*.output_layout, wlr_output);
|
||||||
|
|
||||||
|
// Creating the global adds a wl_output global to the display, which Wayland
|
||||||
|
// clients can see to find out information about the output (such as
|
||||||
|
// DPI, scale factor, manufacturer, etc).
|
||||||
|
c.wlr_output_create_global(wlr_output);
|
||||||
|
}
|
63
src/render.zig
Normal file
63
src/render.zig
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
const RenderData = struct {
|
||||||
|
output: *c.wlr_output,
|
||||||
|
renderer: *c.wlr_renderer,
|
||||||
|
view: *View,
|
||||||
|
when: *c.struct_timespec,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn render_surface(surface: [*c]c.wlr_surface, sx: c_int, sy: c_int, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This function is called for every surface that needs to be rendered.
|
||||||
|
var rdata = @ptrCast(*RenderData, @alignCast(@alignOf(RenderData), data));
|
||||||
|
var view = rdata.*.view;
|
||||||
|
var output = rdata.*.output;
|
||||||
|
|
||||||
|
// We first obtain a wlr_texture, which is a GPU resource. wlroots
|
||||||
|
// automatically handles negotiating these with the client. The underlying
|
||||||
|
// resource could be an opaque handle passed from the client, or the client
|
||||||
|
// could have sent a pixel buffer which we copied to the GPU, or a few other
|
||||||
|
// means. You don't have to worry about this, wlroots takes care of it.
|
||||||
|
var texture = c.wlr_surface_get_texture(surface);
|
||||||
|
if (texture == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The view has a position in layout coordinates. If you have two displays,
|
||||||
|
// one next to the other, both 1080p, a view on the rightmost display might
|
||||||
|
// have layout coordinates of 2000,100. We need to translate that to
|
||||||
|
// output-local coordinates, or (2000 - 1920).
|
||||||
|
var ox: f64 = 0.0;
|
||||||
|
var oy: f64 = 0.0;
|
||||||
|
c.wlr_output_layout_output_coords(view.*.server.*.output_layout, output, &ox, &oy);
|
||||||
|
ox += @intToFloat(f64, view.*.x + sx);
|
||||||
|
oy += @intToFloat(f64, view.*.y + sy);
|
||||||
|
|
||||||
|
// We also have to apply the scale factor for HiDPI outputs. This is only
|
||||||
|
// part of the puzzle, TinyWL does not fully support HiDPI.
|
||||||
|
var box = c.wlr_box{
|
||||||
|
.x = @floatToInt(c_int, ox * output.*.scale),
|
||||||
|
.y = @floatToInt(c_int, oy * output.*.scale),
|
||||||
|
.width = @floatToInt(c_int, @intToFloat(f32, surface.*.current.width) * output.*.scale),
|
||||||
|
.height = @floatToInt(c_int, @intToFloat(f32, surface.*.current.height) * output.*.scale),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Those familiar with OpenGL are also familiar with the role of matricies
|
||||||
|
// in graphics programming. We need to prepare a matrix to render the view
|
||||||
|
// with. wlr_matrix_project_box is a helper which takes a box with a desired
|
||||||
|
// x, y coordinates, width and height, and an output geometry, then
|
||||||
|
// prepares an orthographic projection and multiplies the necessary
|
||||||
|
// transforms to produce a model-view-projection matrix.
|
||||||
|
//
|
||||||
|
// Naturally you can do this any way you like, for example to make a 3D
|
||||||
|
// compositor.
|
||||||
|
var matrix: [9]f32 = undefined;
|
||||||
|
var transform = c.wlr_output_transform_invert(surface.*.current.transform);
|
||||||
|
c.wlr_matrix_project_box(&matrix, &box, transform, 0.0, &output.*.transform_matrix);
|
||||||
|
|
||||||
|
// This takes our matrix, the texture, and an alpha, and performs the actual
|
||||||
|
// rendering on the GPU.
|
||||||
|
_ = c.wlr_render_texture_with_matrix(rdata.*.renderer, texture, &matrix, 1.0);
|
||||||
|
|
||||||
|
// This lets the client know that we've displayed that frame and it can
|
||||||
|
// prepare another one now if it likes.
|
||||||
|
c.wlr_surface_send_frame_done(surface, rdata.*.when);
|
||||||
|
}
|
71
src/seat.zig
Normal file
71
src/seat.zig
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
// TODO: InputManager and multi-seat support
|
||||||
|
pub const Seat = struct {
|
||||||
|
server: *Server,
|
||||||
|
|
||||||
|
wlr_seat: *c.wlr_seat,
|
||||||
|
listen_new_input: c.wl_listener,
|
||||||
|
|
||||||
|
// Multiple mice are handled by the same Cursor
|
||||||
|
cursor: Cursor,
|
||||||
|
// Mulitple keyboards are handled separately
|
||||||
|
keyboards: std.ArrayList(Keyboard),
|
||||||
|
|
||||||
|
pub fn init(server: *Server, allocator: *std.mem.Allocator) @This() {
|
||||||
|
var seat = @This(){
|
||||||
|
.server = server,
|
||||||
|
// This seems to be the default seat name used by compositors
|
||||||
|
.wlr_seat = c.wlr_seat_create(server.*.wl_display, "seat0"),
|
||||||
|
.cursor = undefined,
|
||||||
|
.keyboards = std.ArrayList(Keyboard).init(allocator),
|
||||||
|
|
||||||
|
.listen_new_input = c.wl_listener{
|
||||||
|
.link = undefined,
|
||||||
|
.notify = handle_new_input,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
seat.cursor = cursor.Cursor.init(server);
|
||||||
|
|
||||||
|
// Set up handler for all new input devices made available. This
|
||||||
|
// includes keyboards, pointers, touch, etc.
|
||||||
|
c.wl_signal_add(&server.*.backend.*.events.new_input, &seat.new_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_keyboard(self: *@This(), device: *c.wlr_input_device) void {
|
||||||
|
self.keyboards.append(Keyboard.init(self, device));
|
||||||
|
c.wlr_seat_set_keyboard(self, device);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_pointer(self: *@This(), device: *c.struct_wlr_input_device) void {
|
||||||
|
// We don't do anything special with pointers. All of our pointer handling
|
||||||
|
// is proxied through wlr_cursor. On another compositor, you might take this
|
||||||
|
// opportunity to do libinput configuration on the device to set
|
||||||
|
// acceleration, etc.
|
||||||
|
c.wlr_cursor_attach_input_device(self.cursor.wlr_cursor, device);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_new_input(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This event is raised by the backend when a new input device becomes available.
|
||||||
|
var seat = @fieldParentPtr(Seat, "listen_new_input", listener);
|
||||||
|
var device = @ptrCast(*c.wlr_input_device, @alignCast(@alignOf(*c.wlr_input_device), data));
|
||||||
|
|
||||||
|
switch (device.*.type) {
|
||||||
|
.WLR_INPUT_DEVICE_KEYBOARD => seat.add_keyboard(device),
|
||||||
|
.WLR_INPUT_DEVICE_POINTER => seat.add_pointer(device),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to let the wlr_seat know what our capabilities are, which is
|
||||||
|
// communiciated to the client. In TinyWL we always have a cursor, even if
|
||||||
|
// there are no pointer devices, so we always include that capability.
|
||||||
|
var caps: u32 = @intCast(u32, c.WL_SEAT_CAPABILITY_POINTER);
|
||||||
|
// if list not empty
|
||||||
|
if (c.wl_list_empty(&server.*.keyboards) == 0) {
|
||||||
|
caps |= @intCast(u32, c.WL_SEAT_CAPABILITY_KEYBOARD);
|
||||||
|
}
|
||||||
|
c.wlr_seat_set_capabilities(server.*.seat, caps);
|
||||||
|
}
|
||||||
|
};
|
71
src/server.zig
Normal file
71
src/server.zig
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
pub const Server = struct {
|
||||||
|
wl_display: *c.wl_display,
|
||||||
|
backend: *c.wlr_backend,
|
||||||
|
renderer: *c.wlr_renderer,
|
||||||
|
xdg_shell: *c.wlr_xdg_shell,
|
||||||
|
new_xdg_surface: c.wl_listener,
|
||||||
|
views: std.ArrayList(View),
|
||||||
|
|
||||||
|
output_layout: *c.wlr_output_layout,
|
||||||
|
outputs: std.ArrayList(Output),
|
||||||
|
new_output: c.wl_listener,
|
||||||
|
|
||||||
|
pub fn init(allocator: *std.mem.Allocator) !@This() {
|
||||||
|
var server: @This() = undefined;
|
||||||
|
|
||||||
|
// The Wayland display is managed by libwayland. It handles accepting
|
||||||
|
// clients from the Unix socket, manging Wayland globals, and so on.
|
||||||
|
server.wl_display = c.wl_display_create() orelse return error.CantCreateWlDisplay;
|
||||||
|
|
||||||
|
// The backend is a wlroots feature which abstracts the underlying input and
|
||||||
|
// output hardware. The autocreate option will choose the most suitable
|
||||||
|
// backend based on the current environment, such as opening an X11 window
|
||||||
|
// if an X11 server is running. The NULL argument here optionally allows you
|
||||||
|
// to pass in a custom renderer if wlr_renderer doesn't meet your needs. The
|
||||||
|
// backend uses the renderer, for example, to fall back to software cursors
|
||||||
|
// if the backend does not support hardware cursors (some older GPUs
|
||||||
|
// don't).
|
||||||
|
server.backend = c.zag_wlr_backend_autocreate(server.wl_display) orelse return error.CantCreateWlrBackend;
|
||||||
|
|
||||||
|
// If we don't provide a renderer, autocreate makes a GLES2 renderer for us.
|
||||||
|
// The renderer is responsible for defining the various pixel formats it
|
||||||
|
// supports for shared memory, this configures that for clients.
|
||||||
|
server.renderer = c.zag_wlr_backend_get_renderer(server.backend) orelse return error.CantGetWlrRenderer;
|
||||||
|
c.wlr_renderer_init_wl_display(server.renderer, server.wl_display) orelse return error.CantInitWlDisplay;
|
||||||
|
|
||||||
|
// This creates some hands-off wlroots interfaces. The compositor is
|
||||||
|
// necessary for clients to allocate surfaces and the data device manager
|
||||||
|
// handles the clipboard. Each of these wlroots interfaces has room for you
|
||||||
|
// to dig your fingers in and play with their behavior if you want.
|
||||||
|
_ = c.wlr_compositor_create(server.wl_display, server.renderer) orelse return error.CantCreateWlrCompositor;
|
||||||
|
_ = c.wlr_data_device_manager_create(server.wl_display) orelse return error.CantCreateWlrDataDeviceManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_keybinding(self: *@This(), sym: c.xkb_keysym_t) bool {
|
||||||
|
// Here we handle compositor keybindings. This is when the compositor is
|
||||||
|
// processing keys, rather than passing them on to the client for its own
|
||||||
|
// processing.
|
||||||
|
//
|
||||||
|
// This function assumes the proper modifier is held down.
|
||||||
|
switch (sym) {
|
||||||
|
c.XKB_KEY_Escape => c.wl_display_terminate(server.*.wl_display),
|
||||||
|
c.XKB_KEY_F1 => {
|
||||||
|
// Cycle to the next view
|
||||||
|
//if (c.wl_list_length(&server.*.views) > 1) {
|
||||||
|
// const current_view = @fieldParentPtr(View, "link", server.*.views.next);
|
||||||
|
// const next_view = @fieldParentPtr(View, "link", current_view.*.link.next);
|
||||||
|
// focus_view(next_view, next_view.*.xdg_surface.*.surface);
|
||||||
|
// // Move the previous view to the end of the list
|
||||||
|
// c.wl_list_remove(¤t_view.*.link);
|
||||||
|
// c.wl_list_insert(server.*.views.prev, ¤t_view.*.link);
|
||||||
|
//}
|
||||||
|
},
|
||||||
|
else => return false,
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
92
src/view.zig
Normal file
92
src/view.zig
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
pub const View = struct {
|
||||||
|
server: *Server,
|
||||||
|
xdg_surface: *c.wlr_xdg_surface,
|
||||||
|
map: c.wl_listener,
|
||||||
|
unmap: c.wl_listener,
|
||||||
|
destroy: c.wl_listener,
|
||||||
|
request_move: c.wl_listener,
|
||||||
|
request_resize: c.wl_listener,
|
||||||
|
mapped: bool,
|
||||||
|
x: c_int,
|
||||||
|
y: c_int,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn focus_view(view: *View, surface: *c.wlr_surface) void {
|
||||||
|
const server = view.server;
|
||||||
|
const seat = server.*.seat;
|
||||||
|
const prev_surface = seat.*.keyboard_state.focused_surface;
|
||||||
|
|
||||||
|
if (prev_surface == surface) {
|
||||||
|
// Don't re-focus an already focused surface.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev_surface != null) {
|
||||||
|
// Deactivate the previously focused surface. This lets the client know
|
||||||
|
// it no longer has focus and the client will repaint accordingly, e.g.
|
||||||
|
// stop displaying a caret.
|
||||||
|
var prev_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(prev_surface);
|
||||||
|
_ = c.wlr_xdg_toplevel_set_activated(prev_xdg_surface, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the index
|
||||||
|
const idx = for (server.*.views.span()) |*v, i| {
|
||||||
|
if (v == view) {
|
||||||
|
break i;
|
||||||
|
}
|
||||||
|
} else unreachable;
|
||||||
|
|
||||||
|
// Move the view to the front
|
||||||
|
server.*.views.append(server.*.views.orderedRemove(idx)) catch unreachable;
|
||||||
|
|
||||||
|
var moved_view = &server.*.views.span()[server.*.views.span().len - 1];
|
||||||
|
|
||||||
|
// Activate the new surface
|
||||||
|
_ = c.wlr_xdg_toplevel_set_activated(moved_view.*.xdg_surface, true);
|
||||||
|
|
||||||
|
// Tell the seat to have the keyboard enter this surface. wlroots will keep
|
||||||
|
// track of this and automatically send key events to the appropriate
|
||||||
|
// clients without additional work on your part.
|
||||||
|
var keyboard = c.wlr_seat_get_keyboard(seat);
|
||||||
|
c.wlr_seat_keyboard_notify_enter(seat, moved_view.*.xdg_surface.*.surface, &keyboard.*.keycodes, keyboard.*.num_keycodes, &keyboard.*.modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_at(view: *View, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) bool {
|
||||||
|
// XDG toplevels may have nested surfaces, such as popup windows for context
|
||||||
|
// menus or tooltips. This function tests if any of those are underneath the
|
||||||
|
// coordinates lx and ly (in output Layout Coordinates). If so, it sets the
|
||||||
|
// surface pointer to that wlr_surface and the sx and sy coordinates to the
|
||||||
|
// coordinates relative to that surface's top-left corner.
|
||||||
|
var view_sx = lx - @intToFloat(f64, view.*.x);
|
||||||
|
var view_sy = ly - @intToFloat(f64, view.*.y);
|
||||||
|
|
||||||
|
// This variable seems to have been unsued in TinyWL
|
||||||
|
// struct wlr_surface_state *state = &view->xdg_surface->surface->current;
|
||||||
|
|
||||||
|
var _sx: f64 = undefined;
|
||||||
|
var _sy: f64 = undefined;
|
||||||
|
var _surface = c.wlr_xdg_surface_surface_at(view.*.xdg_surface, view_sx, view_sy, &_sx, &_sy);
|
||||||
|
|
||||||
|
if (_surface) |surface_at| {
|
||||||
|
sx.* = _sx;
|
||||||
|
sy.* = _sy;
|
||||||
|
surface.* = surface_at;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desktop_view_at(server: *Server, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) ?*View {
|
||||||
|
// This iterates over all of our surfaces and attempts to find one under the
|
||||||
|
// cursor. This relies on server.*.views being ordered from top-to-bottom.
|
||||||
|
for (server.*.views.span()) |*view| {
|
||||||
|
if (view_at(view, lx, ly, surface, sx, sy)) {
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
68
src/xdg_shell.zig
Normal file
68
src/xdg_shell.zig
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
fn xdg_surface_map(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// Called when the surface is mapped, or ready to display on-screen.
|
||||||
|
var view = @fieldParentPtr(View, "map", listener);
|
||||||
|
view.*.mapped = true;
|
||||||
|
focus_view(view, view.*.xdg_surface.*.surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xdg_surface_unmap(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
var view = @fieldParentPtr(View, "unmap", listener);
|
||||||
|
view.*.mapped = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xdg_surface_destroy(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
var view = @fieldParentPtr(View, "destroy", listener);
|
||||||
|
var server = view.*.server;
|
||||||
|
const idx = for (server.*.views.span()) |*v, i| {
|
||||||
|
if (v == view) {
|
||||||
|
break i;
|
||||||
|
}
|
||||||
|
} else return;
|
||||||
|
_ = server.*.views.orderedRemove(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xdg_toplevel_request_move(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// ignore for now
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xdg_toplevel_request_resize(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// ignore for now
|
||||||
|
}
|
||||||
|
|
||||||
|
fn server_new_xdg_surface(listener: [*c]c.wl_listener, data: ?*c_void) callconv(.C) void {
|
||||||
|
// This event is raised when wlr_xdg_shell receives a new xdg surface from a
|
||||||
|
// client, either a toplevel (application window) or popup.
|
||||||
|
var server = @fieldParentPtr(Server, "new_xdg_surface", listener);
|
||||||
|
var xdg_surface = @ptrCast(*c.wlr_xdg_surface, @alignCast(@alignOf(*c.wlr_xdg_surface), data));
|
||||||
|
|
||||||
|
if (xdg_surface.*.role != c.enum_wlr_xdg_surface_role.WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate a View for this surface
|
||||||
|
server.*.views.append(undefined) catch unreachable;
|
||||||
|
var view = &server.*.views.span()[server.*.views.span().len - 1];
|
||||||
|
|
||||||
|
view.*.server = server;
|
||||||
|
view.*.xdg_surface = xdg_surface;
|
||||||
|
|
||||||
|
// Listen to the various events it can emit
|
||||||
|
view.*.map.notify = xdg_surface_map;
|
||||||
|
c.wl_signal_add(&xdg_surface.*.events.map, &view.*.map);
|
||||||
|
|
||||||
|
view.*.unmap.notify = xdg_surface_unmap;
|
||||||
|
c.wl_signal_add(&xdg_surface.*.events.unmap, &view.*.unmap);
|
||||||
|
|
||||||
|
view.*.destroy.notify = xdg_surface_destroy;
|
||||||
|
c.wl_signal_add(&xdg_surface.*.events.destroy, &view.*.destroy);
|
||||||
|
|
||||||
|
var toplevel = xdg_surface.*.unnamed_160.toplevel;
|
||||||
|
view.*.request_move.notify = xdg_toplevel_request_move;
|
||||||
|
c.wl_signal_add(&toplevel.*.events.request_move, &view.*.request_move);
|
||||||
|
|
||||||
|
view.*.request_resize.notify = xdg_toplevel_request_resize;
|
||||||
|
c.wl_signal_add(&toplevel.*.events.request_resize, &view.*.request_resize);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user