├── .gitignore ├── sgl.c ├── sgl.h ├── sgl_keysym.h ├── sgl_win.c └── sgl_x11.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | test* 3 | -------------------------------------------------------------------------------- /sgl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Hans-Kristian Arntzen 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* SGL autoselector. Only build sgl.c. */ 18 | 19 | #if defined(_WIN32) 20 | #include "sgl_win.c" 21 | #elif defined(__APPLE__) 22 | #include "sgl_osx.c" 23 | #else 24 | #include "sgl_x11.c" 25 | #endif 26 | 27 | -------------------------------------------------------------------------------- /sgl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Hans-Kristian Arntzen 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef SGL_H__ 18 | #define SGL_H__ 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | #define SGL_SCREEN_WINDOWED 0 25 | #define SGL_SCREEN_FULLSCREEN 1 26 | #define SGL_SCREEN_WINDOWED_FULLSCREEN 2 27 | 28 | #define SGL_CONTEXT_LEGACY 0 29 | #define SGL_CONTEXT_MODERN 1 30 | 31 | #ifdef SGL_HAVE_EGL 32 | #define SGL_CONTEXT_GLES 2 33 | #endif 34 | 35 | struct sgl_resolution 36 | { 37 | /* Requested width of window. Ignored if using windowed fullscreen. */ 38 | unsigned width; 39 | /* Requested height of window. Ignored if using windowed fullscreen. */ 40 | unsigned height; 41 | 42 | /* Monitor index. 0 = First monitor, 1 = Second monitor, etc ... */ 43 | unsigned monitor_index; 44 | }; 45 | 46 | struct sgl_context_options 47 | { 48 | /* Resolution info. */ 49 | struct sgl_resolution res; 50 | 51 | /* Context style. */ 52 | struct 53 | { 54 | int style; 55 | 56 | /* Major/minor OpenGL version used for modern contexts. */ 57 | unsigned major; 58 | unsigned minor; 59 | } context; 60 | 61 | /* Window type. */ 62 | int screen_type; 63 | 64 | /* Swap interval. 0 = No VSync, 1 = VSync. */ 65 | unsigned swap_interval; 66 | 67 | /* Multisampling (AA). A value of 0 implies 1xAA. */ 68 | unsigned samples; 69 | 70 | /* Initial window title. */ 71 | const char *title; 72 | }; 73 | 74 | #define GL_GLEXT_PROTOTYPES 75 | 76 | /* Win32 */ 77 | #if defined(_WIN32) 78 | #define SGL_WINDOWS 79 | #define WIN32_LEAN_AND_MEAN 80 | #include 81 | #include 82 | #include 83 | #ifdef SGL_EXPOSE_INTERNAL 84 | struct sgl_handles 85 | { 86 | HWND hwnd; 87 | HGLRC hglrc; 88 | HDC hdc; 89 | }; 90 | #else 91 | struct sgl_handles; 92 | #endif 93 | 94 | /* OSX */ 95 | #elif defined(__APPLE__) 96 | #define SGL_OSX 97 | #else 98 | 99 | /* X11 */ 100 | #define SGL_X11 101 | #ifdef SGL_HAVE_EGL 102 | #include 103 | #include 104 | #include 105 | #include 106 | #else 107 | #define GL_GLEXT_PROTOTYPES 108 | #include 109 | #include 110 | #endif 111 | #ifdef SGL_EXPOSE_INTERNAL 112 | #include 113 | struct sgl_handles 114 | { 115 | Display *dpy; 116 | Window win; 117 | GLXContext ctx; 118 | }; 119 | #else 120 | struct sgl_handles; 121 | #endif 122 | #endif 123 | 124 | #define SGL_OK 1 125 | #define SGL_ERROR 0 126 | #define SGL_TRUE 1 127 | #define SGL_FALSE 0 128 | 129 | /* Get information about the available desktop modes. 130 | * modes[0] will refer to the current desktop resolution. 131 | * Can be called before sgl_init(). 132 | * The pointer must be free'd using free(). */ 133 | struct sgl_resolution *sgl_get_desktop_modes(unsigned *num_modes); 134 | 135 | int sgl_init(const struct sgl_context_options *opts); 136 | void sgl_deinit(void); 137 | 138 | void sgl_set_window_title(const char *title); 139 | 140 | /* Check if window was resized. Might be resized even if the user didn't explicitly resize. */ 141 | int sgl_check_resize(unsigned *width, unsigned *height); 142 | 143 | void sgl_set_swap_interval(unsigned interval); 144 | void sgl_swap_buffers(void); 145 | 146 | int sgl_has_focus(void); 147 | 148 | /* When this returns 0, the window or application was killed (SIGINT/SIGTERM). */ 149 | int sgl_is_alive(void); 150 | 151 | /* Get underlying platform specific window handles. Use it to implement input. */ 152 | void sgl_get_handles(struct sgl_handles *handles); 153 | 154 | /* GetProcAddress() wrapper. */ 155 | typedef void (*sgl_function_t)(void); 156 | sgl_function_t sgl_get_proc_address(const char *sym); 157 | 158 | /* Input callbacks. If non-NULL a callback may be called one or more times in calls to sgl_is_alive(). 159 | * Coordinates for mouse are absolute with respect to the window. */ 160 | typedef void (*sgl_key_callback_t)(int key, int pressed); 161 | typedef void (*sgl_mouse_move_callback_t)(int x, int y); 162 | typedef void (*sgl_mouse_button_callback_t)(int button, int pressed, int x, int y); 163 | struct sgl_input_callbacks 164 | { 165 | sgl_key_callback_t key_cb; 166 | sgl_mouse_move_callback_t mouse_move_cb; 167 | sgl_mouse_button_callback_t mouse_button_cb; 168 | }; 169 | 170 | void sgl_set_input_callbacks(const struct sgl_input_callbacks *cbs); 171 | void sgl_set_mouse_mode(int capture, int relative, int visible); 172 | 173 | #ifdef __cplusplus 174 | } 175 | #endif 176 | 177 | #endif 178 | 179 | -------------------------------------------------------------------------------- /sgl_keysym.h: -------------------------------------------------------------------------------- 1 | #ifndef SGL_KEYSYM_H__ 2 | #define SGL_KEYSYM_H__ 3 | 4 | /* 5 | * Copyright (c) 2011, Hans-Kristian Arntzen 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | enum sgl_key 21 | { 22 | SGLK_UNKNOWN = 0, 23 | SGLK_FIRST = 0, 24 | SGLK_BACKSPACE = 8, 25 | SGLK_TAB = 9, 26 | SGLK_CLEAR = 12, 27 | SGLK_RETURN = 13, 28 | SGLK_PAUSE = 19, 29 | SGLK_ESCAPE = 27, 30 | SGLK_SPACE = 32, 31 | SGLK_EXCLAIM = 33, 32 | SGLK_QUOTEDBL = 34, 33 | SGLK_HASH = 35, 34 | SGLK_DOLLAR = 36, 35 | SGLK_AMPERSAND = 38, 36 | SGLK_QUOTE = 39, 37 | SGLK_LEFTPAREN = 40, 38 | SGLK_RIGHTPAREN = 41, 39 | SGLK_ASTERISK = 42, 40 | SGLK_PLUS = 43, 41 | SGLK_COMMA = 44, 42 | SGLK_MINUS = 45, 43 | SGLK_PERIOD = 46, 44 | SGLK_SLASH = 47, 45 | SGLK_0 = 48, 46 | SGLK_1 = 49, 47 | SGLK_2 = 50, 48 | SGLK_3 = 51, 49 | SGLK_4 = 52, 50 | SGLK_5 = 53, 51 | SGLK_6 = 54, 52 | SGLK_7 = 55, 53 | SGLK_8 = 56, 54 | SGLK_9 = 57, 55 | SGLK_COLON = 58, 56 | SGLK_SEMICOLON = 59, 57 | SGLK_LESS = 60, 58 | SGLK_EQUALS = 61, 59 | SGLK_GREATER = 62, 60 | SGLK_QUESTION = 63, 61 | SGLK_AT = 64, 62 | SGLK_LEFTBRACKET = 91, 63 | SGLK_BACKSLASH = 92, 64 | SGLK_RIGHTBRACKET = 93, 65 | SGLK_CARET = 94, 66 | SGLK_UNDERSCORE = 95, 67 | SGLK_BACKQUOTE = 96, 68 | SGLK_a = 97, 69 | SGLK_b = 98, 70 | SGLK_c = 99, 71 | SGLK_d = 100, 72 | SGLK_e = 101, 73 | SGLK_f = 102, 74 | SGLK_g = 103, 75 | SGLK_h = 104, 76 | SGLK_i = 105, 77 | SGLK_j = 106, 78 | SGLK_k = 107, 79 | SGLK_l = 108, 80 | SGLK_m = 109, 81 | SGLK_n = 110, 82 | SGLK_o = 111, 83 | SGLK_p = 112, 84 | SGLK_q = 113, 85 | SGLK_r = 114, 86 | SGLK_s = 115, 87 | SGLK_t = 116, 88 | SGLK_u = 117, 89 | SGLK_v = 118, 90 | SGLK_w = 119, 91 | SGLK_x = 120, 92 | SGLK_y = 121, 93 | SGLK_z = 122, 94 | SGLK_DELETE = 127, 95 | 96 | SGLK_KP0 = 256, 97 | SGLK_KP1 = 257, 98 | SGLK_KP2 = 258, 99 | SGLK_KP3 = 259, 100 | SGLK_KP4 = 260, 101 | SGLK_KP5 = 261, 102 | SGLK_KP6 = 262, 103 | SGLK_KP7 = 263, 104 | SGLK_KP8 = 264, 105 | SGLK_KP9 = 265, 106 | SGLK_KP_PERIOD = 266, 107 | SGLK_KP_DIVIDE = 267, 108 | SGLK_KP_MULTIPLY = 268, 109 | SGLK_KP_MINUS = 269, 110 | SGLK_KP_PLUS = 270, 111 | SGLK_KP_ENTER = 271, 112 | SGLK_KP_EQUALS = 272, 113 | 114 | SGLK_UP = 273, 115 | SGLK_DOWN = 274, 116 | SGLK_RIGHT = 275, 117 | SGLK_LEFT = 276, 118 | SGLK_INSERT = 277, 119 | SGLK_HOME = 278, 120 | SGLK_END = 279, 121 | SGLK_PAGEUP = 280, 122 | SGLK_PAGEDOWN = 281, 123 | 124 | SGLK_F1 = 282, 125 | SGLK_F2 = 283, 126 | SGLK_F3 = 284, 127 | SGLK_F4 = 285, 128 | SGLK_F5 = 286, 129 | SGLK_F6 = 287, 130 | SGLK_F7 = 288, 131 | SGLK_F8 = 289, 132 | SGLK_F9 = 290, 133 | SGLK_F10 = 291, 134 | SGLK_F11 = 292, 135 | SGLK_F12 = 293, 136 | SGLK_F13 = 294, 137 | SGLK_F14 = 295, 138 | SGLK_F15 = 296, 139 | 140 | SGLK_NUMLOCK = 300, 141 | SGLK_CAPSLOCK = 301, 142 | SGLK_SCROLLOCK = 302, 143 | SGLK_RSHIFT = 303, 144 | SGLK_LSHIFT = 304, 145 | SGLK_RCTRL = 305, 146 | SGLK_LCTRL = 306, 147 | SGLK_RALT = 307, 148 | SGLK_LALT = 308, 149 | SGLK_RMETA = 309, 150 | SGLK_LMETA = 310, 151 | SGLK_LSUPER = 311, 152 | SGLK_RSUPER = 312, 153 | SGLK_MODE = 313, 154 | SGLK_COMPOSE = 314, 155 | 156 | SGLK_HELP = 315, 157 | SGLK_PRINT = 316, 158 | SGLK_SYSREQ = 317, 159 | SGLK_BREAK = 318, 160 | SGLK_MENU = 319, 161 | SGLK_POWER = 320, 162 | SGLK_EURO = 321, 163 | SGLK_UNDO = 322, 164 | 165 | SGLK_LAST 166 | }; 167 | 168 | #endif 169 | 170 | -------------------------------------------------------------------------------- /sgl_win.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Hans-Kristian Arntzen 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #define SGL_EXPOSE_INTERNAL 18 | #include "sgl.h" 19 | #include "sgl_keysym.h" 20 | 21 | #define WGL_WGLEXT_PROTOTYPES 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | static HWND g_hwnd; 29 | static HGLRC g_hrc; 30 | static HDC g_hdc; 31 | 32 | static BOOL g_quit; 33 | static BOOL g_inited; 34 | 35 | static BOOL g_resized; 36 | static unsigned g_resize_width; 37 | static unsigned g_resize_height; 38 | 39 | static BOOL g_fullscreen; 40 | 41 | static BOOL g_ctx_modern; 42 | static unsigned g_gl_major; 43 | static unsigned g_gl_minor; 44 | static unsigned g_samples; 45 | 46 | static struct sgl_input_callbacks g_input_cbs; 47 | static BOOL g_mouse_relative; 48 | static BOOL g_mouse_grabbed; 49 | static int g_mouse_last_x; 50 | static int g_mouse_last_y; 51 | static BOOL g_mouse_delta_invalid; 52 | 53 | static void setup_pixel_format(HDC hdc) 54 | { 55 | int num_pixel_format; 56 | static PIXELFORMATDESCRIPTOR pfd = {0}; 57 | pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); 58 | pfd.nVersion = 1; 59 | pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 60 | pfd.iPixelType = PFD_TYPE_RGBA; 61 | pfd.cColorBits = 32; 62 | pfd.cDepthBits = 24; 63 | pfd.cStencilBits = 8; 64 | pfd.iLayerType = PFD_MAIN_PLANE; 65 | 66 | num_pixel_format = ChoosePixelFormat(hdc, &pfd); 67 | SetPixelFormat(hdc, num_pixel_format, &pfd); 68 | } 69 | 70 | static PFNWGLCHOOSEPIXELFORMATEXTPROC pwglChoosePixelFormatARB; 71 | static PFNWGLCREATECONTEXTATTRIBSARBPROC pwglCreateContextAttribsARB; 72 | 73 | static void setup_dummy_window(void) 74 | { 75 | HWND dummy; 76 | HDC hdc; 77 | HGLRC ctx; 78 | WNDCLASSEXA dummy_class = {0}; 79 | dummy_class.cbSize = sizeof(dummy_class); 80 | dummy_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 81 | dummy_class.lpfnWndProc = DefWindowProc; 82 | dummy_class.hInstance = GetModuleHandle(NULL); 83 | dummy_class.hCursor = LoadCursor(NULL, IDC_ARROW); 84 | dummy_class.lpszClassName = "Dummy Window"; 85 | 86 | RegisterClassExA(&dummy_class); 87 | dummy = CreateWindowExA(0, "Dummy Window", "", 88 | WS_OVERLAPPEDWINDOW, 89 | CW_USEDEFAULT, CW_USEDEFAULT, 1, 1, 90 | NULL, NULL, NULL, NULL); 91 | 92 | ShowWindow(dummy, SW_HIDE); 93 | hdc = GetDC(dummy); 94 | setup_pixel_format(hdc); 95 | ctx = wglCreateContext(hdc); 96 | wglMakeCurrent(hdc, ctx); 97 | 98 | pwglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATEXTPROC)wglGetProcAddress("wglChoosePixelFormatARB"); 99 | pwglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB"); 100 | 101 | wglMakeCurrent(NULL, NULL); 102 | wglDeleteContext(ctx); 103 | DestroyWindow(dummy); 104 | UnregisterClassA("Dummy Window", GetModuleHandle(NULL)); 105 | } 106 | 107 | static void setup_pixel_format_modern(HDC hdc) 108 | { 109 | int pixel_format; 110 | UINT num_formats; 111 | PIXELFORMATDESCRIPTOR pfd; 112 | float fattrs[2]; 113 | int attribs[] = { 114 | WGL_DOUBLE_BUFFER_ARB, TRUE, 115 | WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, 116 | WGL_RED_BITS_ARB, 8, 117 | WGL_GREEN_BITS_ARB, 8, 118 | WGL_BLUE_BITS_ARB, 8, 119 | WGL_ALPHA_BITS_ARB, 8, 120 | WGL_DEPTH_BITS_ARB, 24, 121 | WGL_STENCIL_BITS_ARB, 8, 122 | WGL_SAMPLE_BUFFERS_ARB, 1, 123 | WGL_SAMPLES_ARB, 0, 124 | 0, 0, 125 | }; 126 | 127 | attribs[19] = g_samples; 128 | fattrs[0] = fattrs[1] = 0.0f; 129 | 130 | pwglChoosePixelFormatARB(hdc, attribs, fattrs, 1, &pixel_format, &num_formats); 131 | 132 | DescribePixelFormat(hdc, pixel_format, sizeof(pfd), &pfd); 133 | SetPixelFormat(hdc, pixel_format, &pfd); 134 | } 135 | 136 | static void create_gl_context(HWND hwnd) 137 | { 138 | BOOL has_modern; 139 | g_hdc = GetDC(hwnd); 140 | 141 | has_modern = pwglChoosePixelFormatARB && pwglCreateContextAttribsARB; 142 | 143 | if (has_modern) 144 | setup_pixel_format_modern(g_hdc); 145 | else 146 | setup_pixel_format(g_hdc); 147 | 148 | if (g_ctx_modern && has_modern) 149 | { 150 | int attribs[] = { 151 | WGL_CONTEXT_MAJOR_VERSION_ARB, 0, 152 | WGL_CONTEXT_MINOR_VERSION_ARB, 0, 153 | WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 154 | #ifdef DEBUG 155 | WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, 156 | #endif 157 | 0 158 | }; 159 | 160 | attribs[1] = g_gl_major; 161 | attribs[3] = g_gl_minor; 162 | 163 | g_hrc = pwglCreateContextAttribsARB(g_hdc, NULL, attribs); 164 | wglMakeCurrent(g_hdc, g_hrc); 165 | } 166 | else 167 | { 168 | g_hrc = wglCreateContext(g_hdc); 169 | wglMakeCurrent(g_hdc, g_hrc); 170 | } 171 | } 172 | 173 | static void handle_key_press(WPARAM key, int pressed); 174 | static void handle_mouse_move(int x, int y); 175 | static void handle_mouse_press(UINT message, int x, int y); 176 | 177 | static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, 178 | WPARAM wparam, LPARAM lparam) 179 | { 180 | switch (message) 181 | { 182 | case WM_SYSCOMMAND: 183 | /* Prevent screensavers, etc, while running :) */ 184 | switch (wparam) 185 | { 186 | case SC_SCREENSAVE: 187 | case SC_MONITORPOWER: 188 | return 0; 189 | } 190 | break; 191 | 192 | case WM_KEYDOWN: 193 | case WM_KEYUP: 194 | handle_key_press(wparam, message == WM_KEYDOWN); 195 | return 0; 196 | 197 | case WM_MOUSEMOVE: 198 | handle_mouse_move(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)); 199 | return 0; 200 | 201 | case WM_LBUTTONDOWN: 202 | case WM_RBUTTONDOWN: 203 | case WM_MBUTTONDOWN: 204 | case WM_LBUTTONUP: 205 | case WM_RBUTTONUP: 206 | case WM_MBUTTONUP: 207 | handle_mouse_press(message, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)); 208 | return 0; 209 | 210 | case WM_CREATE: 211 | create_gl_context(hwnd); 212 | return 0; 213 | 214 | case WM_CLOSE: 215 | case WM_DESTROY: 216 | case WM_QUIT: 217 | g_quit = TRUE; 218 | return 0; 219 | 220 | case WM_SIZE: 221 | /* Do not send resize message if we minimize ... */ 222 | if (wparam != SIZE_MAXHIDE && wparam != SIZE_MINIMIZED) 223 | { 224 | g_resize_width = LOWORD(lparam); 225 | g_resize_height = HIWORD(lparam); 226 | g_resized = TRUE; 227 | } 228 | return 0; 229 | } 230 | 231 | return DefWindowProc(hwnd, message, wparam, lparam); 232 | } 233 | 234 | static BOOL set_fullscreen(unsigned width, unsigned height) 235 | { 236 | DEVMODE devmode; 237 | memset(&devmode, 0, sizeof(devmode)); 238 | devmode.dmSize = sizeof(DEVMODE); 239 | devmode.dmPelsWidth = width; 240 | devmode.dmPelsHeight = height; 241 | devmode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT; 242 | 243 | return ChangeDisplaySettings(&devmode, CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL; 244 | } 245 | 246 | struct sgl_resolution *sgl_get_desktop_modes(unsigned *num_modes) 247 | { 248 | RECT rect; 249 | struct sgl_resolution *sgl_modes = (struct sgl_resolution*)calloc(1, sizeof(*sgl_modes)); 250 | if (!sgl_modes) 251 | return NULL; 252 | 253 | *num_modes = 1; 254 | 255 | GetClientRect(GetDesktopWindow(), &rect); 256 | sgl_modes[0].width = rect.right - rect.left; 257 | sgl_modes[0].height = rect.bottom - rect.top; 258 | return sgl_modes; 259 | } 260 | 261 | int sgl_init(const struct sgl_context_options *opts) 262 | { 263 | unsigned width, height; 264 | DWORD style; 265 | RECT rect; 266 | WNDCLASSEXA wndclass = {0}; 267 | 268 | if (g_inited) 269 | return SGL_ERROR; 270 | 271 | g_quit = FALSE; 272 | g_resized = FALSE; 273 | 274 | g_ctx_modern = opts->context.style == SGL_CONTEXT_MODERN; 275 | g_gl_major = opts->context.major; 276 | g_gl_minor = opts->context.minor; 277 | g_samples = opts->samples == 0 ? 1 : opts->samples; 278 | 279 | setup_dummy_window(); 280 | 281 | wndclass.cbSize = sizeof(wndclass); 282 | wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 283 | wndclass.lpfnWndProc = WndProc; 284 | wndclass.hInstance = GetModuleHandle(NULL); 285 | wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); 286 | wndclass.lpszClassName = "SGL Window"; 287 | 288 | 289 | if (!RegisterClassExA(&wndclass)) 290 | return SGL_ERROR; 291 | 292 | width = opts->res.width; 293 | height = opts->res.height; 294 | style = 0; 295 | GetClientRect(GetDesktopWindow(), &rect); 296 | 297 | switch (opts->screen_type) 298 | { 299 | case SGL_SCREEN_WINDOWED: 300 | { 301 | RECT rect = {0}; 302 | rect.right = width; 303 | rect.bottom = height; 304 | 305 | style = WS_OVERLAPPEDWINDOW; 306 | AdjustWindowRect(&rect, style, FALSE); 307 | width = rect.right - rect.left; 308 | height = rect.bottom - rect.top; 309 | break; 310 | } 311 | 312 | case SGL_SCREEN_FULLSCREEN: 313 | g_fullscreen = TRUE; 314 | style = WS_POPUP | WS_VISIBLE; 315 | 316 | /* Recover, just use windowed fullscreen instead. */ 317 | if (!set_fullscreen(width, height)) 318 | { 319 | g_fullscreen = FALSE; 320 | width = rect.right - rect.left; 321 | height = rect.bottom - rect.top; 322 | } 323 | break; 324 | 325 | case SGL_SCREEN_WINDOWED_FULLSCREEN: 326 | style = WS_POPUP | WS_VISIBLE; 327 | width = rect.right - rect.left; 328 | height = rect.bottom - rect.top; 329 | break; 330 | 331 | default: 332 | UnregisterClassA("SGL Window", GetModuleHandle(NULL)); 333 | return FALSE; 334 | } 335 | 336 | g_hwnd = CreateWindowExA(0, "SGL Window", opts->title ? opts->title : "SGL Window", 337 | style, 338 | CW_USEDEFAULT, CW_USEDEFAULT, width, height, 339 | NULL, NULL, NULL, NULL); 340 | 341 | if (!g_hwnd) 342 | { 343 | UnregisterClassA("SGL Window", GetModuleHandle(NULL)); 344 | return SGL_ERROR; 345 | } 346 | 347 | if (opts->screen_type == SGL_SCREEN_WINDOWED) 348 | { 349 | ShowWindow(g_hwnd, SW_RESTORE); 350 | UpdateWindow(g_hwnd); 351 | SetForegroundWindow(g_hwnd); 352 | SetFocus(g_hwnd); 353 | } 354 | 355 | sgl_set_swap_interval(opts->swap_interval); 356 | 357 | g_inited = TRUE; 358 | return SGL_OK; 359 | } 360 | 361 | void sgl_deinit(void) 362 | { 363 | g_inited = FALSE; 364 | 365 | if (g_quit) 366 | { 367 | wglMakeCurrent(NULL, NULL); 368 | wglDeleteContext(g_hrc); 369 | } 370 | 371 | DestroyWindow(g_hwnd); 372 | UnregisterClassA("SGL Window", GetModuleHandle(NULL)); 373 | 374 | if (g_fullscreen) 375 | ChangeDisplaySettings(NULL, 0); 376 | g_fullscreen = FALSE; 377 | } 378 | 379 | void sgl_set_window_title(const char *title) 380 | { 381 | SetWindowTextA(g_hwnd, title); 382 | } 383 | 384 | int sgl_check_resize(unsigned *width, unsigned *height) 385 | { 386 | if (g_resized) 387 | { 388 | *width = g_resize_width; 389 | *height = g_resize_height; 390 | g_resized = FALSE; 391 | return SGL_TRUE; 392 | } 393 | else 394 | return SGL_FALSE; 395 | } 396 | 397 | void sgl_set_swap_interval(unsigned interval) 398 | { 399 | static BOOL (APIENTRY *swap_interval)(int) = NULL; 400 | if (!swap_interval) 401 | swap_interval = (BOOL (APIENTRY *)(int))sgl_get_proc_address("wglSwapIntervalEXT"); 402 | 403 | if (swap_interval) 404 | swap_interval(interval); 405 | } 406 | 407 | void sgl_swap_buffers(void) 408 | { 409 | SwapBuffers(g_hdc); 410 | } 411 | 412 | int sgl_has_focus(void) 413 | { 414 | return GetFocus() == g_hwnd; 415 | } 416 | 417 | int sgl_is_alive(void) 418 | { 419 | int old_x = g_mouse_last_x; 420 | int old_y = g_mouse_last_y; 421 | 422 | MSG msg; 423 | while (PeekMessage(&msg, g_hwnd, 0, 0, PM_REMOVE)) 424 | { 425 | TranslateMessage(&msg); 426 | DispatchMessage(&msg); 427 | } 428 | 429 | if (g_mouse_relative && g_input_cbs.mouse_move_cb) 430 | { 431 | POINT p; 432 | GetCursorPos(&p); 433 | 434 | if (!g_mouse_delta_invalid) 435 | { 436 | int delta_x = p.x - old_x; 437 | int delta_y = p.y - old_y; 438 | if (delta_x || delta_y) 439 | g_input_cbs.mouse_move_cb(delta_x, delta_y); 440 | } 441 | else 442 | g_mouse_delta_invalid = FALSE; 443 | 444 | if (g_mouse_grabbed) 445 | { 446 | RECT rect; 447 | GetWindowRect(g_hwnd, &rect); 448 | SetCursorPos((rect.left + rect.right) / 2, (rect.bottom + rect.top) / 2); 449 | } 450 | 451 | GetCursorPos(&p); 452 | g_mouse_last_x = p.x; 453 | g_mouse_last_y = p.y; 454 | } 455 | 456 | return !g_quit; 457 | } 458 | 459 | sgl_function_t sgl_get_proc_address(const char *sym) 460 | { 461 | return (sgl_function_t)wglGetProcAddress(sym); 462 | } 463 | 464 | void sgl_get_handles(struct sgl_handles *handles) 465 | { 466 | handles->hwnd = g_hwnd; 467 | handles->hglrc = g_hrc; 468 | handles->hdc = g_hdc; 469 | } 470 | 471 | void sgl_set_input_callbacks(const struct sgl_input_callbacks *cbs) 472 | { 473 | g_input_cbs = *cbs; 474 | } 475 | 476 | void sgl_set_mouse_mode(int capture, int relative, int visible) 477 | { 478 | static BOOL mouse_hidden = FALSE; 479 | if (!visible && !mouse_hidden) 480 | { 481 | ShowCursor(FALSE); 482 | mouse_hidden = TRUE; 483 | } 484 | else if (visible && mouse_hidden) 485 | { 486 | ShowCursor(TRUE); 487 | mouse_hidden = FALSE; 488 | } 489 | 490 | g_mouse_relative = relative; 491 | 492 | g_mouse_delta_invalid = TRUE; 493 | if (capture && !g_mouse_grabbed) 494 | { 495 | RECT rect; 496 | POINT p; 497 | 498 | GetWindowRect(g_hwnd, &rect); 499 | SetCapture(g_hwnd); 500 | ClipCursor(&rect); 501 | SetCursorPos((rect.left + rect.right) / 2, (rect.bottom + rect.top) / 2); 502 | GetCursorPos(&p); 503 | g_mouse_last_x = p.x; 504 | g_mouse_last_y = p.y; 505 | g_mouse_grabbed = TRUE; 506 | } 507 | else if (!capture && g_mouse_grabbed) 508 | { 509 | ClipCursor(NULL); 510 | ReleaseCapture(); 511 | g_mouse_grabbed = FALSE; 512 | } 513 | } 514 | 515 | struct key_map 516 | { 517 | int win; 518 | int sglk; 519 | }; 520 | 521 | static const struct key_map bind_map[] = { 522 | { VK_ESCAPE, SGLK_ESCAPE }, 523 | { VK_UP, SGLK_UP }, 524 | { VK_DOWN, SGLK_DOWN }, 525 | { VK_LEFT, SGLK_LEFT }, 526 | { VK_RIGHT, SGLK_RIGHT }, 527 | { VK_SPACE, SGLK_SPACE }, 528 | { 'A', SGLK_a }, 529 | { 'B', SGLK_b }, 530 | { 'C', SGLK_c }, 531 | { 'D', SGLK_d }, 532 | { 'E', SGLK_e }, 533 | { 'F', SGLK_f }, 534 | { 'G', SGLK_g }, 535 | { 'H', SGLK_h }, 536 | { 'I', SGLK_i }, 537 | { 'J', SGLK_j }, 538 | { 'K', SGLK_k }, 539 | { 'L', SGLK_l }, 540 | { 'M', SGLK_m }, 541 | { 'N', SGLK_n }, 542 | { 'O', SGLK_o }, 543 | { 'P', SGLK_p }, 544 | { 'Q', SGLK_q }, 545 | { 'R', SGLK_r }, 546 | { 'S', SGLK_s }, 547 | { 'T', SGLK_t }, 548 | { 'U', SGLK_u }, 549 | { 'V', SGLK_v }, 550 | { 'W', SGLK_w }, 551 | { 'X', SGLK_x }, 552 | { 'Y', SGLK_y }, 553 | { 'Z', SGLK_z }, 554 | }; 555 | 556 | static void handle_key_press(WPARAM key, int pressed) 557 | { 558 | size_t i; 559 | if (!g_input_cbs.key_cb) 560 | return; 561 | 562 | for (i = 0; i < sizeof(bind_map) / sizeof(bind_map[0]); i++) 563 | { 564 | if (bind_map[i].win == key) 565 | { 566 | g_input_cbs.key_cb(bind_map[i].sglk, pressed); 567 | return; 568 | } 569 | } 570 | } 571 | 572 | static void handle_mouse_move(int x, int y) 573 | { 574 | if (!g_input_cbs.mouse_move_cb) 575 | return; 576 | 577 | if (!g_mouse_relative) 578 | g_input_cbs.mouse_move_cb(x, y); 579 | } 580 | 581 | static void handle_mouse_press(UINT message, int x, int y) 582 | { 583 | int pressed, button; 584 | if (!g_input_cbs.mouse_button_cb) 585 | return; 586 | 587 | switch (message) 588 | { 589 | case WM_LBUTTONDOWN: 590 | case WM_RBUTTONDOWN: 591 | case WM_MBUTTONDOWN: 592 | pressed = SGL_TRUE; 593 | break; 594 | 595 | case WM_LBUTTONUP: 596 | case WM_RBUTTONUP: 597 | case WM_MBUTTONUP: 598 | pressed = SGL_FALSE; 599 | 600 | default: 601 | pressed = SGL_FALSE; 602 | } 603 | 604 | switch (message) 605 | { 606 | case WM_LBUTTONDOWN: 607 | case WM_LBUTTONUP: 608 | button = 1; 609 | break; 610 | 611 | case WM_MBUTTONDOWN: 612 | case WM_MBUTTONUP: 613 | button = 2; 614 | break; 615 | 616 | case WM_RBUTTONDOWN: 617 | case WM_RBUTTONUP: 618 | button = 3; 619 | break; 620 | 621 | default: 622 | button = 0; 623 | } 624 | 625 | g_input_cbs.mouse_button_cb(button, pressed, x, y); 626 | } 627 | 628 | 629 | -------------------------------------------------------------------------------- /sgl_x11.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Hans-Kristian Arntzen 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #define SGL_EXPOSE_INTERNAL 18 | #include "sgl.h" 19 | #include "sgl_keysym.h" 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | static Display *g_dpy; 31 | static Window g_win; 32 | static GLXContext g_ctx; 33 | static Colormap g_cmap; 34 | 35 | static bool g_egl; 36 | #ifdef SGL_HAVE_EGL 37 | static EGLContext g_egl_ctx; 38 | static EGLSurface g_egl_surf; 39 | static EGLDisplay g_egl_dpy; 40 | #endif 41 | 42 | static bool g_inited; 43 | static bool g_is_double_buffered; 44 | 45 | static int g_last_width; 46 | static int g_last_height; 47 | static bool g_resized; 48 | 49 | static Atom g_quit_atom; 50 | static volatile sig_atomic_t g_quit; 51 | static bool g_has_focus; 52 | 53 | static XF86VidModeModeInfo g_desktop_mode; 54 | static bool g_should_reset_mode; 55 | 56 | static struct sgl_input_callbacks g_input_cbs; 57 | static bool g_mouse_grabbed; 58 | static bool g_mouse_relative; 59 | static int g_mouse_last_x; 60 | static int g_mouse_last_y; 61 | 62 | static int (*g_pglSwapInterval)(int); 63 | 64 | static void sighandler(int sig) 65 | { 66 | (void)sig; 67 | g_quit = 1; 68 | } 69 | 70 | static Bool glx_wait_notify(Display *d, XEvent *e, char *arg) 71 | { 72 | (void)d; 73 | (void)arg; 74 | return (e->type == MapNotify) && (e->xmap.window == g_win); 75 | } 76 | 77 | static void hide_mouse(void) 78 | { 79 | Cursor no_ptr; 80 | Pixmap bm_no; 81 | XColor black, dummy; 82 | Colormap colormap; 83 | static char bm_no_data[] = {0, 0, 0, 0, 0, 0, 0, 0}; 84 | 85 | colormap = DefaultColormap(g_dpy, DefaultScreen(g_dpy)); 86 | if (!XAllocNamedColor(g_dpy, colormap, "black", &black, &dummy)) 87 | return; 88 | 89 | bm_no = XCreateBitmapFromData(g_dpy, g_win, bm_no_data, 8, 8); 90 | no_ptr = XCreatePixmapCursor(g_dpy, bm_no, bm_no, &black, &black, 0, 0); 91 | XDefineCursor(g_dpy, g_win, no_ptr); 92 | XFreeCursor(g_dpy, no_ptr); 93 | if (bm_no != None) 94 | XFreePixmap(g_dpy, bm_no); 95 | XFreeColors(g_dpy, colormap, &black.pixel, 1, 0); 96 | } 97 | 98 | static void show_mouse(void) 99 | { 100 | XUndefineCursor(g_dpy, g_win); 101 | } 102 | 103 | static Atom XA_NET_WM_STATE; 104 | static Atom XA_NET_WM_STATE_FULLSCREEN; 105 | #define XA_INIT(x) XA##x = XInternAtom(g_dpy, #x, False) 106 | #define _NET_WM_STATE_ADD 1 107 | static void set_windowed_fullscreen(void) 108 | { 109 | XA_INIT(_NET_WM_STATE); 110 | XA_INIT(_NET_WM_STATE_FULLSCREEN); 111 | 112 | if (!XA_NET_WM_STATE || !XA_NET_WM_STATE_FULLSCREEN) 113 | { 114 | fprintf(stderr, "[SGL]: GLX cannot set fullscreen :(\n"); 115 | return; 116 | } 117 | 118 | XEvent xev; 119 | 120 | xev.xclient.type = ClientMessage; 121 | xev.xclient.serial = 0; 122 | xev.xclient.send_event = True; 123 | xev.xclient.message_type = XA_NET_WM_STATE; 124 | xev.xclient.window = g_win; 125 | xev.xclient.format = 32; 126 | xev.xclient.data.l[0] = _NET_WM_STATE_ADD; 127 | xev.xclient.data.l[1] = XA_NET_WM_STATE_FULLSCREEN; 128 | xev.xclient.data.l[2] = 0; 129 | xev.xclient.data.l[3] = 0; 130 | xev.xclient.data.l[4] = 0; 131 | 132 | XSendEvent(g_dpy, DefaultRootWindow(g_dpy), False, 133 | SubstructureRedirectMask | SubstructureNotifyMask, 134 | &xev); 135 | } 136 | 137 | struct sgl_resolution *sgl_get_desktop_modes(unsigned *num_modes) 138 | { 139 | XF86VidModeModeInfo **modes; 140 | int mode_num; 141 | Display *dpy = XOpenDisplay(NULL); 142 | if (!dpy) 143 | return NULL; 144 | 145 | XF86VidModeGetAllModeLines(dpy, DefaultScreen(dpy), &mode_num, &modes); 146 | 147 | struct sgl_resolution *sgl_modes = calloc(mode_num, sizeof(*sgl_modes)); 148 | if (!sgl_modes) 149 | goto error; 150 | 151 | for (int i = 0; i < mode_num; i++) 152 | { 153 | sgl_modes[i].width = modes[i]->hdisplay; 154 | sgl_modes[i].height = modes[i]->vdisplay; 155 | } 156 | 157 | XCloseDisplay(dpy); 158 | XFree(modes); 159 | 160 | *num_modes = mode_num; 161 | 162 | return sgl_modes; 163 | 164 | error: 165 | XCloseDisplay(dpy); 166 | XFree(modes); 167 | return NULL; 168 | } 169 | 170 | static void set_desktop_mode(void) 171 | { 172 | XF86VidModeModeInfo **modes; 173 | int num_modes; 174 | XF86VidModeGetAllModeLines(g_dpy, DefaultScreen(g_dpy), &num_modes, &modes); 175 | g_desktop_mode = *modes[0]; 176 | XFree(modes); 177 | } 178 | 179 | static bool get_video_mode(int width, int height, XF86VidModeModeInfo *mode) 180 | { 181 | XF86VidModeModeInfo **modes; 182 | int num_modes; 183 | XF86VidModeGetAllModeLines(g_dpy, DefaultScreen(g_dpy), &num_modes, &modes); 184 | 185 | bool ret = false; 186 | for (int i = 0; i < num_modes; i++) 187 | { 188 | if (modes[i]->hdisplay == width && modes[i]->vdisplay == height) 189 | { 190 | *mode = *modes[i]; 191 | ret = true; 192 | break; 193 | } 194 | } 195 | 196 | XFree(modes); 197 | return ret; 198 | } 199 | 200 | int sgl_init_glx(const struct sgl_context_options *opts) 201 | { 202 | if (g_inited) 203 | return SGL_ERROR; 204 | 205 | g_quit = 0; 206 | g_has_focus = true; 207 | g_resized = false; 208 | 209 | g_dpy = XOpenDisplay(NULL); 210 | if (!g_dpy) 211 | goto error; 212 | 213 | // Need GLX 1.3+. 214 | int major, minor; 215 | glXQueryVersion(g_dpy, &major, &minor); 216 | if (major < 1 || (major == 1 && minor < 3)) 217 | goto error; 218 | 219 | // Initialize FBConfig and XVisuals. 220 | const int visual_attribs[] = { 221 | GLX_X_RENDERABLE , True, 222 | GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT, 223 | GLX_RENDER_TYPE , GLX_RGBA_BIT, 224 | GLX_DOUBLEBUFFER , True, 225 | GLX_RED_SIZE , 8, 226 | GLX_GREEN_SIZE , 8, 227 | GLX_BLUE_SIZE , 8, 228 | GLX_ALPHA_SIZE , 8, 229 | GLX_DEPTH_SIZE , 24, 230 | GLX_STENCIL_SIZE , 8, 231 | 232 | opts->samples == 0 ? None : GLX_SAMPLE_BUFFERS, 1, 233 | opts->samples == 0 ? None : GLX_SAMPLES, opts->samples, 234 | None 235 | }; 236 | 237 | int nelements; 238 | GLXFBConfig *fbc_temp = glXChooseFBConfig(g_dpy, DefaultScreen(g_dpy), 239 | visual_attribs, &nelements); 240 | 241 | if (!fbc_temp) 242 | goto error; 243 | 244 | GLXFBConfig fbc = fbc_temp[0]; 245 | XFree(fbc_temp); 246 | 247 | XVisualInfo *vi = glXGetVisualFromFBConfig(g_dpy, fbc); 248 | if (!vi) 249 | goto error; 250 | 251 | // Create Window. 252 | bool fullscreen = opts->screen_type == SGL_SCREEN_FULLSCREEN; 253 | 254 | XSetWindowAttributes swa = { 255 | .colormap = g_cmap = XCreateColormap(g_dpy, RootWindow(g_dpy, vi->screen), vi->visual, AllocNone), 256 | .border_pixel = 0, 257 | .event_mask = StructureNotifyMask, 258 | .override_redirect = fullscreen ? True : False, 259 | }; 260 | 261 | unsigned width = opts->res.width; 262 | unsigned height = opts->res.height; 263 | 264 | set_desktop_mode(); 265 | 266 | if (fullscreen) 267 | { 268 | XF86VidModeModeInfo mode; 269 | if (get_video_mode(width, height, &mode)) 270 | { 271 | XF86VidModeSwitchToMode(g_dpy, DefaultScreen(g_dpy), &mode); 272 | XF86VidModeSetViewPort(g_dpy, DefaultScreen(g_dpy), 0, 0); 273 | g_should_reset_mode = true; 274 | } 275 | else 276 | goto error; 277 | } 278 | else if (opts->screen_type == SGL_SCREEN_WINDOWED_FULLSCREEN) 279 | { 280 | width = g_desktop_mode.hdisplay; 281 | height = g_desktop_mode.vdisplay; 282 | } 283 | 284 | g_win = XCreateWindow(g_dpy, RootWindow(g_dpy, vi->screen), 285 | 0, 0, width, height, 0, 286 | vi->depth, InputOutput, vi->visual, 287 | CWBorderPixel | CWColormap | CWEventMask | (fullscreen ? CWOverrideRedirect : 0), &swa); 288 | XSetWindowBackground(g_dpy, g_win, 0); 289 | 290 | g_last_width = opts->res.width; 291 | g_last_height = opts->res.height; 292 | 293 | sgl_set_window_title(opts->title); 294 | 295 | if (fullscreen) 296 | { 297 | XMapRaised(g_dpy, g_win); 298 | XGrabKeyboard(g_dpy, g_win, True, GrabModeAsync, GrabModeAsync, CurrentTime); 299 | } 300 | else 301 | XMapWindow(g_dpy, g_win); 302 | 303 | if (opts->screen_type == SGL_SCREEN_WINDOWED_FULLSCREEN) 304 | set_windowed_fullscreen(); 305 | 306 | g_quit_atom = XInternAtom(g_dpy, "WM_DELETE_WINDOW", False); 307 | if (g_quit_atom) 308 | XSetWMProtocols(g_dpy, g_win, &g_quit_atom, 1); 309 | 310 | // Catch signals. 311 | struct sigaction sa = { 312 | .sa_handler = sighandler, 313 | .sa_flags = SA_RESTART, 314 | }; 315 | sigemptyset(&sa.sa_mask); 316 | sigaction(SIGINT, &sa, NULL); 317 | sigaction(SIGTERM, &sa, NULL); 318 | 319 | XEvent event; 320 | XIfEvent(g_dpy, &event, glx_wait_notify, NULL); 321 | 322 | // Create context. 323 | if (opts->context.style == SGL_CONTEXT_MODERN) 324 | { 325 | typedef GLXContext (*ContextProc)(Display*, GLXFBConfig, 326 | GLXContext, Bool, const int *); 327 | 328 | ContextProc proc = (ContextProc)glXGetProcAddress((const GLubyte *)"glXCreateContextAttribsARB"); 329 | if (!proc) 330 | { 331 | fprintf(stderr, "[SGL]: Failed to get glXCreateContextAttribsARB symbol!\n"); 332 | goto error; 333 | } 334 | 335 | const int attribs[] = { 336 | GLX_CONTEXT_MAJOR_VERSION_ARB, opts->context.major, 337 | GLX_CONTEXT_MINOR_VERSION_ARB, opts->context.minor, 338 | GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 339 | #ifdef DEBUG 340 | GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB, 341 | #endif 342 | None, 343 | }; 344 | 345 | g_ctx = proc(g_dpy, fbc, 0, true, attribs); 346 | } 347 | else 348 | g_ctx = glXCreateNewContext(g_dpy, fbc, GLX_RGBA_TYPE, 0, True); 349 | 350 | if (!g_ctx) 351 | { 352 | fprintf(stderr, "[SGL]: Failed to create GLX context.\n"); 353 | goto error; 354 | } 355 | 356 | glXMakeCurrent(g_dpy, g_win, g_ctx); 357 | XSync(g_dpy, False); 358 | 359 | int val; 360 | glXGetConfig(g_dpy, vi, GLX_DOUBLEBUFFER, &val); 361 | 362 | g_is_double_buffered = val; 363 | if (g_is_double_buffered) 364 | { 365 | if (!g_pglSwapInterval) 366 | g_pglSwapInterval = (int (*)(int))glXGetProcAddress((const GLubyte*)"glXSwapIntervalSGI"); 367 | if (!g_pglSwapInterval) 368 | g_pglSwapInterval = (int (*)(int))glXGetProcAddress((const GLubyte*)"glXSwapIntervalMESA"); 369 | if (g_pglSwapInterval) 370 | g_pglSwapInterval(opts->swap_interval); 371 | } 372 | else 373 | fprintf(stderr, "[SGL]: GLX is not double buffered!\n"); 374 | 375 | g_inited = true; 376 | return SGL_OK; 377 | 378 | error: 379 | sgl_deinit(); 380 | return SGL_ERROR; 381 | } 382 | 383 | #ifdef SGL_HAVE_EGL 384 | int sgl_init_egl(const struct sgl_context_options *opts) 385 | { 386 | if (g_inited) 387 | return SGL_ERROR; 388 | 389 | g_quit = 0; 390 | g_has_focus = true; 391 | g_resized = false; 392 | 393 | g_dpy = XOpenDisplay(NULL); 394 | if (!g_dpy) 395 | goto error; 396 | 397 | EGLConfig config; 398 | EGLint num_configs, egl_major, egl_minor; 399 | 400 | g_egl_dpy = eglGetDisplay(g_dpy); 401 | if (!g_egl_dpy) 402 | { 403 | fprintf(stderr, "[SGL]: Couldn't get EGL display.\n"); 404 | goto error; 405 | } 406 | 407 | if (!eglInitialize(g_egl_dpy, &egl_major, &egl_minor)) 408 | { 409 | fprintf(stderr, "[SGL]: eglInitialize() failed.\n"); 410 | goto error; 411 | } 412 | 413 | const EGLint egl_attribs[] = { 414 | EGL_RED_SIZE, 1, 415 | EGL_GREEN_SIZE, 1, 416 | EGL_BLUE_SIZE, 1, 417 | EGL_DEPTH_SIZE, 1, 418 | EGL_RENDERABLE_TYPE, opts->context.major == 2 ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES_BIT, 419 | EGL_NONE, 420 | }; 421 | 422 | const EGLint egl_ctx_attribs[] = { 423 | EGL_CONTEXT_CLIENT_VERSION, opts->context.major, 424 | EGL_NONE, 425 | }; 426 | 427 | if (!eglChooseConfig(g_egl_dpy, egl_attribs, &config, 1, &num_configs) 428 | || num_configs == 0 || !config) 429 | { 430 | fprintf(stderr, "[SGL]: eglChooseConfig() failed.\n"); 431 | goto error; 432 | } 433 | 434 | EGLint vid = 0; 435 | if (!eglGetConfigAttrib(g_egl_dpy, config, EGL_NATIVE_VISUAL_ID, &vid)) 436 | { 437 | fprintf(stderr, "[SGL]: eglGetConfigAttrib() failed.\n"); 438 | goto error; 439 | } 440 | 441 | XVisualInfo vis_template = { .visualid = vid }; 442 | int num_visuals = 0; 443 | XVisualInfo *vi = XGetVisualInfo(g_dpy, VisualIDMask, &vis_template, &num_visuals); 444 | if (!vi) 445 | { 446 | fprintf(stderr, "[SGL]: XGetVisualInfo() failed.\n"); 447 | goto error; 448 | } 449 | 450 | // Create Window. 451 | bool fullscreen = opts->screen_type == SGL_SCREEN_FULLSCREEN; 452 | 453 | XSetWindowAttributes swa = { 454 | .colormap = g_cmap = XCreateColormap(g_dpy, RootWindow(g_dpy, vi->screen), vi->visual, AllocNone), 455 | .border_pixel = 0, 456 | .event_mask = StructureNotifyMask, 457 | .override_redirect = fullscreen ? True : False, 458 | }; 459 | 460 | unsigned width = opts->res.width; 461 | unsigned height = opts->res.height; 462 | 463 | set_desktop_mode(); 464 | 465 | if (fullscreen) 466 | { 467 | XF86VidModeModeInfo mode; 468 | if (get_video_mode(width, height, &mode)) 469 | { 470 | XF86VidModeSwitchToMode(g_dpy, DefaultScreen(g_dpy), &mode); 471 | XF86VidModeSetViewPort(g_dpy, DefaultScreen(g_dpy), 0, 0); 472 | g_should_reset_mode = true; 473 | } 474 | else 475 | goto error; 476 | } 477 | else if (opts->screen_type == SGL_SCREEN_WINDOWED_FULLSCREEN) 478 | { 479 | width = g_desktop_mode.hdisplay; 480 | height = g_desktop_mode.vdisplay; 481 | } 482 | 483 | // Create window. 484 | g_win = XCreateWindow(g_dpy, RootWindow(g_dpy, vi->screen), 485 | 0, 0, width, height, 0, 486 | vi->depth, InputOutput, vi->visual, 487 | CWBorderPixel | CWColormap | CWEventMask | (fullscreen ? CWOverrideRedirect : 0), &swa); 488 | XSetWindowBackground(g_dpy, g_win, 0); 489 | 490 | eglBindAPI(opts->context.major == 2 ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES_BIT); 491 | 492 | g_last_width = opts->res.width; 493 | g_last_height = opts->res.height; 494 | 495 | // Create context. 496 | g_egl_ctx = eglCreateContext(g_egl_dpy, config, EGL_NO_CONTEXT, egl_ctx_attribs); 497 | 498 | if (!g_egl_ctx) 499 | { 500 | fprintf(stderr, "[SGL]: Failed to create EGL context.\n"); 501 | goto error; 502 | } 503 | 504 | g_egl_surf = eglCreateWindowSurface(g_egl_dpy, config, g_win, NULL); 505 | if (!g_egl_surf) 506 | { 507 | fprintf(stderr, "[SGL]: Failed to create EGL surface.\n"); 508 | goto error; 509 | } 510 | 511 | // Set up window. 512 | sgl_set_window_title(opts->title); 513 | 514 | if (fullscreen) 515 | { 516 | XMapRaised(g_dpy, g_win); 517 | XGrabKeyboard(g_dpy, g_win, True, GrabModeAsync, GrabModeAsync, CurrentTime); 518 | } 519 | else 520 | XMapWindow(g_dpy, g_win); 521 | 522 | if (opts->screen_type == SGL_SCREEN_WINDOWED_FULLSCREEN) 523 | set_windowed_fullscreen(); 524 | 525 | g_quit_atom = XInternAtom(g_dpy, "WM_DELETE_WINDOW", False); 526 | if (g_quit_atom) 527 | XSetWMProtocols(g_dpy, g_win, &g_quit_atom, 1); 528 | 529 | // Catch signals. 530 | struct sigaction sa = { 531 | .sa_handler = sighandler, 532 | .sa_flags = SA_RESTART, 533 | }; 534 | sigemptyset(&sa.sa_mask); 535 | sigaction(SIGINT, &sa, NULL); 536 | sigaction(SIGTERM, &sa, NULL); 537 | 538 | XEvent event; 539 | XIfEvent(g_dpy, &event, glx_wait_notify, NULL); 540 | 541 | // Bind context. 542 | if (!eglMakeCurrent(g_egl_dpy, g_egl_surf, g_egl_surf, g_egl_ctx)) 543 | { 544 | fprintf(stderr, "[SGL]: Failed to make EGL context current.\n"); 545 | goto error; 546 | } 547 | 548 | XFree(vi); 549 | 550 | g_egl = true; 551 | eglSwapInterval(g_egl_dpy, opts->swap_interval); 552 | 553 | g_inited = true; 554 | return SGL_OK; 555 | 556 | error: 557 | sgl_deinit(); 558 | return SGL_ERROR; 559 | } 560 | #endif 561 | 562 | int sgl_init(const struct sgl_context_options *opts) 563 | { 564 | #ifdef SGL_HAVE_EGL 565 | if (opts->context.style != SGL_CONTEXT_GLES) 566 | return sgl_init_glx(opts); 567 | else 568 | return sgl_init_egl(opts); 569 | #else 570 | return sgl_init_glx(opts); 571 | #endif 572 | } 573 | 574 | void sgl_deinit(void) 575 | { 576 | #ifdef SGL_HAVE_EGL 577 | if (g_egl_dpy) 578 | { 579 | if (g_egl_ctx) 580 | eglDestroyContext(g_egl_dpy, g_egl_ctx); 581 | if (g_egl_surf) 582 | eglDestroySurface(g_egl_dpy, g_egl_surf); 583 | eglTerminate(g_egl_dpy); 584 | } 585 | #endif 586 | 587 | g_egl = false; 588 | 589 | if (g_ctx) 590 | { 591 | glFinish(); 592 | glXMakeCurrent(g_dpy, None, NULL); 593 | glXDestroyContext(g_dpy, g_ctx); 594 | g_ctx = NULL; 595 | } 596 | 597 | if (g_win) 598 | { 599 | XDestroyWindow(g_dpy, g_win); 600 | g_win = None; 601 | } 602 | 603 | if (g_cmap) 604 | { 605 | XFreeColormap(g_dpy, g_cmap); 606 | g_cmap = None; 607 | } 608 | 609 | if (g_should_reset_mode) 610 | { 611 | XF86VidModeSwitchToMode(g_dpy, DefaultScreen(g_dpy), &g_desktop_mode); 612 | XF86VidModeSetViewPort(g_dpy, DefaultScreen(g_dpy), 0, 0); 613 | g_should_reset_mode = false; 614 | } 615 | 616 | if (g_dpy) 617 | { 618 | XCloseDisplay(g_dpy); 619 | g_dpy = NULL; 620 | } 621 | 622 | g_inited = false; 623 | } 624 | 625 | void sgl_swap_buffers(void) 626 | { 627 | if (g_is_double_buffered && !g_egl) 628 | glXSwapBuffers(g_dpy, g_win); 629 | #ifdef SGL_HAVE_EGL 630 | else 631 | eglSwapBuffers(g_egl_dpy, g_egl_surf); 632 | #endif 633 | } 634 | 635 | void sgl_set_swap_interval(unsigned interval) 636 | { 637 | if (g_pglSwapInterval && !g_egl) 638 | g_pglSwapInterval(interval); 639 | #ifdef SGL_HAVE_EGL 640 | else 641 | eglSwapInterval(g_egl_dpy, interval); 642 | #endif 643 | } 644 | 645 | int sgl_check_resize(unsigned *width, unsigned *height) 646 | { 647 | XWindowAttributes target; 648 | XGetWindowAttributes(g_dpy, g_win, &target); 649 | 650 | if (target.width != g_last_width || target.height != g_last_height) 651 | { 652 | g_resized = true; 653 | g_last_width = target.width; 654 | g_last_height = target.height; 655 | } 656 | 657 | if (g_resized) 658 | { 659 | *width = g_last_width; 660 | *height = g_last_height; 661 | g_resized = false; 662 | return SGL_TRUE; 663 | } 664 | else 665 | return SGL_FALSE; 666 | } 667 | 668 | static void handle_key_press(int key, int pressed); 669 | static void handle_button_press(int button, int pressed, int x, int y); 670 | static void handle_motion(int x, int y); 671 | 672 | int sgl_is_alive(void) 673 | { 674 | int old_x = g_mouse_last_x; 675 | int old_y = g_mouse_last_y; 676 | 677 | XEvent event; 678 | while (XPending(g_dpy)) 679 | { 680 | XNextEvent(g_dpy, &event); 681 | switch (event.type) 682 | { 683 | case KeyPress: 684 | case KeyRelease: 685 | handle_key_press(XLookupKeysym(&event.xkey, 0), event.xkey.type == KeyPress); 686 | break; 687 | 688 | case ButtonPress: 689 | case ButtonRelease: 690 | handle_button_press(event.xbutton.button, 691 | event.xbutton.type == ButtonPress, 692 | event.xbutton.x, 693 | event.xbutton.y); 694 | break; 695 | 696 | case MotionNotify: 697 | handle_motion(event.xmotion.x, event.xmotion.y); 698 | break; 699 | 700 | case ClientMessage: 701 | if ((Atom)event.xclient.data.l[0] == g_quit_atom) 702 | g_quit = true; 703 | break; 704 | 705 | case DestroyNotify: 706 | g_quit = true; 707 | break; 708 | 709 | case MapNotify: 710 | g_has_focus = true; 711 | break; 712 | 713 | case UnmapNotify: 714 | g_has_focus = false; 715 | break; 716 | } 717 | } 718 | 719 | if (g_mouse_relative && g_input_cbs.mouse_move_cb) 720 | { 721 | int old_mouse_x = g_mouse_grabbed ? g_last_width >> 1 : old_x; 722 | int old_mouse_y = g_mouse_grabbed ? g_last_height >> 1 : old_y; 723 | 724 | int delta_x = g_mouse_last_x - old_mouse_x; 725 | int delta_y = g_mouse_last_y - old_mouse_y; 726 | 727 | if (delta_x || delta_y) 728 | g_input_cbs.mouse_move_cb(delta_x, delta_y); 729 | } 730 | 731 | if (g_mouse_grabbed) 732 | { 733 | XWarpPointer(g_dpy, None, g_win, 0, 0, 0, 0, 734 | g_last_width >> 1, g_last_height >> 1); 735 | g_mouse_last_x = g_last_width >> 1; 736 | g_mouse_last_y = g_last_height >> 1; 737 | } 738 | 739 | return !g_quit; 740 | } 741 | 742 | int sgl_has_focus(void) 743 | { 744 | if (!sgl_is_alive()) 745 | return SGL_FALSE; 746 | 747 | Window win; 748 | int rev; 749 | XGetInputFocus(g_dpy, &win, &rev); 750 | 751 | return (win == g_win && g_has_focus) || g_should_reset_mode; // Fullscreen 752 | } 753 | 754 | void sgl_set_window_title(const char *name) 755 | { 756 | if (name) 757 | XStoreName(g_dpy, g_win, (char*)name); 758 | } 759 | 760 | sgl_function_t sgl_get_proc_address(const char *sym) 761 | { 762 | return glXGetProcAddress((const GLubyte*)sym); 763 | } 764 | 765 | void sgl_get_handles(struct sgl_handles *handles) 766 | { 767 | handles->dpy = g_dpy; 768 | handles->win = g_win; 769 | handles->ctx = g_ctx; 770 | } 771 | 772 | // Input. 773 | struct key_bind 774 | { 775 | int x; 776 | int sglk; 777 | }; 778 | 779 | static const struct key_bind lut_binds[] = { 780 | { XK_Left, SGLK_LEFT }, 781 | { XK_Right, SGLK_RIGHT }, 782 | { XK_Up, SGLK_UP }, 783 | { XK_Down, SGLK_DOWN }, 784 | { XK_Return, SGLK_RETURN }, 785 | { XK_Tab, SGLK_TAB }, 786 | { XK_Insert, SGLK_INSERT }, 787 | { XK_Delete, SGLK_DELETE }, 788 | { XK_Shift_R, SGLK_RSHIFT }, 789 | { XK_Shift_L, SGLK_LSHIFT }, 790 | { XK_Control_L, SGLK_LCTRL }, 791 | { XK_Alt_L, SGLK_LALT }, 792 | { XK_space, SGLK_SPACE }, 793 | { XK_Escape, SGLK_ESCAPE }, 794 | { XK_BackSpace, SGLK_BACKSPACE }, 795 | { XK_KP_Enter, SGLK_KP_ENTER }, 796 | { XK_KP_Add, SGLK_KP_PLUS }, 797 | { XK_KP_Subtract, SGLK_KP_MINUS }, 798 | { XK_KP_Multiply, SGLK_KP_MULTIPLY }, 799 | { XK_KP_Divide, SGLK_KP_DIVIDE }, 800 | { XK_grave, SGLK_BACKQUOTE }, 801 | { XK_Pause, SGLK_PAUSE }, 802 | { XK_KP_0, SGLK_KP0 }, 803 | { XK_KP_1, SGLK_KP1 }, 804 | { XK_KP_2, SGLK_KP2 }, 805 | { XK_KP_3, SGLK_KP3 }, 806 | { XK_KP_4, SGLK_KP4 }, 807 | { XK_KP_5, SGLK_KP5 }, 808 | { XK_KP_6, SGLK_KP6 }, 809 | { XK_KP_7, SGLK_KP7 }, 810 | { XK_KP_8, SGLK_KP8 }, 811 | { XK_KP_9, SGLK_KP9 }, 812 | { XK_0, SGLK_0 }, 813 | { XK_1, SGLK_1 }, 814 | { XK_2, SGLK_2 }, 815 | { XK_3, SGLK_3 }, 816 | { XK_4, SGLK_4 }, 817 | { XK_5, SGLK_5 }, 818 | { XK_6, SGLK_6 }, 819 | { XK_7, SGLK_7 }, 820 | { XK_8, SGLK_8 }, 821 | { XK_9, SGLK_9 }, 822 | { XK_F1, SGLK_F1 }, 823 | { XK_F2, SGLK_F2 }, 824 | { XK_F3, SGLK_F3 }, 825 | { XK_F4, SGLK_F4 }, 826 | { XK_F5, SGLK_F5 }, 827 | { XK_F6, SGLK_F6 }, 828 | { XK_F7, SGLK_F7 }, 829 | { XK_F8, SGLK_F8 }, 830 | { XK_F9, SGLK_F9 }, 831 | { XK_F10, SGLK_F10 }, 832 | { XK_F11, SGLK_F11 }, 833 | { XK_F12, SGLK_F12 }, 834 | { XK_a, SGLK_a }, 835 | { XK_b, SGLK_b }, 836 | { XK_c, SGLK_c }, 837 | { XK_d, SGLK_d }, 838 | { XK_e, SGLK_e }, 839 | { XK_f, SGLK_f }, 840 | { XK_g, SGLK_g }, 841 | { XK_h, SGLK_h }, 842 | { XK_i, SGLK_i }, 843 | { XK_j, SGLK_j }, 844 | { XK_k, SGLK_k }, 845 | { XK_l, SGLK_l }, 846 | { XK_m, SGLK_m }, 847 | { XK_n, SGLK_n }, 848 | { XK_o, SGLK_o }, 849 | { XK_p, SGLK_p }, 850 | { XK_q, SGLK_q }, 851 | { XK_r, SGLK_r }, 852 | { XK_s, SGLK_s }, 853 | { XK_t, SGLK_t }, 854 | { XK_u, SGLK_u }, 855 | { XK_v, SGLK_v }, 856 | { XK_w, SGLK_w }, 857 | { XK_x, SGLK_x }, 858 | { XK_y, SGLK_y }, 859 | { XK_z, SGLK_z }, 860 | }; 861 | 862 | void sgl_set_input_callbacks(const struct sgl_input_callbacks *cbs) 863 | { 864 | g_input_cbs = *cbs; 865 | XSelectInput(g_dpy, g_win, 866 | (cbs->key_cb ? KeyPressMask | KeyReleaseMask : 0) | 867 | (cbs->mouse_button_cb ? ButtonPressMask | ButtonReleaseMask : 0) | 868 | (cbs->mouse_move_cb ? PointerMotionMask : 0)); 869 | } 870 | 871 | static void handle_key_press(int key, int pressed) 872 | { 873 | if (!g_input_cbs.key_cb) 874 | return; 875 | 876 | for (unsigned i = 0; i < sizeof(lut_binds) / sizeof(lut_binds[0]); i++) 877 | { 878 | if (key == lut_binds[i].x) 879 | { 880 | g_input_cbs.key_cb(lut_binds[i].sglk, pressed); 881 | return; 882 | } 883 | } 884 | } 885 | 886 | static void handle_button_press(int button, int pressed, int x, int y) 887 | { 888 | if (!g_input_cbs.mouse_button_cb) 889 | return; 890 | 891 | g_input_cbs.mouse_button_cb(button, pressed, x, y); 892 | } 893 | 894 | static void handle_motion(int x, int y) 895 | { 896 | if (!g_input_cbs.mouse_move_cb) 897 | return; 898 | 899 | if (!g_mouse_relative) 900 | g_input_cbs.mouse_move_cb(x, y); 901 | 902 | g_mouse_last_x = x; 903 | g_mouse_last_y = y; 904 | } 905 | 906 | void sgl_set_mouse_mode(int grab, int relative, int visible) 907 | { 908 | g_mouse_relative = relative; 909 | 910 | if (g_should_reset_mode) // Fullscreen 911 | return; 912 | 913 | g_mouse_grabbed = grab; 914 | if (grab) 915 | { 916 | g_mouse_last_x = g_last_width >> 1; 917 | g_mouse_last_y = g_last_height >> 1; 918 | XWarpPointer(g_dpy, None, g_win, 0, 0, 0, 0, 919 | g_last_width >> 1, g_last_height >> 1); 920 | 921 | XGrabPointer(g_dpy, g_win, True, 922 | ButtonPressMask | ButtonReleaseMask | PointerMotionMask, 923 | GrabModeAsync, GrabModeAsync, g_win, None, CurrentTime); 924 | } 925 | else 926 | XUngrabPointer(g_dpy, CurrentTime); 927 | 928 | if (visible) 929 | show_mouse(); 930 | else 931 | hide_mouse(); 932 | } 933 | 934 | --------------------------------------------------------------------------------