wip: devdraw: wayland support

Absolutum obsoletum

Signed-off-by: Hank Donnay <hdonnay@gmail.com>
This commit is contained in:
Hank Donnay 2022-04-17 14:26:07 -05:00
parent c44015fd9a
commit 80fbccc87f
18 changed files with 2182 additions and 3 deletions

5
bin/9l
View file

@ -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

View file

@ -9,7 +9,7 @@ invoked via
.SH DESCRIPTION
.I Devdraw
serves a custom graphics protocol and is the only program
that talks directly to X window servers.
that talks directly to host OS window servers.
On Macintosh, setting
.BI devdrawretina
to
@ -17,6 +17,9 @@ to
will cause
.I devdraw
to use all available physical pixels on a retina display.
.PP
On Linux with Wayland support, mouse button 1 on the frame will allow
dragging to resize, and button 2 will allow for moving the frame.
.SH SOURCE
.B \*9/src/cmd/devdraw
.SH "SEE ALSO

View file

@ -99,8 +99,16 @@ and
Values more complex than single words should be quoted
with single quotes.
.PP
On modern Linux systems, wayland development packages need to be installed to build native wayland support.
On Fedora, the required packages are
.BR wayland-devel ,
.BR wayland-protocol-devel ,
.BR libxkbcommon-devel ,
.BR freetype-devel ,
and
.BR fontconfig-devel .
On most Linux systems, the X11 header packages need to be installed
to build using X11. On Debian. the required packages are
to build using X11. On Debian, the required packages are
libfontconfig1-dev, libx11-dev, libxext-dev, and libxt-dev.
On Ubuntu, it suffices to install xorg-dev.
.PP

1
src/cmd/devdraw/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*-protocol.[ch]

View file

@ -25,6 +25,8 @@ HFILES=\
<$PLAN9/src/mkone
<mkwayland
$O.drawclient: drawclient.$O
$LD -o $target $prereq

19
src/cmd/devdraw/mkwayland Normal file
View file

@ -0,0 +1,19 @@
way_proto=/usr/share/wayland-protocols
xdg-shell-protocol.c: $way_proto/stable/xdg-shell/xdg-shell.xml
wayland-scanner private-code < $prereq > $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

View file

@ -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='

View file

@ -0,0 +1,157 @@
#include <u.h>
#include "wayland-inc.h"
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include "wayland-shm.h"
#include <sys/mman.h>
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);
}

View file

@ -0,0 +1,114 @@
#include <u.h>
#include <libc.h>
#include <wayland-client.h>
#include <wayland-cursor.h>
#include <xkbcommon/xkbcommon.h>
#include "xdg-shell-client-protocol.h"
#include "xdg-decoration-client-protocol.h"
#include "pointer-constraints-client-protocol.h"
struct bounds {
int x, y;
};
struct output_elem {
struct wl_output *o;
int scale, dpi, on;
double px, mm;
struct wl_list link;
};
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;
struct wl_list output_list;
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 *pool;
} decoration;
struct bounds bounds, cursize, wantsize;
int scale;
int resizing;
// 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_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;
extern const int curBufSz;

View file

@ -0,0 +1,289 @@
#include <u.h>
#include <libc.h>
#include "wayland-inc.h"
#include <draw.h>
#include <memdraw.h>
#include <memlayer.h>
#include <keyboard.h>
#include <mouse.h>
#include <cursor.h>
#include "devdraw.h"
#include <sys/mman.h>
AUTOLIB(xkbcommon);
static void keymap(void *opaque, struct wl_keyboard *kb, uint32_t format, int32_t fd, uint32_t size);
static void enter(void *opaque, struct wl_keyboard *kb, uint32_t serial, struct wl_surface *surface, struct wl_array *keys);
static void leave(void *opaque, struct wl_keyboard *kb, uint32_t serial, struct wl_surface *surface);
static void key(void *opaque, struct wl_keyboard *kb, uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
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);
static void repeat_info(void *opaque, struct wl_keyboard *kb, int32_t rate, int32_t 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);
static void repeat_cb(void *opaque, struct wl_callback *cb, uint time);
// This callback handles key repeat events by repeatedly scheduling itself
// while a key is held down.
static const struct wl_callback_listener repeat_listener = {
.done = repeat_cb,
};
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;
w->kb.event = WL_KEYBOARD_KEY_STATE_RELEASED;
}
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;
}
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;
}
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);
}

View file

@ -0,0 +1,69 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <memlayer.h>
#include <mouse.h>
#include <cursor.h>
#include "devdraw.h"
#include "wayland-inc.h"
#include <math.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);
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);
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,
};
static const double mmtoinch = 25.4;
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)
{
struct output_elem *e;
e = opaque;
e->mm = sqrt(pow(physical_width, 2) + pow(physical_height, 2));
}
static void
wl_output_mode(void *opaque, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh)
{
struct output_elem *e;
e = opaque;
e->px = sqrt(pow(width, 2) + pow(height, 2));
}
static void
wl_output_scale(void *opaque, struct wl_output *wl_output, int32_t factor)
{
struct output_elem *e;
e = opaque;
e->scale = 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)
{
struct output_elem *e;
e = opaque;
e->dpi = (int)(e->px / (e->mm / mmtoinch));
//fprint(2, "output: %fpx/%fmm(%d)@%d\n", e->px, e->mm, e->dpi, e->scale);
}

View file

@ -0,0 +1,391 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <memlayer.h>
#include <mouse.h>
#include <cursor.h>
#include "devdraw.h"
#include "bigarrow.h"
#include "wayland-inc.h"
// Big enough for a Cursor2:
const int curBufSz = 32 * 32 * sizeof(uint32);
static void enter(void *opaque, struct wl_pointer *p, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y);
static void leave(void *opaque, struct wl_pointer *p, uint32_t serial, struct wl_surface *surface);
static void motion(void *opaque, struct wl_pointer *p, uint32_t time, wl_fixed_t x, wl_fixed_t y);
static void button(void *opaque, struct wl_pointer *p, uint32_t serial, uint32_t time, uint32_t button, uint32_t state);
static void frame(void *opaque, struct wl_pointer *p);
static void axis(void *opaque, struct wl_pointer *p, uint32 time, uint32 axis, wl_fixed_t val);
static void axis_discrete(void *opaque, struct wl_pointer *p, uint32 axis, int32 discrete);
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,
};
static void
cursor_buffer_release(void *opaque, struct wl_buffer *buf)
{
wl_buffer_destroy(buf);
};
static const struct wl_buffer_listener cursor_buffer_listener = {
.release = cursor_buffer_release,
};
void
internal_setcursor(Client *c, Cursor *cur, Cursor2 *cur2)
{
struct wl_buffer *curBuf;
window *w;
int off;
int curSz;
w = (void *)c->view;
if (cur == nil && cur2 == nil) {
cur2 = &bigarrow2;
cur = &bigarrow;
}
off = 0;
memset((void *)w->cursor.buf, 0x00, curBufSz);
if (w->scale > 1) {
curSz = 32;
for (int y = 0; y < curSz; y++) { // y line, same in mask and px
int r = y * sizeof(uint32);
uint32 clrrow = cur2->clr[r + 0] << 24 | cur2->clr[r + 1] << 16 | cur2->clr[r + 2] << 8 | cur2->clr[r + 3];
uint32 setrow = cur2->set[r + 0] << 24 | cur2->set[r + 1] << 16 | cur2->set[r + 2] << 8 | cur2->set[r + 3];
for (int x = 0; x < curSz; x++) { // for each bit / pixel
uint32 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++;
}
}
} else {
curSz = 16;
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++;
}
off += 16;
}
}
curBuf = wl_shm_pool_create_buffer(w->cursor.pool, 0, curSz, curSz, 32 * sizeof(uint32), WL_SHM_FORMAT_ARGB8888);
wl_buffer_add_listener(curBuf, &cursor_buffer_listener, nil);
w->cursor.x = -cur->offset.x;
w->cursor.y = -cur->offset.y;
wl_surface_attach(w->cursor.surface, curBuf, 0, 0);
wl_surface_set_buffer_scale(w->cursor.surface, (w->scale > 1) ? 2 : 1);
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);
}
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, dx, dy;
char *name;
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;
}
}
fprint(2, "martian surface\n");
return;
Found:
w->m.S = serial;
w->m.X = wl_fixed_to_int(x);
w->m.Y = wl_fixed_to_int(y);
dx = Dx(c->screenimage->r);
dy = Dy(c->screenimage->r);
switch (w->m.in) {
case 0:
if (w->m.X < 50)
name = "nw-resize";
else if ((dx - w->m.X) < 50)
name = "ne-resize";
else
name = "n-resize";
break;
case 1:
if (w->m.X < 50)
name = "sw-resize";
else if ((dx - w->m.X) < 50)
name = "se-resize";
else
name = "s-resize";
break;
case 2:
if (w->m.Y < 50)
name = "nw-resize";
else if ((dy - w->m.Y) < 50)
name = "sw-resize";
else
name = "w-resize";
break;
case 3:
if (w->m.Y < 50)
name = "ne-resize";
else if ((dy - w->m.Y) < 50)
name = "se-resize";
else
name = "e-resize";
break;
case 4:
internal_setcursor(c, nil, nil);
goto Done;
}
Again:
cur = wl_cursor_theme_get_cursor(w->global->wl_cursor_theme, name);
if (cur == nil) {
name = "left_ptr";
goto Again;
}
img = cur->images[0];
w->cursor.x = img->hotspot_x;
w->cursor.y = img->hotspot_y;
buf = wl_cursor_image_get_buffer(img);
wl_surface_attach(w->cursor.surface, buf, 0, 0);
wl_surface_commit(w->cursor.surface);
wl_pointer_set_cursor(p, w->m.S, w->cursor.surface, w->cursor.x, w->cursor.y);
Done:
wl_display_flush(w->global->wl_display);
}
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)
{
}

View file

@ -0,0 +1,995 @@
#define _GNU_SOURCE
#include <u.h>
#include <libc.h>
#include "wayland-inc.h"
#include <draw.h>
#include <memdraw.h>
#include <memlayer.h>
#include <keyboard.h>
#include <mouse.h>
#include <cursor.h>
#include <thread.h>
#include <mp.h>
#include <libsec.h>
#include "devdraw.h"
#include "wayland-screen.h"
#include "wayland-shm.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <errno.h>
Globals procState = {0};
const char *dataMimeType = "text/plain;charset=utf-8";
static const int _border = 4;
static struct wl_data_device_listener data_device_listener;
extern const struct wl_keyboard_listener wl_keyboard_listener; // See wayland-keyboard.c
extern void
internal_setcursor(Client *c, Cursor *cur, Cursor2 *cur2);
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;
wl_list_init(&g->output_list);
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, &registry_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;
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);
// Load small cursors by default.
g->wl_cursor_theme = wl_cursor_theme_load(NULL, 16, 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, wl_output_interface.name) == 0) {
struct output_elem *e;
e = malloc(sizeof(struct output_elem));
e->o = wl_registry_bind(wl_registry, name, &wl_output_interface, 2);
wl_output_add_listener(e->o, &output_listener, e);
wl_list_insert(&g->output_list, &e->link);
} 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, 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->resizing = 1;
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, scale, stride, ppb;
g = w->global;
wd = Dx(r);
ht = Dy(r);
scale = w->scale;
skipv = (w->bounds.x != 0 && (wd + (2 * _border)) > w->bounds.x);
skiph = (w->bounds.y != 0 && (ht + (2 * _border)) > w->bounds.y);
ppb = _border * scale;
stride = ppb * sizeof(uint32);
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, -_border);
break;
case 2:
wl_subsurface_set_position(e->subsurface, -_border, -_border);
break;
}
}
// It'd be better if this could have a 0 stride, but that seems
// disallowed.
//
// As is, we keep one pool of pixels for the borders, slice them up as
// vertical rectangles, and then ask the compositor to rotate if
// needed.
switch (i) {
case 1:
wl_subsurface_set_position(e->subsurface, 0, ht);
case 0:
buf = wl_shm_pool_create_buffer(w->decoration.pool, 0, ppb, (wd * scale), stride, WL_SHM_FORMAT_XRGB8888);
wl_surface_set_buffer_transform(e->surface, WL_OUTPUT_TRANSFORM_90);
break;
case 3:
wl_subsurface_set_position(e->subsurface, wd, -_border);
case 2:
buf = wl_shm_pool_create_buffer(w->decoration.pool, 0, ppb, (ht * scale) + (2 * ppb), stride, WL_SHM_FORMAT_XRGB8888);
break;
}
wl_buffer_add_listener(buf, &wl_buffer_listener, nil);
wl_surface_set_buffer_scale(e->surface, scale);
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, *cur;
Memdata *md;
w = (void *)c->view;
r = Rect(0, 0, w->wantsize.x, w->wantsize.y);
cur = c->screenimage;
if (rectinrect(r, cur->r)) {
md = c->screenimage->data;
m = allocmemimaged(r, strtochan("x8r8g8b8"), md, cur->X);
} else {
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;
char id[64];
w = c->view;
snprint(id, 64, "plan9port.%s", label);
xdg_toplevel_set_title(w->xdg_toplevel, label);
}
static void
flush_release(void *opaque, struct wl_buffer *buf)
{
int *done;
done = opaque;
wl_buffer_destroy(buf);
*done = 1;
}
const struct wl_buffer_listener flush_listener = {
.release = flush_release,
};
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, scale, done, ret;
w = (void *)c->view;
if (w->resizing == 1)
return;
done = 0;
scale = (w->scale > 1) ? 2 : 1;
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) * scale;
y = Dy(i->r) * scale;
assert(y > 0 && x > 0);
buf = wl_shm_pool_create_buffer(pool, off, x, y, i->width * scale * sizeof(uint32), WL_SHM_FORMAT_XRGB8888);
wl_shm_pool_destroy(pool);
wl_buffer_add_listener(buf, &flush_listener, &done);
wl_surface_attach(w->wl_surface, buf, 0, 0);
wl_surface_set_buffer_scale(w->wl_surface, scale);
wl_surface_damage_buffer(w->wl_surface, r.min.x * scale, r.min.y * scale, Dx(r) * scale, Dy(r) * scale);
// We get border updates "for free" because they're synced with the parent
// surface.
wl_surface_commit(w->wl_surface);
while (done == 0 && ret >= 0)
ret = wl_display_flush(w->global->wl_display);
}
void
rpc_setcursor(Client *c, Cursor *cur, Cursor2 *cur2)
{
window *w;
w = (void *)c->view;
internal_setcursor(c, cur, cur2);
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 void
setscale(Client *c, struct wl_list *list)
{
struct output_elem *e;
window *w;
int scale, dpi;
scale = 1;
dpi = 100;
w = (void *)c->view;
wl_list_for_each(e, list, link)
{
if (e->on != 1)
continue;
if (e->scale > scale)
scale = e->scale;
if (e->dpi > dpi)
dpi = e->dpi;
}
c->displaydpi = dpi;
w->scale = scale;
}
static void
surface_enter(void *opaque, struct wl_surface *s, struct wl_output *o)
{
Client *c;
Globals *g;
window *w;
struct output_elem *e;
c = opaque;
w = (void *)c->view;
g = w->global;
wl_list_for_each(e, &g->output_list, link)
{
if (e->o == o)
e->on = 1;
}
setscale(c, &g->output_list);
// wl_output_add_listener(o, &output_listener, opaque);
}
static void
surface_leave(void *opaque, struct wl_surface *s, struct wl_output *o)
{
Client *c;
Globals *g;
window *w;
struct output_elem *e;
c = opaque;
w = (void *)c->view;
g = w->global;
wl_list_for_each(e, &g->output_list, link)
{
if (e->o == o)
e->on = 0;
}
setscale(c, &g->output_list);
}
static const struct wl_surface_listener wl_surface_listener = {
.enter = surface_enter,
.leave = surface_leave,
};
// Guesses the parent's arg0 by looking at the cmdline file in proc.
//
// Pgid might be a better pid to look at, but requires looking at whether rc(1)
// uses setpgid the same way bash(1) et.al. do.
static char *
guessappid(void)
{
int ppid;
char buf[64];
int fd;
ppid = getppid();
snprint(buf, 64, "/proc/%d/cmdline", ppid);
fd = open(buf, OREAD);
if (fd == -1)
return nil;
read(fd, buf, 64);
close(fd);
buf[63] = 0x00;
return strdup(buf);
}
static window *
setupwindow(Client *c, Globals *g)
{
window *w;
char *app_id;
struct zxdg_toplevel_decoration_v1 *d;
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;
}
app_id = guessappid();
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);
wl_surface_add_listener(w->wl_surface, &wl_surface_listener, c);
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");
if (app_id == nil)
xdg_toplevel_set_app_id(w->xdg_toplevel, "devdraw");
else {
fprint(2, "app_id: %s\n", app_id);
xdg_toplevel_set_app_id(w->xdg_toplevel, app_id);
free(app_id);
}
// Signal we're drawing our own decoration (for better or worse).
if (g->zxdg_decoration_manager_v1 != NULL) {
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);
}
setscale(c, &g->output_list);
setupdecoration(w);
return w;
};
static void
setupdecoration(window *w)
{
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 > y) ? x : y + (2 * _border)) * _border * sizeof(uint32);
fd = allocate_shm_file(sz);
map = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
w->decoration.pool = wl_shm_create_pool(w->global->wl_shm, fd, sz);
close(fd);
for (int i = 0; i * sizeof(uint32) < sz; i++)
map[i] = 0x55AAAA;
};
// 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);
r.max = mulpt(r.max, (w->scale > 1) ? 2 : 1);
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;
};

View file

@ -0,0 +1,63 @@
// 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;

View file

@ -0,0 +1,52 @@
#define _POSIX_C_SOURCE 200112L
#define _DEFAULT_SOURCE
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/memfd.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
// 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;
}

View file

@ -0,0 +1 @@
extern int allocate_shm_file(size_t sz);

View file

@ -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

View file

@ -0,0 +1 @@
#include "x11.c"