aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Noll <nbnoll@eml.cc>2021-09-28 13:40:30 -0700
committerNicholas Noll <nbnoll@eml.cc>2021-09-28 13:40:30 -0700
commitb58f62d4ef7f6e2442bdf8170f8652ba1e08bd12 (patch)
tree41946b147e7943a92b0208d9684df1b2e5a05e2b
parentbc53100f1ef063e09d77e8670e1796bc67017411 (diff)
Feat: added skeleton of wayland window manager
-rw-r--r--sys/cmd/wm/main.c755
-rw-r--r--sys/cmd/wm/rules.mk26
-rw-r--r--sys/cmd/wm/wm.h26
3 files changed, 807 insertions, 0 deletions
diff --git a/sys/cmd/wm/main.c b/sys/cmd/wm/main.c
new file mode 100644
index 0000000..6f0e5ab
--- /dev/null
+++ b/sys/cmd/wm/main.c
@@ -0,0 +1,755 @@
+#include "wm.h"
+
+// -----------------------------------------------------------------------
+// types
+
+enum
+{
+ CursorPassthrough,
+ CursorMove,
+ CursorResize,
+};
+
+typedef struct Keyboard Keyboard;
+typedef struct View View;
+typedef struct Monitor Monitor;
+typedef struct Server Server;
+typedef struct Payload Payload;
+
+struct Keyboard
+{
+ struct wl_list link;
+ struct wlr_input_device *device;
+ struct {
+ struct wl_listener press;
+ struct wl_listener modify;
+ } event;
+};
+
+struct View
+{
+ struct wl_list link;
+ struct wlr_xdg_surface *xdg;
+ struct {
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener destroy;
+ struct wl_listener request_move;
+ struct wl_listener request_resize;
+ } event;
+ int x, y, mapped;
+};
+
+struct Monitor
+{
+ struct wl_list link;
+ struct wlr_output *output;
+ struct {
+ struct wl_listener render;
+ } event;
+};
+
+struct Payload
+{
+ View *view;
+ struct wlr_output *output;
+ struct wlr_renderer *renderer;
+ struct timespec *when;
+};
+
+struct Server
+{
+ struct wl_display *display;
+ struct wlr_backend *backend;
+ struct wlr_renderer *renderer;
+
+ struct {
+ struct wlr_xdg_shell *xdg;
+ struct wl_list list;
+ } shell;
+
+ struct {
+ View *view;
+ double x, y;
+ struct wlr_box box;
+ } grab;
+ uint32 resize;
+
+ struct {
+ struct wlr_output_layout *layout;
+ struct wl_list list;
+ } output;
+
+ struct {
+ struct wlr_cursor *dot;
+ struct wlr_xcursor_manager *manager;
+ int mode;
+ } cursor;
+
+ struct {
+ struct wlr_seat *seat;
+ struct wl_list keyboards;
+ } input;
+
+ struct {
+ struct wl_listener new_output;
+ struct wl_listener new_input;
+ struct wl_listener cursor_move;
+ struct wl_listener cursor_move_abs;
+ struct wl_listener cursor_button;
+ struct wl_listener cursor_axis;
+ struct wl_listener cursor_frame;
+ struct wl_listener request_cursor;
+ struct wl_listener request_set_selection;
+ struct wl_listener new_surface;
+ } event;
+};
+static struct Server server;
+
+static
+void
+focus(View *view, struct wlr_surface *new)
+{
+ struct wlr_seat *seat;
+ struct wlr_surface *old;
+ struct wlr_xdg_surface *xdg;
+ struct wlr_keyboard *keyboard;
+
+ if(!view)
+ return;
+
+ seat = server.input.seat;
+ old = seat->keyboard_state.focused_surface;
+ if(old == new)
+ return;
+
+ if(old) {
+ xdg = wlr_xdg_surface_from_wlr_surface(seat->keyboard_state.focused_surface);
+ wlr_xdg_toplevel_set_activated(xdg, false);
+ }
+
+ keyboard = wlr_seat_get_keyboard(seat);
+
+ wl_list_remove(&view->link);
+ wl_list_insert(&server.shell.list, &view->link);
+
+ wlr_xdg_toplevel_set_activated(view->xdg, true);
+ wlr_seat_keyboard_notify_enter(seat, view->xdg->surface,
+ keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers);
+}
+
+static
+int
+viewhas(View *view, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy)
+{
+ double x, y, vsx = lx - view->x, vsy = ly - view->y;
+ struct wlr_surface *find = nil;
+
+ find = wlr_xdg_surface_surface_at(view->xdg, vsx, vsy, &x, &y);
+ if(find) {
+ *sx = x;
+ *sy = y;
+ *surface = find;
+ return true;
+ }
+
+ return false;
+}
+
+static
+View*
+viewat(double lx, double ly, struct wlr_surface **surface, double *sx, double *sy)
+{
+ View *it;
+ wl_list_for_each(it, &server.shell.list, link) {
+ if(viewhas(it, lx, ly, surface, sx, sy))
+ return it;
+ }
+
+ return nil;
+}
+
+// -----------------------------------------------------------------------
+// callbacks
+
+static
+void
+keymodifier(struct wl_listener *l, void *data)
+{
+ Keyboard *keyboard = wl_container_of(l, keyboard, event.modify);
+
+ wlr_seat_set_keyboard(server.input.seat, keyboard->device);
+ wlr_seat_keyboard_notify_modifiers(server.input.seat, &keyboard->device->keyboard->modifiers);
+}
+
+static
+int
+keybinding(xkb_keysym_t sym)
+{
+ View *current, *next;
+
+ switch(sym) {
+ case XKB_KEY_Escape:
+ wl_display_terminate(server.display);
+ break;
+ case XKB_KEY_F1:
+ /* cycle to the next view */
+ if(wl_list_length(&server.shell.list) < 2) break;
+
+ current = wl_container_of(server.shell.list.next, current, link);
+ next = wl_container_of(current->link.next, next, link);
+
+ focus(next, next->xdg->surface);
+
+ /* move previous view to the end of the list */
+ wl_list_remove(&current->link);
+ wl_list_insert(server.shell.list.prev, &current->link);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+static
+void
+keypress(struct wl_listener *l, void *data)
+{
+ int i,h,n;
+ uint32 keycode, modifier;
+ const xkb_keysym_t *syms;
+ struct Keyboard *keyboard = wl_container_of(l, keyboard, event.press);
+ struct wlr_event_keyboard_key *event = data;
+ struct wlr_seat *seat = server.input.seat;
+
+ keycode = event->keycode + 8;
+
+ h = 0;
+ n = xkb_state_key_get_syms(keyboard->device->keyboard->xkb_state, keycode, &syms);
+
+ modifier = wlr_keyboard_get_modifiers(keyboard->device->keyboard);
+ if((modifier & WLR_MODIFIER_ALT) && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+ for(i=0; i<n; i++)
+ h=keybinding(syms[i]);
+ }
+
+ if(!h) {
+ wlr_seat_set_keyboard(seat, keyboard->device);
+ wlr_seat_keyboard_notify_key(seat, event->time_msec,
+ event->keycode, event->state);
+ }
+}
+
+static
+void
+new_keyboard(struct wlr_input_device *device)
+{
+ Keyboard *keyboard;
+ struct xkb_context *context;
+ struct xkb_keymap *keymap;
+
+ context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ keymap = xkb_keymap_new_from_names(context, nil, XKB_KEYMAP_COMPILE_NO_FLAGS);
+
+ wlr_keyboard_set_keymap(device->keyboard, keymap);
+ wlr_keyboard_set_keymap(device->keyboard, keymap);
+
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(context);
+
+ wlr_keyboard_set_repeat_info(device->keyboard, 25, 600);
+
+ keyboard = calloc(1, sizeof(*keyboard));
+ keyboard->device = device;
+
+ keyboard->event.modify.notify = keymodifier;
+ wl_signal_add(&device->keyboard->events.modifiers, &keyboard->event.modify);
+ keyboard->event.press.notify = keypress;
+ wl_signal_add(&device->keyboard->events.key, &keyboard->event.press);
+
+ wlr_seat_set_keyboard(server.input.seat, device);
+ wl_list_insert(&server.input.keyboards, &keyboard->link);
+}
+
+static
+void
+new_pointer(struct wlr_input_device *device)
+{
+ wlr_cursor_attach_input_device(server.cursor.dot, device);
+}
+
+static
+void
+new_input(struct wl_listener *l, void *data)
+{
+ uint32 capability;
+ struct wlr_input_device *device = data;
+
+ switch(device->type) {
+ case WLR_INPUT_DEVICE_KEYBOARD:
+ new_keyboard(device);
+ break;
+ case WLR_INPUT_DEVICE_POINTER:
+ new_pointer(device);
+ /* fallthrough */
+ default:
+ break;
+ }
+
+ capability = WL_SEAT_CAPABILITY_POINTER;
+ if(!wl_list_empty(&server.input.keyboards))
+ capability |= WL_SEAT_CAPABILITY_KEYBOARD;
+ wlr_seat_set_capabilities(server.input.seat, capability);
+}
+
+static
+void
+process_cursor_move(uint32 time) {
+ server.grab.x = server.cursor.dot->x - server.grab.x;
+ server.grab.y = server.cursor.dot->y - server.grab.y;
+}
+
+static
+void
+process_cursor_resize(uint32 time)
+{
+ struct wlr_box box;
+ View *view = server.grab.view;
+ double bx = server.cursor.dot->x - server.grab.x;
+ double by = server.cursor.dot->y - server.grab.y;
+ int new_left = server.grab.box.x;
+ int new_right = server.grab.box.x + server.grab.box.width;
+ int new_top = server.grab.box.y;
+ int new_bottom = server.grab.box.y + server.grab.box.height;
+ int new_width, new_height;
+
+ if(server.resize & WLR_EDGE_TOP) {
+ new_top = by;
+ if (new_top >= new_bottom)
+ new_top = new_bottom - 1;
+ }else if (server.resize & WLR_EDGE_BOTTOM) {
+ new_bottom = by;
+ if (new_bottom <= new_top)
+ new_bottom = new_top + 1;
+ }
+
+ if(server.resize & WLR_EDGE_LEFT) {
+ new_left = bx;
+ if (new_left >= new_right)
+ new_left = new_right - 1;
+ } else if(server.resize & WLR_EDGE_RIGHT) {
+ new_right = bx;
+ if (new_right <= new_left)
+ new_right = new_left + 1;
+ }
+
+ wlr_xdg_surface_get_geometry(view->xdg, &box);
+ view->x = new_left - box.x;
+ view->y = new_top - box.y;
+
+ new_width = new_right - new_left;
+ new_height = new_bottom - new_top;
+ wlr_xdg_toplevel_set_size(view->xdg, new_width, new_height);
+}
+
+static
+void
+process_cursor_motion(uint32 time)
+{
+ double sx, sy;
+ View *view;
+ struct wlr_seat *seat;
+ struct wlr_surface *surface;
+
+ if(server.cursor.mode == CursorMove)
+ return process_cursor_move(time);
+ else if(server.cursor.mode == CursorResize)
+ return process_cursor_resize(time);
+
+ /* Otherwise, find the view under the pointer and send the event along. */
+ seat = server.input.seat;
+ surface = nil;
+ view = viewat(server.cursor.dot->x, server.cursor.dot->y, &surface, &sx, &sy);
+ if(!view)
+ wlr_xcursor_manager_set_cursor_image(server.cursor.manager, "left_ptr", server.cursor.dot);
+
+ if(surface) {
+ wlr_seat_pointer_notify_enter(seat, surface, sx, sy);
+ wlr_seat_pointer_notify_motion(seat, time, sx, sy);
+ } else {
+ wlr_seat_pointer_clear_focus(seat);
+ }
+}
+
+static
+void
+cursor_move(struct wl_listener *l, void *data)
+{
+ struct wlr_event_pointer_motion *event = data;
+ wlr_cursor_move(server.cursor.dot, event->device, event->delta_x, event->delta_y);
+ process_cursor_motion(event->time_msec);
+}
+
+static
+void
+cursor_move_abs(struct wl_listener *l, void *data)
+{
+ struct wlr_event_pointer_motion_absolute *event = data;
+ wlr_cursor_warp_absolute(server.cursor.dot, event->device, event->x, event->y);
+ process_cursor_motion(event->time_msec);
+}
+
+static
+void
+cursor_button(struct wl_listener *l, void *data)
+{
+ View *view;
+ double sx, sy;
+ struct wlr_surface *surface;
+ struct wlr_event_pointer_button *event = data;
+
+ wlr_seat_pointer_notify_button(server.input.seat,
+ event->time_msec, event->button, event->state);
+
+ view = viewat(server.cursor.dot->x, server.cursor.dot->y, &surface, &sx, &sy);
+ if (event->state == WLR_BUTTON_RELEASED)
+ server.cursor.mode = CursorPassthrough;
+ else
+ focus(view, surface);
+}
+
+static
+void
+cursor_axis(struct wl_listener *l, void *data)
+{
+ struct wlr_event_pointer_axis *event = data;
+ /* Notify the client with pointer focus of the axis event. */
+ wlr_seat_pointer_notify_axis(server.input.seat,
+ event->time_msec, event->orientation, event->delta,
+ event->delta_discrete, event->source);
+}
+
+static
+void
+cursor_frame(struct wl_listener *l, void *data)
+{
+ wlr_seat_pointer_notify_frame(server.input.seat);
+}
+
+static
+void
+request_cursor(struct wl_listener *l, void *data)
+{
+ struct wlr_seat_pointer_request_set_cursor_event *event = data;
+ struct wlr_seat_client *focused_client = server.input.seat->pointer_state.focused_client;
+ if(focused_client == event->seat_client)
+ wlr_cursor_set_surface(server.cursor.dot, event->surface, event->hotspot_x, event->hotspot_y);
+}
+
+static
+void
+request_set_selection(struct wl_listener *l, void *data)
+{
+ struct wlr_seat_request_set_selection_event *event = data;
+ wlr_seat_set_selection(server.input.seat, event->source, event->serial);
+}
+
+static
+void
+surface_map(struct wl_listener *l, void *data)
+{
+ View *view = wl_container_of(l, view, event.map);
+ view->mapped = true;
+
+ focus(view, view->xdg->surface);
+}
+
+static
+void
+surface_unmap(struct wl_listener *l, void *data)
+{
+ View *view = wl_container_of(l, view, event.unmap);
+ view->mapped = false;
+}
+
+static
+void
+surface_destroy(struct wl_listener *l, void *data)
+{
+ View *view = wl_container_of(l, view, event.destroy);
+ wl_list_remove(&view->link);
+ free(view);
+}
+
+static
+void
+interactive(View *view, int mode, uint32 edges) {
+ double bx, by;
+ struct wlr_box box;
+ struct wlr_surface *focused = server.input.seat->pointer_state.focused_surface;
+
+ if(view->xdg->surface != focused)
+ return;
+
+ server.grab.view = view;
+ server.cursor.mode = mode;
+
+ if(mode == CursorMove) {
+ server.grab.x = server.cursor.dot->x - view->x;
+ server.grab.y = server.cursor.dot->y - view->y;
+ } else {
+ wlr_xdg_surface_get_geometry(view->xdg, &box);
+
+ bx = (view->x + box.x) + ((edges & WLR_EDGE_RIGHT) ? box.width : 0);
+ by = (view->y + box.y) + ((edges & WLR_EDGE_BOTTOM) ? box.height : 0);
+
+ server.grab.x = server.cursor.dot->x - bx;
+ server.grab.y = server.cursor.dot->y - by;
+
+ server.grab.box = box;
+ server.grab.box.x += view->x;
+ server.grab.box.y += view->y;
+
+ server.resize = edges;
+ }
+}
+
+static
+void
+toplevel_request_move(struct wl_listener *l, void *data)
+{
+ View *view = wl_container_of(l, view, event.request_move);
+ interactive(view, CursorMove, 0);
+}
+
+static
+void
+toplevel_request_resize(struct wl_listener *l, void *data)
+{
+ struct wlr_xdg_toplevel_resize_event *event = data;
+ View *view = wl_container_of(l, view, event.request_resize);
+ interactive(view, CursorResize, event->edges);
+}
+
+static
+void
+new_surface(struct wl_listener *l, void *data)
+{
+ View *view;
+ struct wlr_xdg_toplevel *toplevel;
+ struct wlr_xdg_surface *xdg = data;
+
+ if(xdg->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL)
+ return;
+
+ view = calloc(1, sizeof(*view));
+ view->xdg = xdg;
+
+ view->event.map.notify = surface_map;
+ wl_signal_add(&xdg->events.map, &view->event.map);
+ view->event.unmap.notify = surface_unmap;
+ wl_signal_add(&xdg->events.unmap, &view->event.unmap);
+ view->event.destroy.notify = surface_destroy;
+ wl_signal_add(&xdg->events.destroy, &view->event.destroy);
+
+ /* cotd */
+ toplevel = xdg->toplevel;
+ view->event.request_move.notify = toplevel_request_move;
+ wl_signal_add(&toplevel->events.request_move, &view->event.request_move);
+ view->event.request_resize.notify = toplevel_request_resize;
+ wl_signal_add(&toplevel->events.request_resize, &view->event.request_resize);
+
+ /* Add it to the list of views. */
+ wl_list_insert(&server.shell.list, &view->link);
+}
+
+static
+void
+draw(struct wlr_surface *surface, int sx, int sy, void *data)
+{
+ float matrix[9];
+ double ox, oy;
+ View *view;
+ Payload *payload;
+ struct wlr_box box;
+ struct wlr_output *output;
+ struct wlr_texture *texture;
+ enum wl_output_transform transform;
+
+ payload = data;
+ view = payload->view;
+ output = payload->output;
+
+ texture = wlr_surface_get_texture(surface);
+ if(!texture)
+ return;
+
+ ox = 0, oy = 0;
+ wlr_output_layout_output_coords(server.output.layout, output, &ox, &oy);
+ ox += view->x + sx, oy += view->y + sy;
+
+ box = (struct wlr_box){
+ .x = ox * output->scale,
+ .y = oy * output->scale,
+ .width = surface->current.width * output->scale,
+ .height = surface->current.height * output->scale,
+ };
+
+ transform = wlr_output_transform_invert(surface->current.transform);
+ wlr_matrix_project_box(matrix, &box, transform, 0, output->transform_matrix);
+
+ wlr_render_texture_with_matrix(payload->renderer, texture, matrix, 1);
+ wlr_surface_send_frame_done(surface, payload->when);
+}
+
+static
+void
+render(struct wl_listener *l, void *data)
+{
+ int width, height;
+ struct timespec now;
+ View *view;
+ Monitor *monitor;
+ struct wlr_renderer *renderer;
+ float color[4] = {0.3, 0.3, 0.3, 1.0};
+
+ monitor = wl_container_of(l, monitor, event.render);
+ renderer = server.renderer;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ if(!wlr_output_attach_render(monitor->output, nil))
+ return;
+
+ wlr_output_effective_resolution(monitor->output, &width, &height);
+
+ /* start of rendering kernel */
+ wlr_renderer_begin(renderer, width, height);
+ wlr_renderer_clear(renderer, color);
+
+ wl_list_for_each_reverse(view, &server.shell.list, link) {
+ if(!view->mapped)
+ continue;
+
+ Payload payload = {
+ .output = monitor->output,
+ .view = view,
+ .renderer = renderer,
+ .when = &now,
+ };
+
+ wlr_xdg_surface_for_each_surface(view->xdg, draw, &payload);
+ }
+
+ wlr_output_render_software_cursors(monitor->output, nil);
+
+ wlr_renderer_end(renderer);
+ wlr_output_commit(monitor->output);
+}
+
+static
+void
+new_output(struct wl_listener *l, void *data)
+{
+ struct wlr_output_mode *mode;
+ struct Monitor *monitor;
+ struct wlr_output *output = data;
+
+ if (!wl_list_empty(&output->modes)) {
+ mode = wlr_output_preferred_mode(output);
+ wlr_output_set_mode(output, mode);
+ wlr_output_enable(output, true);
+ if (!wlr_output_commit(output)) {
+ return;
+ }
+ }
+
+ monitor = calloc(1, sizeof(*monitor));
+ monitor->output = output;
+
+ monitor->event.render.notify = render;
+ wl_signal_add(&output->events.frame, &monitor->event.render);
+ wl_list_insert(&server.output.list, &monitor->link);
+
+ wlr_output_layout_add_auto(server.output.layout, output);
+}
+
+// -----------------------------------------------------------------------
+// main point of entry
+
+int
+main(int argc, char *argv[])
+{
+ char *socket;
+
+ wlr_log_init(WLR_DEBUG, nil);
+
+ server.display = wl_display_create();
+ server.backend = wlr_backend_autocreate(server.display);
+ server.renderer = wlr_backend_get_renderer(server.backend);
+
+ wlr_renderer_init_wl_display(server.renderer, server.display);
+
+ wlr_compositor_create(server.display, server.renderer);
+ wlr_data_device_manager_create(server.display);
+
+ server.output.layout = wlr_output_layout_create();
+
+ wl_list_init(&server.output.list);
+ server.event.new_output.notify = new_output;
+ wl_signal_add(&server.backend->events.new_output, &server.event.new_output);
+
+ wl_list_init(&server.shell.list);
+ server.shell.xdg = wlr_xdg_shell_create(server.display);
+ server.event.new_surface.notify = new_surface;
+ wl_signal_add(&server.shell.xdg->events.new_surface, &server.event.new_surface);
+
+ server.cursor.dot = wlr_cursor_create();
+ wlr_cursor_attach_output_layout(server.cursor.dot, server.output.layout);
+
+ server.cursor.manager = wlr_xcursor_manager_create(nil, 24);
+ wlr_xcursor_manager_load(server.cursor.manager, 1);
+
+ server.event.cursor_move.notify = cursor_move;
+ wl_signal_add(&server.cursor.dot->events.motion, &server.event.cursor_move);
+ server.event.cursor_move_abs.notify = cursor_move_abs;
+ wl_signal_add(&server.cursor.dot->events.motion_absolute, &server.event.cursor_move_abs);
+ server.event.cursor_button.notify = cursor_button;
+ wl_signal_add(&server.cursor.dot->events.button, &server.event.cursor_button);
+ server.event.cursor_axis.notify = cursor_axis;
+ wl_signal_add(&server.cursor.dot->events.axis, &server.event.cursor_axis);
+ server.event.cursor_frame.notify = cursor_frame;
+ wl_signal_add(&server.cursor.dot->events.frame, &server.event.cursor_frame);
+
+ wl_list_init(&server.input.keyboards);
+ server.event.new_input.notify = new_input;
+ wl_signal_add(&server.backend->events.new_input, &server.event.new_input);
+
+ server.input.seat = wlr_seat_create(server.display, "seat0");
+
+ server.event.request_cursor.notify = request_cursor;
+ wl_signal_add(&server.input.seat->events.request_set_cursor, &server.event.request_cursor);
+ server.event.request_set_selection.notify = request_set_selection;
+ wl_signal_add(&server.input.seat->events.request_set_selection, &server.event.request_set_selection);
+
+ if(!(socket=(char*)wl_display_add_socket_auto(server.display))) {
+ wlr_backend_destroy(server.backend);
+ return 1;
+ }
+
+ if(!(wlr_backend_start(server.backend))) {
+ wlr_backend_destroy(server.backend);
+ wl_display_destroy(server.display);
+ return 1;
+ }
+
+ setenv("WAYLAND_DISPLAY", socket, true);
+ wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", socket);
+
+ /* start loop */
+ wl_display_run(server.display);
+ /* end loop */
+
+ wl_display_destroy_clients(server.display);
+ wl_display_destroy(server.display);
+ return 0;
+}
diff --git a/sys/cmd/wm/rules.mk b/sys/cmd/wm/rules.mk
new file mode 100644
index 0000000..92e108d
--- /dev/null
+++ b/sys/cmd/wm/rules.mk
@@ -0,0 +1,26 @@
+include share/push.mk
+# Iterate through subdirectory tree
+
+# Local sources
+SRCS_$(d) := \
+ $(d)/main.c
+BINS_$(d) := $(d)/wm
+
+include share/paths.mk
+
+# Local rules
+include share/dynamic.mk
+$(BINS_$(d)): TCFLAGS = \
+ `$(PKG) --cflags wlroots ` \
+ `$(PKG) --cflags wayland-server` \
+ `$(PKG) --cflags xkbcommon`
+
+$(BINS_$(d)): TCLIBS = \
+ `$(PKG) --libs wlroots` \
+ `$(PKG) --libs wayland-server` \
+ `$(PKG) --libs xkbcommon`
+
+$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/sys/libn/libn.a
+ $(COMPLINK)
+
+include share/pop.mk
diff --git a/sys/cmd/wm/wm.h b/sys/cmd/wm/wm.h
new file mode 100644
index 0000000..356c408
--- /dev/null
+++ b/sys/cmd/wm/wm.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <u.h>
+#include <libn.h>
+#include <wayland-server-core.h>
+
+#define WLR_USE_UNSTABLE
+#include <wlr/backend.h>
+#include <wlr/render/wlr_renderer.h>
+
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_pointer.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/types/wlr_xdg_shell.h>
+
+#include <wlr/util/log.h>
+
+#include <xkbcommon/xkbcommon.h>