From bf22af822b21ef6aaf2a0fb554d555f2a21cf242 Mon Sep 17 00:00:00 2001 From: Deuce <shurd@sasktel.net> Date: Sun, 4 Jun 2023 19:00:13 -0400 Subject: [PATCH] Add X11 fullscreen and do a bunch of other fixes Just use _NET_WM_STATE protocol for fullscreen... messing around with the methods is pretty painful. Because we're not messing with other stuff, we can likely remove all the code I added to prepare for this. :D Testing this really highlighted other broken bits, so a bunch of that has been fixed as well... including the bug that Ragnorok hadn't reported yet as of last time I looked (corrupted screen when maximixed). --- src/conio/x_cio.c | 12 +++ src/conio/x_events.c | 201 ++++++++++++++++++++++++++++++------------- src/conio/x_events.h | 3 + 3 files changed, 157 insertions(+), 59 deletions(-) diff --git a/src/conio/x_cio.c b/src/conio/x_cio.c index 2aa8ff45e0..e07b3e43bc 100644 --- a/src/conio/x_cio.c +++ b/src/conio/x_cio.c @@ -519,6 +519,18 @@ int x_init(void) xp_dlclose(dl); return(-1); } + if((x11.XChangeWindowAttributes=xp_dlsym(dl,XChangeWindowAttributes))==NULL) { + xp_dlclose(dl); + return(-1); + } + if((x11.XConfigureWindow=xp_dlsym(dl,XConfigureWindow))==NULL) { + xp_dlclose(dl); + return(-1); + } + if((x11.XMoveWindow=xp_dlsym(dl,XMoveWindow))==NULL) { + xp_dlclose(dl); + return(-1); + } #ifdef WITH_XRENDER xrender_found = true; if ((dl2 = xp_dlopen(libnames2,RTLD_LAZY,1)) == NULL) { diff --git a/src/conio/x_events.c b/src/conio/x_events.c index 65cb55fbe3..dde918a190 100644 --- a/src/conio/x_events.c +++ b/src/conio/x_events.c @@ -69,6 +69,12 @@ bool x_internal_scaling = true; #define CONSOLE_CLIPBOARD XA_PRIMARY static Atom WM_DELETE_WINDOW = None; static Atom _NET_WM_PING = None; +static Atom _NET_WM_STATE = None; +static const long _NET_WM_STATE_REMOVE = 0; +static const long _NET_WM_STATE_ADD = 1; +static Atom _NET_WM_STATE_FULLSCREEN = None; +static Atom _NET_WM_STATE_MAXIMIZED_VERT = None; +static Atom _NET_WM_STATE_MAXIMIZED_HORZ = None; static Display *dpy=NULL; static Window win; @@ -98,6 +104,8 @@ static Picture xrender_src_pict = None; static Picture xrender_dst_pict = None; #endif static bool fullscreen; +static Window parent; +static Window root; /* Array of Graphics Contexts */ static GC gc; @@ -351,12 +359,12 @@ x11_get_maxsize(int *w, int *h) long *ret; unsigned long nir; unsigned long bytes_left = 4; - Window root = DefaultRootWindow(dpy); unsigned char *prop; if (dpy == NULL) return false; if (fullscreen) { + // TODO: We may not need this if we can wait for ConfigurNotify on fullscreen... return fullscreen_geometry(NULL, NULL, w, h); } else { @@ -368,8 +376,10 @@ x11_get_maxsize(int *w, int *h) for (i = 0, offset = 0; bytes_left; i++) { if (x11.XGetWindowProperty(dpy, root, x11.workarea, offset, 4, False, XA_CARDINAL, &atr, &afr, &nir, &bytes_left, &prop) != Success) break; - if (atr != XA_CARDINAL) + if (atr != XA_CARDINAL) { + x11.XFree(prop); break; + } if (nir > 0) offset += nir; ret = (long *)prop; @@ -388,6 +398,7 @@ x11_get_maxsize(int *w, int *h) // Couldn't get work area, get size of screen instead. :( if (maxw == 0 || maxh == 0) { + // We could just use root window size here... fullscreen_geometry(NULL, NULL, &maxw, &maxh); } @@ -567,9 +578,9 @@ set_icon(const void *data, size_t width, XWMHints *hints) * Leaving this here though since it is marginally "better" aside * from the insane method to create a Pixmap. */ - icn = x11.XCreatePixmap(dpy, DefaultRootWindow(dpy), width, width, depth); + icn = x11.XCreatePixmap(dpy, root, width, width, depth); igc = x11.XCreateGC(dpy, icn, GCFunction | GCForeground | GCBackground | GCGraphicsExposures, &gcv); - icn_mask = x11.XCreatePixmap(dpy, DefaultRootWindow(dpy), width, width, 1); + icn_mask = x11.XCreatePixmap(dpy, root, width, width, 1); imgc = x11.XCreateGC(dpy, icn_mask, GCFunction | GCForeground | GCBackground | GCGraphicsExposures, &gcv); fail = (!icn) || (!icn_mask); if (!fail) { @@ -700,7 +711,9 @@ static int init_window() /* Create window, but defer setting a size and GC. */ XSetWindowAttributes wa = {0}; - wincmap = x11.XCreateColormap(dpy, DefaultRootWindow(dpy), visual, AllocNone); + root = DefaultRootWindow(dpy); + parent = root; + wincmap = x11.XCreateColormap(dpy, root, visual, AllocNone); x11.XInstallColormap(dpy, wincmap); wa.colormap = wincmap; wa.background_pixel = black; @@ -712,7 +725,7 @@ static int init_window() vstat.winheight = x_cvstat.winheight = h; vstat.scaling = x_cvstat.scaling; pthread_mutex_unlock(&vstatlock); - win = x11.XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, + win = x11.XCreateWindow(dpy, parent, 0, 0, w, h, 2, depth, InputOutput, visual, CWColormap | CWBorderPixel | CWBackPixel, &wa); classhints=x11.XAllocClassHint(); @@ -749,6 +762,10 @@ static int init_window() WM_DELETE_WINDOW = x11.XInternAtom(dpy, "WM_DELETE_WINDOW", False); _NET_WM_PING = x11.XInternAtom(dpy, "_NET_WM_PING", False); + _NET_WM_STATE = x11.XInternAtom(dpy, "_NET_WM_STATE", False); + _NET_WM_STATE_FULLSCREEN = x11.XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + _NET_WM_STATE_MAXIMIZED_VERT = x11.XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_VERT", False); + _NET_WM_STATE_MAXIMIZED_HORZ = x11.XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_HORZ", False); gcv.function = GXcopy; gcv.foreground = black | 0xff000000; @@ -773,40 +790,79 @@ static int init_window() return(0); } +static bool +send_fullscreen(bool set) +{ + static bool last = false; + XEvent ev = {0}; + bool ret = false; + + if (_NET_WM_STATE != None && _NET_WM_STATE_FULLSCREEN != None) { + if (last != set) { + ev.xclient.type = ClientMessage; + ev.xclient.serial = 0; // Populated by XSendEvent + ev.xclient.send_event = True; // Populated by XSendEvent + ev.xclient.display = dpy; + ev.xclient.window = win; + ev.xclient.message_type = _NET_WM_STATE; + ev.xclient.format = 32; + memset(&ev.xclient.data.l, 0, sizeof(ev.xclient.data.l)); + ev.xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + ev.xclient.data.l[1] = _NET_WM_STATE_FULLSCREEN; + ev.xclient.data.l[3] = 1; + ret = x11.XSendEvent(dpy, root, False, SubstructureNotifyMask | SubstructureRedirectMask, &ev) != 0; + if (ret) { + x11.XFlush(dpy); + last = set; + } + } + else + ret = true; + } + return ret; +} + /* Resize the window. This function is called after a mode change. */ +// It's also called after scaling is changed! static void resize_window() { - int width = x_cvstat.scrnwidth * x_cvstat.scaling; - int height = x_cvstat.scrnheight * x_cvstat.scaling; + int width; + int height; int max_width, max_height; + bool resize; + double new_scaling; pthread_mutex_lock(&vstatlock); - if (fullscreen) { - // TODO: Position window correctly... - fullscreen_geometry(NULL, NULL, &width, &height); + if (fullscreen && send_fullscreen(true)) { } else { - bitmap_get_scaled_win_size(x_cvstat.scaling, &width, &height, 0, 0); + send_fullscreen(false); + fullscreen = false; + new_scaling = x_cvstat.scaling; + bitmap_get_scaled_win_size(new_scaling, &width, &height, 0, 0); if (x11_get_maxsize(&max_width, &max_height)) { if (width > max_width || height > max_height) { - x_cvstat.scaling = bitmap_double_mult_inside(max_width, max_height); - bitmap_get_scaled_win_size(x_cvstat.scaling, &width, &height, 0, 0); + new_scaling = bitmap_double_mult_inside(max_width, max_height); + bitmap_get_scaled_win_size(new_scaling, &width, &height, 0, 0); } } + new_scaling = bitmap_double_mult_inside(width, height); if (width == vstat.winwidth && height == vstat.winheight) { - x_cvstat.scaling = bitmap_double_mult_inside(width, height); - vstat.scaling = x_cvstat.scaling; - pthread_mutex_unlock(&vstatlock); - resize_xim(); + if (new_scaling != vstat.scaling) { + vstat.scaling = x_cvstat.scaling = new_scaling; + pthread_mutex_unlock(&vstatlock); + resize_xim(); + } + else + pthread_mutex_unlock(&vstatlock); return; } + resize = new_scaling != vstat.scaling; + x_cvstat.scaling = vstat.scaling; + if (resize) + x11.XResizeWindow(dpy, win, width, height); } - x_cvstat.winwidth = width; - x_cvstat.winheight = height; - x_cvstat.scaling = bitmap_double_mult_inside(width, height); pthread_mutex_unlock(&vstatlock); - x11.XResizeWindow(dpy, win, width, height); - resize_xim(); return; } @@ -1074,39 +1130,20 @@ local_draw_rect(struct rectlist *rect) static void handle_resize_event(int width, int height) { - bool resize = false; - double new_scaling; - - if (fullscreen) { - fullscreen = false; - resize = true; - } pthread_mutex_lock(&vstatlock); - vstat.winwidth = width; - vstat.winheight = height; - new_scaling = bitmap_double_mult_inside(width, height); - if (new_scaling != vstat.scaling) { - x_cvstat.scaling = new_scaling; - resize = true; - } - if (new_scaling > 16) { - new_scaling = 16; - pthread_mutex_lock(&scalinglock); - pthread_mutex_unlock(&scalinglock); - x_cvstat.scaling = new_scaling; - resize = true; + if (fullscreen) { + if ((width != x_cvstat.winwidth || height != x_cvstat.winheight)) { + fullscreen = false; + } } + x_cvstat.winwidth = vstat.winwidth = width; + x_cvstat.winheight = vstat.winheight = height; + vstat.scaling = bitmap_double_mult_inside(width, height); + if (vstat.scaling > 16) + vstat.scaling = 16; + x_cvstat.scaling = vstat.scaling; pthread_mutex_unlock(&vstatlock); - /* - * We only need to resize if the width/height are not even multiples, - * or if the two axis don't scale the same way. - * Otherwise, we can simply resend everything - */ - if (resize) { - resize_window(); - } - else - resize_xim(); + resize_xim(); bitmap_drv_request_pixels(); } @@ -1208,29 +1245,73 @@ xlat_mouse_xy(int *x, int *y) return true; } +bool +is_maximized(void) +{ + bool is = false; + Atom atr; + int afr; + Atom *ret; + unsigned long nir; + unsigned long bytes_left = 4; + unsigned char *prop; + size_t offset; + + if (_NET_WM_STATE != None) { + for (offset = 0; bytes_left; offset += nir) { + if (x11.XGetWindowProperty(dpy, win, _NET_WM_STATE, offset, 1, False, XA_ATOM, &atr, &afr, &nir, &bytes_left, &prop) != Success) + break; + if (atr != XA_ATOM) { + x11.XFree(prop); + break; + } + ret = (Atom *)prop; + if (*ret == _NET_WM_STATE_MAXIMIZED_VERT || *ret == _NET_WM_STATE_MAXIMIZED_HORZ) + is = true; + x11.XFree(prop); + if (is) + break; + } + } + return is; +} + static int x11_event(XEvent *ev) { if (x11.XFilterEvent(ev, win)) return 0; switch (ev->type) { + case ReparentNotify: + parent = ev->xreparent.parent; + break; case ClientMessage: if (ev->xclient.format == 32 && ev->xclient.data.l[0] == WM_DELETE_WINDOW && WM_DELETE_WINDOW != None) { uint16_t key=CIO_KEY_QUIT; write(key_pipe[1], &key, 2); } else if(ev->xclient.format == 32 && ev->xclient.data.l[0] == _NET_WM_PING && _NET_WM_PING != None) { - ev->xclient.window = DefaultRootWindow(dpy); + ev->xclient.window = root; x11.XSendEvent(dpy, ev->xclient.window, False, SubstructureNotifyMask | SubstructureRedirectMask, ev); } break; /* Graphics related events */ case ConfigureNotify: { bool resize = false; + int ax, ay; + Window cr; + // TODO: Maybe we care about parent events? if (ev->xconfigure.window == win) { - if (x11_window_xpos != ev->xconfigure.x || x11_window_ypos != ev->xconfigure.y) { - x11_window_xpos=ev->xconfigure.x; - x11_window_ypos=ev->xconfigure.y; + // TODO: Verify this hack... + if (ev->xconfigure.above != None) + x11.XTranslateCoordinates(dpy, ev->xconfigure.window, root, ev->xconfigure.x, ev->xconfigure.y, &ax, &ay, &cr); + else { + ax = ev->xconfigure.x; + ay = ev->xconfigure.y; + } + if ((x11_window_xpos != ax || x11_window_ypos != ay)) { + x11_window_xpos = ax; + x11_window_ypos = ay; } pthread_mutex_lock(&vstatlock); if (ev->xconfigure.width != vstat.winwidth || ev->xconfigure.height != vstat.winheight) { @@ -1482,6 +1563,8 @@ static int x11_event(XEvent *ev) case XK_KP_Enter: if (ev->xkey.state & Mod1Mask) { // ALT-Enter, toggle full-screen + fullscreen = !fullscreen; + resize_window(); } scan = 28; goto docode; @@ -1532,7 +1615,7 @@ static int x11_event(XEvent *ev) nlock = 1; case XK_Left: case XK_KP_Left: - if (ev->xkey.state & Mod1Mask) { + if (ev->xkey.state & Mod1Mask && !is_maximized()) { double fval; double ival; fval = modf(x_cvstat.scaling, &ival); @@ -1561,7 +1644,7 @@ static int x11_event(XEvent *ev) case XK_Right: case XK_KP_Right: scan = 77; - if (ev->xkey.state & Mod1Mask) { + if (ev->xkey.state & Mod1Mask && !is_maximized()) { double ival; modf(x_cvstat.scaling, &ival); if (ival < 1.0) diff --git a/src/conio/x_events.h b/src/conio/x_events.h index e8a6c5f057..2f3d7f9bd0 100644 --- a/src/conio/x_events.h +++ b/src/conio/x_events.h @@ -113,6 +113,9 @@ struct x11 { XWMHints *(*XGetWMHints)(Display *, Window); int (*XSetWMHints)(Display *, Window, XWMHints *); Bool (*XTranslateCoordinates)(Display *, Window, Window, int, int, int *, int *, Window *); + int (*XChangeWindowAttributes)(Display *, Window, unsigned long, XSetWindowAttributes *); + int (*XConfigureWindow)(Display *, Window, unsigned int, XWindowChanges *); + int (*XMoveWindow)(Display *, Window, int, int); #ifndef DefaultDepth int (*DefaultDepth)(Display *, int); #endif -- GitLab