#include #include #include #include #include "XRandR.h" #include "X11WindowEventHandler.h" #include "Xlib.h" #include "wm_hints.h" #include "GLXContext.h" #include "X11Display.h" // Sometimes not defined in headers. #ifndef _NET_WM_STATE_TOGGLE #define _NET_WM_STATE_TOGGLE 2 #endif namespace sp { namespace _priv { // Pointer to the display that has focus (or NULL if none have). X11Display* focused_display = NULL; } X11Display* X11Display::getFocused() { return _priv::focused_display; } X11Display:: X11Display() : m_screen (0), m_size (200,200), m_cur_last (0), m_cur_hidden (0) { } bool X11Display::create(DisplayDescription description) { XSetWindowAttributes attr; XVisualInfo* vi; Atom protocols; Visual* visual; Window root_win; ::Display* disp = Xlib::getDisplay(); m_screen = DefaultScreen(disp); root_win = XRootWindow(disp, m_screen); visual = DefaultVisual(disp, m_screen); attr.border_pixel = BlackPixel(disp, m_screen); attr.background_pixel = WhitePixel(disp, m_screen); //attr.override_redirect = True; attr.colormap = ::XCreateColormap(disp, root_win, visual, AllocNone); // We want InputFocus,Keyboard,Mouse,Resize,Exposure (repaint) events. attr.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | ExposureMask; m_win = ::XCreateWindow(disp, root_win, 0, 0, /* Position */ m_size.x, m_size.y, 0 /* Border width */, DefaultDepth(disp, m_screen), InputOutput, visual, CWBackPixel | CWColormap | CWBorderPixel | CWEventMask, &attr); // Register event handler X11WindowEventHandler::registerHandler(disp, m_win, this); // X11 does not handle pressing the X button on a window. // that is the job of the window manager. // Here we can request the WM to send us a delete event so we can handle it ourself. protocols = Xlib::getAtom("WM_DELETE_WINDOW"); XSetWMProtocols(disp, m_win, &protocols, 1); setVisible(true); setSize(description.mode.width, description.mode.height); setDecoration(description.decoration); // Clear and take focus XClearWindow(disp, m_win); XMapRaised(disp, m_win); Log::info("X11: Created display"); createHiddenCursor(); return true; } void X11Display::destroy() { if (m_win) { ::Display* disp = Xlib::getDisplay(); X11WindowEventHandler::unregisterHandler(disp, m_win); ::XDestroyWindow(disp, m_win); } } bool X11Display::isValid() { return m_win; } void* X11Display::getHandle() const { return (void*) m_win; } void X11Display::setSize(unsigned int width, unsigned int height) { Log::info("X11: Set size %dx%d", width, height); // X11 does not like if width or height is zero. if (width == 0) { width = 1; } if (height == 0) { height = 1; } m_size = Vector2u(width, height); ::XResizeWindow(Xlib::getDisplay(), m_win, m_size.x, m_size.y); } Vector2u X11Display::getSize() const { int x, y; unsigned int w, h, bw, d; ::Display* disp = Xlib::getDisplay(); ::Window ancestor = m_win; ::Window root = DefaultRootWindow(disp); while (Xlib::getParentWindow(ancestor) != root) { // Next window up (parent window). ancestor = Xlib::getParentWindow(ancestor); } ::XGetGeometry(disp, ancestor, &root, &x, &y, &w, &h, &bw, &d); return Vector2u(w, h); } void X11Display::setPosition(unsigned int x, unsigned int y) { ::XMoveWindow(Xlib::getDisplay(), m_win, x, y); } Vector2u X11Display::getPosition() const { int x, y; unsigned int w, h, bw, d; ::Display* disp = Xlib::getDisplay(); ::Window ancestor = m_win; ::Window root = DefaultRootWindow(disp); while (Xlib::getParentWindow(ancestor) != root) { // Next window up (parent window). ancestor = Xlib::getParentWindow(ancestor); } ::XGetGeometry(disp, ancestor, &root, &x, &y, &w, &h, &bw, &d); return Vector2u(x, y); } void X11Display::setVisible(bool visible) { if (visible) { ::XMapWindow(Xlib::getDisplay(), m_win); } else { ::XUnmapWindow(Xlib::getDisplay(), m_win); } } void X11Display::setDecoration(unsigned decoration) { Atom WMHintsAtom = Xlib::getAtom("_MOTIF_WM_HINTS", false); Log::info("X11: Decoration"); if (WMHintsAtom) { WMHints hints; std::memset(&hints, 0, sizeof(hints)); hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; hints.decorations = 0; hints.functions = 0; if (decoration & DisplayDecorate::Menu) { Log::info("X11: Decoration Menu"); hints.decorations |= MWM_DECOR_BORDER | MWM_DECOR_TITLE | MWM_DECOR_MINIMIZE | MWM_DECOR_MENU; hints.functions |= MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE; } if (decoration & DisplayDecorate::Resize) { Log::info("X11: Decoration Resize"); hints.decorations |= MWM_DECOR_MAXIMIZE | MWM_DECOR_RESIZEH; hints.functions |= MWM_FUNC_MAXIMIZE | MWM_FUNC_RESIZE; } if (decoration & DisplayDecorate::Close) { Log::info("X11: Decoration Close"); hints.decorations |= 0; hints.functions |= MWM_FUNC_CLOSE; } ::XChangeProperty(Xlib::getDisplay(), m_win, WMHintsAtom, WMHintsAtom, 32, PropModeReplace, (const unsigned char*)&hints, WMHINTS_NUM_ELEMENTS); } } void X11Display::minimize() { ::XIconifyWindow(Xlib::getDisplay(), m_win, m_screen); } void X11Display::maximize() { ::XClientMessageEvent ev = {}; ::Display* disp = Xlib::getDisplay(); ev.type = ClientMessage; ev.window = m_win; ev.message_type = Xlib::getAtom("_NET_WM_STATE"); ev.format = 32; ev.data.l[0] = _NET_WM_STATE_TOGGLE; ev.data.l[1] = Xlib::getAtom("_NET_WM_STATE_MAXIMIZED_HORZ"); ev.data.l[2] = Xlib::getAtom("_NET_WM_STATE_MAXIMIZED_VERT"); ev.data.l[3] = 1; ::XSendEvent(disp, DefaultRootWindow(disp), False, SubstructureNotifyMask, (XEvent*) &ev); } void X11Display::enterFullscreen(DisplayMode mode) { if (!XRandR::FindMode(Xlib::getDisplay(), mode.width, mode.height, mode.bpp, &m_fullscreen_mode.size, &m_fullscreen_mode.rate)) { Log::warn("X11: Failed to find a mode"); return; } toggleFullscreen(true); } void X11Display::toggleFullscreen(bool enable) { ::Display* disp = Xlib::getDisplay(); ::Window root; ::XRRScreenConfiguration *conf; XRandR::VideoMode *mode = &m_desktop_mode; root = RootWindow(disp, 0); conf = XRRGetScreenInfo(disp, root); if (enable) { if (m_desktop_mode.rate < 1) { Rotation r; m_desktop_mode.rate = XRRConfigCurrentRate(conf); m_desktop_mode.size = XRRConfigCurrentConfiguration(conf, &r); } mode = &m_fullscreen_mode; } wm_fullscreen(enable); XRRSetScreenConfigAndRate(disp, conf, root, mode->size, RR_Rotate_0, mode->rate, CurrentTime); } void X11Display::exitFullscreen() { if (m_fullscreen_mode.rate > 0) { toggleFullscreen(false); m_fullscreen_mode.rate = 0; m_fullscreen_mode.size = 0; m_desktop_mode.rate = 0; m_desktop_mode.size = 0; } } #define _NET_WM_STATE_REMOVE 0 /* remove/unset property */ #define _NET_WM_STATE_ADD 1 /* add/set property */ void X11Display::wm_fullscreen(bool enabled) { ::Display* disp = sp::Xlib::getDisplay(); XEvent xev; Atom wm_state = sp::Xlib::getAtom("_NET_WM_STATE", False); Atom wm_fs = sp::Xlib::getAtom("_NET_WM_STATE_FULLSCREEN", False); memset(&xev, 0, sizeof(xev)); xev.type = ClientMessage; xev.xclient.window = m_win; xev.xclient.message_type = wm_state; xev.xclient.format = 32; xev.xclient.data.l[0] = enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; xev.xclient.data.l[1] = wm_fs; xev.xclient.data.l[2] = 0; ::XSendEvent(disp, ::XDefaultRootWindow(disp), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); } void X11Display::setCaption(const std::string& caption) { ::XStoreName(Xlib::getDisplay(), m_win, caption.c_str()); } void X11Display::setIcon(unsigned int width, unsigned int height, const uint8_t *pixels) { ::Display* disp = Xlib::getDisplay(); ::Atom net_wm_icon = Xlib::getAtom("_NET_WM_ICON", False); ::Atom cardinal = Xlib::getAtom("CARDINAL", False); std::vector buffer(2 + width * height); uint64_t *ptr = &buffer[0]; *ptr++ = width; *ptr++ = height; // TODO: Conversion between different formats should be defined as functions in Graphics/PixelFormat.h for (std::size_t i = 0; i < width * height; i++) { *ptr++ = (pixels[i * 4 + 2] << 0) | (pixels[i * 4 + 1] << 8) | (pixels[i * 4 + 0] << 16) | (pixels[i * 4 + 3] << 24); } ::XChangeProperty(disp, m_win, net_wm_icon, cardinal, 32, PropModeReplace, reinterpret_cast(&buffer[0]), 2 + width * height); XFlush(disp); } void X11Display::createHiddenCursor() { ::Display* disp = Xlib::getDisplay(); XColor c; Pixmap pix = ::XCreatePixmap(disp, m_win, 1, 1, 1); GC gc = ::XCreateGC(disp, pix, 0, NULL); // Draw transparent pixel. ::XDrawPoint(disp, pix, gc, 0, 0); c.red = c.green = c.blue = 0; c.flags = DoRed | DoGreen | DoBlue; m_cur_hidden = XCreatePixmapCursor(disp, pix, pix, &c, &c, 0, 0); // Free GC and pixmap. ::XFreePixmap(disp, pix); ::XFreeGC(disp, gc); } void X11Display::showCursor(bool value) { XDefineCursor(Xlib::getDisplay(), m_win, value ? m_cur_last : m_cur_hidden); } void X11Display::grabCursor(bool value) { // TODO (this is abit harder on X11 than windows.) } void X11Display::processEvent(const ::XEvent& event) { Vector2u size; switch(event.xany.type) { case ConfigureNotify: size.x = event.xconfigure.width; size.y = event.xconfigure.height; if (m_size != size) { m_size = size; Log::info("X11: Resize event %dx%d", m_size.x, m_size.y); onReshape(size.x, size.y); } break; case MotionNotify : // Generates to much events to log :) break; case FocusIn: Log::debug("X11: FocusIn"); _priv::focused_display = this; if (m_fullscreen_mode.rate > 0) { toggleFullscreen(true); } break; case FocusOut: Log::debug("X11: FocusOut"); _priv::focused_display = NULL; if (m_fullscreen_mode.rate > 0) { toggleFullscreen(false); minimize(); } break; } } } // namespace sp