mirror of
https://github.com/9fans/plan9port.git
synced 2025-01-12 11:10:07 +00:00
wip: devdraw: wayland support
Absolutum obsoletum Signed-off-by: Hank Donnay <hdonnay@gmail.com>
This commit is contained in:
parent
c44015fd9a
commit
80fbccc87f
18 changed files with 2182 additions and 3 deletions
5
bin/9l
5
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
1
src/cmd/devdraw/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*-protocol.[ch]
|
|
@ -25,6 +25,8 @@ HFILES=\
|
|||
|
||||
<$PLAN9/src/mkone
|
||||
|
||||
<mkwayland
|
||||
|
||||
$O.drawclient: drawclient.$O
|
||||
$LD -o $target $prereq
|
||||
|
||||
|
|
19
src/cmd/devdraw/mkwayland
Normal file
19
src/cmd/devdraw/mkwayland
Normal 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
|
|
@ -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='
|
||||
|
|
157
src/cmd/devdraw/wayland-draw.c
Normal file
157
src/cmd/devdraw/wayland-draw.c
Normal 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);
|
||||
}
|
114
src/cmd/devdraw/wayland-inc.h
Normal file
114
src/cmd/devdraw/wayland-inc.h
Normal 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;
|
289
src/cmd/devdraw/wayland-keyboard.c
Normal file
289
src/cmd/devdraw/wayland-keyboard.c
Normal 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);
|
||||
}
|
69
src/cmd/devdraw/wayland-output.c
Normal file
69
src/cmd/devdraw/wayland-output.c
Normal 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);
|
||||
}
|
391
src/cmd/devdraw/wayland-pointer.c
Normal file
391
src/cmd/devdraw/wayland-pointer.c
Normal 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)
|
||||
{
|
||||
}
|
||||
|
995
src/cmd/devdraw/wayland-screen.c
Normal file
995
src/cmd/devdraw/wayland-screen.c
Normal 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, ®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;
|
||||
|
||||
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;
|
||||
};
|
63
src/cmd/devdraw/wayland-screen.h
Normal file
63
src/cmd/devdraw/wayland-screen.h
Normal 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;
|
52
src/cmd/devdraw/wayland-shm.c
Normal file
52
src/cmd/devdraw/wayland-shm.c
Normal 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;
|
||||
}
|
1
src/cmd/devdraw/wayland-shm.h
Normal file
1
src/cmd/devdraw/wayland-shm.h
Normal file
|
@ -0,0 +1 @@
|
|||
extern int allocate_shm_file(size_t sz);
|
|
@ -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
|
||||
|
|
1
src/cmd/fontsrv/wayland.c
Normal file
1
src/cmd/fontsrv/wayland.c
Normal file
|
@ -0,0 +1 @@
|
|||
#include "x11.c"
|
Loading…
Reference in a new issue