diff --git a/bin/9l b/bin/9l index d7c9e2ddcb..a6a6cbbf23 100755 --- a/bin/9l +++ b/bin/9l @@ -259,6 +259,11 @@ then fi libsl="$libsl -lX11" fi + + if [ "x$needwayland" = xtrue -a "x$WSYSTYPE" != xnowsys ] + then + libsl="$(echo $libsl | sed 's/wayland/wayland-client -lwayland-cursor -lrt/')" + fi fi if $doautoframework then diff --git a/src/cmd/devdraw/mkfile b/src/cmd/devdraw/mkfile index 6bcf18909e..6b39e006d0 100644 --- a/src/cmd/devdraw/mkfile +++ b/src/cmd/devdraw/mkfile @@ -25,6 +25,8 @@ HFILES=\ <$PLAN9/src/mkone + $target + +xdg-shell-client-protocol.h: $way_proto/stable/xdg-shell/xdg-shell.xml + wayland-scanner client-header < $prereq > $target + +xdg-decoration-protocol.c: $way_proto/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml + wayland-scanner private-code < $prereq > $target + +xdg-decoration-client-protocol.h: $way_proto/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml + wayland-scanner client-header < $prereq > $target + +pointer-constraints-client-protocol.h: $way_proto/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml + wayland-scanner client-header < $prereq > $target + +pointer-constraints-protocol.c: $way_proto/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml + wayland-scanner private-code < $prereq > $target diff --git a/src/cmd/devdraw/mkwsysrules.sh b/src/cmd/devdraw/mkwsysrules.sh index 56dff55a3e..3a496f9ddd 100644 --- a/src/cmd/devdraw/mkwsysrules.sh +++ b/src/cmd/devdraw/mkwsysrules.sh @@ -29,6 +29,8 @@ if [ "x$WSYSTYPE" = "x" ]; then exit 1 fi WSYSTYPE=mac + elif command -v wayland-scanner >/dev/null 2>&1; then + WSYSTYPE=wayland elif [ -d "$X11" ]; then WSYSTYPE=x11 else @@ -54,6 +56,13 @@ if [ $WSYSTYPE = x11 ]; then XO=`ls x11-*.c 2>/dev/null | sed 's/\.c$/.o/'` echo 'WSYSOFILES=$WSYSOFILES '$XO echo 'WSYSHFILES=x11-inc.h x11-keysym2ucs.h x11-memdraw.h' +elif [ $WSYSTYPE = wayland ]; then + protos='pointer-constraints xdg-decoration xdg-shell' + PROTOO=$(for p in $protos; do printf '%s-protocol.o ' $p; done; printf '\n'; ) + PROTOH=$(for p in $protos; do printf '%s-client-protocol.h ' $p; done; printf '\n'; ) + WAYO=`ls wayland-*.c 2>/dev/null | sed 's/\.c$/.o/' | paste -s -d ' '` + echo "WSYSOFILES=\$WSYSOFILES $WAYO $PROTOO" + echo "WSYSHFILES=wayland-inc.h $PROTOH" elif [ $WSYSTYPE = mac ]; then echo 'WSYSOFILES=$WSYSOFILES mac-draw.o mac-screen.o' echo 'WSYSHFILES=' diff --git a/src/cmd/devdraw/wayland-draw.c b/src/cmd/devdraw/wayland-draw.c new file mode 100644 index 0000000000..c46c365d0a --- /dev/null +++ b/src/cmd/devdraw/wayland-draw.c @@ -0,0 +1,157 @@ +#include +#include "wayland-inc.h" +#include +#include +#include + +#include "wayland-shm.h" +#include + +AUTOLIB(wayland); + +Memimage * +allocmemimage(Rectangle r, u32int chan) { + int d; + Memimage *i; + if ((d = chantodepth(chan)) == 0) { + werrstr("bad channel descriptor %.8lux", chan); + return nil; + } + // fprint(2, "allocmemimage: start\n"); + // Too lazy to write a real allocator, just create shm mappings and rely on + // wayland's book-keeping to tear them down. + int l = wordsperline(r, d); + int nw = l * Dy(r); + uint32 sz = sizeof(shmTab) + ((1 + nw) * sizeof(ulong)); + Memdata *md = malloc(sizeof(Memdata)); + if (md == nil) { + // fprint(2, "allocmemimage: malloc fail\n"); + return nil; + } + // fprint(2, "allocmemimage: shm\n"); + int fd = allocate_shm_file(sz); + if (fd == -1) { + // fprint(2, "allocmemimage: shm fail\n"); + free(md); + return nil; + } + // fprint(2, "allocmemimage: mmap\n"); + uchar *data = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + // fprint(2, "allocmemimage: map fail\n"); + free(md); + close(fd); + return nil; + } + + md->ref = 1; + md->base = (u32int *)data; + shmTab *t = (shmTab *)data; + t->md = md; + t->fd = fd; + t->sz = sz; + // Should only be 24 bytes used, max. + md->bdata = (uchar *)(md->base + sizeof(shmTab)); + md->allocd = 1; + // fprint(2, "allocmemimage: allocmemimaged\n"); + i = allocmemimaged(r, chan, md, t); + if (i == nil) { + // fprint(2, "allocmemimage: allocmemimaged fail\n"); + munmap(data, sz); + free(md); + close(fd); + return nil; + } + md->imref = i; + return i; +} + +/* +static void +buffer_release(void *opaque, struct wl_buffer *buf) { + wl_buffer_destroy(buf); +} + +static struct wl_buffer_listener kill_buffer = { + .release = buffer_release, +}; + +void +mk_buffer(window *w, Memimage *i) { + if (i == nil) + return; + if (i->X != nil) + return; + if (i->data->ref == 0 || !i->data->allocd) // zombie memimage ??? + return; + shmTab *tab = (shmTab *)i->data->base; + Globals *g = w->global; + struct wl_shm_pool *pool = wl_shm_create_pool(g->wl_shm, tab->fd, tab->sz); + if (pool == nil) { + fprint(2, "mk_buffer: pool fail\n"); + return; + } + struct wl_buffer *buf = wl_shm_pool_create_buffer( + pool, 64, Dx(i->r), Dy(i->r), Dx(i->r) * 4, WL_SHM_FORMAT_XRGB8888); + if (buf == nil) { + fprint(2, "mk_buffer: buffer fail\n"); + return; + } + wl_shm_pool_destroy(pool); + fprint(2, "mkbuffer: %p\n", i); + i->X = buf; + wl_buffer_add_listener(buf, &kill_buffer, NULL); +} +*/ + +void +freememimage(Memimage *i) { + if (i == nil) + return; + if (i->data->ref-- == 1 && i->data->allocd) { + if (i->data->base) { + shmTab *tab = (shmTab *)i->data->base; + if (tab->md != i->data) { + fprint(2, "maritan memdata\n"); + return _freememimage(i); + }; + close(tab->fd); + munmap(i->data->base, tab->sz); + } + free(i->data); + } + free(i); +} + +/* +void +memimagedraw(Memimage *dst, Rectangle r, Memimage *src, Point sp, + Memimage *mask, Point mp, int op) { + fprint(2, "memimagedraw\n"); +} + +void +memfillcolor(Memimage *m, u32int val) { + _memfillcolor(m, val); +} + +u32int +pixelbits(Memimage *m, Point p) { + return _pixelbits(m, p); +} +*/ + +int +loadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata) { + return _loadmemimage(i, r, data, ndata); +} + +int +cloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata) { + return _cloadmemimage(i, r, data, ndata); +} + +int +unloadmemimage(Memimage *i, Rectangle r, uchar *data, int ndata) { + return _unloadmemimage(i, r, data, ndata); +} diff --git a/src/cmd/devdraw/wayland-inc.h b/src/cmd/devdraw/wayland-inc.h new file mode 100644 index 0000000000..742089c0c7 --- /dev/null +++ b/src/cmd/devdraw/wayland-inc.h @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include + +#include "xdg-shell-client-protocol.h" +#include "xdg-decoration-client-protocol.h" +#include "pointer-constraints-client-protocol.h" + +struct bounds { + int x, y; +}; + +typedef struct Globals { + struct wl_display *wl_display; + struct wl_registry *wl_registry; + struct wl_shm *wl_shm; + struct wl_cursor_theme *wl_cursor_theme; + struct wl_compositor *wl_compositor; + struct wl_subcompositor *wl_subcompositor; + struct xdg_wm_base *xdg_wm_base; + struct wl_seat *wl_seat; + struct wl_data_device_manager *data_device_manager; + struct wl_data_device *data_device; + struct wl_data_offer *snarf_offer; + int snarf_fd; + char *snarf_buf; + struct zwp_pointer_constraints_v1 *pointer_constraints; + struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1; + + uint32_t seat_capabilities; + struct pixfmt { + uint32_t wl; + uint32_t p9; + } pixfmt; + struct bounds bounds; +} Globals; + +// This is the singleton for a given process. +// +// Wayland specific code should be using the passed data pointer, this is just +// for the plan9 interface callbacks. +extern Globals procState; + +// Window is a big bag of state for painting a window on screen. +typedef struct window { + // RWLock mu; + Globals *global; + struct wl_keyboard *wl_keyboard; + struct wl_pointer *wl_pointer; + struct wl_surface *wl_surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct zwp_pointer_constraints_v1 *pointer_constraints; + // Frame and border need to be re-organized: "frame" was originally the + // window contents with the main surface being the border, but this + // arrangement breaks pointer warping in GNOME. This state of affair is + // weird, but actually works. + struct border { + struct edge { + struct wl_subsurface *subsurface; + struct wl_surface *surface; + int skip; + int x, y; + } edge[4]; // top, bottom, left, right + struct wl_shm_pool *x, *y; + } decoration; + struct bounds bounds, cursize, wantsize; + // Mouse members: x, y, button state, time and entry serial. + struct mouse { + // RWLock mu; + int X, Y, B, T, S; + int in; // which surface? + } m; + struct cursor { + // RWLock mu; + struct wl_surface *surface; + struct wl_cursor *arrow; + struct wl_shm_pool *pool; + uint32 *buf; + int x, y; + } cursor; + // Keyboard members: xkb members and serial + struct keyboard { + // RWLock mu; + struct xkb_context *context; + struct xkb_keymap *keymap; + struct xkb_state *state; + int S; + int32 rate, delay; + int32 time, key, event, delayed; + } kb; +} window; + +struct Memdata; +typedef struct shmTab { + struct Memdata *md; + int fd; + size_t sz; +} shmTab; + +extern int borderSz; +extern int barSz; diff --git a/src/cmd/devdraw/wayland-keyboard.c b/src/cmd/devdraw/wayland-keyboard.c new file mode 100644 index 0000000000..be2f9998e2 --- /dev/null +++ b/src/cmd/devdraw/wayland-keyboard.c @@ -0,0 +1,284 @@ +#include +#include +#include "wayland-inc.h" +#include +#include +#include +#include +#include +#include +#include "devdraw.h" +#include + +AUTOLIB(xkbcommon); + +static Rune +top9key(struct xkb_state *s, uint key); + +static void +keymap(void *opaque, struct wl_keyboard *kb, uint32_t format, int32_t fd, uint32_t size) +{ + Client *c; + window *w; + char *map_shm; + struct xkb_keymap *keymap; + struct xkb_state *state; + + c = opaque; + w = (window *)(c->view); + switch (format) { + case WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP: + fprint(2, "??? no keymap ??? \n"); + return; + case WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1: + break; + default: + sysfatal("unknown keymap format: %x", format); + }; + map_shm = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map_shm == MAP_FAILED) { + fprint(2, "unable to mmap keymap\n"); + return; + } + keymap = xkb_keymap_new_from_string( + w->kb.context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(map_shm, size); + close(fd); + state = xkb_state_new(keymap); + + xkb_keymap_unref(w->kb.keymap); + xkb_state_unref(w->kb.state); + w->kb.keymap = keymap; + w->kb.state = state; +} + +static void +enter(void *opaque, struct wl_keyboard *kb, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) +{ + Client *c; + window *w; + uint32 *key; + Rune r; + + c = opaque; + w = (void *)(c->view); + if (w->kb.S > serial) + return; + w->kb.S = serial; + wl_array_for_each(key, keys) + { + r = top9key(w->kb.state, *key); + if (r == -1) + continue; + gfx_keystroke(c, r); + } +} + +static void +leave(void *opaque, struct wl_keyboard *kb, uint32_t serial, struct wl_surface *surface) +{ + Client *c; + window *w; + + c = opaque; + w = (void *)(c->view); + if (w->kb.S > serial) + return; + w->kb.S = serial; +} + +// This callback handles key repeat events by repeatedly scheduling itself +// while a key is held down. +static const struct wl_callback_listener repeat_listener; + +static void +repeat_cb(void *opaque, struct wl_callback *cb, uint time) +{ + Client *c; + window *w; + struct wl_callback *next; + int32 delta; + + c = opaque; + w = (void *)c->view; + wl_callback_destroy(cb); + + if (w->kb.event != WL_KEYBOARD_KEY_STATE_PRESSED) + return; + + if (!w->kb.delayed) + delta = w->kb.delay; + else + delta = w->kb.rate; + if (time > (w->kb.time + delta)) { + w->kb.delayed = 1; + w->kb.time = time; + gfx_keystroke(c, w->kb.key); + } + next = wl_surface_frame(w->wl_surface); + wl_callback_add_listener(next, &repeat_listener, c); + wl_surface_commit(w->wl_surface); +} + +static const struct wl_callback_listener repeat_listener = { + .done = repeat_cb, +}; + +static void +key(void *opaque, struct wl_keyboard *kb, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + Client *c; + window *w; + Rune r; + struct wl_callback *repeat; + + c = opaque; + w = (void *)(c->view); + if (w->kb.S > serial) + return; + w->kb.S = serial; + + r = top9key(w->kb.state, key); + if (r == -1) + return; + + w->kb.time = time; + w->kb.event = state; + if (xkb_state_mod_name_is_active(w->kb.state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) == 1) + gfx_keystroke(c, Kalt); + if (r != 0 && state == WL_KEYBOARD_KEY_STATE_PRESSED) { + w->kb.key = r; + w->kb.delayed = 0; + gfx_keystroke(c, r); + repeat = wl_surface_frame(w->wl_surface); + wl_callback_add_listener(repeat, &repeat_listener, c); + wl_surface_commit(w->wl_surface); + } +} + +static void +modifiers(void *opaque, struct wl_keyboard *kb, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) +{ + Client *c; + window *w; + + c = opaque; + w = (void *)(c->view); + if (w->kb.S > serial) + return; + w->kb.S = serial; + xkb_state_update_mask(w->kb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group); +} + +static void +repeat_info(void *opaque, struct wl_keyboard *kb, int32_t rate, int32_t delay) +{ + Client *c; + window *w; + + c = opaque; + w = (void *)c->view; + w->kb.rate = rate; + w->kb.delay = delay; +} + +const struct wl_keyboard_listener wl_keyboard_listener = { + .keymap = keymap, + .enter = enter, + .leave = leave, + .key = key, + .modifiers = modifiers, + .repeat_info = repeat_info, +}; + +static Rune +top9key(struct xkb_state *s, uint key) +{ + // Adapted from the X11 input handling. + xkb_keysym_t sym; + Rune r; + + sym = xkb_state_key_get_one_sym(s, (key + 8)); + switch (sym) { + case XKB_KEY_BackSpace: + case XKB_KEY_Tab: + case XKB_KEY_Escape: + case XKB_KEY_Delete: + case XKB_KEY_KP_0: + case XKB_KEY_KP_1: + case XKB_KEY_KP_2: + case XKB_KEY_KP_3: + case XKB_KEY_KP_4: + case XKB_KEY_KP_5: + case XKB_KEY_KP_6: + case XKB_KEY_KP_7: + case XKB_KEY_KP_8: + case XKB_KEY_KP_9: + case XKB_KEY_KP_Divide: + case XKB_KEY_KP_Multiply: + case XKB_KEY_KP_Subtract: + case XKB_KEY_KP_Add: + case XKB_KEY_KP_Decimal: + r = sym & 0x7F; + break; + case XKB_KEY_Linefeed: + r = '\r'; + break; + case XKB_KEY_KP_Space: + r = ' '; + break; + case XKB_KEY_Home: + case XKB_KEY_KP_Home: + r = Khome; + break; + case XKB_KEY_Left: + case XKB_KEY_KP_Left: + r = Kleft; + break; + case XKB_KEY_Up: + case XKB_KEY_KP_Up: + r = Kup; + break; + case XKB_KEY_Down: + case XKB_KEY_KP_Down: + r = Kdown; + break; + case XKB_KEY_Right: + case XKB_KEY_KP_Right: + r = Kright; + break; + case XKB_KEY_Page_Down: + case XKB_KEY_KP_Page_Down: + r = Kpgdown; + break; + case XKB_KEY_End: + case XKB_KEY_KP_End: + r = Kend; + break; + case XKB_KEY_Page_Up: + case XKB_KEY_KP_Page_Up: + r = Kpgup; + break; + case XKB_KEY_Insert: + case XKB_KEY_KP_Insert: + r = Kins; + break; + case XKB_KEY_KP_Enter: + case XKB_KEY_Return: + r = '\n'; + break; + case XKB_KEY_Alt_L: + case XKB_KEY_Meta_L: /* Shift Alt on PCs */ + case XKB_KEY_Alt_R: + case XKB_KEY_Meta_R: /* Shift Alt on PCs */ + case XKB_KEY_Multi_key: + return -1; + default: + r = xkb_keysym_to_utf32(sym); + } + if (xkb_state_mod_name_is_active(s, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) == 1) + r &= 0x9f; + + return r; +} diff --git a/src/cmd/devdraw/wayland-output.c b/src/cmd/devdraw/wayland-output.c new file mode 100644 index 0000000000..6d5e050fe2 --- /dev/null +++ b/src/cmd/devdraw/wayland-output.c @@ -0,0 +1,38 @@ +#include +#include +#include "wayland-inc.h" + +static void +wl_output_geometry(void *opaque, struct wl_output *wl_output, int32_t x, + int32_t y, int32_t physical_width, int32_t physical_height, + int32_t subpixel, const char *make, const char *model, + int32_t transform) { + Globals *g = opaque; + if (x > g->bounds.x) + g->bounds.x = x; + if (y > g->bounds.y) + g->bounds.y = y; +} + +static void +wl_output_mode(void *opaque, struct wl_output *wl_output, uint32_t flags, + int32_t width, int32_t height, int32_t refresh) {} + +static void +wl_output_scale(void *opaque, struct wl_output *wl_output, int32_t factor) {} + +static void +wl_output_name(void *opaque, struct wl_output *wl_output, const char *name) {} + +static void +wl_output_done(void *opaque, struct wl_output *wl_output) { + wl_output_destroy(wl_output); +} + +const struct wl_output_listener output_listener = { + .geometry = wl_output_geometry, + .mode = wl_output_mode, + .scale = wl_output_scale, + .name = wl_output_name, + .done = wl_output_done, +}; diff --git a/src/cmd/devdraw/wayland-pointer.c b/src/cmd/devdraw/wayland-pointer.c new file mode 100644 index 0000000000..bd8e0b1219 --- /dev/null +++ b/src/cmd/devdraw/wayland-pointer.c @@ -0,0 +1,291 @@ +#include +#include +#include +#include +#include +#include +#include +#include "devdraw.h" +#include "wayland-inc.h" + +static void +enter(void *opaque, struct wl_pointer *p, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y) +{ + Client *c; + window *w; + struct wl_cursor *cur; + struct wl_cursor_image *img; + struct wl_buffer *buf; + struct wl_surface *t; + int i; + + c = opaque; + w = (void *)c->view; + if (w->m.S > serial) + return; + for (i = 0; i < 5; i++) { + if (i == 4) + t = w->wl_surface; + else + t = w->decoration.edge[i].surface; + if (surface == t) { + w->m.in = i; + goto Found; + } + } + return; + +Found: + w->m.S = serial; + w->m.X = wl_fixed_to_int(x); + w->m.Y = wl_fixed_to_int(y); + switch (w->m.in) { + case 0: + if (w->m.X < 50) + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "top_left_side"); + else if ((w->cursize.x - w->m.X) < 50) + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "top_right_side"); + else + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "top_side"); + break; + case 1: + if (w->m.X < 50) + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "bottom_left_side"); + else if ((w->cursize.x - w->m.X) < 50) + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "bottom_right_side"); + else + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "bottom_side"); + break; + case 2: + if (w->m.Y < 50) + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "top_right_side"); + else if ((w->cursize.y - w->m.Y) < 50) + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "bottom_right_side"); + else + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "right_side"); + break; + case 3: + if (w->m.Y < 50) + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "top_left_side"); + else if ((w->cursize.y - w->m.Y) < 50) + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "bottom_left_side"); + else + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "left_side"); + break; + default: + cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, "left_ptr"); + } + img = cur->images[0]; + buf = wl_cursor_image_get_buffer(img); + wl_surface_attach(w->cursor.surface, buf, 0, 0); + wl_pointer_set_cursor(p, w->m.S, w->cursor.surface, img->hotspot_x, img->hotspot_y); +} + +static void +leave(void *opaque, struct wl_pointer *p, uint32_t serial, struct wl_surface *surface) +{ + Client *c = opaque; + window *w = (void *)c->view; + w->m.in = -1; + w->m.S = serial; + w->m.B = 0; +} + +static void +motion(void *opaque, struct wl_pointer *p, uint32_t time, wl_fixed_t x, wl_fixed_t y) +{ + Client *c; + window *w; + + c = opaque; + w = (void *)c->view; + w->m.T = time; + w->m.X = wl_fixed_to_int(x); + w->m.Y = wl_fixed_to_int(y); +} + +// Linux's pointer button events: +#define BTN_MOUSE 0x110 +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 +#define BTN_SIDE 0x113 +#define BTN_EXTRA 0x114 +#define BTN_FORWARD 0x115 +#define BTN_BACK 0x116 +#define BTN_TASK 0x117 + +static void +button(void *opaque, struct wl_pointer *p, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) +{ + Client *c; + window *w; + uint32 resize; + int b; + + c = opaque; + w = (void *)c->view; + if (w->m.S > serial) // If we have a martian event, somehow. + return; + + if (w->m.in < 0 || w->m.in > 4) + // martian button press. + return; + if (w->m.in == 4) + goto Record; + + resize = 0; + switch (button) { + case BTN_MIDDLE: + xdg_toplevel_move(w->xdg_toplevel, w->global->wl_seat, serial); + break; + case BTN_LEFT: + switch (w->m.in) { + case 0: + resize |= XDG_TOPLEVEL_RESIZE_EDGE_TOP; + break; + case 1: + resize |= XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; + break; + case 2: + resize |= XDG_TOPLEVEL_RESIZE_EDGE_LEFT; + break; + case 3: + resize |= XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; + break; + } + if (w->m.X < 50) + resize |= XDG_TOPLEVEL_RESIZE_EDGE_LEFT; + else if ((w->cursize.x - w->m.X) < 50) + resize |= XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; + if (w->m.Y < 50) + resize |= XDG_TOPLEVEL_RESIZE_EDGE_TOP; + else if ((w->cursize.y - w->m.Y) < 50) + resize |= XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; + xdg_toplevel_resize(w->xdg_toplevel, w->global->wl_seat, serial, resize); + break; + case BTN_RIGHT: + if (w->m.in == 0) + xdg_toplevel_show_window_menu(w->xdg_toplevel, w->global->wl_seat, serial, w->m.X, w->m.Y); + } + return; + +Record: + w->m.T = time; + switch (button) { + case BTN_LEFT: + b = 1; + break; + case BTN_MIDDLE: + b = 2; + break; + case BTN_RIGHT: + b = 4; + break; + default: + fprint(2, "unhandled button event: %x\n", button); + return; + }; + switch (state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + w->m.B |= b; + break; + case WL_POINTER_BUTTON_STATE_RELEASED: + w->m.B ^= b; + break; + default: + fprint(2, "martian button state: %x\n", state); + } +} + +// The stored mouse coordinates flit between the surfaces, depending on where +// the pointer is. Making sure to only call gfx_mousetrack when the pointer is +// in the application's surface means it should always see its own coordinates. + +static void +frame(void *opaque, struct wl_pointer *p) +{ + Client *c; + window *w; + + c = opaque; + w = (void *)c->view; + if (w->m.in) + gfx_mousetrack(c, w->m.X, w->m.Y, w->m.B, w->m.T); +} + +static void +axis(void *opaque, struct wl_pointer *p, uint32 time, uint32 axis, wl_fixed_t val) +{ + Client *c; + window *w; + + c = opaque; + w = (void *)c->view; + if (!w->m.in) + return; + w->m.T = time; + switch (axis) { + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + return; + case WL_POINTER_AXIS_VERTICAL_SCROLL: + break; + default: + sysfatal("martian axis: %x", axis); + } + gfx_mousetrack(c, w->m.X, w->m.Y, w->m.B, w->m.T); +} + +static void +axis_discrete(void *opaque, struct wl_pointer *p, uint32 axis, int32 discrete) +{ + Client *c; + window *w; + int b; + + c = opaque; + w = (void *)c->view; + if (!w->m.in) + return; + switch (axis) { + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + return; + case WL_POINTER_AXIS_VERTICAL_SCROLL: + break; + default: + sysfatal("martian axis: %x", axis); + } + b = w->m.B; + if (discrete > 0) + b |= 16; + else { + discrete *= -1; + b |= 8; + } + for (int i = 0; i < discrete; i++) { + gfx_mousetrack(c, w->m.X, w->m.Y, b, w->m.T); + gfx_mousetrack(c, w->m.X, w->m.Y, w->m.B, w->m.T); + } +} + +static void +axis_source(void *opaque, struct wl_pointer *p, uint32 src) +{ +} + +static void +axis_stop(void *opaque, struct wl_pointer *p, uint32 time, uint32 axis) +{ +} + +const struct wl_pointer_listener wl_pointer_listener = { + .enter = enter, + .leave = leave, + .motion = motion, + .button = button, + .frame = frame, + .axis = axis, + .axis_discrete = axis_discrete, + .axis_source = axis_source, + .axis_stop = axis_stop, +}; diff --git a/src/cmd/devdraw/wayland-screen.c b/src/cmd/devdraw/wayland-screen.c new file mode 100644 index 0000000000..abc13dedb5 --- /dev/null +++ b/src/cmd/devdraw/wayland-screen.c @@ -0,0 +1,893 @@ +#define _GNU_SOURCE +#include +#include "wayland-inc.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "devdraw.h" +#include "bigarrow.h" + +#include "wayland-screen.h" +#include "wayland-shm.h" +#include +#include +#include + +Globals procState = {0}; +static const int curSz = 16; +static const int curBufSz = curSz * curSz * sizeof(uint32); +static const int useBigarrow = 0; +const char *dataMimeType = "text/plain;charset=utf-8"; + +static struct wl_data_device_listener data_device_listener; + +static void +rpc_resizeimg(Client *c); +static void +rpc_resizewindow(Client *c, Rectangle r); +static void +rpc_setcursor(Client *c, Cursor *cur, Cursor2 *cur2); +static void +rpc_setlabel(Client *c, char *label); +static void +rpc_setmouse(Client *c, Point p); +static void +rpc_topwin(Client *c); +static void +rpc_bouncemouse(Client *c, Mouse m); +static void +rpc_flush(Client *c, Rectangle r); + +static ClientImpl wlImpl = { + .rpc_resizeimg = rpc_resizeimg, + .rpc_resizewindow = rpc_resizewindow, + .rpc_setcursor = rpc_setcursor, + .rpc_setlabel = rpc_setlabel, + .rpc_setmouse = rpc_setmouse, + .rpc_topwin = rpc_topwin, + .rpc_bouncemouse = rpc_bouncemouse, + .rpc_flush = rpc_flush, +}; + +static void +setupdecoration(window *w); +static window * +setupwindow(Client *c, Globals *g); +static void +updatedecoration(window *w, Rectangle r); + +// This setup allows the gfx thread to not need to spawn ioprocs to handle the +// snarf/clipboard interaction. +// +// Without this, the event loop deadlocks trying to read a pipe it's also +// writing. +char *selfMimeType; +static void +setselfmimetype() +{ + uchar id[12]; + + fmtinstall('[', encodefmt); + genrandom(id, 12); + selfMimeType = smprint("application/x-plan9port-devdraw-%.*[", sizeof(id), id); +}; + +// Gfx_main is the only gfx_* function that's allowed to dispatch events. +void +gfx_main(void) +{ + Globals *g; + + // Take this, it's dangerous to go alone: + // - https://wayland.app + // - https://wayland-book.com + g = &procState; + setselfmimetype(); + // Set up display and registry first thing. + g->wl_display = wl_display_connect(NULL); + if (g->wl_display == NULL) { + werrstr("unable to open display"); + goto err; + } + g->wl_registry = wl_display_get_registry(g->wl_display); + if (g->wl_registry == NULL) { + werrstr("unable to get registry"); + goto err; + } + wl_registry_add_listener(g->wl_registry, ®istry_listener, g); + // Roundtrip blocks until all pending requests are handled. + if (wl_display_roundtrip(g->wl_display) == -1) { + werrstr("unable to roundtrip"); + goto err; + } + // Set up the data source & sink: + g->data_device = wl_data_device_manager_get_data_device(g->data_device_manager, g->wl_seat); + wl_data_device_add_listener(g->data_device, &data_device_listener, g); + g->snarf_fd = allocate_shm_file(4096); + if (g->snarf_fd < 0) + goto err; + g->snarf_buf = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, g->snarf_fd, 0); + memset(g->snarf_buf, 0x00, 4096); + + gfx_started(); + while (wl_display_dispatch(g->wl_display)) { + } +err: + sysfatal("%r"); +} + +static void +sync_cb(void *opaque, struct wl_callback *cb, uint serial) +{ + int *done; + + done = opaque; + *done = 1; + wl_callback_destroy(cb); +} + +static const struct wl_callback_listener sync_listener = { + .done = sync_cb, +}; + +// syncpoint waits until all queued graphics events are handled. +// +// It's like wl_display_roundtrip, but flushes events instead of dispatching +// them. Calling on the graphics thread is likely to deadlock. +static void +syncpoint(struct wl_display *d) +{ + struct wl_callback *cb; + int done, ret; + + done = 0; + ret = 0; + cb = wl_display_sync(d); + wl_callback_add_listener(cb, &sync_listener, &done); + while (done == 0 && ret >= 0) + ret = wl_display_flush(d); + if (ret == -1 && done == 0) + wl_callback_destroy(cb); +} + +static int +pixfmtPriority(uint32 f) +{ + static const uint32 prio[] = { + WL_SHM_FORMAT_XRGB8888, + }; + const int N = sizeof(prio) / (sizeof(prio[0])); + for (int i = 0; i < N; i++) + if (prio[i] == f) + return i; + return -1; +} + +static void +shm_format(void *opaque, struct wl_shm *wl_shm, uint32 format) +{ + Globals *g; + int p; + + if ((p = pixfmtPriority(format)) < 0) + return; + g = opaque; + if (p < pixfmtPriority(g->pixfmt.wl)) + return; + g->pixfmt.wl = format; + g->pixfmt.p9 = format; // TODO write conversion func and actually use negotiated values. +} + +static const struct wl_shm_listener wl_shm_formats = { + .format = shm_format, +}; + +static void +registry_global(void *opaque, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) +{ + Globals *g = opaque; + if (strcmp(interface, wl_shm_interface.name) == 0) { + g->wl_shm = wl_registry_bind(wl_registry, name, &wl_shm_interface, 1); + wl_shm_add_listener(g->wl_shm, &wl_shm_formats, g); + g->wl_cursor_theme = wl_cursor_theme_load(NULL, curSz, g->wl_shm); + } else if (strcmp(interface, wl_compositor_interface.name) == 0) { + g->wl_compositor = wl_registry_bind(wl_registry, name, &wl_compositor_interface, 4); + } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { + g->wl_subcompositor = wl_registry_bind(wl_registry, name, &wl_subcompositor_interface, 1); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + g->xdg_wm_base = wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, 3); + xdg_wm_base_add_listener(g->xdg_wm_base, &xdg_wm_base_listener, nil); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + g->wl_seat = wl_registry_bind(wl_registry, name, &wl_seat_interface, 5); + wl_seat_add_listener(g->wl_seat, &wl_seat_listener, g); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + g->zxdg_decoration_manager_v1 = wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + // TODO Something with pulling out scaling and bounds. + struct wl_output *o = wl_registry_bind(wl_registry, name, &wl_output_interface, 2); + wl_output_add_listener(o, &output_listener, g); + } else if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) { + g->pointer_constraints = wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1); + } else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { + g->data_device_manager = wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 3); + } +} + +static void +registry_global_remove(void *opaque, struct wl_registry *wl_registry, uint32_t name) +{ + fprint(2, "should remove something, not going to\n"); +} + +static void +xdg_wm_base_ping(void *opaque, struct xdg_wm_base *base, uint32_t serial) +{ + xdg_wm_base_pong(base, serial); +} + +static void +xdg_surface_configure(void *opaque, struct xdg_surface *s, uint32_t serial) +{ + Client *c; + window *w; + Rectangle r; + Memimage *new; + + c = opaque; + w = (void *)c->view; + if (w->wantsize.x == 0 && w->wantsize.y == 0) + goto Done; + if (w->cursize.x == w->wantsize.x && w->cursize.y == w->wantsize.y) + goto Done; + + r = Rect(0, 0, w->wantsize.x, w->wantsize.y); + new = allocmemimage(r, strtochan("x8r8g8b8")); + if (new == nil) + sysfatal("%r"); + c->mouserect = r; + gfx_replacescreenimage(c, new); + updatedecoration(w, r); + xdg_surface_set_window_geometry(w->xdg_surface, 0, 0, Dx(r), Dy(r)); + w->cursize.x = w->wantsize.x; + w->cursize.y = w->wantsize.y; +Done: + xdg_surface_ack_configure(w->xdg_surface, serial); +} + +static void +xdg_toplevel_configure_bounds(void *opaque, struct xdg_toplevel *t, int32_t wd, int32_t ht) +{ + Client *c; + window *w; + + c = opaque; + w = (void *)c->view; + w->bounds.x = wd; + w->bounds.y = ht; +} + +static void +xdg_toplevel_configure(void *opaque, struct xdg_toplevel *t, int32_t wd, int32_t ht, struct wl_array *states) +{ + Client *c; + window *w; + enum xdg_toplevel_state *s; + + c = opaque; + w = (void *)c->view; + wl_array_for_each(s, states) + { + // TODO: use this to toggle drawing decorations + switch (*s) { + case XDG_TOPLEVEL_STATE_ACTIVATED: + break; + case XDG_TOPLEVEL_STATE_FULLSCREEN: + case XDG_TOPLEVEL_STATE_MAXIMIZED: + w->bounds.x = wd; + w->bounds.y = ht; + case XDG_TOPLEVEL_STATE_RESIZING: + w->wantsize.x = wd; + w->wantsize.y = ht; + break; + case XDG_TOPLEVEL_STATE_TILED_TOP: + break; + case XDG_TOPLEVEL_STATE_TILED_BOTTOM: + break; + case XDG_TOPLEVEL_STATE_TILED_LEFT: + break; + case XDG_TOPLEVEL_STATE_TILED_RIGHT: + break; + } + }; +} + +static void +xdg_toplevel_close(void *opaque, struct xdg_toplevel *t) +{ + Globals *g; + + g = opaque; + wl_display_disconnect(g->wl_display); + threadexitsall(nil); +} + +static void +wl_buffer_release(void *opaque, struct wl_buffer *wl_buffer) +{ + /* Sent by the compositor when it's no longer using this buffer */ + wl_buffer_destroy(wl_buffer); +} + +static void +wl_seat_capabilities(void *opaque, struct wl_seat *wl_seat, uint32_t caps) +{ + Globals *g; + + g = opaque; + g->seat_capabilities = caps; +} + +static void +wl_seat_name(void *opaque, struct wl_seat *wl_seat, const char *name) +{ +} + +static void +updatedecoration(window *w, Rectangle r) +{ + Globals *g; + struct wl_buffer *buf; + struct edge *e; + int wd, ht, skipv, skiph; + + g = w->global; + wd = Dx(r); + ht = Dy(r); + skipv = (w->bounds.x != 0 && (wd + 4) > w->bounds.x); + skiph = (w->bounds.y != 0 && (ht + 4) > w->bounds.y); + for (int i = 0; i < 4; i++) { + e = &w->decoration.edge[i]; + if ((i < 2 && skiph) || (i > 1 && skipv)) { + if (e->subsurface != nil) { + wl_subsurface_destroy(e->subsurface); + e->subsurface = nil; + } + continue; + } + if (e->subsurface == nil) { + e->subsurface = wl_subcompositor_get_subsurface(g->wl_subcompositor, e->surface, w->wl_surface); + wl_subsurface_place_below(e->subsurface, w->wl_surface); + switch (i) { + case 0: + wl_subsurface_set_position(e->subsurface, 0, -2); + break; + case 2: + wl_subsurface_set_position(e->subsurface, -2, -2); + break; + } + } + switch (i) { + case 1: + wl_subsurface_set_position(e->subsurface, 0, ht); + case 0: + buf = wl_shm_pool_create_buffer(w->decoration.x, 0, wd, 2, wd * sizeof(uint32), WL_SHM_FORMAT_XRGB8888); + break; + case 3: + wl_subsurface_set_position(e->subsurface, wd, -2); + case 2: + buf = wl_shm_pool_create_buffer(w->decoration.y, 0, 2, ht + 4, 2 * sizeof(uint32), WL_SHM_FORMAT_XRGB8888); + break; + } + wl_buffer_add_listener(buf, &wl_buffer_listener, nil); + wl_surface_attach(e->surface, buf, 0, 0); + wl_surface_commit(e->surface); + } +} + +// All the devdraw hooks start here: + +// Resizewindow resizes the window decoration (which should already be set up +// via setupdecoration). The draw thread should have already modified the +// memimage, so it will be drawn correctly on the next flush call. +static void +rpc_resizewindow(Client *c, Rectangle r) +{ + window *w; + + w = (void *)c->view; + updatedecoration(w, r); + xdg_surface_set_window_geometry(w->xdg_surface, 0, 0, Dx(r), Dy(r)); + wl_display_flush(w->global->wl_display); +} + +static void +rpc_resizeimg(Client *c) +{ + window *w; + Rectangle r; + Memimage *m; + + w = (void *)c->view; + r = Rect(0, 0, w->wantsize.x, w->wantsize.y); + m = allocmemimage(r, strtochan("x8r8g8b8")); + c->mouserect = r; + gfx_replacescreenimage(c, m); +} + +static void +data_offer_offer(void *opaque, struct wl_data_offer *offer, const char *kind) +{ + Globals *g; + + g = opaque; + if (strcmp(kind, selfMimeType) == 0) { + wl_data_offer_destroy(offer); + g->snarf_offer = nil; + } else if (strcmp(kind, dataMimeType) == 0) + g->snarf_offer = offer; + // TODO Look into how some applications turn drag-n-drop into file names. +} + +static const struct wl_data_offer_listener data_offer_listener = { + .offer = data_offer_offer, +}; + +static void +data_device_selection(void *opaque, struct wl_data_device *dev, struct wl_data_offer *offer) +{ + Globals *g; + struct stat fs; + int fds[2]; + long off; + int ct; + + g = opaque; + if (offer == nil) { + // This means the clipboard is empty. Make sure to clear the offer we + // have stored, if any. + if (g->snarf_offer != nil) + wl_data_offer_destroy(g->snarf_offer); + g->snarf_offer = nil; + return; + } + + // Need a "normal" pipe. +#undef pipe + pipe(fds); +#define pipe p9pipe + fstat(g->snarf_fd, &fs); + wl_data_offer_receive(g->snarf_offer, dataMimeType, fds[1]); + close(fds[1]); + wl_display_flush(g->wl_display); + + off = 0; + // Arbitrary limit on incoming paste data. Should maybe be INT_MAX? + ct = splice(fds[0], NULL, g->snarf_fd, &off, 32 * 1024 * 1024, 0); + if (ct < 0) + fprint(2, "oops: %r\n"); + close(fds[0]); + ct++; + if (ct > fs.st_size) { + munmap(g->snarf_buf, fs.st_size); + g->snarf_buf = mmap(NULL, ct, PROT_READ | PROT_WRITE, MAP_SHARED, g->snarf_fd, 0); + } + g->snarf_buf[--ct] = 0x00; + wl_data_offer_destroy(g->snarf_offer); + g->snarf_offer = nil; +} +static void +data_device_data_offer(void *opaque, struct wl_data_device *dev, struct wl_data_offer *offer) +{ + wl_data_offer_add_listener(offer, &data_offer_listener, opaque); +} + +static struct wl_data_device_listener data_device_listener = { + .selection = data_device_selection, + .data_offer = data_device_data_offer, +}; + +static void +getsnarf_cb(void *opaque, struct wl_callback *cb, uint serial) +{ + char **out; + + out = opaque; + *out = strdup(procState.snarf_buf); +} + +static const struct wl_callback_listener getsnarf_listener = { + .done = getsnarf_cb, +}; + +char * +rpc_getsnarf(void) +{ + char *out = NULL; + int ret; + struct wl_display *d = procState.wl_display; + struct wl_callback *cb = wl_display_sync(d); + wl_callback_add_listener(cb, &getsnarf_listener, &out); + while (out == NULL && ret >= 0) + ret = wl_display_flush(d); + wl_callback_destroy(cb); + return out; +} + +static void +data_source_send(void *opaque, struct wl_data_source *src, const char *kind, int fd) +{ + Globals *g; + long off; + size_t len; + + if (strcmp(kind, dataMimeType) != 0) + return; + g = opaque; + off = 0; + len = strlen(g->snarf_buf); // Sending text, not C strings. + if (sendfile(fd, g->snarf_fd, &off, len) < 0) + fprint(2, "sendfile error: %r\n"); + close(fd); +} +static void +data_source_cancel(void *opaque, struct wl_data_source *src) +{ + wl_data_source_destroy(src); +} + +static const struct wl_data_source_listener data_source_listener = { +#undef send + .send = data_source_send, +#define send chansend + .cancelled = data_source_cancel, +}; + +struct putsnarf_cmd { + int done; + int len; + char *data; +}; + +static void +putsnarf_cb(void *opaque, struct wl_callback *cb, uint serial) +{ + // This gets hairy because it pulls the window out of the default client, + // but the function signature just doesn't allow for passing it in cleanly. + Globals *g; + window *w; + struct putsnarf_cmd *c; + struct wl_data_source *src; + struct stat fs; + int need; + + g = &procState; + c = opaque; + if (fstat(g->snarf_fd, &fs) < 0) { + fprint(2, "unable to stat snarf fd: %r\n"); + goto Done; + } + need = c->len + 1; + if (need > fs.st_size) { + // Need to grow the file and update the mapping. + int np = need / 4096; + if ((need % 4096) != 0) + np++; + int sz = np * 4096; + ftruncate(g->snarf_fd, sz); + munmap(g->snarf_buf, fs.st_size); + g->snarf_buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, g->snarf_fd, 0); + if (g->snarf_buf == NULL) + sysfatal("unable to remap snarf buf: %r\n"); + } + strncpy(g->snarf_buf, c->data, need); + w = (window *)client0->view; + src = wl_data_device_manager_create_data_source(g->data_device_manager); + wl_data_source_add_listener(src, &data_source_listener, &procState); + wl_data_source_offer(src, dataMimeType); + wl_data_source_offer(src, selfMimeType); + wl_data_device_set_selection(procState.data_device, src, w->kb.S); + +Done: + c->done = 1; +} + +static const struct wl_callback_listener putsnarf_listener = { + .done = putsnarf_cb, +}; + +void +rpc_putsnarf(char *data) +{ + struct putsnarf_cmd c = { + .done = 0, + .len = strlen(data), + .data = data, + }; + int ret; + struct wl_display *d = procState.wl_display; + struct wl_callback *cb = wl_display_sync(d); + wl_callback_add_listener(cb, &putsnarf_listener, &c); + while (c.done == 0 && ret >= 0) + ret = wl_display_flush(d); + wl_callback_destroy(cb); + return; +} + +static void +shutdown_cb(void *opaque, struct wl_callback *cb, uint serial) +{ + xdg_toplevel_close(opaque, nil); +} + +static const struct wl_callback_listener shutdown_listener = { + .done = shutdown_cb, +}; + +void +rpc_shutdown(void) +{ + Globals *g; + struct wl_callback *cb; + + g = &procState; + cb = wl_display_sync(g->wl_display); + wl_callback_add_listener(cb, &shutdown_listener, g); +} + +void +rpc_gfxdrawlock(void) +{ +} +void +rpc_gfxdrawunlock(void) +{ +} + +static void +rpc_setlabel(Client *c, char *label) +{ + const window *w; + + w = c->view; + xdg_toplevel_set_title(w->xdg_toplevel, label); +} + +static void +rpc_flush(Client *c, Rectangle r) +{ + window *w; + Memimage *i; + shmTab *t; + struct wl_shm_pool *pool; + struct wl_buffer *buf; + ptrdiff_t off; + int x, y; + + w = (void *)c->view; + i = c->screenimage; + t = i->X; + pool = wl_shm_create_pool(w->global->wl_shm, t->fd, t->sz); + off = (void *)(i->data->bdata + i->zero) - (void *)i->data->base; + x = Dx(i->r); + assert(x > 0); + y = Dy(i->r); + assert(y > 0); + buf = wl_shm_pool_create_buffer(pool, off, x, y, i->width * sizeof(uint32), WL_SHM_FORMAT_XRGB8888); + wl_shm_pool_destroy(pool); + wl_buffer_add_listener(buf, &wl_buffer_listener, nil); + wl_surface_attach(w->wl_surface, buf, 0, 0); + wl_surface_set_buffer_scale(w->wl_surface, 1); // TODO(hank) HiDPI? + wl_surface_damage(w->wl_surface, r.min.x, r.min.y, Dx(r), Dy(r)); + // We get border updates "for free" because they're synced with the parent + // surface. + wl_surface_commit(w->wl_surface); + syncpoint(w->global->wl_display); +} + +static void +rpc_setcursor(Client *c, Cursor *cur, Cursor2 *cur2) +{ + struct wl_buffer *curBuf; + window *w; + int off; + + w = (void *)c->view; + if (cur == nil && cur2 == nil) { + if (useBigarrow) + cur = &bigarrow; + else { + struct wl_cursor_image *ci; + + ci = w->cursor.arrow->images[0]; + curBuf = wl_cursor_image_get_buffer(ci); + w->cursor.x = ci->hotspot_x; + w->cursor.y = ci->hotspot_y; + goto Set; + } + } + off = 0; + memset((void *)w->cursor.buf, 0x00, curBufSz); + for (int y = 0; y < curSz; y++) { // y line, same in mask and px + int r = y * sizeof(uint16); + uint16 clrrow = cur->clr[r + 0] << 8 | cur->clr[r + 1]; + uint16 setrow = cur->set[r + 0] << 8 | cur->set[r + 1]; + for (int x = 0; x < curSz; x++) { // for each bit / pixel + uint16 t = 1 << (curSz - x); + int clr = (t & clrrow), set = (t & setrow); + if (clr != 0) + w->cursor.buf[off] = 0xFFFFFFFF; + if (set != 0) + w->cursor.buf[off] = 0xFF000000; + off++; + } + } + curBuf = wl_shm_pool_create_buffer(w->cursor.pool, 0, curSz, curSz, curSz * sizeof(uint32), WL_SHM_FORMAT_ARGB8888); + wl_buffer_add_listener(curBuf, &wl_buffer_listener, nil); + w->cursor.x = -cur->offset.x; + w->cursor.y = -cur->offset.y; +Set: + wl_surface_attach(w->cursor.surface, curBuf, 0, 0); + wl_surface_commit(w->cursor.surface); + wl_pointer_set_cursor(w->wl_pointer, w->m.S, w->cursor.surface, w->cursor.x, w->cursor.y); + syncpoint(w->global->wl_display); +} + +// Rpc_setmouse warps the pointer by converting into the surface space, +// asking the compositor to lock the mouse, hinting the correct position, +// and then unlocking the mouse. +static void +rpc_setmouse(Client *c, Point p) +{ + Globals *g; + window *w; + struct zwp_locked_pointer_v1 *l; + + w = (void *)c->view; + g = w->global; + if (g->pointer_constraints == nil) { + fprint(2, "pointer warping unsupported\n"); + return; + } + + l = zwp_pointer_constraints_v1_lock_pointer(g->pointer_constraints, w->wl_surface, w->wl_pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); + zwp_locked_pointer_v1_set_cursor_position_hint(l, wl_fixed_from_int(p.x), wl_fixed_from_int(p.y)); + w->m.X = p.x; + w->m.Y = p.y; + // wl_surface_commit(w->wl_surface); + syncpoint(g->wl_display); + zwp_locked_pointer_v1_destroy(l); +} + +static void +rpc_topwin(Client *c) +{ + // I think this is impossible in the Wayland world. +} + +static void +rpc_bouncemouse(Client *c, Mouse m) +{ + // Unsure what bouncemouse should do. +} + +static window * +setupwindow(Client *c, Globals *g) +{ + window *w; + int fd; + + w = mallocz(sizeof(window), 1); + // Populate everything: + c->view = w; + w->global = g; + w->kb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (w->kb.context == nil) { + free(w); + werrstr("unable to allocate new xkb_context"); + return nil; + } + w->wl_surface = wl_compositor_create_surface(g->wl_compositor); + w->xdg_surface = xdg_wm_base_get_xdg_surface(g->xdg_wm_base, w->wl_surface); + w->xdg_toplevel = xdg_surface_get_toplevel(w->xdg_surface); + for (int i = 0; i < 4; i++) { + w->decoration.edge[i].surface = wl_compositor_create_surface(g->wl_compositor); + } + w->wl_pointer = wl_seat_get_pointer(g->wl_seat); + w->wl_keyboard = wl_seat_get_keyboard(g->wl_seat); + w->cursor.surface = wl_compositor_create_surface(g->wl_compositor); + fd = allocate_shm_file(curBufSz); + w->cursor.pool = wl_shm_create_pool(w->global->wl_shm, fd, curBufSz); + w->cursor.buf = mmap(NULL, curBufSz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + close(fd); + w->cursor.arrow = wl_cursor_theme_get_cursor(g->wl_cursor_theme, "left_ptr"); + + xdg_surface_add_listener(w->xdg_surface, &xdg_surface_listener, c); + xdg_toplevel_add_listener(w->xdg_toplevel, &xdg_toplevel_listener, c); + xdg_toplevel_set_title(w->xdg_toplevel, "devdraw"); + + // Signal we're drawing our own decoration (for better or worse). + if (g->zxdg_decoration_manager_v1 != NULL) { + struct zxdg_toplevel_decoration_v1 *d; + d = zxdg_decoration_manager_v1_get_toplevel_decoration(g->zxdg_decoration_manager_v1, w->xdg_toplevel); + zxdg_toplevel_decoration_v1_set_mode(d, ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); + zxdg_toplevel_decoration_v1_destroy(d); + } + setupdecoration(w); + return w; +}; + +static void +setupdecoration(window *w) +{ + const int edge = 2; + int sz, fd, x, y; + uint32 *map; + + // Allocate backing buffers big enough for the whole screen and then just + // don't worry about it. + x = w->global->bounds.x; + if (x == 0) + x = 1920; + y = w->global->bounds.y; + if (y == 0) + y = 1080; + + sz = x * edge * 4; + fd = allocate_shm_file(sz); + map = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + w->decoration.x = wl_shm_create_pool(w->global->wl_shm, fd, sz); + close(fd); + memset(map, 0x00, sz); + + sz = y * edge * 4; + fd = allocate_shm_file(sz); + map = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + w->decoration.y = wl_shm_create_pool(w->global->wl_shm, fd, sz); + close(fd); + memset(map, 0x00, sz); +}; + +// Rpc_attach creates a new window and associates it with the passed +// *client. +Memimage * +rpc_attach(Client *client, char *label, char *winsize) +{ + Globals *g; + window *w; + Rectangle r; + int havemin; + Memimage *i; + + r = Rect(0, 0, 800, 600); + if (winsize && winsize[0]) + if (parsewinsize(winsize, &r, &havemin) < 0) + sysfatal("%r"); + g = &procState; + w = setupwindow(client, g); + if (w == nil) + sysfatal("unable to allocate new window"); + wl_pointer_add_listener(w->wl_pointer, &wl_pointer_listener, client); + wl_keyboard_add_listener(w->wl_keyboard, &wl_keyboard_listener, client); + syncpoint(w->global->wl_display); + if (w->cursize.x != 0 && w->cursize.y != 0) + // May have had bounds suggested by callbacks run during the syncpoint. + r = Rect(0, 0, w->cursize.x, w->cursize.y); + + i = allocmemimage(r, strtochan("x8r8g8b8")); + // Set up the client object: + client->impl = &wlImpl; + client->mouserect = r; + if (label && label[0]) + rpc_setlabel(client, label); + rpc_resizewindow(client, r); // updates decorations + rpc_setcursor(client, nil, nil); // Cursor has an implicit flush + return i; +}; diff --git a/src/cmd/devdraw/wayland-screen.h b/src/cmd/devdraw/wayland-screen.h new file mode 100644 index 0000000000..1c4326b33e --- /dev/null +++ b/src/cmd/devdraw/wayland-screen.h @@ -0,0 +1,65 @@ +// Put forward declarations for a lot of the pageantry here, so the actual +// source reads better. + +// Global registry handler +static void +registry_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version); +static void +registry_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name); +static const struct wl_registry_listener registry_listener = { + .global = registry_global, + .global_remove = registry_global_remove, +}; + +// XDG protocol handler +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *base, uint32_t serial); +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping, +}; + +// surface listener +static void +xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial); +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure, +}; + +// toplevel listener +static void +xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t wd, int32_t ht, struct wl_array *states); +static void +xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t wd, int32_t ht); +static void +xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel); +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure, + .configure_bounds = xdg_toplevel_configure_bounds, + .close = xdg_toplevel_close, +}; + +// buffer listener +static void +wl_buffer_release(void *data, struct wl_buffer *wl_buffer); +static const struct wl_buffer_listener wl_buffer_listener = { + .release = wl_buffer_release, +}; + +// seat listener +static void +wl_seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t caps); +static void +wl_seat_name(void *data, struct wl_seat *wl_seat, const char *name); +static const struct wl_seat_listener wl_seat_listener = { + .capabilities = wl_seat_capabilities, + .name = wl_seat_name, +}; + +// output handler +extern struct wl_output_listener output_listener; + +// See wayland-pointer.c +extern const struct wl_pointer_listener wl_pointer_listener; +extern const struct zwp_pointer_constraints_listener pointer_constraints; +// See wayland-keyboard.c +extern const struct wl_keyboard_listener wl_keyboard_listener; diff --git a/src/cmd/devdraw/wayland-shm.c b/src/cmd/devdraw/wayland-shm.c new file mode 100644 index 0000000000..31e44c1f17 --- /dev/null +++ b/src/cmd/devdraw/wayland-shm.c @@ -0,0 +1,52 @@ +#define _POSIX_C_SOURCE 200112L +#define _DEFAULT_SOURCE +#include +#include +#include + +#include +#include +#include +#include + +// See also: https://github.com/a-darwish/memfd-examples +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif + +static int +memfd_create(const char *name, unsigned int flags) { + return syscall(__NR_memfd_create, name, flags); +}; + +/* +Using memfds instead of shm files makes this all a lot simpler, even including +the definitions above. + +Memfd(2) is basically, "what if malloc(3) returned a file descriptor?" +*/ + +int +allocate_shm_file(size_t size) { + int fd = memfd_create("devdraw", MFD_CLOEXEC | MFD_ALLOW_SEALING); + int ret; + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); + return fd; +} diff --git a/src/cmd/devdraw/wayland-shm.h b/src/cmd/devdraw/wayland-shm.h new file mode 100644 index 0000000000..6b3c6a76f9 --- /dev/null +++ b/src/cmd/devdraw/wayland-shm.h @@ -0,0 +1 @@ +extern int allocate_shm_file(size_t sz); diff --git a/src/cmd/fontsrv/freetyperules.sh b/src/cmd/fontsrv/freetyperules.sh index 5ca9157347..072ba7e1ed 100644 --- a/src/cmd/fontsrv/freetyperules.sh +++ b/src/cmd/fontsrv/freetyperules.sh @@ -1,6 +1,6 @@ #!/bin/sh -if [ "x$1" = "xx11" ]; then +if [ "x$1" = "xx11" -o "x$1" = xwayland ]; then if [ "x$2" = "x" ]; then i="-I/usr/include" else diff --git a/src/cmd/fontsrv/wayland.c b/src/cmd/fontsrv/wayland.c new file mode 100644 index 0000000000..53a00c662f --- /dev/null +++ b/src/cmd/fontsrv/wayland.c @@ -0,0 +1 @@ +#include "x11.c"