├── .gitignore ├── LICENSE ├── SConstruct ├── assets ├── demo_screenshot.png └── hedgehog.jpg ├── backend ├── Backend.cpp ├── Backend.h ├── BackendDRM.cpp ├── BackendX11Base.cpp ├── BackendX11Base.h ├── BackendX11EGL.cpp ├── BackendX11GLX.cpp ├── drm │ ├── common.c │ ├── common.h │ ├── drm-common.c │ ├── drm-common.h │ └── drm-legacy.c └── libinput │ └── libinput.cpp ├── main ├── main.cpp ├── util.cpp └── util.h ├── opengl ├── RectRenderer.cpp ├── RectRenderer.h ├── ShaderProgram.cpp ├── ShaderProgram.h ├── Texture.cpp └── Texture.h ├── readme.md ├── scene ├── InputInterface.h ├── Scene.cpp ├── Scene.h └── WindowInterface.h └── wayland ├── Resource.cpp ├── Resource.h ├── WaylandEGL.cpp ├── WaylandEGL.h ├── WaylandServer.cpp ├── WaylandServer.h ├── WlArray.h ├── WlDataDeviceManager.cpp ├── WlDataDeviceManager.h ├── WlRegion.cpp ├── WlRegion.h ├── WlSeat.cpp ├── WlSeat.h ├── WlShellSurface.cpp ├── WlShellSurface.h ├── WlSurface.cpp ├── WlSurface.h ├── XdgShellV6Surface.cpp ├── XdgShellV6Surface.h └── protocols ├── gen_headers.py ├── xdg-shell-unstable-v5.xml └── xdg-shell-unstable-v6.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.gch 3 | bin 4 | build 5 | *.sconsign.dblite 6 | nogit 7 | *__pycache__ 8 | wayland/protocols/*.h 9 | wayland/protocols/*.c 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 William Wold 4 | Copyright (c) 2017 Rob Clark 5 | Copyright (c) 2014 Red Hat, Inc. 6 | Copyright (c) 2017 Drew DeVault 7 | Copyright (c) 2014 Jari Vetoniemi 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | env = Environment() 4 | 5 | obj_file_path = 'build/obj' 6 | exe_bin_path = 'build/run' 7 | protocol_path = 'wayland/protocols' 8 | debug_symbols = True 9 | 10 | libs = [ 11 | 'wayland-server', 12 | 'X11', 13 | 'EGL', 14 | 'GL', 15 | 'xkbcommon-x11', 16 | 'xkbcommon', 17 | 'GLU', 18 | 'GLEW', 19 | 'SOIL', 20 | 'gbm', 21 | 'udev', 22 | 'evdev', 23 | 'libinput', 24 | 'drm', 25 | ] 26 | 27 | pkg_config_libs = [ 28 | 'libdrm', 29 | 'libevdev', 30 | ] 31 | 32 | env.Append( 33 | CCFLAGS = [ 34 | '-g' if debug_symbols else None, 35 | '-Wall', 36 | ], 37 | LINKFLAGS = [ 38 | ] 39 | ) 40 | 41 | for lib in pkg_config_libs: 42 | env.ParseConfig('pkg-config --cflags --libs ' + lib); 43 | 44 | 45 | def get_contents_of_dir(base): 46 | return [ os.path.abspath(os.path.join(base, i)) for i in os.listdir(base) if not i.startswith('.') ] 47 | 48 | def get_subdirs(base): 49 | return [ i for i in get_contents_of_dir(base) if os.path.isdir(i) ] 50 | 51 | def get_all_subdirs(base): 52 | return [base] + [i for j in get_subdirs(base) for i in get_all_subdirs(j)] 53 | 54 | def has_extension(base, extensions): 55 | if type(extensions) != type([]): 56 | raise TypeError('has_extension must be sent a path and an array of extensions') 57 | for extension in extensions: 58 | if base.endswith(extension): 59 | return True 60 | return False 61 | 62 | def get_all_files(base): 63 | return [ path for subdir in get_all_subdirs(base) for path in get_contents_of_dir(subdir) if os.path.isfile(path) ] 64 | 65 | def get_all_files_with_extension(base, extensions): 66 | return [ path for path in get_all_files(base) if has_extension(path, extensions) ] 67 | 68 | def get_all_cpp_files(): 69 | return get_all_files_with_extension('.', ['.cpp', '.c']) 70 | 71 | if get_all_files_with_extension(protocol_path, ['.h']) != get_all_files_with_extension(protocol_path, ['.xml']): 72 | os.system('python3 ' + protocol_path + '/gen_headers.py ' + protocol_path) 73 | 74 | sources = get_all_cpp_files() 75 | 76 | objects = [] 77 | 78 | for source in sources: 79 | #obj_name = '.obj/' + source.path.split('/', 1)[1].rsplit('.', 1)[0] + '.o' 80 | obj_path = os.path.join(obj_file_path, os.path.relpath(source.rsplit('.', 1)[0] + '.o')) 81 | obj = env.Object(target=obj_path, source=source) 82 | objects.append(obj) 83 | 84 | program = env.Program(target=exe_bin_path, source=objects, LIBS=libs) 85 | -------------------------------------------------------------------------------- /assets/demo_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmww/Hedgehog/299c01e70224e43cbb3c853f4707ad9170c4c761/assets/demo_screenshot.png -------------------------------------------------------------------------------- /assets/hedgehog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmww/Hedgehog/299c01e70224e43cbb3c853f4707ad9170c4c761/assets/hedgehog.jpg -------------------------------------------------------------------------------- /backend/Backend.cpp: -------------------------------------------------------------------------------- 1 | #include "Backend.h" 2 | 3 | #include 4 | 5 | // change to toggle debug statements on and off 6 | #define debug debug_off 7 | 8 | unique_ptr Backend::instance; 9 | 10 | unique_ptr makeX11GLXBackend(V2i dim); 11 | unique_ptr makeX11EGLBackend(V2i dim); 12 | unique_ptr makeDRMBackend(); 13 | 14 | Backend::Backend() 15 | { 16 | // TODO: don't leak keymap and context memory 17 | struct xkb_rule_names rules; 18 | // all these environment vars are empty on my setup 19 | rules.rules = getenv("XKB_DEFAULT_RULES"); 20 | rules.model = getenv("XKB_DEFAULT_MODEL"); 21 | rules.layout = getenv("XKB_DEFAULT_LAYOUT"); 22 | rules.variant = getenv("XKB_DEFAULT_VARIANT"); 23 | rules.options = getenv("XKB_DEFAULT_OPTIONS"); 24 | struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 25 | ASSERT_ELSE(context, return); 26 | xkb_keymap * keymap = xkb_map_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); 27 | ASSERT_THEN(keymap) 28 | { 29 | keymapString = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1); 30 | } 31 | if (keymapString == "") 32 | warning("keymap string is empty"); 33 | } 34 | 35 | void Backend::setup(Type type) 36 | { 37 | ASSERT_ELSE(instance == nullptr, return); 38 | static const V2i defaultDim = V2i(800, 800); 39 | switch (type) 40 | { 41 | case X11_GLX: 42 | instance = makeX11GLXBackend(defaultDim); 43 | break; 44 | case X11_EGL: 45 | instance = makeX11EGLBackend(defaultDim); 46 | break; 47 | case DRM: 48 | instance = makeDRMBackend(); 49 | break; 50 | } 51 | ASSERT_FATAL(instance); 52 | } 53 | -------------------------------------------------------------------------------- /backend/Backend.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | #include "../scene/InputInterface.h" 5 | 6 | class Backend 7 | { 8 | public: 9 | virtual void swapBuffer() = 0; 10 | virtual void checkEvents() = 0; 11 | string getKeymap() { return keymapString; } 12 | void setInputInterface(weak_ptr ptr) { inputInterface = ptr; } 13 | 14 | enum Type {X11_GLX, X11_EGL, DRM}; 15 | static void setup(Type type); 16 | 17 | static unique_ptr instance; 18 | 19 | protected: 20 | Backend(); 21 | 22 | weak_ptr inputInterface; 23 | 24 | private: 25 | string keymapString; 26 | }; 27 | 28 | -------------------------------------------------------------------------------- /backend/BackendDRM.cpp: -------------------------------------------------------------------------------- 1 | #include "Backend.h" 2 | #include "../wayland/WaylandEGL.h" 3 | 4 | // change to toggle debug statements on and off 5 | #define debug debug_off 6 | 7 | extern "C" 8 | { 9 | 10 | int drmSetup(); 11 | void drmSwapBuffers(); 12 | void * drmGetEglDisplay(); 13 | void * drmGetEglContext(); 14 | 15 | } 16 | 17 | extern bool stop; 18 | bool libinput_setup(); 19 | void libinput_destroy(); 20 | void libinput_check_events(InputInterface * interface); 21 | 22 | struct BackendDRM: Backend 23 | { 24 | BackendDRM() 25 | { 26 | int setupRet = drmSetup(); 27 | ASSERT_FATAL(setupRet == 0); 28 | WaylandEGL::setEglVars(drmGetEglDisplay(), drmGetEglContext()); 29 | ASSERT_FATAL(libinput_setup()); 30 | } 31 | 32 | ~BackendDRM() 33 | { 34 | warning("~BackendDRM not implemented"); 35 | libinput_destroy(); 36 | } 37 | 38 | void swapBuffer() 39 | { 40 | drmSwapBuffers(); 41 | } 42 | 43 | void checkEvents() 44 | { 45 | if (auto input = inputInterface.lock()) 46 | { 47 | libinput_check_events(&*input); 48 | if (stop) 49 | Backend::instance = nullptr; 50 | } 51 | } 52 | }; 53 | 54 | unique_ptr makeDRMBackend() 55 | { 56 | return make_unique(); 57 | } 58 | 59 | -------------------------------------------------------------------------------- /backend/BackendX11Base.cpp: -------------------------------------------------------------------------------- 1 | #include "BackendX11Base.h" 2 | 3 | #include // for BTN_LEFT and maybe other stuff 4 | #include 5 | #include 6 | 7 | // change to toggle debug statements on and off 8 | #define debug debug_off 9 | 10 | uint x11BtnToLinuxBtn(uint x11Btn) 11 | { 12 | switch (x11Btn) 13 | { 14 | case Button1: 15 | return BTN_LEFT; 16 | case Button2: 17 | return BTN_MIDDLE; 18 | case Button3: 19 | return BTN_RIGHT; 20 | default: 21 | warning("your mouse has a weird-ass button"); 22 | return BTN_EXTRA; 23 | } 24 | } 25 | 26 | BackendX11Base::BackendX11Base(V2i dim) 27 | { 28 | this->dim = dim; 29 | 30 | debug("opening X display"); 31 | xDisplay = XOpenDisplay(0); 32 | 33 | debug("setting up XKB (keymap shit)"); 34 | } 35 | 36 | BackendX11Base::~BackendX11Base() 37 | { 38 | XCloseDisplay(xDisplay); 39 | } 40 | 41 | void BackendX11Base::setWindowName(string name) 42 | { 43 | ASSERT(window); 44 | // We use the XTextProperty structure to store the title. 45 | XTextProperty windowName; 46 | windowName.value = (unsigned char *)name.c_str(); 47 | 48 | // XA_STRING is not defined, but its value appears to be 31 49 | // I should probably just find the right header to include, but hey, who doesn't love magic numbers? 50 | windowName.encoding = 31; 51 | // windowName.encoding = XA_STRING; 52 | 53 | windowName.format = 8; 54 | windowName.nitems = name.size(); 55 | 56 | XSetWMName(xDisplay, window, &windowName); 57 | } 58 | 59 | void BackendX11Base::openWindow(XVisualInfo * visual, string name) 60 | { 61 | XSetWindowAttributes windowAttribs; 62 | windowAttribs.colormap = XCreateColormap(xDisplay, RootWindow(xDisplay, visual->screen), visual->visual, AllocNone); 63 | windowAttribs.border_pixel = 0; 64 | windowAttribs.event_mask = 65 | ExposureMask | 66 | KeyPressMask | 67 | KeyReleaseMask | 68 | ButtonPressMask | 69 | ButtonReleaseMask | 70 | PointerMotionMask | 71 | EnterWindowMask | 72 | LeaveWindowMask | 73 | FocusChangeMask | 74 | StructureNotifyMask ; 75 | 76 | int x = 0; 77 | int y = 0; 78 | 79 | debug("creating window"); 80 | 81 | window = XCreateWindow( 82 | xDisplay, 83 | RootWindow(xDisplay, visual->screen), // parent 84 | x, y, dim.x, dim.y, // geometry 85 | 0, 86 | visual->depth, 87 | InputOutput, 88 | visual->visual, 89 | CWBorderPixel|CWColormap|CWEventMask, 90 | &windowAttribs 91 | ); 92 | 93 | Atom WM_DELETE_WINDOW = XInternAtom(xDisplay, "WM_DELETE_WINDOW", false); 94 | XSetWMProtocols(xDisplay, window, &WM_DELETE_WINDOW, 1); 95 | 96 | setWindowName(name); 97 | 98 | ASSERT(window != 0); 99 | 100 | debug("mapping window"); 101 | 102 | XMapWindow(xDisplay, window); 103 | } 104 | 105 | void BackendX11Base::checkEvents() 106 | { 107 | auto interface = inputInterface.lock(); 108 | if (!interface) 109 | return; 110 | 111 | while (XPending(xDisplay)) 112 | { 113 | XEvent event; 114 | XNextEvent(xDisplay, &event); 115 | 116 | switch (event.type) 117 | { 118 | case MotionNotify: { 119 | // pointer motion 120 | auto movement = V2d((double)event.xbutton.x / dim.x, 1 - (double)event.xbutton.y / dim.y); 121 | interface->pointerMotion(movement); 122 | } break; 123 | 124 | case ButtonPress: 125 | interface->pointerClick(x11BtnToLinuxBtn(event.xbutton.button), true); 126 | break; 127 | 128 | case ButtonRelease: 129 | interface->pointerClick(x11BtnToLinuxBtn(event.xbutton.button), false); 130 | break; 131 | 132 | case KeyPress: 133 | interface->keyPress(event.xkey.keycode - 8, true); 134 | break; 135 | 136 | case KeyRelease: 137 | interface->keyPress(event.xkey.keycode - 8, false); 138 | break; 139 | 140 | case ConfigureNotify: 141 | // window was resized 142 | dim = V2i(event.xconfigure.width, event.xconfigure.height); 143 | glViewport(0, 0, dim.x, dim.y); 144 | break; 145 | 146 | case DestroyNotify: 147 | // no need to use this, do stuff in the destructor instead 148 | break; 149 | 150 | case ClientMessage: 151 | // assume this is a window close event because that is the only atom we support 152 | debug("X window closed"); 153 | XDestroyWindow(xDisplay, window); 154 | instance = nullptr; 155 | return; // 'this' will now be invalid 156 | 157 | default: 158 | // ignore other events 159 | break; 160 | } 161 | } 162 | } 163 | 164 | 165 | -------------------------------------------------------------------------------- /backend/BackendX11Base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // for getting the keymap 4 | #include 5 | 6 | #include "Backend.h" 7 | 8 | class BackendX11Base: public Backend 9 | { 10 | protected: 11 | Display * xDisplay = nullptr; 12 | Window window; 13 | V2i dim; // window dimensions 14 | 15 | void openWindow(XVisualInfo * visual, string name); 16 | void setWindowName(string name); 17 | 18 | public: 19 | BackendX11Base(V2i dim); 20 | ~BackendX11Base(); 21 | void checkEvents(); 22 | }; 23 | -------------------------------------------------------------------------------- /backend/BackendX11EGL.cpp: -------------------------------------------------------------------------------- 1 | #include "BackendX11Base.h" 2 | #include "../wayland/WaylandEGL.h" 3 | 4 | #include 5 | 6 | // change to toggle debug statements on and off 7 | #define debug debug_off 8 | 9 | struct BackendX11EGL: BackendX11Base 10 | { 11 | EGLDisplay eglDisplay; 12 | EGLConfig config; 13 | EGLContext windowContext; 14 | EGLSurface windowSurface; 15 | bool valid = false; 16 | 17 | BackendX11EGL(V2i dim): BackendX11Base(dim) 18 | { 19 | // if returns before 'valid' is set to true, this object will be considered invalid 20 | ASSERT_ELSE(xDisplay, return); 21 | 22 | eglDisplay = eglGetDisplay(xDisplay); 23 | ASSERT_ELSE(eglDisplay != EGL_NO_DISPLAY, return); 24 | 25 | bool eglInitializeSuccess = eglInitialize(eglDisplay, nullptr, nullptr); 26 | ASSERT_ELSE(eglInitializeSuccess, return); 27 | 28 | // setup EGL 29 | EGLint eglAttribs[] = { 30 | EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, 31 | EGL_RED_SIZE, 1, 32 | EGL_GREEN_SIZE, 1, 33 | EGL_BLUE_SIZE, 1, 34 | EGL_NONE 35 | }; 36 | EGLint configsCount; 37 | bool eglConfigSuccess = eglChooseConfig(eglDisplay, eglAttribs, &config, 1, &configsCount); 38 | ASSERT_ELSE(eglConfigSuccess, return); 39 | EGLint visualId; 40 | eglGetConfigAttrib(eglDisplay, config, EGL_NATIVE_VISUAL_ID, &visualId); 41 | XVisualInfo visualTemplate; 42 | visualTemplate.visualid = visualId; 43 | int visualsCount; 44 | XVisualInfo * visual = XGetVisualInfo(xDisplay, VisualIDMask, &visualTemplate, &visualsCount); 45 | ASSERT_ELSE(visual, return); 46 | 47 | openWindow(visual, "Hedgehog"); 48 | 49 | // EGL context and surface 50 | eglBindAPI(EGL_OPENGL_API); 51 | const EGLint moreAttribs[] = { 52 | EGL_CONTEXT_MAJOR_VERSION_KHR, 3, 53 | EGL_CONTEXT_MINOR_VERSION_KHR, 3, 54 | EGL_NONE 55 | }; 56 | windowContext = eglCreateContext(eglDisplay, config, EGL_NO_CONTEXT, moreAttribs); 57 | ASSERT(windowContext != EGL_NO_CONTEXT); 58 | windowSurface = eglCreateWindowSurface(eglDisplay, config, window, nullptr); 59 | ASSERT(windowSurface != EGL_NO_SURFACE); 60 | eglMakeCurrent(eglDisplay, windowSurface, windowSurface, windowContext); 61 | valid = true; 62 | 63 | WaylandEGL::setEglVars(eglDisplay, windowContext); 64 | } 65 | 66 | ~BackendX11EGL() 67 | { 68 | warning("~BackendEGL not implemented"); 69 | } 70 | 71 | void swapBuffer() 72 | { 73 | ASSERT_ELSE(valid, return); 74 | eglSwapBuffers(eglDisplay, windowSurface); 75 | } 76 | }; 77 | 78 | unique_ptr makeX11EGLBackend(V2i dim) 79 | { 80 | return make_unique(dim); 81 | } 82 | 83 | -------------------------------------------------------------------------------- /backend/BackendX11GLX.cpp: -------------------------------------------------------------------------------- 1 | #include "BackendX11Base.h" 2 | 3 | #include 4 | #include 5 | 6 | // change to toggle debug statements on and off 7 | #define debug debug_off 8 | 9 | typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); 10 | 11 | struct BackendX11GLX: BackendX11Base 12 | { 13 | GLXContext glxContext; 14 | 15 | BackendX11GLX(V2i dim): BackendX11Base(dim) 16 | { 17 | static int visual_attribs[] = 18 | { 19 | GLX_RENDER_TYPE, GLX_RGBA_BIT, 20 | GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 21 | GLX_DOUBLEBUFFER, true, 22 | GLX_RED_SIZE, 1, 23 | GLX_GREEN_SIZE, 1, 24 | GLX_BLUE_SIZE, 1, 25 | None 26 | }; 27 | 28 | debug("getting framebuffer config"); 29 | int fbcount; 30 | GLXFBConfig * glxfb = glXChooseFBConfig(xDisplay, DefaultScreen(xDisplay), visual_attribs, &fbcount); 31 | ASSERT(glxfb != nullptr); 32 | 33 | debug("getting XVisualInfo"); 34 | XVisualInfo * visual = glXGetVisualFromFBConfig(xDisplay, glxfb[0]); 35 | ASSERT(visual != nullptr); 36 | 37 | openWindow(visual, "Hedgehog"); 38 | 39 | glXCreateContextAttribsARBProc glXCreateContextAttribsARB = nullptr; 40 | 41 | // Create an oldstyle context first, to get the correct function pointer for glXCreateContextAttribsARB 42 | GLXContext oldContext = glXCreateContext(xDisplay, visual, 0, GL_TRUE); 43 | glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)glXGetProcAddress((const GLubyte*)"glXCreateContextAttribsARB"); 44 | glXMakeCurrent(xDisplay, 0, 0); 45 | glXDestroyContext(xDisplay, oldContext); 46 | ASSERT(glXCreateContextAttribsARB != nullptr); 47 | 48 | static int context_attribs[] = 49 | { 50 | GLX_CONTEXT_MAJOR_VERSION_ARB, 3, 51 | GLX_CONTEXT_MINOR_VERSION_ARB, 3, 52 | None 53 | }; 54 | 55 | debug("creating context..."); 56 | 57 | glxContext = glXCreateContextAttribsARB(xDisplay, glxfb[0], NULL, true, context_attribs); 58 | ASSERT(glxContext != nullptr); 59 | 60 | debug("Making context current"); 61 | glXMakeCurrent(xDisplay, window, glxContext); 62 | 63 | XFree(visual); 64 | XFree(glxfb); 65 | 66 | } 67 | 68 | ~BackendX11GLX() 69 | { 70 | debug("cleaning up GLX context"); 71 | glXDestroyContext(xDisplay, glxContext); 72 | } 73 | 74 | void swapBuffer() 75 | { 76 | glXSwapBuffers(xDisplay, window); 77 | } 78 | }; 79 | 80 | unique_ptr makeX11GLXBackend(V2i dim) 81 | { 82 | return make_unique(dim); 83 | } 84 | 85 | -------------------------------------------------------------------------------- /backend/drm/common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Rob Clark 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sub license, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice (including the 12 | * next paragraph) shall be included in all copies or substantial portions 13 | * of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "common.h" 31 | #include "drm-common.h" 32 | 33 | static struct gbm gbm; 34 | 35 | #ifdef HAVE_GBM_MODIFIERS 36 | static int 37 | get_modifiers(uint64_t **mods) 38 | { 39 | /* Assumed LINEAR is supported everywhere */ 40 | static uint64_t modifiers[] = {DRM_FORMAT_MOD_LINEAR}; 41 | *mods = modifiers; 42 | return 1; 43 | } 44 | #endif 45 | 46 | const struct gbm * init_gbm(int drm_fd, int w, int h, uint64_t modifier) 47 | { 48 | gbm.dev = gbm_create_device(drm_fd); 49 | 50 | #ifndef HAVE_GBM_MODIFIERS 51 | if (modifier != DRM_FORMAT_MOD_INVALID) { 52 | fprintf(stderr, "Modifiers requested but support isn't available\n"); 53 | return NULL; 54 | } 55 | gbm.surface = gbm_surface_create(gbm.dev, w, h, 56 | GBM_FORMAT_XRGB8888, 57 | GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); 58 | #else 59 | uint64_t *mods; 60 | int count; 61 | if (modifier != DRM_FORMAT_MOD_INVALID) { 62 | count = 1; 63 | mods = &modifier; 64 | } else { 65 | count = get_modifiers(&mods); 66 | } 67 | gbm.surface = gbm_surface_create_with_modifiers(gbm.dev, w, h, 68 | GBM_FORMAT_XRGB8888, mods, count); 69 | #endif 70 | 71 | if (!gbm.surface) { 72 | printf("failed to create gbm surface\n"); 73 | return NULL; 74 | } 75 | 76 | gbm.width = w; 77 | gbm.height = h; 78 | 79 | return &gbm; 80 | } 81 | 82 | int init_egl(struct egl *egl, const struct gbm *gbm) 83 | { 84 | EGLint major, minor, n; 85 | 86 | static const EGLint context_attribs[] = { 87 | EGL_CONTEXT_MAJOR_VERSION_KHR, 3, 88 | EGL_CONTEXT_MINOR_VERSION_KHR, 3, 89 | EGL_NONE 90 | }; 91 | 92 | static const EGLint config_attribs[] = { 93 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 94 | EGL_RED_SIZE, 1, 95 | EGL_GREEN_SIZE, 1, 96 | EGL_BLUE_SIZE, 1, 97 | EGL_ALPHA_SIZE, 0, 98 | EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, 99 | EGL_NONE 100 | }; 101 | 102 | #define get_proc(name) do { \ 103 | egl->name = (void *)eglGetProcAddress(#name); \ 104 | } while (0) 105 | 106 | get_proc(eglGetPlatformDisplayEXT); 107 | get_proc(eglCreateImageKHR); 108 | get_proc(eglDestroyImageKHR); 109 | //get_proc(glEGLImageTargetTexture2DOES); 110 | get_proc(eglCreateSyncKHR); 111 | get_proc(eglDestroySyncKHR); 112 | get_proc(eglWaitSyncKHR); 113 | get_proc(eglDupNativeFenceFDANDROID); 114 | 115 | if (egl->eglGetPlatformDisplayEXT) { 116 | egl->display = egl->eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, 117 | gbm->dev, NULL); 118 | } else { 119 | egl->display = eglGetDisplay((void *)gbm->dev); 120 | } 121 | 122 | if (!eglInitialize(egl->display, &major, &minor)) { 123 | printf("failed to initialize\n"); 124 | return -1; 125 | } 126 | 127 | printf("Using display %p with EGL version %d.%d\n", 128 | egl->display, major, minor); 129 | 130 | printf("===================================\n"); 131 | printf("EGL information:\n"); 132 | printf(" version: \"%s\"\n", eglQueryString(egl->display, EGL_VERSION)); 133 | printf(" vendor: \"%s\"\n", eglQueryString(egl->display, EGL_VENDOR)); 134 | printf(" extensions: \"%s\"\n", eglQueryString(egl->display, EGL_EXTENSIONS)); 135 | printf("===================================\n"); 136 | 137 | if (!eglBindAPI(EGL_OPENGL_API)) { 138 | printf("failed to bind api EGL_OPENGL_API\n"); 139 | return -1; 140 | } 141 | 142 | if (!eglChooseConfig(egl->display, config_attribs, &egl->config, 1, &n) || n != 1) { 143 | printf("failed to choose config: %d\n", n); 144 | return -1; 145 | } 146 | 147 | egl->context = eglCreateContext(egl->display, egl->config, 148 | EGL_NO_CONTEXT, context_attribs); 149 | if (egl->context == NULL) { 150 | printf("failed to create context\n"); 151 | return -1; 152 | } 153 | 154 | egl->surface = eglCreateWindowSurface(egl->display, egl->config, 155 | (EGLNativeWindowType)gbm->surface, NULL); 156 | if (egl->surface == EGL_NO_SURFACE) { 157 | printf("failed to create egl surface\n"); 158 | return -1; 159 | } 160 | 161 | /* connect the context to the surface */ 162 | eglMakeCurrent(egl->display, egl->surface, egl->surface, egl->context); 163 | 164 | printf("OpenGL information:\n"); 165 | printf(" version: \"%s\"\n", glGetString(GL_VERSION)); 166 | printf(" shading language version: \"%s\"\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); 167 | printf(" vendor: \"%s\"\n", glGetString(GL_VENDOR)); 168 | printf(" renderer: \"%s\"\n", glGetString(GL_RENDERER)); 169 | printf(" extensions: \"%s\"\n", glGetString(GL_EXTENSIONS)); 170 | printf("===================================\n"); 171 | 172 | glEnable(GL_BLEND); 173 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 174 | glewInit(); 175 | 176 | return 0; 177 | } 178 | 179 | 180 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 181 | 182 | struct egl egl; 183 | static const struct drm *drm; 184 | 185 | int drmSetup() 186 | { 187 | const char *device = "/dev/dri/card0"; 188 | uint64_t modifier = DRM_FORMAT_MOD_INVALID; 189 | 190 | //drm = init_drm_atomic(device); 191 | drm = init_drm_legacy(device); 192 | 193 | if (!drm) { 194 | printf("failed to initialize DRM\n"); 195 | return -1; 196 | } 197 | 198 | const struct gbm * ret = init_gbm(drm->fd, drm->mode->hdisplay, drm->mode->vdisplay, 199 | modifier); 200 | if (!ret) { 201 | printf("failed to initialize GBM\n"); 202 | return -1; 203 | } 204 | 205 | int ret2 = init_egl(&egl, &gbm); 206 | 207 | if (ret2 != 0) { 208 | printf("failed to initialize EGL\n"); 209 | return -1; 210 | } 211 | 212 | glViewport(0, 0, gbm.width, gbm.height); 213 | 214 | // clear the color buffer 215 | glClearColor(0.5, 0.5, 0.5, 1.0); 216 | glClear(GL_COLOR_BUFFER_BIT); 217 | 218 | drm_legacy_setup_with_egl(&gbm, &egl); 219 | //drm->run(&gbm, egl); 220 | 221 | return 0; 222 | } 223 | 224 | void drmSwapBuffers() 225 | { 226 | drm_legacy_swap_buffers(&gbm, &egl); 227 | } 228 | 229 | void * drmGetEglDisplay() 230 | { 231 | return egl.display; 232 | } 233 | 234 | void * drmGetEglContext() 235 | { 236 | return egl.context; 237 | } 238 | -------------------------------------------------------------------------------- /backend/drm/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Rob Clark 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sub license, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice (including the 12 | * next paragraph) shall be included in all copies or substantial portions 13 | * of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #ifndef _COMMON_H 25 | #define _COMMON_H 26 | 27 | #ifdef __cplusplus 28 | extern "C" 29 | { 30 | #endif 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | 39 | #ifndef DRM_FORMAT_MOD_LINEAR 40 | #define DRM_FORMAT_MOD_LINEAR 0 41 | #endif 42 | 43 | #ifndef DRM_FORMAT_MOD_INVALID 44 | #define DRM_FORMAT_MOD_INVALID ((((__u64)0) << 56) | ((1ULL << 56) - 1)) 45 | #endif 46 | 47 | #ifndef EGL_KHR_platform_gbm 48 | #define EGL_KHR_platform_gbm 1 49 | #define EGL_PLATFORM_GBM_KHR 0x31D7 50 | #endif /* EGL_KHR_platform_gbm */ 51 | 52 | #ifndef EGL_EXT_platform_base 53 | #define EGL_EXT_platform_base 1 54 | typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list); 55 | typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list); 56 | typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLint *attrib_list); 57 | #ifdef EGL_EGLEXT_PROTOTYPES 58 | EGLAPI EGLDisplay EGLAPIENTRY eglGetPlatformDisplayEXT (EGLenum platform, void *native_display, const EGLint *attrib_list); 59 | EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformWindowSurfaceEXT (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list); 60 | EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformPixmapSurfaceEXT (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLint *attrib_list); 61 | #endif 62 | #endif /* EGL_EXT_platform_base */ 63 | 64 | struct gbm { 65 | struct gbm_device *dev; 66 | struct gbm_surface *surface; 67 | int width, height; 68 | }; 69 | 70 | const struct gbm * init_gbm(int drm_fd, int w, int h, uint64_t modifier); 71 | 72 | 73 | struct egl { 74 | EGLDisplay display; 75 | EGLConfig config; 76 | EGLContext context; 77 | EGLSurface surface; 78 | 79 | PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT; 80 | PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; 81 | PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; 82 | //PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; 83 | PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR; 84 | PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR; 85 | PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR; 86 | PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID; 87 | 88 | void (*draw)(unsigned i); 89 | }; 90 | 91 | const struct egl * init_egl_view(const struct gbm *gbm); 92 | int init_egl(struct egl *egl, const struct gbm *gbm); 93 | int create_program(const char *vs_src, const char *fs_src); 94 | int link_program(unsigned program); 95 | 96 | const struct egl * init_cube_smooth(const struct gbm *gbm); 97 | 98 | #ifdef __cplusplus 99 | } 100 | #endif 101 | 102 | #endif /* _COMMON_H */ 103 | -------------------------------------------------------------------------------- /backend/drm/drm-common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Rob Clark 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sub license, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice (including the 12 | * next paragraph) shall be included in all copies or substantial portions 13 | * of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "common.h" 31 | #include "drm-common.h" 32 | 33 | static void 34 | drm_fb_destroy_callback(struct gbm_bo *bo, void *data) 35 | { 36 | int drm_fd = gbm_device_get_fd(gbm_bo_get_device(bo)); 37 | struct drm_fb *fb = data; 38 | 39 | if (fb->fb_id) 40 | drmModeRmFB(drm_fd, fb->fb_id); 41 | 42 | free(fb); 43 | } 44 | 45 | struct drm_fb * drm_fb_get_from_bo(struct gbm_bo *bo) 46 | { 47 | int drm_fd = gbm_device_get_fd(gbm_bo_get_device(bo)); 48 | struct drm_fb *fb = gbm_bo_get_user_data(bo); 49 | uint32_t width, height, 50 | strides[4] = {0}, handles[4] = {0}, 51 | offsets[4] = {0}, flags = 0; 52 | int ret = -1; 53 | 54 | if (fb) 55 | return fb; 56 | 57 | fb = calloc(1, sizeof *fb); 58 | fb->bo = bo; 59 | 60 | width = gbm_bo_get_width(bo); 61 | height = gbm_bo_get_height(bo); 62 | 63 | #ifdef HAVE_GBM_MODIFIERS 64 | uint64_t modifiers[4] = {0}; 65 | modifiers[0] = gbm_bo_get_modifier(bo); 66 | const int num_planes = gbm_bo_get_plane_count(bo); 67 | for (int i = 0; i < num_planes; i++) { 68 | strides[i] = gbm_bo_get_stride_for_plane(bo, i); 69 | handles[i] = gbm_bo_get_handle(bo).u32; 70 | offsets[i] = gbm_bo_get_offset(bo, i); 71 | modifiers[i] = modifiers[0]; 72 | } 73 | 74 | if (modifiers[0]) { 75 | flags = DRM_MODE_FB_MODIFIERS; 76 | printf("Using modifier %llx\n", modifiers[0]); 77 | } 78 | 79 | ret = drmModeAddFB2WithModifiers(drm_fd, width, height, 80 | DRM_FORMAT_XRGB8888, handles, strides, offsets, 81 | modifiers, &fb->fb_id, flags); 82 | #endif 83 | if (ret) { 84 | if (flags) 85 | fprintf(stderr, "Modifiers failed!\n"); 86 | 87 | memcpy(handles, (uint32_t [4]){gbm_bo_get_handle(bo).u32,0,0,0}, 16); 88 | memcpy(strides, (uint32_t [4]){gbm_bo_get_stride(bo),0,0,0}, 16); 89 | memset(offsets, 0, 16); 90 | ret = drmModeAddFB2(drm_fd, width, height, DRM_FORMAT_XRGB8888, 91 | handles, strides, offsets, &fb->fb_id, 0); 92 | } 93 | 94 | if (ret) { 95 | printf("failed to create fb: %s\n", strerror(errno)); 96 | free(fb); 97 | return NULL; 98 | } 99 | 100 | gbm_bo_set_user_data(bo, fb, drm_fb_destroy_callback); 101 | 102 | return fb; 103 | } 104 | 105 | static uint32_t find_crtc_for_encoder(const drmModeRes *resources, 106 | const drmModeEncoder *encoder) { 107 | int i; 108 | 109 | for (i = 0; i < resources->count_crtcs; i++) { 110 | /* possible_crtcs is a bitmask as described here: 111 | * https://dvdhrm.wordpress.com/2012/09/13/linux-drm-mode-setting-api 112 | */ 113 | const uint32_t crtc_mask = 1 << i; 114 | const uint32_t crtc_id = resources->crtcs[i]; 115 | if (encoder->possible_crtcs & crtc_mask) { 116 | return crtc_id; 117 | } 118 | } 119 | 120 | /* no match found */ 121 | return -1; 122 | } 123 | 124 | static uint32_t find_crtc_for_connector(const struct drm *drm, const drmModeRes *resources, 125 | const drmModeConnector *connector) { 126 | int i; 127 | 128 | for (i = 0; i < connector->count_encoders; i++) { 129 | const uint32_t encoder_id = connector->encoders[i]; 130 | drmModeEncoder *encoder = drmModeGetEncoder(drm->fd, encoder_id); 131 | 132 | if (encoder) { 133 | const uint32_t crtc_id = find_crtc_for_encoder(resources, encoder); 134 | 135 | drmModeFreeEncoder(encoder); 136 | if (crtc_id != 0) { 137 | return crtc_id; 138 | } 139 | } 140 | } 141 | 142 | /* no match found */ 143 | return -1; 144 | } 145 | 146 | int init_drm(struct drm *drm, const char *device) 147 | { 148 | drmModeRes *resources; 149 | drmModeConnector *connector = NULL; 150 | drmModeEncoder *encoder = NULL; 151 | int i, area; 152 | 153 | drm->fd = open(device, O_RDWR); 154 | 155 | if (drm->fd < 0) { 156 | printf("could not open drm device\n"); 157 | return -1; 158 | } 159 | 160 | resources = drmModeGetResources(drm->fd); 161 | if (!resources) { 162 | printf("drmModeGetResources failed: %s\n", strerror(errno)); 163 | return -1; 164 | } 165 | 166 | /* find a connected connector: */ 167 | for (i = 0; i < resources->count_connectors; i++) { 168 | connector = drmModeGetConnector(drm->fd, resources->connectors[i]); 169 | if (connector->connection == DRM_MODE_CONNECTED) { 170 | /* it's connected, let's use this! */ 171 | break; 172 | } 173 | drmModeFreeConnector(connector); 174 | connector = NULL; 175 | } 176 | 177 | if (!connector) { 178 | /* we could be fancy and listen for hotplug events and wait for 179 | * a connector.. 180 | */ 181 | printf("no connected connector!\n"); 182 | return -1; 183 | } 184 | 185 | /* find preferred mode or the highest resolution mode: */ 186 | for (i = 0, area = 0; i < connector->count_modes; i++) { 187 | drmModeModeInfo *current_mode = &connector->modes[i]; 188 | 189 | if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { 190 | drm->mode = current_mode; 191 | } 192 | 193 | int current_area = current_mode->hdisplay * current_mode->vdisplay; 194 | if (current_area > area) { 195 | drm->mode = current_mode; 196 | area = current_area; 197 | } 198 | } 199 | 200 | if (!drm->mode) { 201 | printf("could not find mode!\n"); 202 | return -1; 203 | } 204 | 205 | /* find encoder: */ 206 | for (i = 0; i < resources->count_encoders; i++) { 207 | encoder = drmModeGetEncoder(drm->fd, resources->encoders[i]); 208 | if (encoder->encoder_id == connector->encoder_id) 209 | break; 210 | drmModeFreeEncoder(encoder); 211 | encoder = NULL; 212 | } 213 | 214 | if (encoder) { 215 | drm->crtc_id = encoder->crtc_id; 216 | } else { 217 | uint32_t crtc_id = find_crtc_for_connector(drm, resources, connector); 218 | if (crtc_id == 0) { 219 | printf("no crtc found!\n"); 220 | return -1; 221 | } 222 | 223 | drm->crtc_id = crtc_id; 224 | } 225 | 226 | for (i = 0; i < resources->count_crtcs; i++) { 227 | if (resources->crtcs[i] == drm->crtc_id) { 228 | drm->crtc_index = i; 229 | break; 230 | } 231 | } 232 | 233 | drmModeFreeResources(resources); 234 | 235 | drm->connector_id = connector->connector_id; 236 | 237 | return 0; 238 | } 239 | -------------------------------------------------------------------------------- /backend/drm/drm-common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Rob Clark 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sub license, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice (including the 12 | * next paragraph) shall be included in all copies or substantial portions 13 | * of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #ifndef _DRM_COMMON_H 25 | #define _DRM_COMMON_H 26 | 27 | #include 28 | #include 29 | 30 | struct gbm; 31 | struct egl; 32 | 33 | struct plane { 34 | drmModePlane *plane; 35 | drmModeObjectProperties *props; 36 | drmModePropertyRes **props_info; 37 | }; 38 | 39 | struct crtc { 40 | drmModeCrtc *crtc; 41 | drmModeObjectProperties *props; 42 | drmModePropertyRes **props_info; 43 | }; 44 | 45 | struct connector { 46 | drmModeConnector *connector; 47 | drmModeObjectProperties *props; 48 | drmModePropertyRes **props_info; 49 | }; 50 | 51 | struct drm { 52 | int fd; 53 | 54 | /* only used for atomic: */ 55 | struct plane *plane; 56 | struct crtc *crtc; 57 | struct connector *connector; 58 | int crtc_index; 59 | int kms_in_fence_fd; 60 | int kms_out_fence_fd; 61 | 62 | drmModeModeInfo *mode; 63 | uint32_t crtc_id; 64 | uint32_t connector_id; 65 | 66 | //int (*run)(const struct gbm *gbm, const struct egl *egl); 67 | }; 68 | 69 | struct drm_fb { 70 | struct gbm_bo *bo; 71 | uint32_t fb_id; 72 | }; 73 | 74 | struct drm_fb * drm_fb_get_from_bo(struct gbm_bo *bo); 75 | 76 | int init_drm(struct drm *drm, const char *device); 77 | const struct drm * init_drm_legacy(const char *device); 78 | const struct drm * init_drm_atomic(const char *device); 79 | 80 | int drm_legacy_setup_with_egl(const struct gbm *gbm, const struct egl *egl); 81 | void drm_legacy_swap_buffers(const struct gbm *gbm, const struct egl *egl); 82 | 83 | #endif /* _DRM_COMMON_H */ 84 | -------------------------------------------------------------------------------- /backend/drm/drm-legacy.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Rob Clark 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sub license, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice (including the 12 | * next paragraph) shall be included in all copies or substantial portions 13 | * of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | * DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #include "common.h" 25 | #include "drm-common.h" 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | static struct drm drm; 34 | struct drm_fb *fb; 35 | fd_set fds; 36 | struct gbm_bo *bo; 37 | 38 | static void page_flip_handler(int fd, unsigned int frame, 39 | unsigned int sec, unsigned int usec, void *data) 40 | { 41 | /* suppress 'unused parameter' warnings */ 42 | (void)fd, (void)frame, (void)sec, (void)usec; 43 | 44 | int *waiting_for_flip = data; 45 | *waiting_for_flip = 0; 46 | } 47 | 48 | int drm_legacy_setup_with_egl(const struct gbm *gbm, const struct egl *egl) 49 | { 50 | int ret; 51 | 52 | FD_ZERO(&fds); 53 | FD_SET(0, &fds); 54 | FD_SET(drm.fd, &fds); 55 | 56 | eglSwapBuffers(egl->display, egl->surface); 57 | bo = gbm_surface_lock_front_buffer(gbm->surface); 58 | fb = drm_fb_get_from_bo(bo); 59 | if (!fb) { 60 | fprintf(stderr, "Failed to get a new framebuffer BO\n"); 61 | return -1; 62 | } 63 | 64 | /* set mode: */ 65 | ret = drmModeSetCrtc(drm.fd, drm.crtc_id, fb->fb_id, 0, 0, 66 | &drm.connector_id, 1, drm.mode); 67 | if (ret) { 68 | printf("failed to set mode: %s\n", strerror(errno)); 69 | return ret; 70 | } 71 | 72 | return 0; 73 | } 74 | 75 | void drm_legacy_swap_buffers(const struct gbm *gbm, const struct egl *egl) 76 | { 77 | int ret; 78 | 79 | drmEventContext evctx = { 80 | .version = 2, 81 | .page_flip_handler = page_flip_handler, 82 | }; 83 | 84 | struct gbm_bo *next_bo; 85 | int waiting_for_flip = 1; 86 | 87 | eglSwapBuffers(egl->display, egl->surface); 88 | next_bo = gbm_surface_lock_front_buffer(gbm->surface); 89 | if (next_bo == NULL) 90 | { 91 | fprintf(stderr, "next_bo is null, not swapping buffers\n"); 92 | usleep(10000); // without this the entire program becomes unresponsive because this function failing stops the only sleep 93 | return; 94 | } 95 | fb = drm_fb_get_from_bo(next_bo); 96 | if (!fb) { 97 | fprintf(stderr, "Failed to get a new framebuffer BO\n"); 98 | return; 99 | } 100 | 101 | /* 102 | * Here you could also update drm plane layers if you want 103 | * hw composition 104 | */ 105 | 106 | ret = drmModePageFlip(drm.fd, drm.crtc_id, fb->fb_id, 107 | DRM_MODE_PAGE_FLIP_EVENT, &waiting_for_flip); 108 | if (ret) { 109 | printf("failed to queue page flip: %s\n", strerror(errno)); 110 | return; 111 | } 112 | 113 | while (waiting_for_flip) { 114 | ret = select(drm.fd + 1, &fds, NULL, NULL, NULL); 115 | if (ret < 0) { 116 | printf("select err: %s\n", strerror(errno)); 117 | return; 118 | } else if (ret == 0) { 119 | printf("select timeout!\n"); 120 | return; 121 | } else if (FD_ISSET(0, &fds)) { 122 | printf("user interrupted!\n"); 123 | break; 124 | } 125 | drmHandleEvent(drm.fd, &evctx); 126 | } 127 | 128 | /* release last buffer to render on again: */ 129 | gbm_surface_release_buffer(gbm->surface, bo); 130 | bo = next_bo; 131 | } 132 | 133 | const struct drm * init_drm_legacy(const char *device) 134 | { 135 | int ret; 136 | 137 | ret = init_drm(&drm, device); 138 | if (ret) 139 | return NULL; 140 | 141 | return &drm; 142 | } 143 | -------------------------------------------------------------------------------- /backend/libinput/libinput.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Red Hat, Inc. 3 | * Copyright (c) 2017 Drew DeVault 4 | * Copyright (c) 2014 Jari Vetoniemi 5 | * Copyright (c) 2017 William Wold 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice (including the next 15 | * paragraph) shall be included in all copies or substantial portions of the 16 | * Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | * DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | // note to the person trying to figure out the license situation on this file 28 | // I have taken and heavily modified code from wlroots and official libinput examples (both licensed under MIT) 29 | // I think the way you do that is keep one copy of the MIT bit and each copyright line. 30 | // maybe there needs to be a separate license file instead of just the section above? idk. 31 | 32 | #include "../../main/util.h" 33 | #include "../../scene/InputInterface.h" 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | // change to toggle debug statements on and off 43 | #define debug debug_off 44 | 45 | bool stop = false; 46 | 47 | V2d pointerPos; 48 | const double pointerScaleFactor = 0.001; 49 | 50 | struct { 51 | //struct wlr_backend backend; 52 | 53 | //struct wlr_session *session; 54 | struct udev *udev; 55 | struct udev_monitor *mon; 56 | struct wl_event_source *udev_event; 57 | 58 | struct wl_display *display; 59 | 60 | struct libinput *libinput_context; 61 | struct wl_event_source *input_event; 62 | 63 | //struct wl_listener session_signal; 64 | 65 | vector wlr_device_vec; 66 | 67 | //list_t *wlr_device_lists; 68 | } backend_data; 69 | 70 | static const struct libinput_interface libinput_impl = { 71 | .open_restricted = +[](const char *path, int flags, void *_backend) -> int { 72 | debug("libinput_interface.open_restricted called, opening '" + string(path) + "'"); 73 | int ret = open(path, flags); 74 | if (ret >= 0) 75 | return ret; 76 | else 77 | return -errno; 78 | }, 79 | .close_restricted = +[](int fd, void *_backend) { 80 | debug("libinput_interface.close_restricted called"); 81 | close(fd); 82 | } 83 | }; 84 | 85 | static void wlr_libinput_log(struct libinput *libinput_context, 86 | enum libinput_log_priority priority, const char *fmt, va_list args) { 87 | char c_str[255]; 88 | snprintf(c_str, 255, fmt, args); 89 | debug(c_str); 90 | } 91 | 92 | /* 93 | static int wlr_libinput_readable(int fd, uint32_t mask, void *_backend) { 94 | struct wlr_libinput_backend *backend = (wlr_libinput_backend *)_backend; 95 | if (libinput_dispatch(backend->libinput_context) != 0) { 96 | debug("Failed to dispatch libinput"); 97 | // TODO: some kind of abort? 98 | return 0; 99 | } 100 | struct libinput_event *event; 101 | while ((event = libinput_get_event(backend->libinput_context))) { 102 | //wlr_libinput_event(backend, event); 103 | debug("wlroots would have sent event here"); 104 | libinput_event_destroy(event); 105 | } 106 | return 0; 107 | } 108 | */ 109 | 110 | bool libinput_setup() { 111 | auto backend = &backend_data; 112 | debug("Initializing libinput"); 113 | backend->udev = udev_new(); 114 | ASSERT(backend->udev); 115 | backend->libinput_context = libinput_udev_create_context(&libinput_impl, backend, backend->udev); 116 | if (!backend->libinput_context) { 117 | debug("Failed to create libinput context"); 118 | return false; 119 | } 120 | 121 | // TODO: Let user customize seat used 122 | if (libinput_udev_assign_seat(backend->libinput_context, "seat0") != 0) { 123 | debug("Failed to assign libinput seat"); 124 | return false; 125 | } 126 | 127 | libinput_log_set_handler(backend->libinput_context, wlr_libinput_log); 128 | libinput_log_set_priority(backend->libinput_context, LIBINPUT_LOG_PRIORITY_ERROR); 129 | 130 | //int libinput_fd = libinput_get_fd(backend->libinput_context); 131 | 132 | /* 133 | if (backend->wlr_device_vec.size() == 0) { 134 | wlr_libinput_readable(libinput_fd, WL_EVENT_READABLE, backend); 135 | if (backend->wlr_device_vec.size() == 0) { 136 | debug("libinput initialization failed, no input devices"); 137 | debug("Set WLR_LIBINPUT_NO_DEVICES=1 to suppress this check"); 138 | return false; 139 | } 140 | } 141 | */ 142 | 143 | //struct wl_event_loop *event_loop = 144 | // wl_display_get_event_loop(backend->display); 145 | //if (backend->input_event) { 146 | // wl_event_source_remove(backend->input_event); 147 | //} 148 | //backend->input_event = wl_event_loop_add_fd(event_loop, libinput_fd, 149 | // WL_EVENT_READABLE, wlr_libinput_readable, backend); 150 | //if (!backend->input_event) { 151 | // debug("Failed to create input event on event loop"); 152 | // return false; 153 | //} 154 | debug("libinput sucessfully initialized"); 155 | 156 | // struct pollfd fds; 157 | // fds.fd = libinput_get_fd(backend->libinput_context); 158 | // fds.events = POLLIN; 159 | // fds.revents = 0; 160 | 161 | // handle interrupts 162 | struct sigaction act; 163 | memset(&act, 0, sizeof(act)); 164 | act.sa_sigaction = +[](int signal, siginfo_t *siginfo, void *userdata) { stop = true; }; 165 | act.sa_flags = SA_SIGINFO; 166 | if (sigaction(SIGINT, &act, NULL) == -1) { 167 | warning("Failed to set up signal handling (" + string(strerror(errno)) + ")"); 168 | return false; 169 | } 170 | 171 | // Handle already-pending device added events 172 | //if (handle_and_print_events(li)) 173 | // fprintf(stderr, "Expected device added events on startup but got none. " 174 | // "Maybe you don't have the right permissions?\n"); 175 | 176 | // while (!stop && poll(&fds, 1, -1) > -1) 177 | 178 | return true; 179 | 180 | // udev_unref(session->udev); 181 | // libinput_unref(backend->libinput_context); 182 | } 183 | 184 | void libinput_destroy() 185 | { 186 | libinput_unref(backend_data.libinput_context); 187 | // udev_unref(session->udev); 188 | } 189 | 190 | void libinput_check_events(InputInterface * interface) 191 | { 192 | ASSERT_ELSE(interface, return); 193 | struct libinput_event *ev; 194 | auto backend = &backend_data; 195 | 196 | libinput_dispatch(backend->libinput_context); 197 | while ((ev = libinput_get_event(backend->libinput_context))) { 198 | //print_event_header(ev); 199 | 200 | switch (libinput_event_get_type(ev)) { 201 | case LIBINPUT_EVENT_NONE: 202 | debug("LIBINPUT_EVENT_NONE"); 203 | abort(); 204 | case LIBINPUT_EVENT_DEVICE_ADDED: 205 | debug("LIBINPUT_EVENT_DEVICE_ADDED"); 206 | //print_device_notify(ev); 207 | //tools_device_apply_config(libinput_event_get_device(ev), &options); 208 | break; 209 | case LIBINPUT_EVENT_DEVICE_REMOVED: 210 | debug("LIBINPUT_EVENT_DEVICE_REMOVED"); 211 | //print_device_notify(ev); 212 | //tools_device_apply_config(libinput_event_get_device(ev), &options); 213 | break; 214 | case LIBINPUT_EVENT_KEYBOARD_KEY: 215 | { 216 | debug("LIBINPUT_EVENT_KEYBOARD_KEY"); 217 | auto event = libinput_event_get_keyboard_event(ev); 218 | ASSERT_ELSE(event, break); 219 | uint32_t key = libinput_event_keyboard_get_key(event); 220 | libinput_key_state state = libinput_event_keyboard_get_key_state(event); 221 | bool isPressed = false; 222 | switch (state) 223 | { 224 | case LIBINPUT_KEY_STATE_PRESSED: 225 | isPressed = true; 226 | break; 227 | case LIBINPUT_KEY_STATE_RELEASED: 228 | isPressed = false; 229 | break; 230 | } 231 | interface->keyPress(key, isPressed); 232 | //print_key_event(li, ev); 233 | break; 234 | } 235 | case LIBINPUT_EVENT_POINTER_MOTION: 236 | { 237 | // TODO: make everything about this not shitty 238 | auto event = libinput_event_get_pointer_event(ev); 239 | ASSERT_ELSE(event, break); 240 | V2d d; 241 | d.x = libinput_event_pointer_get_dx(event) * pointerScaleFactor; 242 | d.y = - libinput_event_pointer_get_dy(event) * pointerScaleFactor; 243 | pointerPos.x += d.x; 244 | pointerPos.y += d.y; 245 | if (pointerPos.x < 0) 246 | pointerPos.x = 0; 247 | if (pointerPos.x > 1) 248 | pointerPos.x = 1; 249 | if (pointerPos.y < 0) 250 | pointerPos.y = 0; 251 | if (pointerPos.y > 1) 252 | pointerPos.y = 1; 253 | interface->pointerMotion(pointerPos); 254 | debug("pointer moved to " + to_string(pointerPos)); 255 | //print_motion_event(ev); 256 | break; 257 | } 258 | case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: 259 | debug("LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE"); 260 | //print_absmotion_event(ev); 261 | break; 262 | case LIBINPUT_EVENT_POINTER_BUTTON: 263 | { 264 | debug("LIBINPUT_EVENT_POINTER_BUTTON"); 265 | auto event = libinput_event_get_pointer_event(ev); 266 | ASSERT_ELSE(event, break); 267 | uint32_t button = libinput_event_pointer_get_button(event); 268 | libinput_button_state libinputState = libinput_event_pointer_get_button_state(event); 269 | bool isPressed = false; 270 | switch (libinputState) 271 | { 272 | case LIBINPUT_BUTTON_STATE_PRESSED: 273 | isPressed = true; 274 | if (pointerPos.x < 0.1 && pointerPos.y < 0.1) 275 | stop = true; 276 | break; 277 | case LIBINPUT_BUTTON_STATE_RELEASED: 278 | isPressed = false; 279 | } 280 | interface->pointerClick(button, isPressed); 281 | //print_pointer_button_event(ev); 282 | break; 283 | } 284 | case LIBINPUT_EVENT_POINTER_AXIS: 285 | debug("LIBINPUT_EVENT_POINTER_AXIS"); 286 | //print_pointer_axis_event(ev); 287 | break; 288 | /*case LIBINPUT_EVENT_TOUCH_DOWN: 289 | //print_touch_event_with_coords(ev); 290 | break; 291 | case LIBINPUT_EVENT_TOUCH_MOTION: 292 | print_touch_event_with_coords(ev); 293 | break; 294 | case LIBINPUT_EVENT_TOUCH_UP: 295 | print_touch_event_without_coords(ev); 296 | break; 297 | case LIBINPUT_EVENT_TOUCH_CANCEL: 298 | print_touch_event_without_coords(ev); 299 | break; 300 | case LIBINPUT_EVENT_TOUCH_FRAME: 301 | print_touch_event_without_coords(ev); 302 | break; 303 | case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: 304 | print_gesture_event_without_coords(ev); 305 | break; 306 | case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: 307 | print_gesture_event_with_coords(ev); 308 | break; 309 | case LIBINPUT_EVENT_GESTURE_SWIPE_END: 310 | print_gesture_event_without_coords(ev); 311 | break; 312 | case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: 313 | print_gesture_event_without_coords(ev); 314 | break; 315 | case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: 316 | print_gesture_event_with_coords(ev); 317 | break; 318 | case LIBINPUT_EVENT_GESTURE_PINCH_END: 319 | print_gesture_event_without_coords(ev); 320 | break; 321 | case LIBINPUT_EVENT_TABLET_TOOL_AXIS: 322 | print_tablet_axis_event(ev); 323 | break; 324 | case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: 325 | print_proximity_event(ev); 326 | break; 327 | case LIBINPUT_EVENT_TABLET_TOOL_TIP: 328 | print_tablet_tip_event(ev); 329 | break; 330 | case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: 331 | print_tablet_button_event(ev); 332 | break; 333 | case LIBINPUT_EVENT_TABLET_PAD_BUTTON: 334 | print_tablet_pad_button_event(ev); 335 | break; 336 | case LIBINPUT_EVENT_TABLET_PAD_RING: 337 | print_tablet_pad_ring_event(ev); 338 | break; 339 | case LIBINPUT_EVENT_TABLET_PAD_STRIP: 340 | print_tablet_pad_strip_event(ev); 341 | break; 342 | case LIBINPUT_EVENT_SWITCH_TOGGLE: 343 | print_switch_event(ev); 344 | break;*/ 345 | default: 346 | debug("other libinput event"); 347 | break; 348 | } 349 | 350 | libinput_event_destroy(ev); 351 | libinput_dispatch(backend->libinput_context); 352 | } 353 | } 354 | 355 | -------------------------------------------------------------------------------- /main/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../opengl/Texture.h" 9 | #include "../opengl/RectRenderer.h" 10 | #include "../backend/Backend.h" 11 | #include "../wayland/WaylandServer.h" 12 | #include "../scene/Scene.h" 13 | 14 | // change to toggle debug statements on and off 15 | #define debug debug_off 16 | 17 | // select the backend, X11_EGL is recommended to run nested in an X environment 18 | const Backend::Type backendType = Backend::X11_EGL; 19 | // const backendType = Backend::X11_GLX; 20 | // const backendType = Backend::DRM; 21 | 22 | int main (int argc, char ** argv) 23 | { 24 | debug("setting up backend"); 25 | //auto backend = Backend::makeGLX(V2i(800, 800)); 26 | Backend::setup(backendType); 27 | ASSERT_ELSE(Backend::instance, exit(1)); 28 | 29 | glEnable(GL_BLEND); 30 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 31 | glewInit(); 32 | 33 | auto texture = Texture(); 34 | texture.loadFromImage("assets/hedgehog.jpg"); 35 | RectRenderer renderer; 36 | 37 | debug("setting up wayland server"); 38 | 39 | WaylandServer::setup(); 40 | 41 | Scene scene; 42 | scene.setup(); 43 | Backend::instance->setInputInterface(scene.getInputInterface()); 44 | 45 | //double startTime = timeSinceStart(); 46 | //int ticks = 0; 47 | 48 | debug("starting main loop"); 49 | while (Backend::instance) 50 | { 51 | renderer.draw(texture, V2d(0, 0), V2d(1, 1)); 52 | WaylandServer::iteration(); 53 | scene.draw(); 54 | Backend::instance->swapBuffer(); 55 | //sleepForSeconds(0.01667); 56 | Backend::instance->checkEvents(); 57 | //ticks++; 58 | //double endTime = timeSinceStart(); 59 | //debug("FPS: " + to_string(ticks / (endTime - startTime))); 60 | } 61 | 62 | debug("shutting down wayland server"); 63 | WaylandServer::shutdown(); 64 | 65 | std::cout << "exiting" << std::endl; 66 | } 67 | -------------------------------------------------------------------------------- /main/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | #include 4 | 5 | #include 6 | using std::stringstream; 7 | 8 | #include 9 | #include 10 | 11 | #include // for terminal size detection 12 | #include // for terminal size detection 13 | 14 | // change to toggle debug statements on and off 15 | #define debug debug_off 16 | 17 | std::chrono::high_resolution_clock::time_point programStartTime = std::chrono::high_resolution_clock::now(); 18 | 19 | int getTermWidth() 20 | { 21 | struct winsize w; 22 | ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); 23 | return w.ws_col; 24 | } 25 | 26 | void logMessage(string source, MessageType type, string message) 27 | { 28 | //const string indent = " | "; 29 | //const string lastIndent = " |_ "; 30 | 31 | int termWidth = getTermWidth() - 1; 32 | 33 | std::ostream * stream = &std::cerr; 34 | if (type == MESSAGE_DEBUG) 35 | stream = &std::cout; 36 | 37 | string typeStr; 38 | switch (type) 39 | { 40 | case MESSAGE_DEBUG: 41 | typeStr = "debug"; 42 | break; 43 | case MESSAGE_WARNING: 44 | typeStr = "WARNING"; 45 | break; 46 | case MESSAGE_ASSERTION_FAILED: 47 | typeStr = "FATAL ASSERTION"; 48 | break; 49 | case MESSAGE_FATAL_ERROR: 50 | typeStr = "FATAL ERROR"; 51 | break; 52 | default: 53 | typeStr = "UNKNOWN MESSAGE TYPE"; 54 | } 55 | string msg = "[" + source + "]: " + message; 56 | vector lines; 57 | int messageWidth = std::max(termWidth - (int)typeStr.size(), 12); 58 | //string indent = [&]() -> string { string out = ""; for (int i = 0; i < (int)typeStr.size() - 1; i++) { out += " "; } return out; } (); 59 | string indent = [&]() -> string { string out = ""; for (int i = 0; i < 2; i++) { out += " "; } return out; } (); 60 | //string lastIndent = indent + "|_"; 61 | string lastIndent = indent + " "; 62 | //indent += "| "; 63 | indent += " "; 64 | 65 | int start = 0; 66 | int end = (int)msg.size(); 67 | 68 | while (start < end) 69 | { 70 | //int lineEnd = start + messageMaxWidth - (start == 0 ? 0 : (int)typeStr.size()); 71 | int lineEnd = start + messageWidth; 72 | int splitPoint; 73 | if (lineEnd < end) 74 | { 75 | splitPoint = lineEnd - 1; 76 | while (splitPoint > start && msg[splitPoint] != ' ' && msg[splitPoint] != '\n') { splitPoint--; } 77 | if (splitPoint == start) 78 | splitPoint = lineEnd; 79 | } 80 | else 81 | { 82 | splitPoint = lineEnd = end; 83 | } 84 | string line; 85 | if (start == 0) 86 | line += typeStr + " "; 87 | else if (lineEnd == end) 88 | line += lastIndent; 89 | else 90 | line += indent; 91 | line += msg.substr(start, splitPoint - start); 92 | lines.push_back(line); 93 | start = splitPoint; 94 | if (start < end && (msg[start] == ' ')) 95 | start++; 96 | } 97 | 98 | for (auto i: lines) 99 | { 100 | *stream << i << std::endl; 101 | } 102 | 103 | if (type == MESSAGE_FATAL_ERROR || type == MESSAGE_ASSERTION_FAILED) 104 | exit(1); 105 | } 106 | 107 | bool loadFile(string filename, string& contents) 108 | { 109 | std::fstream inFile; 110 | 111 | debug("attempting to open '" + filename + "'..."); 112 | 113 | inFile.open(filename); 114 | 115 | if (!inFile.is_open()) 116 | { 117 | debug("'" + filename + "' failed to open :("); 118 | return false; 119 | } 120 | else 121 | { 122 | debug("file opended, reading file..."); 123 | 124 | stringstream strStream; 125 | strStream << inFile.rdbuf(); // read the file 126 | contents = strStream.str(); // str holds the content of the file 127 | inFile.close(); 128 | 129 | debug("file reading done, '" + filename + "' closed"); 130 | 131 | return true; 132 | } 133 | } 134 | 135 | void sleepForSeconds(double seconds) 136 | { 137 | std::this_thread::sleep_for(std::chrono::milliseconds((int)(seconds * 1000))); 138 | } 139 | 140 | double timeSinceStart() 141 | { 142 | return (std::chrono::duration_cast>(std::chrono::high_resolution_clock::now() - programStartTime)).count(); 143 | } 144 | 145 | int timeSinceStartMili() 146 | { 147 | return timeSinceStart() * 1000; 148 | } 149 | 150 | /* 151 | void MessageLogger::status(string msg) 152 | { 153 | if (verbose) 154 | { 155 | important(msg); 156 | } 157 | } 158 | 159 | void MessageLogger::important(string msg) 160 | { 161 | show(tag + ": " + msg); 162 | } 163 | 164 | void MessageLogger::show(string msg) 165 | { 166 | cout << msg << endl; 167 | } 168 | */ 169 | 170 | -------------------------------------------------------------------------------- /main/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | using std::string; 5 | using std::to_string; 6 | 7 | #include 8 | using std::vector; 9 | 10 | #include 11 | //using std::cout; 12 | //using std::endl; 13 | 14 | #include 15 | using std::shared_ptr; 16 | using std::enable_shared_from_this; 17 | using std::make_shared; 18 | using std::unique_ptr; 19 | using std::make_unique; 20 | using std::weak_ptr; 21 | 22 | #include 23 | using std::function; 24 | 25 | #include 26 | #include 27 | 28 | // a simple 2D vector class (vector in the x, y sense, not the array sense) 29 | template 30 | struct V2 31 | { 32 | T x, y; 33 | 34 | V2() { x = y = T(); } 35 | V2(T xIn, T yIn) { x = xIn; y = yIn; } 36 | }; 37 | 38 | template 39 | string to_string(V2 in) { return "(" + to_string(in.x) + ", " + to_string(in.y) + ")"; } 40 | 41 | typedef V2 V2i; 42 | typedef V2 V2d; 43 | 44 | #define FUNC string(__FUNCTION__) 45 | 46 | // instead of using logMessage directly, it is generally a good idea to use the debug, warning, fatal and assert macros 47 | enum MessageType { MESSAGE_DEBUG, MESSAGE_WARNING, MESSAGE_ASSERTION_FAILED, MESSAGE_FATAL_ERROR }; 48 | void logMessage(string source, MessageType type, string messaage); // this function does NOT support unicode 49 | 50 | // the function-like macros debug, warning and fatal each take a string and print it along with the file and line number 51 | // this is made possible by the __FILE__ and __LINE__ macros 52 | // all debugs in a file can be disabled at 0 run time cost by putting #define NO_DEBUG at the top of the file (before includes) 53 | // fatal automatically kills the program as soon as its done logging 54 | // assert is used to easily check a boolean expression, and fatally error if its false 55 | #define FILE_INFO string(__FILE__) + ":" + (__LINE__ < 100 ? (__LINE__ < 10 ? " " : " ") : (__LINE__ < 1000 ? " " : "")) + std::to_string(__LINE__) 56 | #define debug_off(message) 57 | #define debug_on(message) logMessage(FILE_INFO, MESSAGE_DEBUG, message) 58 | #define warning(message) logMessage(FILE_INFO, MESSAGE_WARNING, message) 59 | #define fatal(message) logMessage(FILE_INFO, MESSAGE_FATAL_ERROR, message) 60 | #define assert(condition) if (!(condition)) { logMessage(FILE_INFO, MESSAGE_ASSERTION_FAILED, "assertion '" #condition "' failed"); } 61 | 62 | //#define ASSERT_OR_BUST(condition) if (!(condition)) { logMessage(FILE_INFO, MESSAGE_ASSERTION_FAILED, "assertion '" #condition "' failed"); } 63 | //#define ASSERT_ELSE_RETURN(condition) if (!(condition)) { logMessage(FILE_INFO, MESSAGE_WARNING, "assertion '" #condition "' failed; returning early from " + FUNC); return; } 64 | #define ASSERT_ELSE(condition, action) if (!(condition)) { logMessage(FILE_INFO, MESSAGE_WARNING, "assertion '" #condition "' failed in " + FUNC); action; } 65 | #define ASSERT_THEN(condition) ASSERT_ELSE(condition, ) else 66 | #define ASSERT_FATAL(condition) if (!(condition)) { logMessage(FILE_INFO, MESSAGE_FATAL_ERROR, "assertion '" #condition "' failed in " + FUNC); exit(1); } 67 | #define ASSERT(condition) ASSERT_ELSE(condition, ) 68 | //#define ASSERT_ELSE_IGNORE(condition) ASSERT_ELSE(condition, ) 69 | 70 | // loads an entire file into the contents string, returns if it succeeded 71 | bool loadFile(string filename, string& contents); 72 | 73 | // sleep for the given number of seconds (millisecond precision) 74 | void sleepForSeconds(double seconds); 75 | 76 | // get the time (in seconds) since the program started 77 | double timeSinceStart(); 78 | int timeSinceStartMili(); 79 | 80 | // returns the path to the folder containing all shaders (ends in slash) 81 | // this is abstracted into a function because in the future it may need to be determined dynamically 82 | inline string getShaderPath() {return "shaders/";} 83 | 84 | // for lazy evaluation of static class members 85 | // to use, put a static function in your class called firstInstanceSetup, and call setupIfFirstInstance with this in the constructor 86 | template 87 | void setupIfFirstInstance(T * type) 88 | { 89 | static bool hasInitialized = false; 90 | 91 | if (!hasInitialized) 92 | { 93 | T::firstInstanceSetup(); 94 | hasInitialized = true; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /opengl/RectRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include "RectRenderer.h" 2 | #include "ShaderProgram.h" 3 | 4 | // change to toggle debug statements on and off 5 | #define debug debug_off 6 | 7 | ShaderProgram shaderProgram; 8 | 9 | const string vertShaderCode = "#version 330 core\n" 10 | "layout (location = 0) in vec2 position; " 11 | "layout (location = 1) in vec2 texturePositionIn; " 12 | "out vec2 texturePosition; " 13 | "uniform mat4 transform; " 14 | "void main() " 15 | "{ " 16 | "gl_Position = transform * vec4(position, 0.0f, 1.0f); " 17 | "texturePosition = texturePositionIn; " 18 | "} "; 19 | 20 | const string fragShaderCode = "#version 330 core\n" 21 | "in vec2 texturePosition; " 22 | "out vec4 color; " 23 | "uniform sampler2D textureData; " 24 | "void main() " 25 | "{ " 26 | "color = texture(textureData, texturePosition); " 27 | "} "; 28 | 29 | RectRenderer::RectRenderer() 30 | { 31 | if (shaderProgram.isNull()) 32 | { 33 | shaderProgram.setupFromCode(vertShaderCode, fragShaderCode); 34 | } 35 | 36 | debug("setting up a VAO"); 37 | 38 | GLuint VBO, EBO; 39 | 40 | GLfloat vertices[] = 41 | { 42 | // position // texture position 43 | 1.0f, 1.0f, 1.0f, 0.0f, // Top Right 44 | 1.0f, -1.0f, 1.0f, 1.0f, // Bottom Right 45 | -1.0f, -1.0f, 0.0f, 1.0f, // Bottom Left 46 | -1.0f, 1.0f, 0.0f, 0.0f, // Top Left 47 | }; 48 | 49 | GLuint indices[] = 50 | { 51 | 0, 1, 3, // First Triangle 52 | 1, 2, 3 // Second Triangle 53 | }; 54 | 55 | //glViewport(0, 0, width, height); 56 | 57 | glGenVertexArrays(1, &vertexArrayID); 58 | glGenBuffers(1, &VBO); 59 | glGenBuffers(1, &EBO); 60 | 61 | // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointer(s). 62 | glBindVertexArray(vertexArrayID); 63 | { 64 | glBindBuffer(GL_ARRAY_BUFFER, VBO); 65 | { 66 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 67 | 68 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0); 69 | glEnableVertexAttribArray(0); 70 | 71 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*) (2 * sizeof(GLfloat))); 72 | glEnableVertexAttribArray(1); 73 | } 74 | glBindBuffer(GL_ARRAY_BUFFER, false); 75 | // Note that this is allowed, the call to glVertexAttribPointer registered VBO as the currently bound vertex buffer object so afterwards we can safely unbind 76 | 77 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 78 | { 79 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); 80 | } 81 | //remember: do NOT unbind the EBO, keep it bound to this VAO 82 | } 83 | glBindVertexArray(false); // Unbind VAO (it's always a good thing to unbind any buffer/array to prevent strange bugs) 84 | 85 | // this is legal as per https://stackoverflow.com/a/13342549/4327513 86 | // the buffers will not really be deleted until the vertex array object is deleted 87 | // its apparently like reference counting, and here we are lowering the reference count 88 | // I know, OpenGL is fucking stupid 89 | glDeleteBuffers(1, &VBO); 90 | glDeleteBuffers(1, &EBO); 91 | } 92 | 93 | RectRenderer::~RectRenderer() 94 | { 95 | debug("~RectRenderer called"); 96 | glDeleteVertexArrays(1, &vertexArrayID); 97 | } 98 | 99 | void RectRenderer::draw(Texture texture, V2d pos, V2d size) 100 | { 101 | shaderProgram.bind(); 102 | { 103 | GLfloat transform[] = { // X and Y are flipped 104 | (GLfloat)size.x, 0.0, 0.0, 0.0, 105 | 0.0, (GLfloat)size.y, 0.0, 0.0, 106 | 0.0, 0.0, 1.0, 0.0, 107 | GLfloat(pos.x*2+size.x-1), GLfloat(pos.y*2+size.y-1), 0.0, 1.0, 108 | }; 109 | shaderProgram.uniformMatrix4fv("transform", transform); 110 | 111 | texture.bind(); 112 | { 113 | glBindVertexArray(vertexArrayID); 114 | { 115 | glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); 116 | } 117 | glBindVertexArray(false); 118 | } 119 | texture.unbind(); 120 | } 121 | shaderProgram.unbind(); 122 | } 123 | -------------------------------------------------------------------------------- /opengl/RectRenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | #include "Texture.h" 5 | 6 | // this class takes some work to initialize and needs an OpenGL context to be bound when created 7 | class RectRenderer 8 | { 9 | public: 10 | RectRenderer(); 11 | RectRenderer(const RectRenderer& that) = delete; // you may not copy this class, use a shared_ptr if you must 12 | ~RectRenderer(); 13 | void draw(Texture texture, V2d pos, V2d size); 14 | 15 | private: 16 | uint vertexArrayID = 0; 17 | }; 18 | -------------------------------------------------------------------------------- /opengl/ShaderProgram.cpp: -------------------------------------------------------------------------------- 1 | #include "ShaderProgram.h" 2 | 3 | // change to toggle debug statements on and off 4 | #define debug debug_off 5 | 6 | struct ShaderProgram::Impl 7 | { 8 | GLuint programId = 0; 9 | 10 | struct SingleShader 11 | { 12 | GLuint shaderId = 0; 13 | 14 | static SingleShader fromFile(string file, GLenum type); 15 | static SingleShader fromCode(string code, GLenum type); 16 | 17 | SingleShader() {} 18 | ~SingleShader(); 19 | 20 | string getInfoLog() 21 | { 22 | GLchar infoLog[1024]; 23 | glGetShaderInfoLog(shaderId, 1024, nullptr, infoLog); 24 | return infoLog; 25 | } 26 | 27 | static string typeToString(GLenum type) 28 | { 29 | switch (type) 30 | { 31 | case GL_VERTEX_SHADER: 32 | return "vertex shader"; 33 | case GL_FRAGMENT_SHADER: 34 | return "fragment shader"; 35 | default: 36 | warning("unknown shader type"); 37 | return "unknown shader type"; 38 | } 39 | } 40 | }; 41 | 42 | Impl(SingleShader vertShader, SingleShader fragShader) 43 | { 44 | debug("creating shader program"); 45 | 46 | programId = glCreateProgram(); 47 | 48 | glAttachShader(programId, vertShader.shaderId); 49 | glAttachShader(programId, fragShader.shaderId); 50 | glLinkProgram(programId); 51 | // Check for linking errors 52 | GLint success; 53 | glGetProgramiv(programId, GL_LINK_STATUS, &success); 54 | if (!success) { 55 | warning("shader linking failed:\n" + getInfoLog()); 56 | } 57 | } 58 | 59 | ~Impl() 60 | { 61 | glDeleteProgram(programId); 62 | } 63 | 64 | string getInfoLog() 65 | { 66 | GLchar infoLog[1024]; 67 | glGetProgramInfoLog(programId, 1024, nullptr, infoLog); 68 | return infoLog; 69 | } 70 | }; 71 | 72 | ShaderProgram::Impl::SingleShader ShaderProgram::Impl::SingleShader::fromFile(string file, GLenum type) 73 | { 74 | string code; 75 | 76 | if (!loadFile(file, code)) 77 | { 78 | warning("shader file '" + file + "' failed to load"); 79 | } 80 | 81 | return fromCode(code, type); 82 | } 83 | 84 | ShaderProgram::Impl::SingleShader ShaderProgram::Impl::SingleShader::fromCode(string code, GLenum type) 85 | { 86 | SingleShader shader; 87 | debug("compiling " + typeToString(type) + "..."); 88 | 89 | const char* codeCharPtr=code.c_str(); 90 | shader.shaderId = glCreateShader(type); 91 | glShaderSource(shader.shaderId, 1, &codeCharPtr, 0); 92 | glCompileShader(shader.shaderId); 93 | // Check for compile time errors 94 | GLint success; 95 | glGetShaderiv(shader.shaderId, GL_COMPILE_STATUS, &success); 96 | if (!success) 97 | { 98 | warning("shader failed to compile:\n" + shader.getInfoLog()); 99 | } 100 | 101 | return shader; 102 | } 103 | 104 | ShaderProgram::Impl::SingleShader::~SingleShader() 105 | { 106 | glDeleteShader(shaderId); 107 | } 108 | 109 | void ShaderProgram::setupFromFiles(string vertFile, string fragFile) 110 | { 111 | ASSERT(!impl); 112 | impl = make_shared( 113 | ShaderProgram::Impl::SingleShader::fromFile(vertFile, GL_VERTEX_SHADER), 114 | ShaderProgram::Impl::SingleShader::fromFile(fragFile, GL_FRAGMENT_SHADER) 115 | ); 116 | } 117 | 118 | void ShaderProgram::setupFromCode(string vertCode, string fragCode) 119 | { 120 | ASSERT(!impl); 121 | impl = make_shared( 122 | ShaderProgram::Impl::SingleShader::fromCode(vertCode, GL_VERTEX_SHADER), 123 | ShaderProgram::Impl::SingleShader::fromCode(fragCode, GL_FRAGMENT_SHADER) 124 | ); 125 | } 126 | 127 | void ShaderProgram::bind() 128 | { 129 | ASSERT_ELSE(impl, return); 130 | 131 | glUseProgram(impl->programId); 132 | } 133 | 134 | void ShaderProgram::unbind() 135 | { 136 | glUseProgram(false); 137 | } 138 | 139 | void ShaderProgram::uniformMatrix4fv(string name, GLfloat * data) 140 | { 141 | ASSERT_ELSE(impl, return); 142 | GLint loc = glGetUniformLocation(impl->programId, name.c_str()); 143 | if (loc == -1) 144 | { 145 | warning("unknown shader uniform name '" + name + "' given"); 146 | return; 147 | } 148 | glUniformMatrix4fv(loc, 1, GL_FALSE, data); 149 | } 150 | -------------------------------------------------------------------------------- /opengl/ShaderProgram.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | 5 | class ShaderProgram 6 | { 7 | public: 8 | 9 | ShaderProgram() {} 10 | 11 | void setupFromFiles(string vertFile, string fragFile); 12 | void setupFromCode(string vertCode, string fragCode); 13 | 14 | void bind(); 15 | void unbind(); 16 | 17 | inline bool isNull() { return impl == nullptr; }; 18 | inline bool isValid() { return !isNull(); }; 19 | 20 | // add more uniform functions as needed 21 | void uniformMatrix4fv(string name, GLfloat * data); 22 | 23 | private: 24 | struct Impl; 25 | shared_ptr impl = nullptr; 26 | ShaderProgram(shared_ptr impl) { this->impl = impl; } 27 | }; 28 | 29 | -------------------------------------------------------------------------------- /opengl/Texture.cpp: -------------------------------------------------------------------------------- 1 | #include "Texture.h" 2 | #include "ShaderProgram.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // change to toggle debug statements on and off 11 | #define debug debug_off 12 | 13 | typedef void (*PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, EGLImage image); 14 | PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; 15 | 16 | struct Texture::Impl 17 | { 18 | GLuint textureId = 0; 19 | V2i dim; 20 | 21 | Impl() 22 | { 23 | if (glEGLImageTargetTexture2DOES == nullptr) 24 | { 25 | glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); 26 | ASSERT(glEGLImageTargetTexture2DOES != nullptr); 27 | } 28 | 29 | debug("setting up OpenGL texture"); 30 | 31 | glGenTextures(1, &textureId); 32 | 33 | glBindTexture(GL_TEXTURE_2D, textureId); 34 | { 35 | // Set the texture wrapping/filtering options (on the currently bound texture object) 36 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 37 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 38 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 39 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 40 | //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); 41 | //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); 42 | //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 43 | //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 44 | } 45 | glBindTexture(GL_TEXTURE_2D, false); 46 | } 47 | 48 | ~Impl() 49 | { 50 | debug("deleting texture"); 51 | glDeleteTextures(1, &textureId); 52 | } 53 | }; 54 | 55 | void Texture::setupEmpty() 56 | { 57 | if (!impl) 58 | impl = make_shared(); 59 | } 60 | 61 | void Texture::loadFromImage(string imagePath) 62 | { 63 | debug("loading '" + imagePath + "' into texture..."); 64 | if (!impl) 65 | impl = make_shared(); 66 | // Load and generate the texture 67 | int width, height; 68 | unsigned char* image = SOIL_load_image(imagePath.c_str(), &width, &height, 0, SOIL_LOAD_RGB); 69 | if (!image) 70 | { 71 | warning(string() + "image loading error: " + SOIL_last_result()); 72 | } 73 | debug("creating texture from image..."); 74 | glBindTexture(GL_TEXTURE_2D, impl->textureId); 75 | { 76 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); 77 | //glGenerateMipmap(GL_TEXTURE_2D); 78 | } 79 | glBindTexture(GL_TEXTURE_2D, false); // Unbind texture when done, so we won't accidentally mess up our texture. 80 | impl->dim = V2i(width, height); 81 | SOIL_free_image_data(image); 82 | } 83 | 84 | void Texture::loadFromData(void * data, V2i dim) 85 | { 86 | if (!impl) 87 | impl = make_shared(); 88 | //impl->important("(" + to_string(dim.x) + ", " + to_string(dim.y) + ")"); 89 | /* 90 | impl->important("loading from data, dim: " + to_string(dim)); 91 | 92 | for (int i = 0; i < dim.x * dim.y; i += 600) 93 | { 94 | impl->important( 95 | "color: " 96 | + to_string(((unsigned char *)data)[i * 4 + 0]) + ", " 97 | + to_string(((unsigned char *)data)[i * 4 + 1]) + ", " 98 | + to_string(((unsigned char *)data)[i * 4 + 2]) + ", " 99 | + to_string(((unsigned char *)data)[i * 4 + 3]) 100 | ); 101 | } 102 | */ 103 | 104 | glBindTexture(GL_TEXTURE_2D, impl->textureId); 105 | { 106 | //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 107 | //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 108 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dim.x, dim.y, 0, GL_BGRA, GL_UNSIGNED_BYTE, data); 109 | //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dim.x, dim.y, 0, GL_BGRA, GL_UNSIGNED_BYTE, data); 110 | //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, dim.x, dim.y, 0, GL_RGB, GL_UNSIGNED_BYTE, data); 111 | //glGenerateMipmap(GL_TEXTURE_2D); 112 | } 113 | glBindTexture(GL_TEXTURE_2D, false); 114 | 115 | impl->dim = dim; 116 | } 117 | 118 | void Texture::loadFromEGLImage(void * image, V2i dim) 119 | { 120 | if (!impl) 121 | impl = make_shared(); 122 | ASSERT_ELSE(glEGLImageTargetTexture2DOES, return); 123 | glBindTexture(GL_TEXTURE_2D, impl->textureId); 124 | { 125 | glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); 126 | } 127 | glBindTexture(GL_TEXTURE_2D, false); 128 | 129 | impl->dim = dim; 130 | } 131 | 132 | void Texture::bind() 133 | { 134 | ASSERT_ELSE(impl, return); 135 | glBindTexture(GL_TEXTURE_2D, impl->textureId); 136 | } 137 | 138 | void Texture::unbind() 139 | { 140 | ASSERT(impl); 141 | glBindTexture(GL_TEXTURE_2D, false); 142 | } 143 | 144 | GLuint Texture::getTextureId() 145 | { 146 | ASSERT_ELSE(impl, return 0); 147 | ASSERT(impl->textureId); 148 | return impl->textureId; 149 | } 150 | -------------------------------------------------------------------------------- /opengl/Texture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | 5 | // there is almost no cost to creating a texture, initialization is lazy and done on first load 6 | class Texture 7 | { 8 | public: 9 | Texture() {} 10 | 11 | void setupEmpty(); 12 | void loadFromImage(string filepath); 13 | void loadFromData(void * data, V2i dim); 14 | void loadFromEGLImage(void * image, V2i dim); 15 | 16 | void bind(); 17 | void unbind(); 18 | 19 | GLuint getTextureId(); 20 | inline bool isNull() { return impl == nullptr; }; 21 | inline bool isValid() { return !isNull(); }; 22 | 23 | private: 24 | struct Impl; 25 | shared_ptr impl; 26 | }; 27 | 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Hedgehog Wayland compositor 2 | 3 | ![demo screenshot](assets/demo_screenshot.png) 4 | 5 | Hedgehog is an incomplete [Wayland](https://en.wikipedia.org/wiki/Wayland_(display_server_protocol)) compositor written in C++. It uses libwayland directly, rather then a library such as weston or wlc. It currently has X11 and DRM backends (switchable at compile time from the top of main/main.cpp). It works well enough to be able to run and use weston and GTK3 apps within it, though some things are still missing, such as subsurfaces. The future of this compositor is unclear, as I intend to focus my energy on building a compositor in Rust going forward, and it makes more sense to use and contribute to a somewhat established library (such as [smithay](https://github.com/Smithay/smithay)) then continue developing my own. 6 | 7 | -------------------------------------------------------------------------------- /scene/InputInterface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | 5 | class InputInterface 6 | { 7 | public: 8 | virtual void pointerMotion(V2d newPos) = 0; 9 | virtual void pointerLeave() = 0; 10 | // button is to be a linux button such as BTN_LEFT as defined in 11 | virtual void pointerClick(uint button, bool down) = 0; 12 | // key is to be some sort of keycode 13 | virtual void keyPress(uint key, bool down) = 0; 14 | }; 15 | -------------------------------------------------------------------------------- /scene/Scene.cpp: -------------------------------------------------------------------------------- 1 | #include "Scene.h" 2 | #include "../opengl/RectRenderer.h" 3 | 4 | // change to toggle debug statements on and off 5 | #define debug debug_off 6 | 7 | Scene Scene::instance; 8 | 9 | struct Scene::Impl: InputInterface 10 | { 11 | struct Window 12 | { 13 | weak_ptr interface; 14 | V2d pos = V2d(); 15 | V2d size = V2d(0.5, 0.5); 16 | }; 17 | 18 | vector windows; 19 | Texture cursorTexture; 20 | V2d cursorHotspot; 21 | RectRenderer renderer; 22 | V2d lastMousePos; 23 | 24 | Window getActiveWindow() 25 | { 26 | // TODO: OPTIMIZATION: do this well 27 | Window active; 28 | for (auto i: windows) 29 | { 30 | if (!i.interface.expired()) 31 | active = i; 32 | } 33 | return active; 34 | } 35 | 36 | void pointerMotion(V2d newPos) 37 | { 38 | Window window = getActiveWindow(); 39 | if (auto interface = window.interface.lock()) 40 | { 41 | auto input = interface->getInputInterface().lock(); 42 | ASSERT_ELSE(input, return); 43 | lastMousePos = newPos; 44 | V2d transformed = V2d((newPos.x - window.pos.x) / window.size.x, (newPos.y - window.pos.y) / window.size.y); 45 | input->pointerMotion(transformed); 46 | } 47 | } 48 | 49 | void pointerLeave() 50 | { 51 | Window window = getActiveWindow(); 52 | if (auto interface = window.interface.lock()) 53 | { 54 | auto input = interface->getInputInterface().lock(); 55 | ASSERT_ELSE(input, return); 56 | input->pointerLeave(); 57 | cursorTexture = Texture(); 58 | cursorHotspot = V2d(); 59 | } 60 | } 61 | 62 | void pointerClick(uint button, bool down) 63 | { 64 | Window window = getActiveWindow(); 65 | if (auto interface = window.interface.lock()) 66 | { 67 | auto input = interface->getInputInterface().lock(); 68 | ASSERT_ELSE(input, return); 69 | input->pointerClick(button, down); 70 | } 71 | } 72 | 73 | void keyPress(uint key, bool down) 74 | { 75 | Window window = getActiveWindow(); 76 | if (auto interface = window.interface.lock()) 77 | { 78 | auto input = interface->getInputInterface().lock(); 79 | ASSERT_ELSE(input, return); 80 | input->keyPress(key, down); 81 | } 82 | } 83 | }; 84 | 85 | void Scene::setup() 86 | { 87 | impl = make_shared(); 88 | assert(instance.impl == nullptr); 89 | instance.impl = impl; 90 | } 91 | 92 | void Scene::addWindow(weak_ptr window) 93 | { 94 | debug("adding window to scene"); 95 | ASSERT_ELSE(impl, return); 96 | Impl::Window data; 97 | data.interface = window; 98 | data.pos = V2d(impl->windows.size() % 2 ? 0 : 0.5, (impl->windows.size() / 2) % 2 ? 0 : 0.5); 99 | impl->windows.push_back(data); 100 | } 101 | 102 | void Scene::setCursor(Texture texture, V2d hotspot) 103 | { 104 | debug("setting cursor"); 105 | ASSERT_ELSE(impl, return); 106 | impl->cursorTexture = texture; 107 | impl->cursorHotspot = hotspot; 108 | } 109 | 110 | weak_ptr Scene::getInputInterface() 111 | { 112 | ASSERT_ELSE(impl, return weak_ptr()); 113 | return impl; 114 | } 115 | 116 | void Scene::draw() 117 | { 118 | ASSERT_ELSE(impl, return); 119 | //warning(to_string(impl->windows.size()) + " windows"); 120 | for (auto window: impl->windows) 121 | { 122 | auto interface = window.interface.lock(); 123 | if (interface && interface->texture.isValid()) 124 | { 125 | impl->renderer.draw(interface->texture, window.pos, window.size); 126 | //mpl->renderer.draw(window->texture, V2d(0, 0), V2d(1, 1)); 127 | } 128 | } 129 | if (impl->cursorTexture.isValid()) 130 | { 131 | impl->renderer.draw(impl->cursorTexture, impl->lastMousePos, V2d(0.2, 0.2)); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /scene/Scene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | #include "InputInterface.h" 5 | #include "WindowInterface.h" 6 | #include "../opengl/Texture.h" 7 | 8 | class Scene 9 | { 10 | public: 11 | void setup(); 12 | void addWindow(weak_ptr window); 13 | void setCursor(Texture texture, V2d hotspot); 14 | weak_ptr getInputInterface(); 15 | void draw(); 16 | 17 | static Scene instance; 18 | private: 19 | struct Impl; 20 | shared_ptr impl; 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /scene/WindowInterface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | #include "InputInterface.h" 5 | #include "../opengl/Texture.h" 6 | 7 | class WindowInterface 8 | { 9 | public: 10 | virtual void setSize(V2i size) = 0; 11 | virtual weak_ptr getInputInterface() = 0; 12 | 13 | Texture texture; 14 | }; 15 | 16 | #include "Scene.h" // must be at bottom for include order 17 | -------------------------------------------------------------------------------- /wayland/Resource.cpp: -------------------------------------------------------------------------------- 1 | #include "Resource.h" 2 | #include 3 | 4 | // change to toggle debug statements on and off 5 | #define debug debug_off 6 | 7 | // the only purpose of this is to be the user data of wl_resources created wit this class 8 | // its only use is error checking 9 | int wlResourceMadeWithResourceClass; 10 | 11 | struct Resource::Impl 12 | { 13 | wl_resource * resource = nullptr; 14 | uint resourceVersion = 0; 15 | shared_ptr data; 16 | 17 | static std::unordered_map> map; 18 | 19 | static void destroyCallback(wl_resource * resourceRaw) 20 | { 21 | auto resource = Resource(resourceRaw); 22 | auto impl = resource.impl.lock(); 23 | ASSERT_ELSE(impl, return); 24 | auto iter = map.find(impl->resource); 25 | ASSERT_ELSE(iter != Impl::map.end(), return); 26 | ASSERT_ELSE(&*iter->second == &*impl, return); 27 | impl->resource = nullptr; // this shouldn't be needed as the entire object should be deleted next line, but best to be safe 28 | impl->data = nullptr; 29 | Impl::map.erase(iter); 30 | debug("'" + string(wl_resource_get_class(resourceRaw)) + "' removed, there are now " + to_string(Impl::map.size()) + " resources"); 31 | return; // 'this' will be invalid now, so don't do anything else! 32 | } 33 | }; 34 | 35 | std::unordered_map> Resource::Impl::map; 36 | 37 | void Resource::setup(shared_ptr dataIn, wl_client * client, uint32_t id, const wl_interface * interface, int version, const void * implStruct) 38 | { 39 | // its fine to call this multiple times on a single instance 40 | auto impl = make_shared(); 41 | this->impl = impl; 42 | wl_resource * resource = wl_resource_create(client, interface, version, id); 43 | ASSERT(Impl::map.find(resource) == Impl::map.end()); 44 | Impl::map[resource] = impl; 45 | debug("'" + string(wl_resource_get_class(resource)) + "' created, there are now " + to_string(Impl::map.size()) + " resources"); 46 | wl_resource_set_implementation(resource, implStruct, &wlResourceMadeWithResourceClass, Impl::destroyCallback); 47 | impl->resource = resource; 48 | impl->resourceVersion = version; 49 | impl->data = dataIn; 50 | } 51 | 52 | Resource::Resource(wl_resource * resource) 53 | { 54 | ASSERT_ELSE(resource, return); 55 | auto iter = Impl::map.find(resource); 56 | ASSERT_ELSE(wl_resource_get_user_data(resource) == &wlResourceMadeWithResourceClass, return); 57 | ASSERT_ELSE(iter != Impl::map.end(), return); 58 | ASSERT(iter->second->resource == resource); 59 | impl = iter->second; 60 | } 61 | 62 | shared_ptr Resource::getData() 63 | { 64 | IMPL_ELSE(return nullptr) 65 | return impl->data; 66 | } 67 | 68 | wl_resource * Resource::getRaw() 69 | { 70 | IMPL_ELSE(return nullptr); 71 | return impl->resource; 72 | } 73 | 74 | uint Resource::getVersion() 75 | { 76 | IMPL_ELSE(return 0); 77 | return impl->resourceVersion; 78 | } 79 | 80 | bool Resource::check(uint version) 81 | { 82 | IMPL_ELSE(return false); 83 | return impl->resourceVersion >= version; 84 | } 85 | 86 | void Resource::destroy() 87 | { 88 | IMPL_ELSE(return); 89 | ASSERT_ELSE(impl->resource, return); 90 | wl_resource_destroy(impl->resource); 91 | } 92 | 93 | -------------------------------------------------------------------------------- /wayland/Resource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | #include 5 | 6 | // these macros are convenient and allow for proper file/line num if assert fails 7 | #define IMPL_ELSE(action) shared_ptr impl = this->impl.lock(); ASSERT_ELSE(impl, action); 8 | #define IMPL_FROM(resource) shared_ptr impl = Resource(resource).get(); if (!impl) { warning(FUNC + " called with invalid resource"); return; } 9 | 10 | // an object that can be retrieved from a resource must inherit from Resource::Data 11 | // always hold weak pointers to objects inheriting from Resource::Data 12 | 13 | class Resource 14 | { 15 | public: 16 | 17 | class Data {}; // the only purpose of this is to inherit from 18 | 19 | Resource() {} 20 | //Resource(shared_ptr dataIn, wl_client * client, uint32_t id, const wl_interface * interface, int version, const void * implStruct); 21 | void setup(shared_ptr dataIn, wl_client * client, uint32_t id, const wl_interface * interface, int version, const void * implStruct); 22 | explicit Resource(wl_resource * resourceIn); 23 | 24 | inline bool isNull() { return impl.expired(); }; 25 | inline bool isValid() { return !isNull(); }; 26 | 27 | shared_ptr getData(); 28 | 29 | template 30 | inline shared_ptr get() 31 | { 32 | return std::static_pointer_cast(getData()); 33 | } 34 | 35 | wl_resource * getRaw(); 36 | uint getVersion(); 37 | bool check(uint version); // check if version is >= given one (also returns false for null resource) 38 | 39 | void destroy(); 40 | 41 | private: 42 | 43 | struct Impl; 44 | weak_ptr impl; 45 | }; 46 | -------------------------------------------------------------------------------- /wayland/WaylandEGL.cpp: -------------------------------------------------------------------------------- 1 | #include "WaylandEGL.h" 2 | 3 | 4 | #include 5 | #include 6 | 7 | // change to toggle debug statements on and off 8 | #define debug debug_off 9 | 10 | namespace WaylandEGL 11 | { 12 | 13 | // pointers to functions that need to be retrieved dynamically 14 | PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = nullptr; 15 | PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr; 16 | PFNEGLQUERYWAYLANDBUFFERWL eglQueryWaylandBufferWL = nullptr; 17 | PFNEGLBINDWAYLANDDISPLAYWL eglBindWaylandDisplayWL = nullptr; 18 | PFNEGLUNBINDWAYLANDDISPLAYWL eglUnbindWaylandDisplayWL = nullptr; 19 | 20 | EGLDisplay eglDisplay = nullptr; 21 | EGLContext eglContext = nullptr; 22 | bool isSetUp = false; 23 | 24 | void setEglVars(EGLDisplay eglDisplay, EGLContext eglContext) 25 | { 26 | ASSERT(!isSetUp); 27 | WaylandEGL::eglDisplay = eglDisplay; 28 | WaylandEGL::eglContext = eglContext; 29 | } 30 | 31 | void setup(wl_display * display) 32 | { 33 | debug(FUNC + " called"); 34 | ASSERT_ELSE(eglDisplay, return); 35 | ASSERT_ELSE(eglContext, return); 36 | ASSERT(!isSetUp); 37 | // function pointers that need to be retrieved at run time. This is Cs sad, pathetic attempt at duck typing. 38 | eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); 39 | ASSERT(eglCreateImageKHR); 40 | eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); 41 | ASSERT(eglDestroyImageKHR); 42 | eglQueryWaylandBufferWL = (PFNEGLQUERYWAYLANDBUFFERWL)eglGetProcAddress("eglQueryWaylandBufferWL"); 43 | ASSERT(eglQueryWaylandBufferWL); 44 | eglBindWaylandDisplayWL = (PFNEGLBINDWAYLANDDISPLAYWL)eglGetProcAddress("eglBindWaylandDisplayWL"); 45 | ASSERT(eglBindWaylandDisplayWL); 46 | eglUnbindWaylandDisplayWL = (PFNEGLUNBINDWAYLANDDISPLAYWL)eglGetProcAddress("eglUnbindWaylandDisplayWL"); 47 | ASSERT(eglUnbindWaylandDisplayWL); 48 | 49 | eglBindWaylandDisplayWL(eglDisplay, display); 50 | 51 | isSetUp = true; 52 | } 53 | 54 | void loadIntoTexture(wl_resource * buffer, Texture texture) 55 | { 56 | debug(FUNC + " called"); 57 | ASSERT_ELSE(isSetUp, return); 58 | debug("using EGL for GPU buffer sharing"); 59 | EGLint width, height; 60 | eglQueryWaylandBufferWL(eglDisplay, buffer, EGL_WIDTH, &width); 61 | eglQueryWaylandBufferWL(eglDisplay, buffer, EGL_WIDTH, &height); 62 | // TODO: I think I need to query for the format 63 | auto bufferDim = V2i(width, height); 64 | EGLint attribs = EGL_NONE; 65 | EGLImageKHR eglImageKhr = eglCreateImageKHR( 66 | eglDisplay, 67 | eglContext, 68 | EGL_WAYLAND_BUFFER_WL, 69 | buffer, 70 | &attribs 71 | ); 72 | texture.loadFromEGLImage((EGLImage)eglImageKhr, bufferDim); 73 | } 74 | 75 | } 76 | 77 | -------------------------------------------------------------------------------- /wayland/WaylandEGL.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | #include "../opengl/Texture.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace WaylandEGL 10 | { 11 | 12 | void setEglVars(EGLDisplay eglDisplay, EGLContext eglContext); 13 | 14 | void setup(wl_display * display); 15 | 16 | void loadIntoTexture(wl_resource * buffer, Texture texture); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /wayland/WaylandServer.cpp: -------------------------------------------------------------------------------- 1 | #include "../main/util.h" 2 | #include "../opengl/Texture.h" 3 | #include "WaylandServer.h" 4 | #include "WlSurface.h" 5 | #include "WlSeat.h" 6 | #include "WlDataDeviceManager.h" 7 | #include "WlRegion.h" 8 | #include "WlShellSurface.h" 9 | #include "XdgShellV6Surface.h" 10 | #include "WaylandEGL.h" 11 | 12 | #include 13 | #include 14 | #include "protocols/xdg-shell-unstable-v6.h" 15 | #include 16 | #include 17 | 18 | #include "../backend/Backend.h" 19 | 20 | // change to toggle debug statements on and off 21 | #define debug debug_off 22 | 23 | const uint wl_compositor_MAX_VERSION = 4; 24 | const uint wl_shell_MAX_VERSION = 1; 25 | const uint zxdg_shell_v6_MAX_VERSION = 1; 26 | const uint wl_output_MAX_VERSION = 3; 27 | 28 | namespace WaylandServer 29 | { 30 | 31 | // no clue what this means 32 | typedef void (*PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, EGLImage image); 33 | 34 | //PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; 35 | 36 | wl_display * display = nullptr; 37 | 38 | struct wl_event_loop *eventLoop = nullptr; 39 | int eventLoopFileDescriptor = 0; 40 | 41 | // callbacks and interfaces 42 | struct wl_compositor_interface compositorImpl = { 43 | 44 | .create_surface = +[](wl_client * client, wl_resource * resource, uint32_t id) { 45 | debug("wl_compositor.create_surface called"); 46 | uint version = Resource(resource).getVersion(); 47 | WlSurface(client, id, version); 48 | }, 49 | .create_region = +[](wl_client * client, wl_resource * resource, uint32_t id) { 50 | debug("wl_compositor.create_region called"); 51 | uint version = Resource(resource).getVersion(); 52 | WlRegion(client, id, version); 53 | } 54 | }; 55 | 56 | void compositorBind(wl_client * client, void * data, uint32_t version, uint32_t id) 57 | { 58 | debug(FUNC + " called"); 59 | ASSERT(version <= wl_compositor_MAX_VERSION); 60 | Resource compositor; 61 | compositor.setup(nullptr, client, id, &wl_compositor_interface, version, &compositorImpl); 62 | }; 63 | 64 | struct wl_shell_interface shellImpl = { 65 | .get_shell_surface = +[](wl_client * client, wl_resource * resource, uint32_t id, wl_resource * surface) { 66 | debug("wl_shell.get_shell_surface called"); 67 | WlShellSurface(client, id, wl_resource_get_version(resource), WlSurface::getFrom(Resource(resource))); 68 | } 69 | }; 70 | 71 | void shellBind(wl_client * client, void * data, uint32_t version, uint32_t id) 72 | { 73 | debug(FUNC + " called"); 74 | ASSERT(version <= wl_shell_MAX_VERSION); 75 | Resource shell; 76 | shell.setup(nullptr, client, id, &wl_shell_interface, version, &shellImpl); 77 | }; 78 | 79 | 80 | struct zxdg_shell_v6_interface xdgShellV6Impl { 81 | .destroy = +[](struct wl_client *client, struct wl_resource *resource) { 82 | debug("zxdg_shell_v6.destroy called"); 83 | Resource(resource).destroy(); 84 | }, 85 | .create_positioner = +[](struct wl_client *client, struct wl_resource *resource, uint32_t id) { 86 | warning("zxdg_shell_v6.create_positioner not implemented"); 87 | }, 88 | .get_xdg_surface = +[](struct wl_client *client,struct wl_resource *resource, uint32_t id, struct wl_resource *surface) { 89 | debug("zxdg_shell_v6.get_xdg_surface called"); 90 | XdgShellV6Surface(client, id, wl_resource_get_version(resource), WlSurface::getFrom(Resource(surface))); 91 | }, 92 | .pong = +[](struct wl_client *client, struct wl_resource *resource, uint32_t serial) 93 | { 94 | debug("zxdg_shell_v6.pong called"); 95 | // this is to test responsiveness, fine to ignore for now 96 | } 97 | }; 98 | 99 | void xdgShellV6Bind(wl_client * client, void * data, uint32_t version, uint32_t id) 100 | { 101 | debug(FUNC + " called"); 102 | ASSERT(version <= zxdg_shell_v6_MAX_VERSION); 103 | Resource shell; 104 | shell.setup(nullptr, client, id, &zxdg_shell_v6_interface, version, &xdgShellV6Impl); 105 | }; 106 | 107 | void seatBind(wl_client * client, void * data, uint32_t version, uint32_t id) 108 | { 109 | debug(FUNC + " called"); 110 | WlSeat(client, id, version); 111 | }; 112 | 113 | void dataDeviceManagerBind(wl_client * client, void * data, uint32_t version, uint32_t id) 114 | { 115 | debug(FUNC + " called"); 116 | WlDataDeviceManager(client, id, version); 117 | } 118 | 119 | const struct wl_output_interface outputInterface { 120 | .release = +[](struct wl_client *client, struct wl_resource *resource) 121 | { 122 | debug("wl_output.release called"); 123 | Resource(resource).destroy(); 124 | } 125 | }; 126 | 127 | void outputBind(wl_client * client, void * data, uint32_t version, uint32_t id) 128 | { 129 | debug("outputBindCallback called"); 130 | ASSERT(version <= wl_output_MAX_VERSION); 131 | Resource output; 132 | output.setup(nullptr, client, id, &wl_output_interface, version, &outputInterface); 133 | if (version >= WL_OUTPUT_GEOMETRY_SINCE_VERSION) 134 | wl_output_send_geometry(output.getRaw(), 0, 0, 1600, 1024, WL_OUTPUT_SUBPIXEL_NONE, "", "", WL_OUTPUT_TRANSFORM_NORMAL); 135 | if (version >= WL_OUTPUT_SCALE_SINCE_VERSION) 136 | wl_output_send_scale(output.getRaw(), 1); 137 | if (version >= WL_OUTPUT_MODE_SINCE_VERSION) 138 | wl_output_send_mode(output.getRaw(), WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED, 800, 800, 60000); 139 | if (version >= WL_OUTPUT_DONE_SINCE_VERSION) 140 | wl_output_send_done(output.getRaw()); 141 | } 142 | 143 | void setup() 144 | { 145 | debug("starting Wayland server"); 146 | 147 | // get function pointer 148 | //glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); 149 | 150 | display = wl_display_create(); 151 | 152 | // automatically find a free socket and connect it to the display 153 | wl_display_add_socket_auto(display); 154 | 155 | // create global objects 156 | wl_global_create(display, &wl_compositor_interface, wl_compositor_MAX_VERSION, nullptr, compositorBind); 157 | wl_global_create(display, &wl_shell_interface, wl_shell_MAX_VERSION, nullptr, shellBind); 158 | wl_global_create(display, &zxdg_shell_v6_interface, zxdg_shell_v6_MAX_VERSION, nullptr, xdgShellV6Bind); 159 | wl_global_create(display, &wl_seat_interface, wl_seat_MAX_VERSION, nullptr, seatBind); 160 | wl_global_create(display, &wl_data_device_manager_interface, wl_data_device_manager_MAX_VERSION, nullptr, dataDeviceManagerBind); 161 | wl_global_create(display, &wl_output_interface, wl_output_MAX_VERSION, nullptr, outputBind); 162 | 163 | wl_display_init_shm(display); 164 | 165 | eventLoop = wl_display_get_event_loop(display); 166 | eventLoopFileDescriptor = wl_event_loop_get_fd(eventLoop); 167 | 168 | WaylandEGL::setup(display); 169 | 170 | debug("Wayland server setup done"); 171 | } 172 | 173 | void shutdown() 174 | { 175 | debug("shutting down Wayland server"); 176 | wl_display_destroy(display); 177 | display = nullptr; 178 | } 179 | 180 | void iteration() 181 | { 182 | wl_event_loop_dispatch(eventLoop, 0); 183 | wl_display_flush_clients(display); 184 | WlSurface::runFrameCallbacks(); 185 | } 186 | 187 | uint32_t lastSerialNum = 1; 188 | 189 | uint32_t nextSerialNum() 190 | { 191 | lastSerialNum++; 192 | return lastSerialNum; 193 | } 194 | 195 | } 196 | 197 | -------------------------------------------------------------------------------- /wayland/WaylandServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | 5 | namespace WaylandServer 6 | { 7 | 8 | void setup(); 9 | 10 | void shutdown(); 11 | 12 | void iteration(); 13 | 14 | uint32_t nextSerialNum(); 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /wayland/WlArray.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | #include 5 | 6 | template 7 | class WlArray 8 | { 9 | public: 10 | WlArray() 11 | { 12 | array = make_unique(); 13 | wl_array_init(&*array); 14 | } 15 | 16 | ~WlArray() 17 | { 18 | wl_array_release(&*array); 19 | } 20 | 21 | void append(T item) 22 | { 23 | T * ptr = (T *)wl_array_add(&*array, sizeof(T)); 24 | *ptr = item; 25 | } 26 | 27 | // returned value should not be stored long term as it will be invalid once this object dies 28 | wl_array * getRaw() 29 | { 30 | return &*array; 31 | } 32 | 33 | private: 34 | unique_ptr array; 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /wayland/WlDataDeviceManager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "WlDataDeviceManager.h" 3 | #include "Resource.h" 4 | #include "WlArray.h" 5 | 6 | // change to toggle debug statements on and off 7 | #define debug debug_off 8 | 9 | struct WlDataDeviceManager::Impl: Resource::Data 10 | { 11 | // instance data 12 | Resource dataDeviceManager; 13 | Resource dataDevice; 14 | 15 | // interface 16 | static const struct wl_data_device_manager_interface dataDeviceManagerInterface; 17 | static const struct wl_data_device_interface dataDeviceInterface; 18 | }; 19 | 20 | const struct wl_data_device_manager_interface WlDataDeviceManager::Impl::dataDeviceManagerInterface { 21 | // create_data_source 22 | +[](struct wl_client *client, struct wl_resource *resource, uint32_t id) 23 | { 24 | warning("wl_data_device_manager_interface.create_data_source not yet implemented"); 25 | }, 26 | // get_data_device 27 | +[](struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *seat) 28 | { 29 | debug("wl_data_device_manager_interface.get_data_device called"); 30 | IMPL_FROM(resource); 31 | ASSERT(impl->dataDevice.isNull()); 32 | impl->dataDevice.setup(impl, client, id, &wl_data_device_interface, 1, &Impl::dataDeviceInterface); 33 | } 34 | }; 35 | 36 | const struct wl_data_device_interface WlDataDeviceManager::Impl::dataDeviceInterface { 37 | // start_drag 38 | +[](struct wl_client *client, struct wl_resource *resource, struct wl_resource *source, struct wl_resource *origin, struct wl_resource *icon, uint32_t serial) 39 | { 40 | warning("wl_data_device_interface.start_drag not yet implemented"); 41 | }, 42 | // set_selection 43 | +[](struct wl_client *client, struct wl_resource *resource, struct wl_resource *source, uint32_t serial) 44 | { 45 | warning("wl_data_device_interface.set_selection not yet implemented"); 46 | }, 47 | // release 48 | +[](struct wl_client *client, struct wl_resource *resource) 49 | { 50 | debug("wl_data_device_interface.release called"); 51 | Resource(resource).destroy(); 52 | }, 53 | }; 54 | 55 | WlDataDeviceManager::WlDataDeviceManager(wl_client * client, uint32_t id, uint version) 56 | { 57 | debug("creating WlDataDeviceManager"); 58 | auto impl = make_shared(); 59 | this->impl = impl; 60 | ASSERT(version <= wl_data_device_manager_MAX_VERSION); 61 | impl->dataDeviceManager.setup(impl, client, id, &wl_data_device_manager_interface, version, &Impl::dataDeviceManagerInterface); 62 | } 63 | -------------------------------------------------------------------------------- /wayland/WlDataDeviceManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "WaylandServer.h" 4 | #include "Resource.h" 5 | 6 | const uint wl_data_device_manager_MAX_VERSION = 3; 7 | 8 | class WlDataDeviceManager 9 | { 10 | public: 11 | WlDataDeviceManager() {} 12 | WlDataDeviceManager(wl_client * client, uint32_t id, uint version); 13 | 14 | private: 15 | struct Impl; 16 | weak_ptr impl; 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /wayland/WlRegion.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "WlRegion.h" 3 | #include "Resource.h" 4 | 5 | // change to toggle debug statements on and off 6 | #define debug debug_off 7 | 8 | struct Area 9 | { 10 | virtual bool checkPoint(V2d pt) = 0; 11 | }; 12 | 13 | struct EmptyArea: Area 14 | { 15 | bool checkPoint(V2d pt) 16 | { 17 | return false; 18 | } 19 | }; 20 | 21 | struct RectArea: Area 22 | { 23 | V2d low; 24 | V2d high; 25 | 26 | RectArea(V2d low, V2d high) 27 | { 28 | this->low = low; 29 | this->high = high; 30 | } 31 | 32 | bool checkPoint(V2d pt) 33 | { 34 | return 35 | pt.x >= low.x && 36 | pt.y >= low.y && 37 | pt.x <= high.x && 38 | pt.y <= high.y; 39 | } 40 | }; 41 | 42 | struct UnionArea: Area 43 | { 44 | unique_ptr a, b; 45 | 46 | UnionArea(unique_ptr a, unique_ptr b) 47 | { 48 | this->a = move(a); 49 | this->b = move(b); 50 | } 51 | 52 | bool checkPoint(V2d pt) 53 | { 54 | return a->checkPoint(pt) || b->checkPoint(pt); 55 | } 56 | }; 57 | 58 | struct IntersectionArea: Area 59 | { 60 | unique_ptr a, b; 61 | 62 | IntersectionArea(unique_ptr a, unique_ptr b) 63 | { 64 | this->a = move(a); 65 | this->b = move(b); 66 | } 67 | 68 | bool checkPoint(V2d pt) 69 | { 70 | return a->checkPoint(pt) && b->checkPoint(pt); 71 | } 72 | }; 73 | 74 | struct InverseArea: Area 75 | { 76 | unique_ptr a; 77 | 78 | InverseArea(unique_ptr a) 79 | { 80 | this->a = move(a); 81 | } 82 | 83 | bool checkPoint(V2d pt) 84 | { 85 | return !a->checkPoint(pt); 86 | } 87 | }; 88 | struct WlRegion::Impl: Resource::Data 89 | { 90 | // instance data 91 | unique_ptr data; 92 | 93 | //WaylandObject2 wlObj; 94 | Resource resource; 95 | 96 | // members 97 | Impl() 98 | { 99 | data = make_unique(); 100 | } 101 | 102 | // interface 103 | static const struct wl_region_interface wlRegionInterface; 104 | }; 105 | 106 | const struct wl_region_interface WlRegion::Impl::wlRegionInterface = { 107 | // destroy 108 | +[](struct wl_client *client, struct wl_resource *resource) 109 | { 110 | debug("wl_region_interface::destroy called"); 111 | Resource(resource).destroy(); 112 | }, 113 | // add 114 | +[](struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) 115 | { 116 | debug("wl_region_interface::add called (" 117 | "x: " + to_string(x) + ", " 118 | "y: " + to_string(y) + ", " 119 | "width: " + to_string(width) + ", " 120 | "height: " + to_string(height) + ")"); 121 | 122 | //GET_IMPL_FROM(resource); 123 | //auto impl = WaylandObject2::get(resource); 124 | //assert(impl); 125 | IMPL_FROM(resource); 126 | if ( 127 | x == INT32_MIN && 128 | y == INT32_MIN && 129 | width == INT32_MAX && 130 | height == INT32_MAX 131 | ) 132 | { 133 | impl->data = make_unique(make_unique()); 134 | } 135 | else 136 | { 137 | auto rect = make_unique(V2d(x, y), V2d(x + width, y + height)); 138 | impl->data = make_unique(move(impl->data), move(rect)); 139 | } 140 | }, 141 | // subtract 142 | +[](struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) 143 | { 144 | debug("wl_region_interface::subtract called (" 145 | "x: " + to_string(x) + ", " 146 | "y: " + to_string(y) + ", " 147 | "width: " + to_string(width) + ", " 148 | "height: " + to_string(height) + ")"); 149 | 150 | IMPL_FROM(resource); 151 | if ( 152 | x == INT32_MIN && 153 | y == INT32_MIN && 154 | width == INT32_MAX && 155 | height == INT32_MAX 156 | ) 157 | { 158 | impl->data = make_unique(); 159 | } 160 | else 161 | { 162 | auto rect = make_unique(V2d(x, y), V2d(x + width, y + height)); 163 | impl->data = make_unique(move(impl->data), make_unique(move(rect))); 164 | } 165 | } 166 | }; 167 | 168 | WlRegion::WlRegion(wl_client * client, uint32_t id, uint version) 169 | { 170 | debug("creating WlRegion"); 171 | auto impl = make_shared(); 172 | this->impl = impl; 173 | impl->resource.setup(impl, client, id, &wl_region_interface, version, &Impl::wlRegionInterface); 174 | } 175 | -------------------------------------------------------------------------------- /wayland/WlRegion.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "WlSurface.h" 4 | 5 | class WlRegion 6 | { 7 | public: 8 | WlRegion(wl_client * client, uint32_t id, uint version); 9 | 10 | private: 11 | struct Impl; 12 | weak_ptr impl; 13 | }; 14 | -------------------------------------------------------------------------------- /wayland/WlSeat.cpp: -------------------------------------------------------------------------------- 1 | #include "WlSeat.h" 2 | #include "Resource.h" 3 | #include "WlArray.h" 4 | #include "WlSurface.h" 5 | #include "../backend/Backend.h" 6 | #include "../scene/Scene.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // change to toggle debug statements on and off 16 | #define debug debug_off 17 | 18 | struct WlSeat::Impl: Resource::Data 19 | { 20 | // instance data 21 | Resource seat; 22 | Resource pointer; 23 | Resource keyboard; 24 | // these are ONLY used to see if the last surface to receive input matches the current one 25 | wl_resource * lastPointerSurfaceRaw = nullptr; 26 | //wl_resource * lastKeyboardSurfaceRaw = nullptr; 27 | 28 | // static 29 | static std::unordered_map> clientToImpl; 30 | 31 | // interface 32 | static const struct wl_pointer_interface pointerInterface; 33 | static const struct wl_keyboard_interface keyboardInterface; 34 | static const struct wl_seat_interface seatInterface; 35 | }; 36 | 37 | std::unordered_map> WlSeat::Impl::clientToImpl; 38 | 39 | const struct wl_pointer_interface WlSeat::Impl::pointerInterface = { 40 | 41 | .set_cursor = +[](wl_client * client, wl_resource * resource, uint32_t serial, wl_resource * _surface, int32_t hotspot_x, int32_t hotspot_y) { 42 | debug("wl_pointer.set_cursor called"); 43 | IMPL_FROM(resource); 44 | if (_surface == nullptr) 45 | { 46 | Scene::instance.setCursor(Texture(), V2d(0, 0)); 47 | } 48 | else 49 | { 50 | WlSurface surface = WlSurface::getFrom(Resource(_surface)); 51 | Texture texture = surface.getTexture(); 52 | ASSERT_ELSE(texture.isValid(), return); 53 | Scene::instance.setCursor(texture, V2d(0, 0)); 54 | } 55 | }, 56 | 57 | .release = +[](wl_client * client, wl_resource *resource) { 58 | debug("wl_pointer.release called"); 59 | Resource(resource).destroy(); 60 | } 61 | }; 62 | 63 | const struct wl_keyboard_interface WlSeat::Impl::keyboardInterface { 64 | // release 65 | +[](wl_client * client, wl_resource *resource) 66 | { 67 | debug("wl_keyboard_interface.release called"); 68 | Resource(resource).destroy(); 69 | } 70 | }; 71 | 72 | const struct wl_seat_interface WlSeat::Impl::seatInterface = { 73 | 74 | .get_pointer = +[](wl_client * client, wl_resource * resource, uint32_t id) { 75 | debug("wl_seat.get_pointer called"); 76 | IMPL_FROM(resource); 77 | ASSERT(impl->pointer.isNull()); 78 | impl->pointer.setup( 79 | impl, 80 | client, 81 | id, 82 | &wl_pointer_interface, 83 | wl_resource_get_version(resource), 84 | &pointerInterface 85 | ); 86 | }, 87 | .get_keyboard = +[](wl_client * client, wl_resource * resource, uint32_t id) { 88 | debug("wl_seat.get_keyboard called"); 89 | IMPL_FROM(resource); 90 | ASSERT(impl->keyboard.isNull()); 91 | impl->keyboard.setup( 92 | impl, 93 | client, 94 | id, 95 | &wl_keyboard_interface, 96 | wl_resource_get_version(resource), 97 | &keyboardInterface 98 | ); 99 | Resource keyboard = impl->keyboard; 100 | if (keyboard.check(WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION)) 101 | { 102 | wl_keyboard_send_repeat_info(keyboard.getRaw(), 0, 0); 103 | } 104 | if (keyboard.check(WL_KEYBOARD_KEYMAP_SINCE_VERSION)) 105 | { 106 | ASSERT_ELSE(Backend::instance, return); 107 | string keymapString = Backend::instance->getKeymap(); 108 | size_t dataSize = keymapString.size() + 1; 109 | string xdgRuntimeDir = getenv("XDG_RUNTIME_DIR"); 110 | ASSERT_ELSE(!xdgRuntimeDir.empty(), return); 111 | int fd = open(xdgRuntimeDir.c_str(), O_TMPFILE|O_RDWR|O_EXCL, 0600); 112 | ftruncate(fd, dataSize); 113 | void * data = mmap(nullptr, dataSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 114 | memcpy(data, keymapString.c_str(), dataSize); 115 | munmap(data, dataSize); 116 | wl_keyboard_send_keymap(impl->keyboard.getRaw(), WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, fd, dataSize); 117 | close(fd); 118 | } 119 | }, 120 | .get_touch = +[](wl_client * client, wl_resource * resource, uint32_t id) { 121 | warning("wl_seat.get_touch not implemented"); 122 | }, 123 | .release = +[](wl_client * client, wl_resource * resource) { 124 | debug("wl_seat.release called"); 125 | Resource(resource).destroy(); 126 | } 127 | }; 128 | 129 | WlSeat::WlSeat(wl_client * client, uint32_t id, uint version) 130 | { 131 | debug("creating WlSeat"); 132 | auto iter = Impl::clientToImpl.find(client); 133 | if (iter != Impl::clientToImpl.end()) 134 | { 135 | warning("single client made multiple seats"); 136 | } 137 | auto impl = make_shared(); 138 | this->impl = impl; 139 | Impl::clientToImpl[client] = impl; 140 | ASSERT(version <= wl_seat_MAX_VERSION); 141 | impl->seat.setup(impl, client, id, &wl_seat_interface, version, &Impl::seatInterface); 142 | wl_seat_send_capabilities(impl->seat.getRaw(), WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_KEYBOARD); 143 | } 144 | 145 | void WlSeat::pointerMotion(V2d position, Resource surface) 146 | { 147 | auto impl = getImplFromSurface(surface); 148 | 149 | if (!impl || impl->pointer.isNull()) 150 | { 151 | debug("client has not created the needed objects"); 152 | return; 153 | } 154 | ASSERT_ELSE(surface.isValid(), return); 155 | Resource pointer = impl->pointer; 156 | auto fixedPos = V2(wl_fixed_from_double(position.x), wl_fixed_from_double(position.y)); 157 | if (impl->lastPointerSurfaceRaw != surface.getRaw()) 158 | { 159 | if (pointer.check(WL_POINTER_ENTER_SINCE_VERSION)) 160 | { 161 | impl->lastPointerSurfaceRaw = surface.getRaw(); 162 | 163 | wl_pointer_send_enter( 164 | pointer.getRaw(), 165 | WaylandServer::nextSerialNum(), 166 | surface.getRaw(), 167 | fixedPos.x, 168 | fixedPos.y 169 | ); 170 | } 171 | } 172 | else 173 | { 174 | if (pointer.check(WL_POINTER_MOTION_SINCE_VERSION)) 175 | { 176 | wl_pointer_send_motion( 177 | pointer.getRaw(), 178 | timeSinceStartMili(), 179 | fixedPos.x, 180 | fixedPos.y 181 | ); 182 | } 183 | } 184 | if (pointer.check(WL_POINTER_FRAME_SINCE_VERSION)) 185 | { 186 | wl_pointer_send_frame(pointer.getRaw()); 187 | } 188 | 189 | } 190 | 191 | void WlSeat::pointerLeave(Resource surface) 192 | { 193 | auto impl = getImplFromSurface(surface); 194 | 195 | if (!impl || impl->pointer.isNull()) 196 | { 197 | debug("client has not created the needed objects"); 198 | return; 199 | } 200 | ASSERT_ELSE(impl->lastPointerSurfaceRaw != nullptr, return); 201 | ASSERT_ELSE(impl->lastPointerSurfaceRaw == surface.getRaw(), return); 202 | Resource pointer = impl->pointer; 203 | impl->lastPointerSurfaceRaw = nullptr; 204 | if (pointer.check(WL_POINTER_LEAVE_SINCE_VERSION)) 205 | { 206 | wl_pointer_send_leave( 207 | pointer.getRaw(), 208 | WaylandServer::nextSerialNum(), 209 | surface.getRaw() 210 | ); 211 | } 212 | if (pointer.check(WL_POINTER_FRAME_SINCE_VERSION)) 213 | { 214 | wl_pointer_send_frame(pointer.getRaw()); 215 | } 216 | } 217 | 218 | void WlSeat::pointerClick(uint button, bool down, Resource surface) 219 | { 220 | debug("mouse button '" + to_string(button) + "' " + (down ? "down" : "up")); 221 | auto impl = getImplFromSurface(surface); 222 | if (!impl || impl->pointer.isNull()) 223 | { 224 | debug("client has not created the needed objects"); 225 | return; 226 | } 227 | ASSERT_ELSE(impl->lastPointerSurfaceRaw != nullptr, return); 228 | ASSERT_ELSE(impl->lastPointerSurfaceRaw == surface.getRaw(), return); 229 | Resource pointer = impl->pointer; 230 | 231 | if (pointer.check(WL_POINTER_BUTTON_SINCE_VERSION)) 232 | { 233 | wl_pointer_send_button( 234 | pointer.getRaw(), 235 | WaylandServer::nextSerialNum(), 236 | timeSinceStartMili(), 237 | button, 238 | down ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED 239 | ); 240 | } 241 | if (pointer.check(WL_POINTER_FRAME_SINCE_VERSION)) 242 | { 243 | wl_pointer_send_frame(pointer.getRaw()); 244 | } 245 | } 246 | 247 | void WlSeat::keyPress(uint key, bool down, Resource surface) 248 | { 249 | debug("key '" + to_string(key) + "' " + (down ? "down" : "up")); 250 | 251 | auto impl = getImplFromSurface(surface); 252 | 253 | if (!impl || impl->keyboard.isNull()) 254 | { 255 | debug("client has not created the needed objects"); 256 | return; 257 | } 258 | ASSERT_ELSE(impl->lastPointerSurfaceRaw != nullptr, return); 259 | ASSERT_ELSE(impl->lastPointerSurfaceRaw == surface.getRaw(), return); 260 | Resource keyboard = impl->keyboard; 261 | 262 | WlArray keysDownArray; // it will be empty for now 263 | 264 | if (keyboard.check(WL_KEYBOARD_ENTER_SINCE_VERSION)) 265 | { 266 | wl_keyboard_send_enter( 267 | keyboard.getRaw(), 268 | WaylandServer::nextSerialNum(), 269 | surface.getRaw(), 270 | keysDownArray.getRaw() 271 | ); 272 | } 273 | if (keyboard.check(WL_KEYBOARD_KEY_SINCE_VERSION)) 274 | { 275 | wl_keyboard_send_key( 276 | keyboard.getRaw(), 277 | WaylandServer::nextSerialNum(), 278 | timeSinceStartMili(), 279 | key, 280 | down ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED 281 | ); 282 | } 283 | if (keyboard.check(WL_KEYBOARD_LEAVE_SINCE_VERSION)) 284 | { 285 | wl_keyboard_send_leave( 286 | keyboard.getRaw(), 287 | WaylandServer::nextSerialNum(), 288 | surface.getRaw() 289 | ); 290 | } 291 | } 292 | 293 | WlSeat WlSeat::getFromClient(wl_client * client) 294 | { 295 | ASSERT(client); 296 | auto iter = Impl::clientToImpl.find(client); 297 | if (iter == Impl::clientToImpl.end()) 298 | { 299 | debug("requested client has not made a seat"); 300 | return WlSeat(); 301 | } 302 | WlSeat seat; 303 | seat.impl = iter->second; 304 | return seat; 305 | 306 | } 307 | 308 | shared_ptr WlSeat::getImplFromSurface(Resource surface) 309 | { 310 | ASSERT_ELSE(surface.isValid(), return nullptr); 311 | ASSERT_ELSE(surface.getRaw(), return nullptr); 312 | wl_client * client = surface.getRaw()->client; 313 | auto impl = getFromClient(client).impl.lock(); 314 | return impl; 315 | } 316 | -------------------------------------------------------------------------------- /wayland/WlSeat.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "WaylandServer.h" 4 | #include "Resource.h" 5 | 6 | const uint wl_seat_MAX_VERSION = 6; 7 | 8 | class WlSeat 9 | { 10 | public: 11 | WlSeat() {} 12 | WlSeat(wl_client * client, uint32_t id, uint version); 13 | static void pointerMotion(V2d position, Resource surface); 14 | static void pointerLeave(Resource surface); // call this before pointerMove on the new surface 15 | static void pointerClick(uint button, bool down, Resource surface); 16 | static void keyPress(uint key, bool down, Resource surface); 17 | static WlSeat getFromClient(wl_client * client); 18 | 19 | private: 20 | struct Impl; 21 | weak_ptr impl; 22 | static shared_ptr getImplFromSurface(Resource surface); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /wayland/WlShellSurface.cpp: -------------------------------------------------------------------------------- 1 | #include "WlShellSurface.h" 2 | #include "../scene/WindowInterface.h" 3 | #include "Resource.h" 4 | #include "WlSeat.h" 5 | 6 | #include 7 | 8 | // change to toggle debug statements on and off 9 | #define debug debug_off 10 | 11 | struct WlShellSurface::Impl: Resource::Data, WindowInterface 12 | { 13 | // instance data 14 | WlSurface waylandSurface; 15 | wl_client * client; 16 | Resource resource; 17 | 18 | void setSize(V2i size) 19 | { 20 | warning(FUNC + " not yet implemented"); 21 | } 22 | 23 | weak_ptr getInputInterface() 24 | { 25 | return waylandSurface.getInputInterface(); 26 | } 27 | 28 | // interface 29 | static const struct wl_shell_surface_interface wlShellSurfaceInterface; 30 | }; 31 | 32 | const struct wl_shell_surface_interface WlShellSurface::Impl::wlShellSurfaceInterface = { 33 | // shell surface pong 34 | +[](struct wl_client *client, wl_resource * resource, uint32_t serial) 35 | { 36 | warning("shell surface interface pong callback called (not yet implemented)"); 37 | }, 38 | // shell surface move 39 | +[](wl_client * client, wl_resource * resource, wl_resource * seat, uint32_t serial) 40 | { 41 | warning("shell surface interface move callback called (not yet implemented)"); 42 | }, 43 | // shell surface resize 44 | +[](wl_client * client, wl_resource * resource, wl_resource * seat, uint32_t serial, uint32_t edges) 45 | { 46 | warning("shell surface interface resize callback called (not yet implemented)"); 47 | }, 48 | // shell surface set toplevel 49 | +[](wl_client * client, wl_resource * resource) 50 | { 51 | debug("shell surface interface set toplevel callback called"); 52 | }, 53 | // shell surface set transient 54 | +[](wl_client * client, wl_resource * resource, wl_resource * parent, int32_t x, int32_t y, uint32_t flags) 55 | { 56 | warning("shell surface interface set transient callback called (not yet implemented)"); 57 | }, 58 | // shell surface set fullscreen 59 | +[](wl_client * client, wl_resource * resource, uint32_t method, uint32_t framerate, wl_resource * output) 60 | { 61 | warning("shell surface interface set fullscreen callback called (not yet implemented)"); 62 | }, 63 | // shell surface set popup 64 | +[](wl_client * client, wl_resource * resource, wl_resource * seat, uint32_t serial, wl_resource * parent, int32_t x, int32_t y, uint32_t flags) 65 | { 66 | warning("shell surface interface set popup callback called (not yet implemented)"); 67 | }, 68 | // shell surface set maximized 69 | +[](wl_client * client, wl_resource * resource, wl_resource * output) 70 | { 71 | warning("shell surface interface set maximized callback called (not yet implemented)"); 72 | }, 73 | // shell surface set title 74 | +[](wl_client * client, wl_resource * resource, const char * title) 75 | { 76 | warning("shell surface interface set title callback called (not yet implemented)"); 77 | }, 78 | // shell surface set class 79 | +[](wl_client * client, wl_resource * resource, const char * class_) 80 | { 81 | warning("shell surface interface set class callback called (not yet implemented)"); 82 | }, 83 | }; 84 | 85 | WlShellSurface::WlShellSurface(wl_client * client, uint32_t id, uint version, WlSurface surface) 86 | { 87 | debug("creating WlShellSurface"); 88 | auto impl = make_shared(); 89 | this->impl = impl; 90 | impl->waylandSurface = surface; 91 | impl->client = client; 92 | impl->texture = surface.getTexture(); 93 | Scene::instance.addWindow(impl); 94 | impl->resource.setup(impl, client, id, &wl_shell_surface_interface, version, &Impl::wlShellSurfaceInterface); 95 | } 96 | -------------------------------------------------------------------------------- /wayland/WlShellSurface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "WlSurface.h" 4 | 5 | class WlShellSurface 6 | { 7 | public: 8 | WlShellSurface(wl_client * client, uint32_t id, uint version, WlSurface surface); 9 | 10 | private: 11 | struct Impl; 12 | weak_ptr impl; 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /wayland/WlSurface.cpp: -------------------------------------------------------------------------------- 1 | #include "WlSurface.h" 2 | #include "WaylandServer.h" 3 | #include "Resource.h" 4 | #include "../backend/Backend.h" 5 | #include "WlSeat.h" 6 | #include "WaylandEGL.h" 7 | 8 | #include 9 | 10 | // change to toggle debug statements on and off 11 | #define debug debug_off 12 | 13 | struct WlSurface::Impl: Resource::Data, InputInterface 14 | { 15 | // instance data 16 | V2d dim; 17 | bool isDamaged = false; 18 | Texture texture; 19 | wl_resource * bufferResourceRaw; 20 | Resource surfaceResource; 21 | //struct wl_resource * surfaceResource = nullptr; 22 | 23 | // interface 24 | static const struct wl_surface_interface surfaceInterface; 25 | 26 | static vector frameCallbacks; 27 | 28 | void pointerMotion(V2d normalizedPos) 29 | { 30 | ASSERT_ELSE(surfaceResource.isValid(), return); 31 | V2d newPos = V2d(normalizedPos.x * dim.x, (1 - normalizedPos.y) * dim.y); 32 | WlSeat::pointerMotion(newPos, surfaceResource); 33 | } 34 | 35 | void pointerLeave() 36 | { 37 | ASSERT_ELSE(surfaceResource.isValid(), return); 38 | WlSeat::pointerLeave(surfaceResource); 39 | } 40 | 41 | void pointerClick(uint button, bool down) 42 | { 43 | ASSERT_ELSE(surfaceResource.isValid(), return); 44 | WlSeat::pointerClick(button, down, surfaceResource); 45 | } 46 | 47 | void keyPress(uint key, bool down) 48 | { 49 | ASSERT_ELSE(surfaceResource.isValid(), return); 50 | WlSeat::keyPress(key, down, surfaceResource); 51 | } 52 | }; 53 | 54 | vector WlSurface::Impl::frameCallbacks; 55 | 56 | const struct wl_surface_interface WlSurface::Impl::surfaceInterface = { 57 | .destroy = +[](wl_client * client, wl_resource * resource) { 58 | debug("wl_surface.destroy called"); 59 | Resource(resource).destroy(); 60 | }, 61 | .attach = +[](wl_client * client, wl_resource * resource, wl_resource * buffer, int32_t x, int32_t y) { 62 | debug("wl_surface.attach called"); 63 | IMPL_FROM(resource); 64 | impl->bufferResourceRaw = buffer; 65 | }, 66 | .damage = +[](wl_client * client, wl_resource * resource, int32_t x, int32_t y, int32_t width, int32_t height) { 67 | debug("wl_surface.damage called"); 68 | // TODO: OPTIMIZATION: only repaint damaged region 69 | IMPL_FROM(resource); 70 | impl->isDamaged = true; 71 | }, 72 | .frame = +[](wl_client * client, wl_resource * resource, uint32_t callback) { 73 | debug("wl_surface.frame called"); 74 | // TODO: OPTIMIZATION: don't call the callback if the window is hidden (this may take some restructuring) 75 | Resource callbackResource; 76 | callbackResource.setup(nullptr, client, callback, &wl_callback_interface, 1, nullptr); 77 | frameCallbacks.push_back(callbackResource); 78 | }, 79 | .set_opaque_region = +[](wl_client * client, wl_resource * resource, wl_resource * region) { 80 | debug("wl_surface.set_opaque_region called"); 81 | // this is just for optimizing the redrawing of things behind this surface, fine to ignore for now 82 | }, 83 | .set_input_region = +[](wl_client * client, wl_resource * resource, wl_resource * region) { 84 | warning("wl_surface.set_input_region not implemented"); 85 | }, 86 | .commit = +[](wl_client * client, wl_resource * resource) { 87 | debug("wl_surface.commit called"); 88 | 89 | IMPL_FROM(resource); 90 | 91 | // get the data we'll need 92 | struct wl_resource * buffer = impl->bufferResourceRaw; 93 | if (buffer != nullptr && impl->isDamaged) 94 | { 95 | //EGLint texture_format; 96 | ASSERT_ELSE(Backend::instance, return); 97 | //Display * display = (Display *)Backend::instance->getXDisplay(); 98 | //ASSERT_ELSE(display, return); 99 | 100 | // make sure this function pointer has been initialized 101 | //assert(Impl::eglQueryWaylandBufferWL); 102 | 103 | // query the texture format of the buffer 104 | //bool idkWhatThisVarMeans = Impl::eglQueryWaylandBufferWL(display, buffer, EGL_TEXTURE_FORMAT, &texture_format); 105 | 106 | V2i bufferDim; 107 | 108 | /*if (idkWhatThisVarMeans) { 109 | // I think this is for using EGL to share a buffer directly on the GPU 110 | // this code path is currently untested 111 | debug("using EGL for GPU buffer sharing"); 112 | EGLint width, height; 113 | Impl::eglQueryWaylandBufferWL(display, buffer, EGL_WIDTH, &width); 114 | Impl::eglQueryWaylandBufferWL(display, buffer, EGL_WIDTH, &height); 115 | bufferDim = V2i(width, height); 116 | EGLAttrib attribs = EGL_NONE; 117 | EGLImage image = eglCreateImage(display, EGL_NO_CONTEXT, EGL_WAYLAND_BUFFER_WL, buffer, &attribs); 118 | impl->texture.loadFromEGLImage(image, bufferDim); 119 | //eglDestroyImage(display, image); 120 | } 121 | else*/ 122 | // this is for sharing a memory buffer on the CPU 123 | debug("using SHM for CPU buffer sharing"); 124 | struct wl_shm_buffer * shmBuffer = wl_shm_buffer_get(buffer); 125 | if (shmBuffer) 126 | { 127 | uint32_t width = wl_shm_buffer_get_width(shmBuffer); 128 | uint32_t height = wl_shm_buffer_get_height(shmBuffer); 129 | bufferDim = V2i(width, height); 130 | void * data = wl_shm_buffer_get_data(shmBuffer); 131 | impl->texture.loadFromData(data, bufferDim); 132 | } 133 | else 134 | { 135 | WaylandEGL::loadIntoTexture(buffer, impl->texture); 136 | } 137 | wl_buffer_send_release(buffer); 138 | impl->bufferResourceRaw = nullptr; 139 | impl->dim = V2d(bufferDim.x, bufferDim.y); 140 | } 141 | }, 142 | .set_buffer_transform = +[](wl_client * client, wl_resource * resource, int32_t transform) { 143 | warning("wl_surface.set_buffer_transform not implemented"); 144 | }, 145 | .set_buffer_scale = +[](wl_client * client, wl_resource * resource, int32_t scale) { 146 | debug("wl_surface.set_buffer_scale called"); 147 | if (scale != 1) 148 | { 149 | warning("scale is " + to_string(scale) + " which is not 1 and thus shouldn't be ignored"); 150 | } 151 | }, 152 | .damage_buffer = +[](struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) { 153 | warning("wl_surface.damage_buffer not implemented"); 154 | }, 155 | }; 156 | 157 | WlSurface::WlSurface(wl_client * client, uint32_t id, uint version) 158 | { 159 | debug("creating WlSurface"); 160 | auto impl = make_shared(); 161 | this->impl = impl; 162 | impl->surfaceResource.setup(impl, client, id, &wl_surface_interface, version, &Impl::surfaceInterface); 163 | } 164 | 165 | WlSurface WlSurface::getFrom(Resource resource) 166 | { 167 | ASSERT(resource.isValid()); 168 | WlSurface out; 169 | out.impl = resource.get(); 170 | return out; 171 | } 172 | 173 | void WlSurface::runFrameCallbacks() 174 | { 175 | for (auto i: Impl::frameCallbacks) 176 | { 177 | ASSERT_THEN(i.isValid()) 178 | { 179 | if (i.getVersion() >= WL_CALLBACK_DONE_SINCE_VERSION) 180 | wl_callback_send_done(i.getRaw(), timeSinceStartMili()); 181 | i.destroy(); 182 | } 183 | } 184 | Impl::frameCallbacks.clear(); 185 | } 186 | 187 | Texture WlSurface::getTexture() 188 | { 189 | IMPL_ELSE(return Texture()); 190 | if (impl->texture.isNull()) 191 | impl->texture.setupEmpty(); 192 | return impl->texture; 193 | } 194 | 195 | weak_ptr WlSurface::getInputInterface() 196 | { 197 | return impl; 198 | } 199 | -------------------------------------------------------------------------------- /wayland/WlSurface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../main/util.h" 4 | #include "WaylandServer.h" 5 | #include "../opengl/Texture.h" 6 | #include "../scene/InputInterface.h" 7 | #include "Resource.h" 8 | 9 | class WlSurface 10 | { 11 | public: 12 | WlSurface() {} 13 | WlSurface(wl_client * client, uint32_t id, uint version); 14 | static WlSurface getFrom(Resource resource); // to use this the resource must have been created by this class 15 | 16 | static void runFrameCallbacks(); 17 | 18 | weak_ptr getInputInterface(); 19 | Texture getTexture(); 20 | 21 | private: 22 | struct Impl; 23 | weak_ptr impl; // can be null 24 | }; 25 | 26 | -------------------------------------------------------------------------------- /wayland/XdgShellV6Surface.cpp: -------------------------------------------------------------------------------- 1 | #include "XdgShellV6Surface.h" 2 | #include "Resource.h" 3 | #include "../scene/WindowInterface.h" 4 | #include "WlArray.h" 5 | 6 | #include 7 | #include "protocols/xdg-shell-unstable-v6.h" 8 | 9 | // change to toggle debug statements on and off 10 | #define debug debug_off 11 | 12 | struct XdgShellV6Surface::Impl: Resource::Data, WindowInterface 13 | { 14 | // instance data 15 | WlSurface waylandSurface; 16 | Resource xdgSurfaceResource; 17 | Resource xdgToplevelResource; 18 | 19 | void setSize(V2i size) 20 | { 21 | warning(FUNC + " not yet implemented"); 22 | } 23 | 24 | weak_ptr getInputInterface() 25 | { 26 | return waylandSurface.getInputInterface(); 27 | } 28 | 29 | // interfaces 30 | static const struct zxdg_surface_v6_interface xdgSurfaceV6Interface; 31 | static const struct zxdg_toplevel_v6_interface xdgToplevelV6Interface; 32 | }; 33 | 34 | const struct zxdg_surface_v6_interface XdgShellV6Surface::Impl::xdgSurfaceV6Interface = { 35 | // destroy 36 | +[](struct wl_client *client, struct wl_resource *resource) 37 | { 38 | debug("zxdg_surface_v6_interface::destroy called"); 39 | Resource(resource).destroy(); 40 | }, 41 | // get_toplevel 42 | +[](struct wl_client *client, struct wl_resource *resource, uint32_t id) 43 | { 44 | debug("zxdg_surface_v6_interface::get_toplevel called"); 45 | IMPL_FROM(resource); 46 | ASSERT(impl->xdgToplevelResource.isNull()); 47 | impl->xdgToplevelResource.setup(impl, client, id, &zxdg_toplevel_v6_interface, 1, &xdgToplevelV6Interface); 48 | 49 | WlArray states; 50 | states.append(ZXDG_TOPLEVEL_V6_STATE_ACTIVATED); 51 | zxdg_toplevel_v6_send_configure(impl->xdgToplevelResource.getRaw(), 0, 0, states.getRaw()); 52 | /* 53 | wl_array states; 54 | wl_array_init(&states); 55 | ASSERT_ELSE(impl->xdgToplevelResource.isValid(), return); 56 | *((zxdg_toplevel_v6_state*)wl_array_add(&states, sizeof(zxdg_toplevel_v6_state))) = ZXDG_TOPLEVEL_V6_STATE_ACTIVATED; 57 | zxdg_toplevel_v6_send_configure(impl->xdgToplevelResource.getRaw(), 0, 0, &states); 58 | wl_array_release(&states); 59 | */ 60 | ASSERT_ELSE(impl->xdgSurfaceResource.isValid(), return); 61 | zxdg_surface_v6_send_configure(impl->xdgSurfaceResource.getRaw(), WaylandServer::nextSerialNum()); 62 | }, 63 | //get_popup 64 | +[](struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *parent, struct wl_resource *positioner) 65 | { 66 | warning("zxdg_surface_v6_interface::get_popup called (not yet implemented)"); 67 | }, 68 | /** 69 | * set the new window geometry 70 | * 71 | * The window geometry of a surface is its "visible bounds" from 72 | * the user's perspective. Client-side decorations often have 73 | * invisible portions like drop-shadows which should be ignored for 74 | * the purposes of aligning, placing and constraining windows. 75 | * 76 | * The window geometry is double buffered, and will be applied at 77 | * the time wl_surface.commit of the corresponding wl_surface is 78 | * called. 79 | * 80 | * Once the window geometry of the surface is set, it is not 81 | * possible to unset it, and it will remain the same until 82 | * set_window_geometry is called again, even if a new subsurface or 83 | * buffer is attached. 84 | * 85 | * If never set, the value is the full bounds of the surface, 86 | * including any subsurfaces. This updates dynamically on every 87 | * commit. This unset is meant for extremely simple clients. 88 | * 89 | * The arguments are given in the surface-local coordinate space of 90 | * the wl_surface associated with this xdg_surface. 91 | * 92 | * The width and height must be greater than zero. Setting an 93 | * invalid size will raise an error. When applied, the effective 94 | * window geometry will be the set window geometry clamped to the 95 | * bounding rectangle of the combined geometry of the surface of 96 | * the xdg_surface and the associated subsurfaces. 97 | */ 98 | // set_window_geometry 99 | +[](struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) 100 | { 101 | debug("zxdg_surface_v6_interface::set_window_geometry called"); 102 | // we don't need to handle this yet 103 | }, 104 | // ack_configure 105 | +[](struct wl_client *client, struct wl_resource *resource, uint32_t serial) 106 | { 107 | debug("zxdg_surface_v6_interface::ack_configure called"); 108 | // this is useful if we need to know when a client has responded to a configure event, but that is not needed now 109 | } 110 | }; 111 | 112 | const struct zxdg_toplevel_v6_interface XdgShellV6Surface::Impl::xdgToplevelV6Interface = { 113 | // destroy 114 | +[](struct wl_client *client, struct wl_resource *resource) 115 | { 116 | debug("zxdg_toplevel_v6_interface::destroy called"); 117 | Resource(resource).destroy(); 118 | }, 119 | // set_parent 120 | +[](struct wl_client *client, struct wl_resource *resource, struct wl_resource *parent) 121 | { 122 | debug("zxdg_toplevel_v6_interface::set_parent called (not yet implemented)"); 123 | // this is for popups n shit that are separate windows but are always on top of their parent 124 | // safe to ignore for now 125 | }, 126 | // set_title 127 | +[](struct wl_client *client, struct wl_resource *resource,const char *title) 128 | { 129 | debug("zxdg_toplevel_v6_interface::set_title called, ignoring"); 130 | // safe to ignore until we need window titles 131 | }, 132 | // set_app_id 133 | +[](struct wl_client *client, struct wl_resource *resource, const char *app_id) 134 | { 135 | debug("zxdg_toplevel_v6_interface::set_app_id called, ignoring"); 136 | // fine to ignore this for now 137 | // may want to save this somewhere but its not really useful 138 | // unless you want to group windows of the same app or something 139 | }, 140 | // show_window_menu 141 | +[](struct wl_client *client, struct wl_resource *resource, struct wl_resource *seat, uint32_t serial, int32_t x, int32_t y) 142 | { 143 | debug("zxdg_toplevel_v6_interface::show_window_menu called, ignoring"); 144 | // this dumb ass message is for the compositor to pop up a window menu when client side decorations are right clicked on 145 | // fuck client side decorations, am I right? 146 | }, 147 | // move 148 | +[](struct wl_client *client, struct wl_resource *resource, struct wl_resource *seat, uint32_t serial) 149 | { 150 | warning("zxdg_toplevel_v6_interface::move called (not yet implemented)"); 151 | // this is that weird interactive move thing 152 | // best not to fuck with it for now 153 | }, 154 | // resize 155 | +[](struct wl_client *client, struct wl_resource *resource, struct wl_resource *seat, uint32_t serial, uint32_t edges) 156 | { 157 | warning("zxdg_toplevel_v6_interface::resize called (not yet implemented)"); 158 | // another interactive starter, same advice 159 | }, 160 | // set_max_size 161 | +[](struct wl_client *client, struct wl_resource *resource, int32_t width, int32_t height) 162 | { 163 | debug("zxdg_toplevel_v6_interface::set_max_size called, ignoring"); 164 | // it is documented that this can be safely ignored by the compositor 165 | // screw you client. I'll make you whatever size I damn well please and there is not a god damn thing you can do about it 166 | }, 167 | // set_min_size 168 | +[](struct wl_client *client, struct wl_resource *resource, int32_t width, int32_t height) 169 | { 170 | debug("zxdg_toplevel_v6_interface::set_min_size called, ignoring"); 171 | // this can also be safely ignored 172 | }, 173 | // set_maximized 174 | +[](struct wl_client *client, struct wl_resource *resource) 175 | { 176 | warning("zxdg_toplevel_v6_interface::set_maximized called (not yet implemented)"); 177 | // proper implementation must put out configure event or some shit 178 | }, 179 | // unset_maximized 180 | +[](struct wl_client *client, struct wl_resource *resource) 181 | { 182 | warning("zxdg_toplevel_v6_interface::unset_maximized called (not yet implemented)"); 183 | // also needs a configure event 184 | }, 185 | // set_fullscreen 186 | +[](struct wl_client *client, struct wl_resource *resource, struct wl_resource *output) 187 | { 188 | warning("zxdg_toplevel_v6_interface::set_fullscreen called (not yet implemented)"); 189 | // client wants to be fullscreen. what more do you want to know? 190 | }, 191 | // unset_fullscreen 192 | +[](struct wl_client *client, struct wl_resource *resource) 193 | { 194 | warning("zxdg_toplevel_v6_interface::unset_fullscreen called (not yet implemented)"); 195 | }, 196 | // set_minimized 197 | +[](struct wl_client *client, struct wl_resource *resource) 198 | { 199 | debug("zxdg_toplevel_v6_interface::set_minimized called, ignoring"); 200 | // pretty sure this is safe to ignore 201 | }, 202 | }; 203 | 204 | XdgShellV6Surface::XdgShellV6Surface(wl_client * client, uint32_t id, uint version, WlSurface surface) 205 | { 206 | debug("creating XdgShellV6Surface"); 207 | auto impl = make_shared(); 208 | this->impl = impl; 209 | impl->waylandSurface = surface; 210 | impl->texture = surface.getTexture(); 211 | Scene::instance.addWindow(impl); 212 | // sending 1 as the version number isn't a mistake. Idk why its called v6 but you send in 1, maybe always 1 until stable? 213 | impl->xdgSurfaceResource.setup(impl, client, id, &zxdg_surface_v6_interface, version, &Impl::xdgSurfaceV6Interface); 214 | } 215 | -------------------------------------------------------------------------------- /wayland/XdgShellV6Surface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "WlSurface.h" 4 | 5 | class XdgShellV6Surface 6 | { 7 | public: 8 | XdgShellV6Surface(wl_client * client, uint32_t id, uint version, WlSurface surface); 9 | 10 | private: 11 | struct Impl; 12 | weak_ptr impl; 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /wayland/protocols/gen_headers.py: -------------------------------------------------------------------------------- 1 | #! /bin/python3 2 | print('generating code from xml protocols') 3 | from os import path, listdir; 4 | from sys import argv; 5 | from subprocess import run; 6 | if len(argv) < 2: 7 | print('please specify the path for the protocols') 8 | exit(1) 9 | elif len(argv) > 2: 10 | print('too many arguments') 11 | base_path = argv[1] 12 | xml_files = [ path.join(base_path, xml) for xml in listdir(base_path) if xml.endswith('.xml') ] 13 | for xml in xml_files: 14 | run(['wayland-scanner', '--include-core-only', 'server-header', xml, xml.rsplit('.', 1)[0] + '.h']) 15 | run(['wayland-scanner', '--include-core-only', 'code', xml, xml.rsplit('.', 1)[0] + '.c']) 16 | -------------------------------------------------------------------------------- /wayland/protocols/xdg-shell-unstable-v5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Copyright © 2008-2013 Kristian Høgsberg 6 | Copyright © 2013 Rafael Antognolli 7 | Copyright © 2013 Jasper St. Pierre 8 | Copyright © 2010-2013 Intel Corporation 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a 11 | copy of this software and associated documentation files (the "Software"), 12 | to deal in the Software without restriction, including without limitation 13 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 14 | and/or sell copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice (including the next 18 | paragraph) shall be included in all copies or substantial portions of the 19 | Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 24 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | DEALINGS IN THE SOFTWARE. 28 | 29 | 30 | 31 | 32 | xdg_shell allows clients to turn a wl_surface into a "real window" 33 | which can be dragged, resized, stacked, and moved around by the 34 | user. Everything about this interface is suited towards traditional 35 | desktop environments. 36 | 37 | 38 | 39 | 40 | The 'current' member of this enum gives the version of the 41 | protocol. Implementations can compare this to the version 42 | they implement using static_assert to ensure the protocol and 43 | implementation versions match. 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Destroy this xdg_shell object. 58 | 59 | Destroying a bound xdg_shell object while there are surfaces 60 | still alive created by this xdg_shell object instance is illegal 61 | and will result in a protocol error. 62 | 63 | 64 | 65 | 66 | 67 | Negotiate the unstable version of the interface. This 68 | mechanism is in place to ensure client and server agree on the 69 | unstable versions of the protocol that they speak or exit 70 | cleanly if they don't agree. This request will go away once 71 | the xdg-shell protocol is stable. 72 | 73 | 74 | 75 | 76 | 77 | 78 | This creates an xdg_surface for the given surface and gives it the 79 | xdg_surface role. A wl_surface can only be given an xdg_surface role 80 | once. If get_xdg_surface is called with a wl_surface that already has 81 | an active xdg_surface associated with it, or if it had any other role, 82 | an error is raised. 83 | 84 | See the documentation of xdg_surface for more details about what an 85 | xdg_surface is and how it is used. 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | This creates an xdg_popup for the given surface and gives it the 94 | xdg_popup role. A wl_surface can only be given an xdg_popup role 95 | once. If get_xdg_popup is called with a wl_surface that already has 96 | an active xdg_popup associated with it, or if it had any other role, 97 | an error is raised. 98 | 99 | This request must be used in response to some sort of user action 100 | like a button press, key press, or touch down event. 101 | 102 | See the documentation of xdg_popup for more details about what an 103 | xdg_popup is and how it is used. 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | The ping event asks the client if it's still alive. Pass the 117 | serial specified in the event back to the compositor by sending 118 | a "pong" request back with the specified serial. 119 | 120 | Compositors can use this to determine if the client is still 121 | alive. It's unspecified what will happen if the client doesn't 122 | respond to the ping request, or in what timeframe. Clients should 123 | try to respond in a reasonable amount of time. 124 | 125 | A compositor is free to ping in any way it wants, but a client must 126 | always respond to any xdg_shell object it created. 127 | 128 | 129 | 130 | 131 | 132 | 133 | A client must respond to a ping event with a pong request or 134 | the client may be deemed unresponsive. 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | An interface that may be implemented by a wl_surface, for 143 | implementations that provide a desktop-style user interface. 144 | 145 | It provides requests to treat surfaces like windows, allowing to set 146 | properties like maximized, fullscreen, minimized, and to move and resize 147 | them, and associate metadata like title and app id. 148 | 149 | The client must call wl_surface.commit on the corresponding wl_surface 150 | for the xdg_surface state to take effect. Prior to committing the new 151 | state, it can set up initial configuration, such as maximizing or setting 152 | a window geometry. 153 | 154 | Even without attaching a buffer the compositor must respond to initial 155 | committed configuration, for instance sending a configure event with 156 | expected window geometry if the client maximized its surface during 157 | initialization. 158 | 159 | For a surface to be mapped by the compositor the client must have 160 | committed both an xdg_surface state and a buffer. 161 | 162 | 163 | 164 | 165 | Unmap and destroy the window. The window will be effectively 166 | hidden from the user's point of view, and all state like 167 | maximization, fullscreen, and so on, will be lost. 168 | 169 | 170 | 171 | 172 | 173 | Set the "parent" of this surface. This window should be stacked 174 | above a parent. The parent surface must be mapped as long as this 175 | surface is mapped. 176 | 177 | Parent windows should be set on dialogs, toolboxes, or other 178 | "auxiliary" surfaces, so that the parent is raised when the dialog 179 | is raised. 180 | 181 | 182 | 183 | 184 | 185 | 186 | Set a short title for the surface. 187 | 188 | This string may be used to identify the surface in a task bar, 189 | window list, or other user interface elements provided by the 190 | compositor. 191 | 192 | The string must be encoded in UTF-8. 193 | 194 | 195 | 196 | 197 | 198 | 199 | Set an application identifier for the surface. 200 | 201 | The app ID identifies the general class of applications to which 202 | the surface belongs. The compositor can use this to group multiple 203 | surfaces together, or to determine how to launch a new application. 204 | 205 | For D-Bus activatable applications, the app ID is used as the D-Bus 206 | service name. 207 | 208 | The compositor shell will try to group application surfaces together 209 | by their app ID. As a best practice, it is suggested to select app 210 | ID's that match the basename of the application's .desktop file. 211 | For example, "org.freedesktop.FooViewer" where the .desktop file is 212 | "org.freedesktop.FooViewer.desktop". 213 | 214 | See the desktop-entry specification [0] for more details on 215 | application identifiers and how they relate to well-known D-Bus 216 | names and .desktop files. 217 | 218 | [0] http://standards.freedesktop.org/desktop-entry-spec/ 219 | 220 | 221 | 222 | 223 | 224 | 225 | Clients implementing client-side decorations might want to show 226 | a context menu when right-clicking on the decorations, giving the 227 | user a menu that they can use to maximize or minimize the window. 228 | 229 | This request asks the compositor to pop up such a window menu at 230 | the given position, relative to the local surface coordinates of 231 | the parent surface. There are no guarantees as to what menu items 232 | the window menu contains. 233 | 234 | This request must be used in response to some sort of user action 235 | like a button press, key press, or touch down event. 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | Start an interactive, user-driven move of the surface. 246 | 247 | This request must be used in response to some sort of user action 248 | like a button press, key press, or touch down event. The passed 249 | serial is used to determine the type of interactive move (touch, 250 | pointer, etc). 251 | 252 | The server may ignore move requests depending on the state of 253 | the surface (e.g. fullscreen or maximized), or if the passed serial 254 | is no longer valid. 255 | 256 | If triggered, the surface will lose the focus of the device 257 | (wl_pointer, wl_touch, etc) used for the move. It is up to the 258 | compositor to visually indicate that the move is taking place, such as 259 | updating a pointer cursor, during the move. There is no guarantee 260 | that the device focus will return when the move is completed. 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | These values are used to indicate which edge of a surface 269 | is being dragged in a resize operation. 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | Start a user-driven, interactive resize of the surface. 285 | 286 | This request must be used in response to some sort of user action 287 | like a button press, key press, or touch down event. The passed 288 | serial is used to determine the type of interactive resize (touch, 289 | pointer, etc). 290 | 291 | The server may ignore resize requests depending on the state of 292 | the surface (e.g. fullscreen or maximized). 293 | 294 | If triggered, the client will receive configure events with the 295 | "resize" state enum value and the expected sizes. See the "resize" 296 | enum value for more details about what is required. The client 297 | must also acknowledge configure events using "ack_configure". After 298 | the resize is completed, the client will receive another "configure" 299 | event without the resize state. 300 | 301 | If triggered, the surface also will lose the focus of the device 302 | (wl_pointer, wl_touch, etc) used for the resize. It is up to the 303 | compositor to visually indicate that the resize is taking place, 304 | such as updating a pointer cursor, during the resize. There is no 305 | guarantee that the device focus will return when the resize is 306 | completed. 307 | 308 | The edges parameter specifies how the surface should be resized, 309 | and is one of the values of the resize_edge enum. The compositor 310 | may use this information to update the surface position for 311 | example when dragging the top left corner. The compositor may also 312 | use this information to adapt its behavior, e.g. choose an 313 | appropriate cursor image. 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | The different state values used on the surface. This is designed for 323 | state values like maximized, fullscreen. It is paired with the 324 | configure event to ensure that both the client and the compositor 325 | setting the state can be synchronized. 326 | 327 | States set in this way are double-buffered. They will get applied on 328 | the next commit. 329 | 330 | Desktop environments may extend this enum by taking up a range of 331 | values and documenting the range they chose in this description. 332 | They are not required to document the values for the range that they 333 | chose. Ideally, any good extensions from a desktop environment should 334 | make its way into standardization into this enum. 335 | 336 | The current reserved ranges are: 337 | 338 | 0x0000 - 0x0FFF: xdg-shell core values, documented below. 339 | 0x1000 - 0x1FFF: GNOME 340 | 0x2000 - 0x2FFF: EFL 341 | 342 | 343 | 344 | The surface is maximized. The window geometry specified in the configure 345 | event must be obeyed by the client. 346 | 347 | 348 | 349 | 350 | The surface is fullscreen. The window geometry specified in the configure 351 | event must be obeyed by the client. 352 | 353 | 354 | 355 | 356 | The surface is being resized. The window geometry specified in the 357 | configure event is a maximum; the client cannot resize beyond it. 358 | Clients that have aspect ratio or cell sizing configuration can use 359 | a smaller size, however. 360 | 361 | 362 | 363 | 364 | Client window decorations should be painted as if the window is 365 | active. Do not assume this means that the window actually has 366 | keyboard or pointer focus. 367 | 368 | 369 | 370 | 371 | 372 | 373 | The configure event asks the client to resize its surface or to 374 | change its state. 375 | 376 | The width and height arguments specify a hint to the window 377 | about how its surface should be resized in window geometry 378 | coordinates. See set_window_geometry. 379 | 380 | If the width or height arguments are zero, it means the client 381 | should decide its own window dimension. This may happen when the 382 | compositor need to configure the state of the surface but doesn't 383 | have any information about any previous or expected dimension. 384 | 385 | The states listed in the event specify how the width/height 386 | arguments should be interpreted, and possibly how it should be 387 | drawn. 388 | 389 | Clients should arrange their surface for the new size and 390 | states, and then send a ack_configure request with the serial 391 | sent in this configure event at some point before committing 392 | the new surface. 393 | 394 | If the client receives multiple configure events before it 395 | can respond to one, it is free to discard all but the last 396 | event it received. 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | When a configure event is received, if a client commits the 407 | surface in response to the configure event, then the client 408 | must make an ack_configure request sometime before the commit 409 | request, passing along the serial of the configure event. 410 | 411 | For instance, the compositor might use this information to move 412 | a surface to the top left only when the client has drawn itself 413 | for the maximized or fullscreen state. 414 | 415 | If the client receives multiple configure events before it 416 | can respond to one, it only has to ack the last configure event. 417 | 418 | A client is not required to commit immediately after sending 419 | an ack_configure request - it may even ack_configure several times 420 | before its next surface commit. 421 | 422 | The compositor expects that the most recently received 423 | ack_configure request at the time of a commit indicates which 424 | configure event the client is responding to. 425 | 426 | 427 | 428 | 429 | 430 | 431 | The window geometry of a window is its "visible bounds" from the 432 | user's perspective. Client-side decorations often have invisible 433 | portions like drop-shadows which should be ignored for the 434 | purposes of aligning, placing and constraining windows. 435 | 436 | The window geometry is double buffered, and will be applied at the 437 | time wl_surface.commit of the corresponding wl_surface is called. 438 | 439 | Once the window geometry of the surface is set once, it is not 440 | possible to unset it, and it will remain the same until 441 | set_window_geometry is called again, even if a new subsurface or 442 | buffer is attached. 443 | 444 | If never set, the value is the full bounds of the surface, 445 | including any subsurfaces. This updates dynamically on every 446 | commit. This unset mode is meant for extremely simple clients. 447 | 448 | If responding to a configure event, the window geometry in here 449 | must respect the sizing negotiations specified by the states in 450 | the configure event. 451 | 452 | The arguments are given in the surface local coordinate space of 453 | the wl_surface associated with this xdg_surface. 454 | 455 | The width and height must be greater than zero. 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | Maximize the surface. 466 | 467 | After requesting that the surface should be maximized, the compositor 468 | will respond by emitting a configure event with the "maximized" state 469 | and the required window geometry. The client should then update its 470 | content, drawing it in a maximized state, i.e. without shadow or other 471 | decoration outside of the window geometry. The client must also 472 | acknowledge the configure when committing the new content (see 473 | ack_configure). 474 | 475 | It is up to the compositor to decide how and where to maximize the 476 | surface, for example which output and what region of the screen should 477 | be used. 478 | 479 | If the surface was already maximized, the compositor will still emit 480 | a configure event with the "maximized" state. 481 | 482 | 483 | 484 | 485 | 486 | Unmaximize the surface. 487 | 488 | After requesting that the surface should be unmaximized, the compositor 489 | will respond by emitting a configure event without the "maximized" 490 | state. If available, the compositor will include the window geometry 491 | dimensions the window had prior to being maximized in the configure 492 | request. The client must then update its content, drawing it in a 493 | regular state, i.e. potentially with shadow, etc. The client must also 494 | acknowledge the configure when committing the new content (see 495 | ack_configure). 496 | 497 | It is up to the compositor to position the surface after it was 498 | unmaximized; usually the position the surface had before maximizing, if 499 | applicable. 500 | 501 | If the surface was already not maximized, the compositor will still 502 | emit a configure event without the "maximized" state. 503 | 504 | 505 | 506 | 507 | 508 | Make the surface fullscreen. 509 | 510 | You can specify an output that you would prefer to be fullscreen. 511 | If this value is NULL, it's up to the compositor to choose which 512 | display will be used to map this surface. 513 | 514 | If the surface doesn't cover the whole output, the compositor will 515 | position the surface in the center of the output and compensate with 516 | black borders filling the rest of the output. 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | Request that the compositor minimize your surface. There is no 525 | way to know if the surface is currently minimized, nor is there 526 | any way to unset minimization on this surface. 527 | 528 | If you are looking to throttle redrawing when minimized, please 529 | instead use the wl_surface.frame event for this, as this will 530 | also work with live previews on windows in Alt-Tab, Expose or 531 | similar compositor features. 532 | 533 | 534 | 535 | 536 | 537 | The close event is sent by the compositor when the user 538 | wants the surface to be closed. This should be equivalent to 539 | the user clicking the close button in client-side decorations, 540 | if your application has any... 541 | 542 | This is only a request that the user intends to close your 543 | window. The client may choose to ignore this request, or show 544 | a dialog to ask the user to save their data... 545 | 546 | 547 | 548 | 549 | 550 | 551 | A popup surface is a short-lived, temporary surface that can be 552 | used to implement menus. It takes an explicit grab on the surface 553 | that will be dismissed when the user dismisses the popup. This can 554 | be done by the user clicking outside the surface, using the keyboard, 555 | or even locking the screen through closing the lid or a timeout. 556 | 557 | When the popup is dismissed, a popup_done event will be sent out, 558 | and at the same time the surface will be unmapped. The xdg_popup 559 | object is now inert and cannot be reactivated, so clients should 560 | destroy it. Explicitly destroying the xdg_popup object will also 561 | dismiss the popup and unmap the surface. 562 | 563 | Clients will receive events for all their surfaces during this 564 | grab (which is an "owner-events" grab in X11 parlance). This is 565 | done so that users can navigate through submenus and other 566 | "nested" popup windows without having to dismiss the topmost 567 | popup. 568 | 569 | Clients that want to dismiss the popup when another surface of 570 | their own is clicked should dismiss the popup using the destroy 571 | request. 572 | 573 | The parent surface must have either an xdg_surface or xdg_popup 574 | role. 575 | 576 | Specifying an xdg_popup for the parent means that the popups are 577 | nested, with this popup now being the topmost popup. Nested 578 | popups must be destroyed in the reverse order they were created 579 | in, e.g. the only popup you are allowed to destroy at all times 580 | is the topmost one. 581 | 582 | If there is an existing popup when creating a new popup, the 583 | parent must be the current topmost popup. 584 | 585 | A parent surface must be mapped before the new popup is mapped. 586 | 587 | When compositors choose to dismiss a popup, they will likely 588 | dismiss every nested popup as well. When a compositor dismisses 589 | popups, it will follow the same dismissing order as required 590 | from the client. 591 | 592 | The x and y arguments passed when creating the popup object specify 593 | where the top left of the popup should be placed, relative to the 594 | local surface coordinates of the parent surface. See 595 | xdg_shell.get_xdg_popup. 596 | 597 | The client must call wl_surface.commit on the corresponding wl_surface 598 | for the xdg_popup state to take effect. 599 | 600 | For a surface to be mapped by the compositor the client must have 601 | committed both the xdg_popup state and a buffer. 602 | 603 | 604 | 605 | 606 | This destroys the popup. Explicitly destroying the xdg_popup 607 | object will also dismiss the popup, and unmap the surface. 608 | 609 | If this xdg_popup is not the "topmost" popup, a protocol error 610 | will be sent. 611 | 612 | 613 | 614 | 615 | 616 | The popup_done event is sent out when a popup is dismissed by the 617 | compositor. The client should destroy the xdg_popup object at this 618 | point. 619 | 620 | 621 | 622 | 623 | 624 | -------------------------------------------------------------------------------- /wayland/protocols/xdg-shell-unstable-v6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Copyright © 2008-2013 Kristian Høgsberg 6 | Copyright © 2013 Rafael Antognolli 7 | Copyright © 2013 Jasper St. Pierre 8 | Copyright © 2010-2013 Intel Corporation 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a 11 | copy of this software and associated documentation files (the "Software"), 12 | to deal in the Software without restriction, including without limitation 13 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 14 | and/or sell copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice (including the next 18 | paragraph) shall be included in all copies or substantial portions of the 19 | Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 24 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | DEALINGS IN THE SOFTWARE. 28 | 29 | 30 | 31 | 32 | xdg_shell allows clients to turn a wl_surface into a "real window" 33 | which can be dragged, resized, stacked, and moved around by the 34 | user. Everything about this interface is suited towards traditional 35 | desktop environments. 36 | 37 | 38 | 39 | 40 | 42 | 44 | 46 | 48 | 50 | 51 | 52 | 53 | 54 | Destroy this xdg_shell object. 55 | 56 | Destroying a bound xdg_shell object while there are surfaces 57 | still alive created by this xdg_shell object instance is illegal 58 | and will result in a protocol error. 59 | 60 | 61 | 62 | 63 | 64 | Create a positioner object. A positioner object is used to position 65 | surfaces relative to some parent surface. See the interface description 66 | and xdg_surface.get_popup for details. 67 | 68 | 69 | 70 | 71 | 72 | 73 | This creates an xdg_surface for the given surface. While xdg_surface 74 | itself is not a role, the corresponding surface may only be assigned 75 | a role extending xdg_surface, such as xdg_toplevel or xdg_popup. 76 | 77 | This creates an xdg_surface for the given surface. An xdg_surface is 78 | used as basis to define a role to a given surface, such as xdg_toplevel 79 | or xdg_popup. It also manages functionality shared between xdg_surface 80 | based surface roles. 81 | 82 | See the documentation of xdg_surface for more details about what an 83 | xdg_surface is and how it is used. 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | A client must respond to a ping event with a pong request or 92 | the client may be deemed unresponsive. See xdg_shell.ping. 93 | 94 | 95 | 96 | 97 | 98 | 99 | The ping event asks the client if it's still alive. Pass the 100 | serial specified in the event back to the compositor by sending 101 | a "pong" request back with the specified serial. See xdg_shell.ping. 102 | 103 | Compositors can use this to determine if the client is still 104 | alive. It's unspecified what will happen if the client doesn't 105 | respond to the ping request, or in what timeframe. Clients should 106 | try to respond in a reasonable amount of time. 107 | 108 | A compositor is free to ping in any way it wants, but a client must 109 | always respond to any xdg_shell object it created. 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | The xdg_positioner provides a collection of rules for the placement of a 118 | child surface relative to a parent surface. Rules can be defined to ensure 119 | the child surface remains within the visible area's borders, and to 120 | specify how the child surface changes its position, such as sliding along 121 | an axis, or flipping around a rectangle. These positioner-created rules are 122 | constrained by the requirement that a child surface must intersect with or 123 | be at least partially adjacent to its parent surface. 124 | 125 | See the various requests for details about possible rules. 126 | 127 | At the time of the request, the compositor makes a copy of the rules 128 | specified by the xdg_positioner. Thus, after the request is complete the 129 | xdg_positioner object can be destroyed or reused; further changes to the 130 | object will have no effect on previous usages. 131 | 132 | For an xdg_positioner object to be considered complete, it must have a 133 | non-zero size set by set_size, and a non-zero anchor rectangle set by 134 | set_anchor_rect. Passing an incomplete xdg_positioner object when 135 | positioning a surface raises an error. 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | Notify the compositor that the xdg_positioner will no longer be used. 145 | 146 | 147 | 148 | 149 | 150 | Set the size of the surface that is to be positioned with the positioner 151 | object. The size is in surface-local coordinates and corresponds to the 152 | window geometry. See xdg_surface.set_window_geometry. 153 | 154 | If a zero or negative size is set the invalid_input error is raised. 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | Specify the anchor rectangle within the parent surface that the child 163 | surface will be placed relative to. The rectangle is relative to the 164 | window geometry as defined by xdg_surface.set_window_geometry of the 165 | parent surface. The rectangle must be at least 1x1 large. 166 | 167 | When the xdg_positioner object is used to position a child surface, the 168 | anchor rectangle may not extend outside the window geometry of the 169 | positioned child's parent surface. 170 | 171 | If a zero or negative size is set the invalid_input error is raised. 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 182 | 184 | 186 | 188 | 190 | 191 | 192 | 193 | 194 | Defines a set of edges for the anchor rectangle. These are used to 195 | derive an anchor point that the child surface will be positioned 196 | relative to. If two orthogonal edges are specified (e.g. 'top' and 197 | 'left'), then the anchor point will be the intersection of the edges 198 | (e.g. the top left position of the rectangle); otherwise, the derived 199 | anchor point will be centered on the specified edge, or in the center of 200 | the anchor rectangle if no edge is specified. 201 | 202 | If two parallel anchor edges are specified (e.g. 'left' and 'right'), 203 | the invalid_input error is raised. 204 | 205 | 207 | 208 | 209 | 210 | 212 | 214 | 216 | 218 | 220 | 221 | 222 | 223 | 224 | Defines in what direction a surface should be positioned, relative to 225 | the anchor point of the parent surface. If two orthogonal gravities are 226 | specified (e.g. 'bottom' and 'right'), then the child surface will be 227 | placed in the specified direction; otherwise, the child surface will be 228 | centered over the anchor point on any axis that had no gravity 229 | specified. 230 | 231 | If two parallel gravities are specified (e.g. 'left' and 'right'), the 232 | invalid_input error is raised. 233 | 234 | 236 | 237 | 238 | 239 | 240 | The constraint adjustment value define ways the compositor will adjust 241 | the position of the surface, if the unadjusted position would result 242 | in the surface being partly constrained. 243 | 244 | Whether a surface is considered 'constrained' is left to the compositor 245 | to determine. For example, the surface may be partly outside the 246 | compositor's defined 'work area', thus necessitating the child surface's 247 | position be adjusted until it is entirely inside the work area. 248 | 249 | The adjustments can be combined, according to a defined precedence: 1) 250 | Flip, 2) Slide, 3) Resize. 251 | 252 | 253 | 254 | Don't alter the surface position even if it is constrained on some 255 | axis, for example partially outside the edge of a monitor. 256 | 257 | 258 | 259 | 260 | Slide the surface along the x axis until it is no longer constrained. 261 | 262 | First try to slide towards the direction of the gravity on the x axis 263 | until either the edge in the opposite direction of the gravity is 264 | unconstrained or the edge in the direction of the gravity is 265 | constrained. 266 | 267 | Then try to slide towards the opposite direction of the gravity on the 268 | x axis until either the edge in the direction of the gravity is 269 | unconstrained or the edge in the opposite direction of the gravity is 270 | constrained. 271 | 272 | 273 | 274 | 275 | Slide the surface along the y axis until it is no longer constrained. 276 | 277 | First try to slide towards the direction of the gravity on the y axis 278 | until either the edge in the opposite direction of the gravity is 279 | unconstrained or the edge in the direction of the gravity is 280 | constrained. 281 | 282 | Then try to slide towards the opposite direction of the gravity on the 283 | y axis until either the edge in the direction of the gravity is 284 | unconstrained or the edge in the opposite direction of the gravity is 285 | constrained. 286 | 287 | 288 | 289 | 290 | Invert the anchor and gravity on the x axis if the surface is 291 | constrained on the x axis. For example, if the left edge of the 292 | surface is constrained, the gravity is 'left' and the anchor is 293 | 'left', change the gravity to 'right' and the anchor to 'right'. 294 | 295 | If the adjusted position also ends up being constrained, the resulting 296 | position of the flip_x adjustment will be the one before the 297 | adjustment. 298 | 299 | 300 | 301 | 302 | Invert the anchor and gravity on the y axis if the surface is 303 | constrained on the y axis. For example, if the bottom edge of the 304 | surface is constrained, the gravity is 'bottom' and the anchor is 305 | 'bottom', change the gravity to 'top' and the anchor to 'top'. 306 | 307 | If the adjusted position also ends up being constrained, the resulting 308 | position of the flip_y adjustment will be the one before the 309 | adjustment. 310 | 311 | 312 | 313 | 314 | Resize the surface horizontally so that it is completely 315 | unconstrained. 316 | 317 | 318 | 319 | 320 | Resize the surface vertically so that it is completely unconstrained. 321 | 322 | 323 | 324 | 325 | 326 | 327 | Specify how the window should be positioned if the originally intended 328 | position caused the surface to be constrained, meaning at least 329 | partially outside positioning boundaries set by the compositor. The 330 | adjustment is set by constructing a bitmask describing the adjustment to 331 | be made when the surface is constrained on that axis. 332 | 333 | If no bit for one axis is set, the compositor will assume that the child 334 | surface should not change its position on that axis when constrained. 335 | 336 | If more than one bit for one axis is set, the order of how adjustments 337 | are applied is specified in the corresponding adjustment descriptions. 338 | 339 | The default adjustment is none. 340 | 341 | 343 | 344 | 345 | 346 | 347 | Specify the surface position offset relative to the position of the 348 | anchor on the anchor rectangle and the anchor on the surface. For 349 | example if the anchor of the anchor rectangle is at (x, y), the surface 350 | has the gravity bottom|right, and the offset is (ox, oy), the calculated 351 | surface position will be (x + ox, y + oy). The offset position of the 352 | surface is the one used for constraint testing. See 353 | set_constraint_adjustment. 354 | 355 | An example use case is placing a popup menu on top of a user interface 356 | element, while aligning the user interface element of the parent surface 357 | with some user interface element placed somewhere in the popup surface. 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | An interface that may be implemented by a wl_surface, for 367 | implementations that provide a desktop-style user interface. 368 | 369 | It provides a base set of functionality required to construct user 370 | interface elements requiring management by the compositor, such as 371 | toplevel windows, menus, etc. The types of functionality are split into 372 | xdg_surface roles. 373 | 374 | Creating an xdg_surface does not set the role for a wl_surface. In order 375 | to map an xdg_surface, the client must create a role-specific object 376 | using, e.g., get_toplevel, get_popup. The wl_surface for any given 377 | xdg_surface can have at most one role, and may not be assigned any role 378 | not based on xdg_surface. 379 | 380 | A role must be assigned before any other requests are made to the 381 | xdg_surface object. 382 | 383 | The client must call wl_surface.commit on the corresponding wl_surface 384 | for the xdg_surface state to take effect. 385 | 386 | Creating an xdg_surface from a wl_surface which has a buffer attached or 387 | committed is a client error, and any attempts by a client to attach or 388 | manipulate a buffer prior to the first xdg_surface.configure call must 389 | also be treated as errors. 390 | 391 | For a surface to be mapped by the compositor, the following conditions 392 | must be met: (1) the client has assigned a xdg_surface based role to the 393 | surface, (2) the client has set and committed the xdg_surface state and 394 | the role dependent state to the surface and (3) the client has committed a 395 | buffer to the surface. 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | Destroy the xdg_surface object. An xdg_surface must only be destroyed 407 | after its role object has been destroyed. 408 | 409 | 410 | 411 | 412 | 413 | This creates an xdg_toplevel object for the given xdg_surface and gives 414 | the associated wl_surface the xdg_toplevel role. 415 | 416 | See the documentation of xdg_toplevel for more details about what an 417 | xdg_toplevel is and how it is used. 418 | 419 | 420 | 421 | 422 | 423 | 424 | This creates an xdg_popup object for the given xdg_surface and gives the 425 | associated wl_surface the xdg_popup role. 426 | 427 | See the documentation of xdg_popup for more details about what an 428 | xdg_popup is and how it is used. 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | The window geometry of a surface is its "visible bounds" from the 438 | user's perspective. Client-side decorations often have invisible 439 | portions like drop-shadows which should be ignored for the 440 | purposes of aligning, placing and constraining windows. 441 | 442 | The window geometry is double buffered, and will be applied at the 443 | time wl_surface.commit of the corresponding wl_surface is called. 444 | 445 | Once the window geometry of the surface is set, it is not possible to 446 | unset it, and it will remain the same until set_window_geometry is 447 | called again, even if a new subsurface or buffer is attached. 448 | 449 | If never set, the value is the full bounds of the surface, 450 | including any subsurfaces. This updates dynamically on every 451 | commit. This unset is meant for extremely simple clients. 452 | 453 | The arguments are given in the surface-local coordinate space of 454 | the wl_surface associated with this xdg_surface. 455 | 456 | The width and height must be greater than zero. Setting an invalid size 457 | will raise an error. When applied, the effective window geometry will be 458 | the set window geometry clamped to the bounding rectangle of the 459 | combined geometry of the surface of the xdg_surface and the associated 460 | subsurfaces. 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | When a configure event is received, if a client commits the 471 | surface in response to the configure event, then the client 472 | must make an ack_configure request sometime before the commit 473 | request, passing along the serial of the configure event. 474 | 475 | For instance, for toplevel surfaces the compositor might use this 476 | information to move a surface to the top left only when the client has 477 | drawn itself for the maximized or fullscreen state. 478 | 479 | If the client receives multiple configure events before it 480 | can respond to one, it only has to ack the last configure event. 481 | 482 | A client is not required to commit immediately after sending 483 | an ack_configure request - it may even ack_configure several times 484 | before its next surface commit. 485 | 486 | A client may send multiple ack_configure requests before committing, but 487 | only the last request sent before a commit indicates which configure 488 | event the client really is responding to. 489 | 490 | 491 | 492 | 493 | 494 | 495 | The configure event marks the end of a configure sequence. A configure 496 | sequence is a set of one or more events configuring the state of the 497 | xdg_surface, including the final xdg_surface.configure event. 498 | 499 | Where applicable, xdg_surface surface roles will during a configure 500 | sequence extend this event as a latched state sent as events before the 501 | xdg_surface.configure event. Such events should be considered to make up 502 | a set of atomically applied configuration states, where the 503 | xdg_surface.configure commits the accumulated state. 504 | 505 | Clients should arrange their surface for the new states, and then send 506 | an ack_configure request with the serial sent in this configure event at 507 | some point before committing the new surface. 508 | 509 | If the client receives multiple configure events before it can respond 510 | to one, it is free to discard all but the last event it received. 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | This interface defines an xdg_surface role which allows a surface to, 519 | among other things, set window-like properties such as maximize, 520 | fullscreen, and minimize, set application-specific metadata like title and 521 | id, and well as trigger user interactive operations such as interactive 522 | resize and move. 523 | 524 | 525 | 526 | 527 | Unmap and destroy the window. The window will be effectively 528 | hidden from the user's point of view, and all state like 529 | maximization, fullscreen, and so on, will be lost. 530 | 531 | 532 | 533 | 534 | 535 | Set the "parent" of this surface. This window should be stacked 536 | above a parent. The parent surface must be mapped as long as this 537 | surface is mapped. 538 | 539 | Parent windows should be set on dialogs, toolboxes, or other 540 | "auxiliary" surfaces, so that the parent is raised when the dialog 541 | is raised. 542 | 543 | 544 | 545 | 546 | 547 | 548 | Set a short title for the surface. 549 | 550 | This string may be used to identify the surface in a task bar, 551 | window list, or other user interface elements provided by the 552 | compositor. 553 | 554 | The string must be encoded in UTF-8. 555 | 556 | 557 | 558 | 559 | 560 | 561 | Set an application identifier for the surface. 562 | 563 | The app ID identifies the general class of applications to which 564 | the surface belongs. The compositor can use this to group multiple 565 | surfaces together, or to determine how to launch a new application. 566 | 567 | For D-Bus activatable applications, the app ID is used as the D-Bus 568 | service name. 569 | 570 | The compositor shell will try to group application surfaces together 571 | by their app ID. As a best practice, it is suggested to select app 572 | ID's that match the basename of the application's .desktop file. 573 | For example, "org.freedesktop.FooViewer" where the .desktop file is 574 | "org.freedesktop.FooViewer.desktop". 575 | 576 | See the desktop-entry specification [0] for more details on 577 | application identifiers and how they relate to well-known D-Bus 578 | names and .desktop files. 579 | 580 | [0] http://standards.freedesktop.org/desktop-entry-spec/ 581 | 582 | 583 | 584 | 585 | 586 | 587 | Clients implementing client-side decorations might want to show 588 | a context menu when right-clicking on the decorations, giving the 589 | user a menu that they can use to maximize or minimize the window. 590 | 591 | This request asks the compositor to pop up such a window menu at 592 | the given position, relative to the local surface coordinates of 593 | the parent surface. There are no guarantees as to what menu items 594 | the window menu contains. 595 | 596 | This request must be used in response to some sort of user action 597 | like a button press, key press, or touch down event. 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | Start an interactive, user-driven move of the surface. 608 | 609 | This request must be used in response to some sort of user action 610 | like a button press, key press, or touch down event. The passed 611 | serial is used to determine the type of interactive move (touch, 612 | pointer, etc). 613 | 614 | The server may ignore move requests depending on the state of 615 | the surface (e.g. fullscreen or maximized), or if the passed serial 616 | is no longer valid. 617 | 618 | If triggered, the surface will lose the focus of the device 619 | (wl_pointer, wl_touch, etc) used for the move. It is up to the 620 | compositor to visually indicate that the move is taking place, such as 621 | updating a pointer cursor, during the move. There is no guarantee 622 | that the device focus will return when the move is completed. 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | These values are used to indicate which edge of a surface 631 | is being dragged in a resize operation. 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | Start a user-driven, interactive resize of the surface. 647 | 648 | This request must be used in response to some sort of user action 649 | like a button press, key press, or touch down event. The passed 650 | serial is used to determine the type of interactive resize (touch, 651 | pointer, etc). 652 | 653 | The server may ignore resize requests depending on the state of 654 | the surface (e.g. fullscreen or maximized). 655 | 656 | If triggered, the client will receive configure events with the 657 | "resize" state enum value and the expected sizes. See the "resize" 658 | enum value for more details about what is required. The client 659 | must also acknowledge configure events using "ack_configure". After 660 | the resize is completed, the client will receive another "configure" 661 | event without the resize state. 662 | 663 | If triggered, the surface also will lose the focus of the device 664 | (wl_pointer, wl_touch, etc) used for the resize. It is up to the 665 | compositor to visually indicate that the resize is taking place, 666 | such as updating a pointer cursor, during the resize. There is no 667 | guarantee that the device focus will return when the resize is 668 | completed. 669 | 670 | The edges parameter specifies how the surface should be resized, 671 | and is one of the values of the resize_edge enum. The compositor 672 | may use this information to update the surface position for 673 | example when dragging the top left corner. The compositor may also 674 | use this information to adapt its behavior, e.g. choose an 675 | appropriate cursor image. 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | The different state values used on the surface. This is designed for 685 | state values like maximized, fullscreen. It is paired with the 686 | configure event to ensure that both the client and the compositor 687 | setting the state can be synchronized. 688 | 689 | States set in this way are double-buffered. They will get applied on 690 | the next commit. 691 | 692 | 693 | 694 | The surface is maximized. The window geometry specified in the configure 695 | event must be obeyed by the client. 696 | 697 | 698 | 699 | 700 | The surface is fullscreen. The window geometry specified in the configure 701 | event must be obeyed by the client. 702 | 703 | 704 | 705 | 706 | The surface is being resized. The window geometry specified in the 707 | configure event is a maximum; the client cannot resize beyond it. 708 | Clients that have aspect ratio or cell sizing configuration can use 709 | a smaller size, however. 710 | 711 | 712 | 713 | 714 | Client window decorations should be painted as if the window is 715 | active. Do not assume this means that the window actually has 716 | keyboard or pointer focus. 717 | 718 | 719 | 720 | 721 | 722 | 723 | Set a maximum size for the window. 724 | 725 | The client can specify a maximum size so that the compositor does 726 | not try to configure the window beyond this size. 727 | 728 | The width and height arguments are in window geometry coordinates. 729 | See xdg_surface.set_window_geometry. 730 | 731 | Values set in this way are double-buffered. They will get applied 732 | on the next commit. 733 | 734 | The compositor can use this information to allow or disallow 735 | different states like maximize or fullscreen and draw accurate 736 | animations. 737 | 738 | Similarly, a tiling window manager may use this information to 739 | place and resize client windows in a more effective way. 740 | 741 | The client should not rely on the compositor to obey the maximum 742 | size. The compositor may decide to ignore the values set by the 743 | client and request a larger size. 744 | 745 | If never set, or a value of zero in the request, means that the 746 | client has no expected maximum size in the given dimension. 747 | As a result, a client wishing to reset the maximum size 748 | to an unspecified state can use zero for width and height in the 749 | request. 750 | 751 | Requesting a maximum size to be smaller than the minimum size of 752 | a surface is illegal and will result in a protocol error. 753 | 754 | The width and height must be greater than or equal to zero. Using 755 | strictly negative values for width and height will result in a 756 | protocol error. 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | Set a minimum size for the window. 765 | 766 | The client can specify a minimum size so that the compositor does 767 | not try to configure the window below this size. 768 | 769 | The width and height arguments are in window geometry coordinates. 770 | See xdg_surface.set_window_geometry. 771 | 772 | Values set in this way are double-buffered. They will get applied 773 | on the next commit. 774 | 775 | The compositor can use this information to allow or disallow 776 | different states like maximize or fullscreen and draw accurate 777 | animations. 778 | 779 | Similarly, a tiling window manager may use this information to 780 | place and resize client windows in a more effective way. 781 | 782 | The client should not rely on the compositor to obey the minimum 783 | size. The compositor may decide to ignore the values set by the 784 | client and request a smaller size. 785 | 786 | If never set, or a value of zero in the request, means that the 787 | client has no expected minimum size in the given dimension. 788 | As a result, a client wishing to reset the minimum size 789 | to an unspecified state can use zero for width and height in the 790 | request. 791 | 792 | Requesting a minimum size to be larger than the maximum size of 793 | a surface is illegal and will result in a protocol error. 794 | 795 | The width and height must be greater than or equal to zero. Using 796 | strictly negative values for width and height will result in a 797 | protocol error. 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | Maximize the surface. 806 | 807 | After requesting that the surface should be maximized, the compositor 808 | will respond by emitting a configure event with the "maximized" state 809 | and the required window geometry. The client should then update its 810 | content, drawing it in a maximized state, i.e. without shadow or other 811 | decoration outside of the window geometry. The client must also 812 | acknowledge the configure when committing the new content (see 813 | ack_configure). 814 | 815 | It is up to the compositor to decide how and where to maximize the 816 | surface, for example which output and what region of the screen should 817 | be used. 818 | 819 | If the surface was already maximized, the compositor will still emit 820 | a configure event with the "maximized" state. 821 | 822 | 823 | 824 | 825 | 826 | Unmaximize the surface. 827 | 828 | After requesting that the surface should be unmaximized, the compositor 829 | will respond by emitting a configure event without the "maximized" 830 | state. If available, the compositor will include the window geometry 831 | dimensions the window had prior to being maximized in the configure 832 | request. The client must then update its content, drawing it in a 833 | regular state, i.e. potentially with shadow, etc. The client must also 834 | acknowledge the configure when committing the new content (see 835 | ack_configure). 836 | 837 | It is up to the compositor to position the surface after it was 838 | unmaximized; usually the position the surface had before maximizing, if 839 | applicable. 840 | 841 | If the surface was already not maximized, the compositor will still 842 | emit a configure event without the "maximized" state. 843 | 844 | 845 | 846 | 847 | 848 | Make the surface fullscreen. 849 | 850 | You can specify an output that you would prefer to be fullscreen. 851 | If this value is NULL, it's up to the compositor to choose which 852 | display will be used to map this surface. 853 | 854 | If the surface doesn't cover the whole output, the compositor will 855 | position the surface in the center of the output and compensate with 856 | black borders filling the rest of the output. 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | Request that the compositor minimize your surface. There is no 865 | way to know if the surface is currently minimized, nor is there 866 | any way to unset minimization on this surface. 867 | 868 | If you are looking to throttle redrawing when minimized, please 869 | instead use the wl_surface.frame event for this, as this will 870 | also work with live previews on windows in Alt-Tab, Expose or 871 | similar compositor features. 872 | 873 | 874 | 875 | 876 | 877 | This configure event asks the client to resize its toplevel surface or 878 | to change its state. The configured state should not be applied 879 | immediately. See xdg_surface.configure for details. 880 | 881 | The width and height arguments specify a hint to the window 882 | about how its surface should be resized in window geometry 883 | coordinates. See set_window_geometry. 884 | 885 | If the width or height arguments are zero, it means the client 886 | should decide its own window dimension. This may happen when the 887 | compositor needs to configure the state of the surface but doesn't 888 | have any information about any previous or expected dimension. 889 | 890 | The states listed in the event specify how the width/height 891 | arguments should be interpreted, and possibly how it should be 892 | drawn. 893 | 894 | Clients must send an ack_configure in response to this event. See 895 | xdg_surface.configure and xdg_surface.ack_configure for details. 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | The close event is sent by the compositor when the user 905 | wants the surface to be closed. This should be equivalent to 906 | the user clicking the close button in client-side decorations, 907 | if your application has any. 908 | 909 | This is only a request that the user intends to close the 910 | window. The client may choose to ignore this request, or show 911 | a dialog to ask the user to save their data, etc. 912 | 913 | 914 | 915 | 916 | 917 | 918 | A popup surface is a short-lived, temporary surface. It can be used to 919 | implement for example menus, popovers, tooltips and other similar user 920 | interface concepts. 921 | 922 | A popup can be made to take an explicit grab. See xdg_popup.grab for 923 | details. 924 | 925 | When the popup is dismissed, a popup_done event will be sent out, and at 926 | the same time the surface will be unmapped. See the xdg_popup.popup_done 927 | event for details. 928 | 929 | Explicitly destroying the xdg_popup object will also dismiss the popup and 930 | unmap the surface. Clients that want to dismiss the popup when another 931 | surface of their own is clicked should dismiss the popup using the destroy 932 | request. 933 | 934 | The parent surface must have either the xdg_toplevel or xdg_popup surface 935 | role. 936 | 937 | A newly created xdg_popup will be stacked on top of all previously created 938 | xdg_popup surfaces associated with the same xdg_toplevel. 939 | 940 | The parent of an xdg_popup must be mapped (see the xdg_surface 941 | description) before the xdg_popup itself. 942 | 943 | The x and y arguments passed when creating the popup object specify 944 | where the top left of the popup should be placed, relative to the 945 | local surface coordinates of the parent surface. See 946 | xdg_surface.get_popup. An xdg_popup must intersect with or be at least 947 | partially adjacent to its parent surface. 948 | 949 | The client must call wl_surface.commit on the corresponding wl_surface 950 | for the xdg_popup state to take effect. 951 | 952 | 953 | 954 | 956 | 957 | 958 | 959 | 960 | This destroys the popup. Explicitly destroying the xdg_popup 961 | object will also dismiss the popup, and unmap the surface. 962 | 963 | If this xdg_popup is not the "topmost" popup, a protocol error 964 | will be sent. 965 | 966 | 967 | 968 | 969 | 970 | This request makes the created popup take an explicit grab. An explicit 971 | grab will be dismissed when the user dismisses the popup, or when the 972 | client destroys the xdg_popup. This can be done by the user clicking 973 | outside the surface, using the keyboard, or even locking the screen 974 | through closing the lid or a timeout. 975 | 976 | If the compositor denies the grab, the popup will be immediately 977 | dismissed. 978 | 979 | This request must be used in response to some sort of user action like a 980 | button press, key press, or touch down event. The serial number of the 981 | event should be passed as 'serial'. 982 | 983 | The parent of a grabbing popup must either be an xdg_toplevel surface or 984 | another xdg_popup with an explicit grab. If the parent is another 985 | xdg_popup it means that the popups are nested, with this popup now being 986 | the topmost popup. 987 | 988 | Nested popups must be destroyed in the reverse order they were created 989 | in, e.g. the only popup you are allowed to destroy at all times is the 990 | topmost one. 991 | 992 | When compositors choose to dismiss a popup, they may dismiss every 993 | nested grabbing popup as well. When a compositor dismisses popups, it 994 | will follow the same dismissing order as required from the client. 995 | 996 | The parent of a grabbing popup must either be another xdg_popup with an 997 | active explicit grab, or an xdg_popup or xdg_toplevel, if there are no 998 | explicit grabs already taken. 999 | 1000 | If the topmost grabbing popup is destroyed, the grab will be returned to 1001 | the parent of the popup, if that parent previously had an explicit grab. 1002 | 1003 | If the parent is a grabbing popup which has already been dismissed, this 1004 | popup will be immediately dismissed. If the parent is a popup that did 1005 | not take an explicit grab, an error will be raised. 1006 | 1007 | During a popup grab, the client owning the grab will receive pointer 1008 | and touch events for all their surfaces as normal (similar to an 1009 | "owner-events" grab in X11 parlance), while the top most grabbing popup 1010 | will always have keyboard focus. 1011 | 1012 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | This event asks the popup surface to configure itself given the 1020 | configuration. The configured state should not be applied immediately. 1021 | See xdg_surface.configure for details. 1022 | 1023 | The x and y arguments represent the position the popup was placed at 1024 | given the xdg_positioner rule, relative to the upper left corner of the 1025 | window geometry of the parent surface. 1026 | 1027 | 1029 | 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | The popup_done event is sent out when a popup is dismissed by the 1038 | compositor. The client should destroy the xdg_popup object at this 1039 | point. 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | --------------------------------------------------------------------------------