123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- /*
- * ui_x11.h
- * https://gitlab.com/bztsrc/smgui
- *
- * Copyright (C) 2024 bzt (bztsrc@gitlab), MIT license
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ANY
- * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
- * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- *
- * @brief X11 backend for SMGUI
- */
- #ifndef UI_X11_H
- #define UI_X11_H 1
- #ifndef UI_BACKEND
- #define UI_BACKEND 1
- #else
- #error "An UI backend has already been included"
- #endif
- #include <stdint.h>
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <Xlib.h>
- #include <Xutil.h>
- #include <Xos.h>
- #include <Xatom.h>
- #include <Xmu/Atoms.h>
- #include <keysymdef.h>
- #ifndef UI_H
- #include <ui.h>
- #undef UI_H
- #endif
- #ifdef __cplusplus
- extern "C" {
- #endif
- /**
- * The UI backend structure, it's backend specific
- */
- typedef struct ui_backend_s {
- void *ctx;
- Display *display;
- Visual *visual;
- int screen, btn, fullscr;
- Window window;
- GC gc;
- Atom wmDel, wmState, wmFullscreen, target, CLIPBOARD, TARGETS, UTF8, XSEL_DATA;
- char selection[1024];
- } ui_backend_t;
- /**
- * Toggle fullscreen
- */
- int ui_backend_fullscreen(ui_backend_t *bck)
- {
- XEvent xev;
- long evmask = SubstructureRedirectMask | SubstructureNotifyMask;
- if(!bck || !bck->display || !bck->window) return UI_ERR_BADINP;
- if(bck->wmState == None || bck->wmFullscreen == None) return UI_ERR_BACKEND;
- bck->fullscr ^= 1;
- XChangeProperty(bck->display, bck->window, bck->wmState, XA_ATOM, 32, PropModeReplace,
- (uint8_t*)&bck->wmFullscreen, bck->fullscr);
- xev.type = ClientMessage;
- xev.xclient.window = bck->window;
- xev.xclient.message_type = bck->wmState;
- xev.xclient.format = 32;
- xev.xclient.data.l[0] = 2;
- xev.xclient.data.l[1] = bck->wmFullscreen;
- xev.xclient.data.l[2] = 0;
- xev.xclient.data.l[3] = 1;
- xev.xclient.data.l[4] = 0;
- if(!XSendEvent(bck->display, RootWindow(bck->display, bck->screen), 0, evmask, &xev))
- return UI_ERR_BACKEND;
- return UI_OK;
- }
- /**
- * Set window focus
- */
- int ui_backend_focus(ui_backend_t *bck)
- {
- if(!bck || !bck->display || !bck->window) return UI_ERR_BADINP;
- XMapWindow(bck->display, bck->window);
- XRaiseWindow(bck->display, bck->window);
- XFlush(bck->display);
- return UI_OK;
- }
- /**
- * Set window title
- */
- int ui_backend_settitle(ui_backend_t *bck, char *title)
- {
- XTextProperty title_property;
- int i;
- if(!bck || !title || !*title || !bck->display || !bck->window) return UI_ERR_BADINP;
- if((i = XStringListToTextProperty(&title, 1, &title_property))) {
- XSetWMName(bck->display, bck->window, &title_property);
- XSetWMIconName(bck->display, bck->window, &title_property);
- }
- return UI_OK;
- }
- /**
- * Get the clipboard's text
- */
- char *ui_backend_getclipboard(ui_backend_t *bck)
- {
- const char *str = (const char*)bck->selection;
- char *ret = NULL;
- if(!bck) return NULL;
- if(*str && (ret = (char*)malloc(strlen(str) + 1)))
- strcpy(ret, str);
- return ret;
- }
- /**
- * Set the clipboard's text
- */
- int ui_backend_setclipboard(ui_backend_t *bck, char *str)
- {
- if(!bck || !str || !bck->display || !bck->window) return UI_ERR_BADINP;
- strncpy(bck->selection, str, sizeof(bck->selection) - 1);
- XSetSelectionOwner(bck->display, XA_CLIPBOARD(bck->display), bck->window, CurrentTime);
- return UI_OK;
- }
- /**
- * Hide hardware mouse cursor
- */
- int ui_backend_hidecursor(ui_backend_t *bck)
- {
- if(!bck || !bck->display || !bck->window) return UI_ERR_BADINP;
- return UI_OK;
- }
- /**
- * Show hardware mouse cursor
- */
- int ui_backend_showcursor(ui_backend_t *bck)
- {
- if(!bck || !bck->display || !bck->window) return UI_ERR_BADINP;
- return UI_OK;
- }
- /**
- * Hide OS-native on-screen keyboard
- */
- int ui_backend_hideosk(ui_backend_t *bck)
- {
- (void)bck;
- return UI_OK;
- }
- /**
- * Show OS-native on-screen keyboard
- */
- int ui_backend_showosk(ui_backend_t *bck)
- {
- (void)bck;
- return UI_OK;
- }
- /**
- * Initialize the backend
- */
- int ui_backend_init(ui_t *ctx, char *title, int w, int h, ui_image_t *icon)
- {
- Atom net_wm_icon, cardinal;
- uint32_t *ptr;
- ui_backend_t *bck;
- if(!ctx || !title || !*title || w < 1 || h < 1) return UI_ERR_BADINP;
- if(!(ctx->bck = bck = (ui_backend_t*)realloc(ctx->bck, sizeof(ui_backend_t)))) return UI_ERR_NOMEM;
- memset(bck, 0, sizeof(ui_backend_t));
- bck->ctx = ctx;
- if(!(bck->display = XOpenDisplay(getenv("DISPLAY")))) return UI_ERR_BACKEND;
- bck->screen = DefaultScreen(bck->display);
- if(!(bck->visual = DefaultVisual(bck->display, bck->screen)) ||
- !(bck->window = XCreateSimpleWindow(bck->display, RootWindow(bck->display, bck->screen), 0, 0, w, h, 0, 0, 0))) {
- XCloseDisplay(bck->display); return UI_ERR_BACKEND;
- }
- XSelectInput(bck->display, bck->window, KeyPressMask|KeyReleaseMask|ButtonPressMask|ButtonReleaseMask|
- ButtonMotionMask|PointerMotionMask|ExposureMask|StructureNotifyMask);
- bck->gc = XCreateGC(bck->display, bck->window, 0, 0);
- bck->wmState = XInternAtom(bck->display, "_NET_WM_STATE", 0);
- bck->wmFullscreen = XInternAtom(bck->display, "_NET_WM_STATE_FULLSCREEN", 0);
- bck->wmDel = XInternAtom(bck->display, "WM_DELETE_WINDOW", False);
- XSetWMProtocols(bck->display, bck->window, &bck->wmDel, 1);
- bck->UTF8 = XInternAtom(bck->display, "UTF8_STRING", 0); if(bck->UTF8 == None) bck->UTF8 = XA_STRING;
- bck->CLIPBOARD = XInternAtom(bck->display, "CLIPBOARD", 0);
- bck->XSEL_DATA = XInternAtom(bck->display, "XSEL_DATA", 0);
- XSetSelectionOwner(bck->display, bck->CLIPBOARD, bck->window, CurrentTime);
- if(icon && icon->buf && (ptr = (uint32_t*)malloc((2 + icon->w * icon->h) * sizeof(uint32_t)))) {
- ptr[0] = icon->w; ptr[1] = icon->h;
- memcpy(&ptr[2], icon->buf, icon->w * icon->h * sizeof(uint32_t));
- net_wm_icon = XInternAtom(bck->display, "_NET_WM_ICON", False);
- cardinal = XInternAtom(bck->display, "CARDINAL", False);
- XChangeProperty(bck->display, bck->window, net_wm_icon, cardinal, 32, PropModeReplace, (uint8_t*)ptr, 2 + icon->w * icon->h);
- free(ptr);
- }
- ui_backend_settitle(bck, title);
- ui_backend_focus(bck);
- return UI_OK;
- }
- /**
- * Return a backend specific window handle
- */
- void *ui_backend_getwindow(ui_backend_t *bck)
- {
- return bck ? (void*)bck->window : NULL;
- }
- /**
- * Main event handler
- */
- int ui_backend_event(ui_backend_t *bck)
- {
- XEvent e = { 0 }, r = { 0 };
- ui_event_t *evt;
- char keystr[8] = { 0 };
- int m;
- KeySym key;
- unsigned long N, size, i;
- Atom target, *data = NULL;
- if(!bck || !bck->display || !bck->window) return UI_ERR_BADINP;
- while(XPending(bck->display)) {
- XNextEvent(bck->display, &e);
- switch(e.type) {
- case ClientMessage: if((Atom)e.xclient.data.l[0] == bck->wmDel) return 1; break;
- case ConfigureNotify: _ui_resize(bck->ctx, e.xconfigure.width, e.xconfigure.height); break;
- case ButtonPress:
- _ui_setmouse(bck->ctx, e.xbutton.x, e.xbutton.y);
- m = 0;
- switch(e.xbutton.button) {
- case 1: m = UI_BTN_L; break;
- case 2: m = UI_BTN_M; break;
- case 3: m = UI_BTN_R; break;
- case 4: m = UI_BTN_U; break;
- case 5: m = UI_BTN_D; break;
- case 6: m = UI_BTN_A; break;
- case 7: m = UI_BTN_B; break;
- }
- bck->btn |= m;
- if((evt = _ui_evtslot(bck->ctx))) {
- evt->x = e.xbutton.x;
- evt->y = e.xbutton.y;
- evt->btn = bck->btn;
- evt->type = UI_EVT_MOUSE;
- }
- break;
- case ButtonRelease:
- _ui_setmouse(bck->ctx, e.xbutton.x, e.xbutton.y);
- m = 0;
- switch(e.xbutton.button) {
- case 1: m = UI_BTN_L; break;
- case 2: m = UI_BTN_M; break;
- case 3: m = UI_BTN_R; break;
- case 4: m = UI_BTN_U; break;
- case 5: m = UI_BTN_D; break;
- case 6: m = UI_BTN_A; break;
- case 7: m = UI_BTN_B; break;
- }
- bck->btn &= ~m;
- if((evt = _ui_evtslot(bck->ctx))) {
- evt->x = e.xbutton.x;
- evt->y = e.xbutton.y;
- evt->btn = bck->btn | UI_BTN_RELEASE;
- evt->type = UI_EVT_MOUSE;
- }
- break;
- case MotionNotify: _ui_setmouse(bck->ctx, e.xmotion.x, e.xmotion.y); break;
- case KeyPress:
- key = XLookupKeysym(&e.xkey, 0);
- switch(key) {
- case XK_Shift_L: bck->btn |= UI_BTN_SHIFT; memcpy(keystr, "LShift", 6); break;
- case XK_Shift_R: bck->btn |= UI_BTN_SHIFT; memcpy(keystr, "RShift", 6); break;
- case XK_Control_L: bck->btn |= UI_BTN_CONTROL; memcpy(keystr, "LCtrl", 5); break;
- case XK_Control_R: bck->btn |= UI_BTN_CONTROL; memcpy(keystr, "RCtrl", 5); break;
- case XK_Alt_L: bck->btn |= UI_BTN_ALT; memcpy(keystr, "LAlt", 4); break;
- case XK_Alt_R: bck->btn |= UI_BTN_ALT; memcpy(keystr, "RAlt", 4); break;
- case XK_Super_L: bck->btn |= UI_BTN_GUI; memcpy(keystr, "LGui", 4); break;
- case XK_Super_R: bck->btn |= UI_BTN_GUI; memcpy(keystr, "RGui", 4); break;
- case XK_Escape: keystr[0] = 0x1b; break;
- case XK_BackSpace: keystr[0] = '\b'; break;
- case XK_Tab: keystr[0] = '\t'; break;
- case XK_Return: keystr[0] = '\n'; break;
- case XK_F1: memcpy(keystr, "F1", 2); break;
- case XK_F2: memcpy(keystr, "F2", 2); break;
- case XK_F3: memcpy(keystr, "F3", 2); break;
- case XK_F4: memcpy(keystr, "F4", 2); break;
- case XK_F5: memcpy(keystr, "F5", 2); break;
- case XK_F6: memcpy(keystr, "F6", 2); break;
- case XK_F7: memcpy(keystr, "F7", 2); break;
- case XK_F8: memcpy(keystr, "F8", 2); break;
- case XK_F9: memcpy(keystr, "F9", 2); break;
- case XK_F10: memcpy(keystr, "F10", 3); break;
- case XK_F11: memcpy(keystr, "F11", 3); break;
- case XK_F12: memcpy(keystr, "F12", 3); break;
- case XK_Sys_Req: memcpy(keystr, "PrScr", 5); break;
- case XK_Caps_Lock: memcpy(keystr, "CLck", 4); break;
- case XK_Scroll_Lock: memcpy(keystr, "SLck", 4); break;
- case XK_Num_Lock: memcpy(keystr, "NLck", 4); break;
- case XK_Up: memcpy(keystr, "Up", 2); break;
- case XK_Down: memcpy(keystr, "Down", 4); break;
- case XK_Left: memcpy(keystr, "Left", 4); break;
- case XK_Right: memcpy(keystr, "Right", 5); break;
- case XK_Home: memcpy(keystr, "Home", 4); break;
- case XK_End: memcpy(keystr, "End", 3); break;
- case XK_Page_Up: memcpy(keystr, "PgUp", 4); break;
- case XK_Page_Down: memcpy(keystr, "PgDown", 6); break;
- case XK_Delete: memcpy(keystr, "Del", 3); break;
- case XK_Multi_key: memcpy(keystr, "Int1", 4); break;
- case XK_Kanji: memcpy(keystr, "Int2", 4); break;
- case XK_Menu: memcpy(keystr, "Menu", 4); break;
- case XK_Z: if(bck->btn == UI_BTN_CONTROL) memcpy(keystr, "Undo", 4); else if(bck->btn & ~UI_BTN_SHIFT) keystr[0] = key; break;
- case XK_Y: if(bck->btn == UI_BTN_CONTROL) memcpy(keystr, "Redo", 4); else if(bck->btn & ~UI_BTN_SHIFT) keystr[0] = key; break;
- case XK_X: if(bck->btn == UI_BTN_CONTROL) memcpy(keystr, "Cut", 3); else if(bck->btn & ~UI_BTN_SHIFT) keystr[0] = key; break;
- case XK_C: if(bck->btn == UI_BTN_CONTROL) memcpy(keystr, "Copy", 4); else if(bck->btn & ~UI_BTN_SHIFT) keystr[0] = key; break;
- case XK_V: if(bck->btn == UI_BTN_CONTROL) memcpy(keystr, "Paste", 5); else if(bck->btn & ~UI_BTN_SHIFT) keystr[0] = key; break;
- case XK_Break: memcpy(keystr, "Stop", 4); break;
- case XK_Redo: memcpy(keystr, "Redo", 4); break;
- case XK_Undo: memcpy(keystr, "Undo", 4); break;
- case XK_Select: memcpy(keystr, "Copy", 4); break;
- case XK_Insert: memcpy(keystr, "Paste", 5); break;
- case XK_Find: memcpy(keystr, "Find", 4); break;
- default: if((bck->btn & ~UI_BTN_SHIFT) && key >= XK_A && key <= XK_Z) keystr[0] = key; break;
- }
- if(keystr[0] && (evt = _ui_evtslot(bck->ctx))) {
- memcpy(evt->key, keystr, 8);
- evt->btn = (bck->btn & 0xf00000);
- evt->type = UI_EVT_KEY;
- }
- break;
- case KeyRelease:
- key = XLookupKeysym(&e.xkey, 0);
- switch(key) {
- case XK_Shift_L: bck->btn &= ~UI_BTN_SHIFT; memcpy(keystr, "LShift", 6); break;
- case XK_Shift_R: bck->btn &= ~UI_BTN_SHIFT; memcpy(keystr, "RShift", 6); break;
- case XK_Control_L: bck->btn &= ~UI_BTN_CONTROL; memcpy(keystr, "LCtrl", 5); break;
- case XK_Control_R: bck->btn &= ~UI_BTN_CONTROL; memcpy(keystr, "RCtrl", 5); break;
- case XK_Alt_L: bck->btn &= ~UI_BTN_ALT; memcpy(keystr, "LAlt", 4); break;
- case XK_Alt_R: bck->btn &= ~UI_BTN_ALT; memcpy(keystr, "RAlt", 4); break;
- case XK_Super_L: bck->btn &= ~UI_BTN_GUI; memcpy(keystr, "LGui", 4); break;
- case XK_Super_R: bck->btn &= ~UI_BTN_GUI; memcpy(keystr, "RGui", 4); break;
- }
- if(keystr[0] && (evt = _ui_evtslot(bck->ctx))) {
- memcpy(evt->key, keystr, 8);
- evt->btn = (bck->btn & 0xf00000) | UI_BTN_RELEASE;
- evt->type = UI_EVT_KEY;
- }
- break;
- case SelectionRequest:
- if(bck->selection[0]) {
- XChangeProperty(bck->display, e.xselectionrequest.requestor, e.xselectionrequest.property,
- XA_UTF8_STRING(bck->display), 8, PropModeReplace, (uint8_t*)bck->selection, strlen(bck->selection));
- r.xselection.type = SelectionNotify;
- r.xselection.requestor = e.xselectionrequest.requestor;
- r.xselection.property = e.xselectionrequest.property;
- r.xselection.display = bck->display;
- r.xselection.selection = e.xselectionrequest.selection;
- r.xselection.target = e.xselectionrequest.target;
- r.xselection.time = e.xselectionrequest.time;
- XSendEvent(bck->display, e.xselectionrequest.requestor, 0, 0, &r);
- XFlush(bck->display);
- }
- break;
- case SelectionNotify:
- if(e.xselection.property != None) {
- XGetWindowProperty(bck->display, e.xselection.requestor, e.xselection.property, 0L,(~0L), 0,
- AnyPropertyType, &target, &m, &size, &N,(unsigned char**)&data);
- if(data) {
- if(e.xselection.target == bck->TARGETS) {
- for(i = 0; i < size; i++)
- if(data[i] == XA_STRING || data[i] == bck->UTF8)
- target = data[i];
- if(target != None) {
- XConvertSelection(bck->display, bck->CLIPBOARD, target, bck->XSEL_DATA, bck->window, CurrentTime);
- bck->target = target;
- }
- } else
- if(e.xselection.target == bck->target) {
- if(size > sizeof(bck->selection) - 1) size = sizeof(bck->selection) - 1;
- memset(bck->selection, 0, sizeof(bck->selection));
- memcpy(bck->selection, data, size);
- }
- XFree(data);
- }
- XDeleteProperty(bck->display, e.xselection.requestor, e.xselection.property);
- }
- break;
- }
- }
- return UI_OK;
- }
- /**
- * Main redraw handler
- */
- int ui_backend_redraw(ui_backend_t *bck)
- {
- ui_t *ctx;
- XImage *xi;
- if(!bck || !bck->display || !bck->window) return UI_ERR_BADINP;
- ctx = (ui_t*)bck->ctx;
- if(!ctx || !ctx->screen.buf) return UI_ERR_BADINP;
- if(!(xi = XCreateImage(bck->display, bck->visual, DefaultDepth(bck->display, bck->screen), ZPixmap, 0, NULL,
- ctx->screen.w, ctx->screen.h, 8, ctx->screen.p))) return UI_ERR_BACKEND;
- xi->data = (char*)ctx->screen.buf;
- xi->byte_order= LSBFirst;
- xi->bits_per_pixel = 32;
- XPutImage(bck->display, bck->window, bck->gc, xi, 0, 0, 0, 0, ctx->screen.w, ctx->screen.h);
- xi->data = NULL;
- XDestroyImage(xi);
- #ifndef UI_BACKEND_NOFLUSH
- XFlush(bck->display);
- #endif
- return UI_OK;
- }
- /**
- * Free backend resources
- */
- int ui_backend_free(ui_backend_t *bck)
- {
- if(!bck) return UI_ERR_BADINP;
- if(bck->display) {
- if(bck->fullscr) ui_backend_fullscreen(bck);
- if(bck->gc) XFreeGC(bck->display, bck->gc);
- if(bck->window) {
- XUnmapWindow(bck->display, bck->window);
- XDestroyWindow(bck->display, bck->window);
- }
- XFlush(bck->display);
- XCloseDisplay(bck->display);
- }
- free(bck);
- return UI_OK;
- }
- #ifdef __cplusplus
- }
- #endif
- #endif /* UI_X11_H */
|