From 645250e1cd8ce9bc1faea599df7a1b05836bfeb8 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Tue, 29 Dec 2020 16:09:43 +0100 Subject: Split UI code into application/plugin UI and UI library. --- dggui/nativewindow_x11.cc | 715 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 715 insertions(+) create mode 100644 dggui/nativewindow_x11.cc (limited to 'dggui/nativewindow_x11.cc') diff --git a/dggui/nativewindow_x11.cc b/dggui/nativewindow_x11.cc new file mode 100644 index 0000000..33dde7b --- /dev/null +++ b/dggui/nativewindow_x11.cc @@ -0,0 +1,715 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * nativewindow_x11.cc + * + * Fri Dec 28 18:45:57 CET 2012 + * Copyright 2012 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "nativewindow_x11.h" + +//http://www.mesa3d.org/brianp/xshm.c + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "window.h" + +namespace GUI +{ + +#define _NET_WM_STATE_REMOVE 0 // remove/unset property +#define _NET_WM_STATE_ADD 1 // add/set property + +void setWindowFront(Display *disp, ::Window wind, bool enable) +{ + Atom wm_state, wm_state_above; + XEvent event; + + if((wm_state = XInternAtom(disp, "_NET_WM_STATE", False)) == None) + { + return; + } + + if((wm_state_above = XInternAtom(disp, "_NET_WM_STATE_ABOVE", False)) == None) + { + return; + } + // + //window = the respective client window + //message_type = _NET_WM_STATE + //format = 32 + //data.l[0] = the action, as listed below + //data.l[1] = first property to alter + //data.l[2] = second property to alter + //data.l[3] = source indication (0-unk,1-normal app,2-pager) + //other data.l[] elements = 0 + // + + // sending a ClientMessage + event.xclient.type = ClientMessage; + + // value unimportant in this case + event.xclient.serial = 0; + + // coming from a SendEvent request, so True + event.xclient.send_event = True; + + // the event originates from disp + event.xclient.display = disp; + + // the window whose state will be modified + event.xclient.window = wind; + + // the component Atom being modified in the window + event.xclient.message_type = wm_state; + + // specifies that data.l will be used + event.xclient.format = 32; + + // 0 is _NET_WM_STATE_REMOVE, 1 is _NET_WM_STATE_ADD + event.xclient.data.l[0] = + enable ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + + // the atom being added + event.xclient.data.l[1] = wm_state_above; + + // unused + event.xclient.data.l[2] = 0; + event.xclient.data.l[3] = 0; + event.xclient.data.l[4] = 0; + + // actually send the event + XSendEvent(disp, DefaultRootWindow(disp), False, + SubstructureRedirectMask | SubstructureNotifyMask, &event); +} + +NativeWindowX11::NativeWindowX11(void* native_window, Window& window) + : window(window) +{ + display = XOpenDisplay(nullptr); + if(display == nullptr) + { + ERR(X11, "XOpenDisplay failed"); + return; + } + + screen = DefaultScreen(display); + visual = DefaultVisual(display, screen); + depth = DefaultDepth(display, screen); + + if(native_window) + { + parent_window = (::Window)native_window; + + // Track size changes on the parent window + XSelectInput(display, parent_window, StructureNotifyMask); + } + else + { + parent_window = DefaultRootWindow(display); + } + + // Create the window + XSetWindowAttributes swa; + swa.backing_store = Always; + xwindow = XCreateWindow(display, + parent_window, + 0, 0, //window.x(), window.y(), + 1, 1, //window.width(), window.height(), + 0, // border + CopyFromParent, // depth + CopyFromParent, // class + CopyFromParent, // visual + 0,//CWBackingStore, + &swa); + + long mask = (StructureNotifyMask | + PointerMotionMask | + ButtonPressMask | + ButtonReleaseMask | + KeyPressMask | + KeyReleaseMask| + ExposureMask | + StructureNotifyMask | + SubstructureNotifyMask | + EnterWindowMask | + LeaveWindowMask); + XSelectInput(display, xwindow, mask); + + // Register the delete window message: + wmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", false); + + Atom protocols[] = { wmDeleteMessage }; + XSetWMProtocols(display, xwindow, protocols, + sizeof(protocols) / sizeof(*protocols)); + + // Create a "Graphics Context" + gc = XCreateGC(display, xwindow, 0, nullptr); +} + +NativeWindowX11::~NativeWindowX11() +{ + if(display == nullptr) + { + return; + } + + deallocateShmImage(); + + XFreeGC(display, gc); + + XDestroyWindow(display, xwindow); + XCloseDisplay(display); +} + +void NativeWindowX11::setFixedSize(std::size_t width, std::size_t height) +{ + if(display == nullptr) + { + return; + } + + resize(width, height); + + XSizeHints size_hints; + memset(&size_hints, 0, sizeof(size_hints)); + + size_hints.flags = PMinSize|PMaxSize; + size_hints.min_width = size_hints.max_width = (int)width; + size_hints.min_height = size_hints.max_height = (int)height; + + XSetNormalHints(display, xwindow, &size_hints); +} + +void NativeWindowX11::setAlwaysOnTop(bool always_on_top) +{ + setWindowFront(display, xwindow, always_on_top); +} + +void NativeWindowX11::resize(std::size_t width, std::size_t height) +{ + if(display == nullptr) + { + return; + } + + XResizeWindow(display, xwindow, width, height); +} + +std::pair NativeWindowX11::getSize() const +{ +// XWindowAttributes attributes; +// XGetWindowAttributes(display, xwindow, &attributes); +// return std::make_pair(attributes.width, attributes.height); + + ::Window root_window; + int x, y; + unsigned int width, height, border, depth; + + XGetGeometry(display, xwindow, &root_window, + &x, &y, + &width, &height, &border, &depth); + + return {width, height}; +} + +void NativeWindowX11::move(int x, int y) +{ + if(display == nullptr) + { + return; + } + + XMoveWindow(display, xwindow, x, y); +} + +std::pair NativeWindowX11::getPosition() const +{ + ::Window root_window; + ::Window child_window; + int x, y; + unsigned int width, height, border, depth; + + XGetGeometry(display, xwindow, &root_window, + &x, &y, + &width, &height, &border, &depth); + + XTranslateCoordinates(display, xwindow, root_window, + 0, 0, &x, &y, &child_window); + + return std::make_pair(x, y); +} + +void NativeWindowX11::show() +{ + if(display == nullptr) + { + return; + } + + XMapWindow(display, xwindow); +} + +void NativeWindowX11::hide() +{ + if(display == nullptr) + { + return; + } + + XUnmapWindow(display, xwindow); +} + +bool NativeWindowX11::visible() const +{ + if(display == nullptr) + { + return false; + } + + XWindowAttributes xwa; + XGetWindowAttributes(display, xwindow, &xwa); + return (xwa.map_state == IsViewable); +} + +void NativeWindowX11::redraw(const Rect& dirty_rect) +{ + if(display == nullptr) + { + return; + } + + auto x1 = dirty_rect.x1; + auto y1 = dirty_rect.y1; + auto x2 = dirty_rect.x2; + auto y2 = dirty_rect.y2; + + // Assert that we don't try to paint a backwards rect. + assert(x1 <= x2); + assert(y1 <= y2); + + updateImageFromBuffer(x1, y1, x2, y2); + + XShmPutImage(display, xwindow, gc, image, x1, y1, x1, y1, + std::min((std::size_t)image->width, (x2 - x1)), + std::min((std::size_t)image->height, (y2 - y1)), false); + XFlush(display); +} + +void NativeWindowX11::setCaption(const std::string &caption) +{ + if(display == nullptr) + { + return; + } + + XStoreName(display, xwindow, caption.c_str()); +} + +void NativeWindowX11::grabMouse(bool grab) +{ + (void)grab; + // Don't need to do anything on this platform... +} + +EventQueue NativeWindowX11::getEvents() +{ + while(XPending(display)) + { + XEvent xEvent; + XNextEvent(display, &xEvent); + translateXMessage(xEvent); + } + + EventQueue events; + std::swap(events, event_queue); + return events; +} + +void* NativeWindowX11::getNativeWindowHandle() const +{ + return (void*)xwindow; +} + +Point NativeWindowX11::translateToScreen(const Point& point) +{ + ::Window child_window; + Point p; + XTranslateCoordinates(display, xwindow, DefaultRootWindow(display), + point.x, point.y, &p.x, &p.y, &child_window); + return p; +} + +void NativeWindowX11::translateXMessage(XEvent& xevent) +{ + switch(xevent.type) + { + case MotionNotify: + //DEBUG(x11, "MotionNotify"); + { + auto mouseMoveEvent = std::make_shared(); + mouseMoveEvent->x = xevent.xmotion.x; + mouseMoveEvent->y = xevent.xmotion.y; + event_queue.push_back(mouseMoveEvent); + } + break; + + case Expose: + //DEBUG(x11, "Expose"); + if(xevent.xexpose.count == 0) + { + auto repaintEvent = std::make_shared(); + repaintEvent->x = xevent.xexpose.x; + repaintEvent->y = xevent.xexpose.y; + repaintEvent->width = xevent.xexpose.width; + repaintEvent->height = xevent.xexpose.height; + event_queue.push_back(repaintEvent); + + if(image) + { + // Redraw the entire window. + Rect rect{0, 0, window.wpixbuf.width, window.wpixbuf.height}; + redraw(rect); + } + } + break; + + case ConfigureNotify: + //DEBUG(x11, "ConfigureNotify"); + + // The parent window size changed, reflect the new size in our own window. + if(xevent.xconfigure.window == parent_window) + { + resize(xevent.xconfigure.width, xevent.xconfigure.height); + return; + } + + { + if((window.width() != (std::size_t)xevent.xconfigure.width) || + (window.height() != (std::size_t)xevent.xconfigure.height)) + { + auto resizeEvent = std::make_shared(); + resizeEvent->width = xevent.xconfigure.width; + resizeEvent->height = xevent.xconfigure.height; + event_queue.push_back(resizeEvent); + } + + if((window.x() != xevent.xconfigure.x) || + (window.y() != xevent.xconfigure.y)) + { + auto moveEvent = std::make_shared(); + moveEvent->x = xevent.xconfigure.x; + moveEvent->y = xevent.xconfigure.y; + event_queue.push_back(moveEvent); + } + } + break; + + case ButtonPress: + case ButtonRelease: + //DEBUG(x11, "ButtonPress"); + { + if((xevent.xbutton.button == 4) || (xevent.xbutton.button == 5)) + { + if(xevent.type == ButtonPress) + { + int scroll = 1; + auto scrollEvent = std::make_shared(); + scrollEvent->x = xevent.xbutton.x; + scrollEvent->y = xevent.xbutton.y; + scrollEvent->delta = scroll * ((xevent.xbutton.button == 4) ? -1 : 1); + event_queue.push_back(scrollEvent); + } + } + else if ((xevent.xbutton.button == 6) || (xevent.xbutton.button == 7)) + { + // Horizontal scrolling case + // FIXME Introduce horizontal scrolling event to handle this. + } + else + { + auto buttonEvent = std::make_shared(); + buttonEvent->x = xevent.xbutton.x; + buttonEvent->y = xevent.xbutton.y; + switch(xevent.xbutton.button) { + case 1: + buttonEvent->button = MouseButton::left; + break; + case 2: + buttonEvent->button = MouseButton::middle; + break; + case 3: + buttonEvent->button = MouseButton::right; + break; + default: + WARN(X11, "Unknown button %d, setting to MouseButton::left\n", + xevent.xbutton.button); + buttonEvent->button = MouseButton::left; + break; + } + + buttonEvent->direction = + (xevent.type == ButtonPress) ? + Direction::down : Direction::up; + + // This is a fix for hosts (e.g. those using JUCE) that set the + // event time to '0'. + if(xevent.xbutton.time == 0) + { + auto now = std::chrono::system_clock::now().time_since_epoch(); + xevent.xbutton.time = + std::chrono::duration_cast(now).count(); + } + + buttonEvent->doubleClick = + (xevent.type == ButtonPress) && + ((xevent.xbutton.time - last_click) < 200); + + if(xevent.type == ButtonPress) + { + last_click = xevent.xbutton.time; + } + event_queue.push_back(buttonEvent); + } + } + break; + + case KeyPress: + case KeyRelease: + //DEBUG(x11, "KeyPress"); + { + auto keyEvent = std::make_shared(); + + switch(xevent.xkey.keycode) { + case 113: keyEvent->keycode = Key::left; break; + case 114: keyEvent->keycode = Key::right; break; + case 111: keyEvent->keycode = Key::up; break; + case 116: keyEvent->keycode = Key::down; break; + case 119: keyEvent->keycode = Key::deleteKey; break; + case 22: keyEvent->keycode = Key::backspace; break; + case 110: keyEvent->keycode = Key::home; break; + case 115: keyEvent->keycode = Key::end; break; + case 117: keyEvent->keycode = Key::pageDown; break; + case 112: keyEvent->keycode = Key::pageUp; break; + case 36: keyEvent->keycode = Key::enter; break; + default: keyEvent->keycode = Key::unknown; break; + } + + char stringBuffer[1024]; + int size = XLookupString(&xevent.xkey, stringBuffer, + sizeof(stringBuffer), nullptr, nullptr); + if(size && keyEvent->keycode == Key::unknown) + { + keyEvent->keycode = Key::character; + } + + keyEvent->text.append(stringBuffer, size); + + keyEvent->direction = + (xevent.type == KeyPress) ? Direction::down : Direction::up; + + event_queue.push_back(keyEvent); + } + break; + + case ClientMessage: + //DEBUG(x11, "ClientMessage"); + if(((unsigned int)xevent.xclient.data.l[0] == wmDeleteMessage)) + { + auto closeEvent = std::make_shared(); + event_queue.push_back(closeEvent); + } + break; + + case EnterNotify: + //DEBUG(x11, "EnterNotify"); + { + auto enterEvent = std::make_shared(); + enterEvent->x = xevent.xcrossing.x; + enterEvent->y = xevent.xcrossing.y; + event_queue.push_back(enterEvent); + } + break; + + case LeaveNotify: + //DEBUG(x11, "LeaveNotify"); + { + auto leaveEvent = std::make_shared(); + leaveEvent->x = xevent.xcrossing.x; + leaveEvent->y = xevent.xcrossing.y; + event_queue.push_back(leaveEvent); + } + break; + + case MapNotify: + case MappingNotify: + //DEBUG(x11, "EnterNotify"); + // There's nothing to do here atm. + break; + + default: + WARN(X11, "Unhandled xevent.type: %d\n", xevent.type); + break; + } +} + +void NativeWindowX11::allocateShmImage(std::size_t width, std::size_t height) +{ + DEBUG(x11, "(Re)alloc XShmImage (%d, %d)", (int)width, (int)height); + + if(image) + { + deallocateShmImage(); + } + + if(!XShmQueryExtension(display)) + { + ERR(x11, "XShmExtension not available"); + return; + } + + image = XShmCreateImage(display, visual, depth, + ZPixmap, nullptr, &shm_info, + width, height); + if(image == nullptr) + { + ERR(x11, "XShmCreateImage failed!\n"); + return; + } + + std::size_t byte_size = image->bytes_per_line * image->height; + + // Allocate shm buffer + int shm_id = shmget(IPC_PRIVATE, byte_size, IPC_CREAT|0777); + if(shm_id == -1) + { + ERR(x11, "shmget failed: %s", strerror(errno)); + return; + } + + shm_info.shmid = shm_id; + + // Attach share memory bufer + void* shm_addr = shmat(shm_id, nullptr, 0); + if(reinterpret_cast(shm_addr) == -1) + { + ERR(x11, "shmat failed: %s", strerror(errno)); + return; + } + + shm_info.shmaddr = reinterpret_cast(shm_addr); + image->data = shm_info.shmaddr; + shm_info.readOnly = false; + + // This may trigger the X protocol error we're ready to catch: + XShmAttach(display, &shm_info); + XSync(display, false); + + // Make the shm id unavailable to others + shmctl(shm_id, IPC_RMID, 0); +} + +void NativeWindowX11::deallocateShmImage() +{ + if(image == nullptr) + { + return; + } + + XFlush(display); + XShmDetach(display, &shm_info); + XDestroyImage(image); + image = nullptr; + shmdt(shm_info.shmaddr); +} + +void NativeWindowX11::updateImageFromBuffer(std::size_t x1, std::size_t y1, + std::size_t x2, std::size_t y2) +{ + //DEBUG(x11, "depth: %d", depth); + + auto width = window.wpixbuf.width; + auto height = window.wpixbuf.height; + + // If image hasn't been allocated yet or if the image backbuffer is + // too small, (re)allocate with a suitable size. + if((image == nullptr) || + ((int)width > image->width) || + ((int)height > image->height)) + { + constexpr std::size_t step_size = 128; // size increments + std::size_t new_width = ((width / step_size) + 1) * step_size; + std::size_t new_height = ((height / step_size) + 1) * step_size; + allocateShmImage(new_width, new_height); + x1 = 0; + y1 = 0; + x2 = width; + y2 = height; + } + + auto stride = image->width; + + std::uint8_t* pixel_buffer = (std::uint8_t*)window.wpixbuf.buf; + if(depth >= 24) // RGB 888 format + { + std::uint32_t* shm_addr = (std::uint32_t*)shm_info.shmaddr; + for(std::size_t y = y1; y < y2; ++y) + { + for(std::size_t x = x1; x < x2; ++x) + { + const std::size_t pin = y * width + x; + const std::size_t pout = y * stride + x; + const std::uint8_t red = pixel_buffer[pin * 3]; + const std::uint8_t green = pixel_buffer[pin * 3 + 1]; + const std::uint8_t blue = pixel_buffer[pin * 3 + 2]; + shm_addr[pout] = (red << 16) | (green << 8) | blue; + } + } + } + else if(depth >= 15) // RGB 565 format + { + std::uint16_t* shm_addr = (std::uint16_t*)shm_info.shmaddr; + + for(std::size_t y = y1; y < y2; ++y) + { + for(std::size_t x = x1; x < x2; ++x) + { + const std::size_t pin = y * width + x; + const std::size_t pout = y * stride + x; + const std::uint8_t red = pixel_buffer[pin * 3]; + const std::uint8_t green = pixel_buffer[pin * 3 + 1]; + const std::uint8_t blue = pixel_buffer[pin * 3 + 2]; + shm_addr[pout] = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3); + } + } + } +} + +} // GUI:: -- cgit v1.2.3