x11window.cpp

main 1170 lines · 37.4 KB Raw
Alex Alex Commit Client Upload Part 2 19/10/2026 00:06
1/*
2 * Copyright (c) 2010-2016 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#if !defined(WIN32) && !defined(__EMSCRIPTEN__)
24
25#include <framework/global.h>
26#include "x11window.h"
27#include <framework/core/resourcemanager.h>
28#include <framework/core/eventdispatcher.h>
29#include <framework/graphics/image.h>
30#include <framework/util/stats.h>
31#include <unistd.h>
32
33#define LSB_BIT_SET(p, n) (p[(n)/8] |= (1 <<((n)%8)))
34
35X11Window::X11Window()
36{
37 m_display = 0;
38 m_visual = 0;
39 m_window = 0;
40 m_rootWindow = 0;
41 m_colormap = 0;
42 m_cursor = 0;
43 m_hiddenCursor = 0;
44 m_xim = 0;
45 m_xic = 0;
46 m_screen = 0;
47 m_wmDelete = 0;
48 m_minimumSize = Size(600,480);
49 m_size = Size(600,480);
50
51#ifdef OPENGL_ES
52 m_eglConfig = 0;
53 m_eglContext = 0;
54 m_eglDisplay = 0;
55 m_eglSurface = 0;
56#else
57 m_fbConfig = 0;
58 m_glxContext = 0;
59#endif
60
61 m_keyMap[XK_Escape] = Fw::KeyEscape;
62 m_keyMap[XK_Tab] = Fw::KeyTab;
63 m_keyMap[XK_Return] = Fw::KeyEnter;
64 m_keyMap[XK_BackSpace] = Fw::KeyBackspace;
65
66 m_keyMap[XK_Page_Up] = Fw::KeyPageUp;
67 m_keyMap[XK_Page_Down] = Fw::KeyPageDown;
68 m_keyMap[XK_Home] = Fw::KeyHome;
69 m_keyMap[XK_End] = Fw::KeyEnd;
70 m_keyMap[XK_Insert] = Fw::KeyInsert;
71 m_keyMap[XK_Delete] = Fw::KeyDelete;
72
73 m_keyMap[XK_Up] = Fw::KeyUp;
74 m_keyMap[XK_Down] = Fw::KeyDown;
75 m_keyMap[XK_Left] = Fw::KeyLeft;
76 m_keyMap[XK_Right] = Fw::KeyRight;
77
78 m_keyMap[XK_Num_Lock] = Fw::KeyNumLock;
79 m_keyMap[XK_Scroll_Lock] = Fw::KeyScrollLock;
80 m_keyMap[XK_Caps_Lock] = Fw::KeyCapsLock;
81 m_keyMap[XK_Print] = Fw::KeyPrintScreen;
82 m_keyMap[XK_Pause] = Fw::KeyPause;
83
84 m_keyMap[XK_Control_L] = Fw::KeyCtrl;
85 m_keyMap[XK_Control_R] = Fw::KeyCtrl;
86 m_keyMap[XK_Shift_R] = Fw::KeyShift;
87 m_keyMap[XK_Shift_L] = Fw::KeyShift;
88 m_keyMap[XK_Alt_R] = Fw::KeyAlt;
89 m_keyMap[XK_Alt_L] = Fw::KeyAlt;
90 m_keyMap[XK_Meta_L] = Fw::KeyMeta;
91 m_keyMap[XK_Meta_R] = Fw::KeyMeta;
92 m_keyMap[XK_Menu] = Fw::KeyMenu;
93
94 // ascii characters
95 m_keyMap[XK_space] = Fw::KeySpace;
96 m_keyMap[XK_exclam] = Fw::KeyExclamation;
97 m_keyMap[XK_quotedbl] = Fw::KeyQuote;
98 m_keyMap[XK_numbersign] = Fw::KeyNumberSign;
99 m_keyMap[XK_dollar] = Fw::KeyDollar;
100 m_keyMap[XK_percent] = Fw::KeyPercent;
101 m_keyMap[XK_ampersand] = Fw::KeyAmpersand;
102 m_keyMap[XK_apostrophe] = Fw::KeyApostrophe;
103 m_keyMap[XK_parenleft] = Fw::KeyLeftParen;
104 m_keyMap[XK_parenright] = Fw::KeyRightParen;
105 m_keyMap[XK_asterisk] = Fw::KeyAsterisk;
106 m_keyMap[XK_plus] = Fw::KeyPlus;
107 m_keyMap[XK_comma] = Fw::KeyComma;
108 m_keyMap[XK_minus] = Fw::KeyMinus;
109 m_keyMap[XK_period] = Fw::KeyPeriod;
110 m_keyMap[XK_slash] = Fw::KeySlash;
111 m_keyMap[XK_1] = Fw::Key1;
112 m_keyMap[XK_2] = Fw::Key2;
113 m_keyMap[XK_3] = Fw::Key3;
114 m_keyMap[XK_4] = Fw::Key4;
115 m_keyMap[XK_5] = Fw::Key5;
116 m_keyMap[XK_6] = Fw::Key6;
117 m_keyMap[XK_7] = Fw::Key7;
118 m_keyMap[XK_8] = Fw::Key8;
119 m_keyMap[XK_9] = Fw::Key9;
120 m_keyMap[XK_0] = Fw::Key0;
121 m_keyMap[XK_colon] = Fw::KeyColon;
122 m_keyMap[XK_semicolon] = Fw::KeySemicolon;
123 m_keyMap[XK_less] = Fw::KeyLess;
124 m_keyMap[XK_equal] = Fw::KeyEqual;
125 m_keyMap[XK_greater] = Fw::KeyGreater;
126 m_keyMap[XK_question] = Fw::KeyQuestion;
127 m_keyMap[XK_at] = Fw::KeyAtSign;
128 m_keyMap[XK_a] = Fw::KeyA;
129 m_keyMap[XK_b] = Fw::KeyB;
130 m_keyMap[XK_c] = Fw::KeyC;
131 m_keyMap[XK_d] = Fw::KeyD;
132 m_keyMap[XK_e] = Fw::KeyE;
133 m_keyMap[XK_f] = Fw::KeyF;
134 m_keyMap[XK_g] = Fw::KeyG;
135 m_keyMap[XK_h] = Fw::KeyH;
136 m_keyMap[XK_i] = Fw::KeyI;
137 m_keyMap[XK_j] = Fw::KeyJ;
138 m_keyMap[XK_k] = Fw::KeyK;
139 m_keyMap[XK_l] = Fw::KeyL;
140 m_keyMap[XK_m] = Fw::KeyM;
141 m_keyMap[XK_n] = Fw::KeyN;
142 m_keyMap[XK_o] = Fw::KeyO;
143 m_keyMap[XK_p] = Fw::KeyP;
144 m_keyMap[XK_q] = Fw::KeyQ;
145 m_keyMap[XK_r] = Fw::KeyR;
146 m_keyMap[XK_s] = Fw::KeyS;
147 m_keyMap[XK_t] = Fw::KeyT;
148 m_keyMap[XK_u] = Fw::KeyU;
149 m_keyMap[XK_v] = Fw::KeyV;
150 m_keyMap[XK_w] = Fw::KeyW;
151 m_keyMap[XK_x] = Fw::KeyX;
152 m_keyMap[XK_y] = Fw::KeyY;
153 m_keyMap[XK_z] = Fw::KeyZ;
154 m_keyMap[XK_bracketleft] = Fw::KeyLeftBracket;
155 m_keyMap[XK_backslash] = Fw::KeyBackslash;
156 m_keyMap[XK_bracketright] = Fw::KeyRightBracket;
157 m_keyMap[XK_asciicircum] = Fw::KeyCaret;
158 m_keyMap[XK_underscore] = Fw::KeyUnderscore;
159 m_keyMap[XK_grave] = Fw::KeyGrave;
160 m_keyMap[XK_braceleft] = Fw::KeyLeftCurly;
161 m_keyMap[XK_bar] = Fw::KeyBar;
162 m_keyMap[XK_braceright] = Fw::KeyRightCurly;
163 m_keyMap[XK_asciitilde] = Fw::KeyTilde;
164
165 // keypad
166 m_keyMap[XK_KP_Add] = Fw::KeyPlus;
167 m_keyMap[XK_KP_Subtract] = Fw::KeyMinus;
168 m_keyMap[XK_KP_Decimal] = Fw::KeyPeriod;
169 m_keyMap[XK_KP_Divide] = Fw::KeySlash;
170 m_keyMap[XK_KP_Multiply] = Fw::KeyAsterisk;
171 m_keyMap[XK_KP_Enter] = Fw::KeyEnter;
172
173 // keypad with numlock off
174 m_keyMap[XK_KP_Insert] = Fw::KeyNumpad0;
175 m_keyMap[XK_KP_End] = Fw::KeyNumpad1;
176 m_keyMap[XK_KP_Down] = Fw::KeyNumpad2;
177 m_keyMap[XK_KP_Page_Down] = Fw::KeyNumpad3;
178 m_keyMap[XK_KP_Left] = Fw::KeyNumpad4;
179 m_keyMap[XK_KP_Begin] = Fw::KeyNumpad5;
180 m_keyMap[XK_KP_Right] = Fw::KeyNumpad6;
181 m_keyMap[XK_KP_Home] = Fw::KeyNumpad7;
182 m_keyMap[XK_KP_Up] = Fw::KeyNumpad8;
183 m_keyMap[XK_KP_Page_Up] = Fw::KeyNumpad9;
184 m_keyMap[XK_KP_Delete] = Fw::KeyDelete;
185
186 // keypad with numlock on
187 m_keyMap[XK_KP_0] = Fw::Key0;
188 m_keyMap[XK_KP_1] = Fw::Key1;
189 m_keyMap[XK_KP_2] = Fw::Key2;
190 m_keyMap[XK_KP_3] = Fw::Key3;
191 m_keyMap[XK_KP_4] = Fw::Key4;
192 m_keyMap[XK_KP_5] = Fw::Key5;
193 m_keyMap[XK_KP_6] = Fw::Key6;
194 m_keyMap[XK_KP_7] = Fw::Key7;
195 m_keyMap[XK_KP_8] = Fw::Key8;
196 m_keyMap[XK_KP_9] = Fw::Key9;
197
198 m_keyMap[XK_F1] = Fw::KeyF1;
199 m_keyMap[XK_F2] = Fw::KeyF2;
200 m_keyMap[XK_F3] = Fw::KeyF3;
201 m_keyMap[XK_F4] = Fw::KeyF4;
202 m_keyMap[XK_F5] = Fw::KeyF5;
203 m_keyMap[XK_F6] = Fw::KeyF6;
204 m_keyMap[XK_F7] = Fw::KeyF7;
205 m_keyMap[XK_F8] = Fw::KeyF8;
206 m_keyMap[XK_F9] = Fw::KeyF9;
207 m_keyMap[XK_F10] = Fw::KeyF10;
208 m_keyMap[XK_F11] = Fw::KeyF11;
209 m_keyMap[XK_F12] = Fw::KeyF12;
210}
211
212void X11Window::init()
213{
214 internalOpenDisplay();
215 internalCheckGL();
216 internalChooseGLVisual();
217 internalCreateGLContext();
218 internalCreateWindow();
219}
220
221void X11Window::terminate()
222{
223 if(m_cursor != None) {
224 XUndefineCursor(m_display, m_window);
225 m_cursor = None;
226 }
227
228 if(m_hiddenCursor) {
229 XFreeCursor(m_display, m_hiddenCursor);
230 m_hiddenCursor = 0;
231 }
232
233 for(Cursor cursor : m_cursors)
234 XFreeCursor(m_display, cursor);
235 m_cursors.clear();
236
237 if(m_window) {
238 XDestroyWindow(m_display, m_window);
239 m_window = 0;
240 }
241
242 if(m_colormap) {
243 XFreeColormap(m_display, m_colormap);
244 m_colormap = 0;
245 }
246
247 internalDestroyGLContext();
248
249 if(m_visual) {
250 XFree(m_visual);
251 m_visual = 0;
252 }
253
254 if(m_xic) {
255 XDestroyIC(m_xic);
256 m_xic = 0;
257 }
258
259 if(m_xim) {
260 XCloseIM(m_xim);
261 m_xim = 0;
262 }
263
264 if(m_display) {
265 XCloseDisplay(m_display);
266 m_display = 0;
267 }
268
269 m_visible = false;
270}
271
272void X11Window::internalOpenDisplay()
273{
274 m_display = XOpenDisplay(NULL);
275 if(!m_display)
276 g_logger.fatal("Unable to open X11 display");
277 m_screen = DefaultScreen(m_display);
278}
279
280void X11Window::internalCreateWindow()
281{
282 Visual *vis;
283 int depth;
284 unsigned int attrsMask;
285 XSetWindowAttributes attrs;
286 memset(&attrs, 0, sizeof(attrs));
287
288 attrs.event_mask = KeyPressMask | KeyReleaseMask |
289 ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
290 ExposureMask | VisibilityChangeMask |
291 StructureNotifyMask | FocusChangeMask;
292
293 m_colormap = XCreateColormap(m_display, m_rootWindow, m_visual->visual, AllocNone);
294 attrs.colormap = m_colormap;
295 attrs.border_pixel = 0;
296 attrs.override_redirect = False;
297 vis = m_visual->visual;
298 depth = m_visual->depth;
299 attrsMask = CWEventMask | CWBorderPixel | CWColormap;
300
301#ifdef OPENGL_ES
302 attrs.override_redirect = False;
303 attrsMask |= CWOverrideRedirect;
304#endif
305
306 updateUnmaximizedCoords();
307 m_window = XCreateWindow(m_display, m_rootWindow,
308 m_position.x, m_position.y, m_size.width(), m_size.height(),
309 0,
310 depth,
311 InputOutput,
312 vis,
313 attrsMask, &attrs);
314 m_visible = true;
315
316 if(!m_window)
317 g_logger.fatal("Unable to create X11 window!");
318
319 // ensure window input focus
320 XWMHints hints;
321 hints.input = True;
322 hints.flags = InputHint;
323 XSetWMHints(m_display, m_window, &hints);
324
325 // ensure window position
326 XMoveWindow(m_display, m_window, m_position.x, m_position.y);
327
328 // handle wm_delete events
329 m_wmDelete = XInternAtom(m_display, "WM_DELETE_WINDOW", True);
330 XSetWMProtocols(m_display, m_window, &m_wmDelete , 1);
331
332 if(!internalSetupWindowInput())
333 g_logger.warning("Input of special keys may be messed up, because window input initialization failed");
334
335 internalConnectGLContext();
336}
337
338bool X11Window::internalSetupWindowInput()
339{
340 // create input context (to have better key input handling)
341 if(!XSupportsLocale()) {
342 g_logger.error("X11 doesn't support the current locale");
343 return false;
344 }
345
346 XSetLocaleModifiers("");
347 m_xim = XOpenIM(m_display, NULL, NULL, NULL);
348 if(!m_xim) {
349 g_logger.error("XOpenIM failed");
350 return false;
351 }
352
353 m_xic = XCreateIC(m_xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, m_window, NULL);
354 if(!m_xic) {
355 g_logger.error("Unable to create the input context");
356 return false;
357 }
358
359 return true;
360}
361
362void X11Window::internalCheckGL()
363{
364#ifdef OPENGL_ES
365 m_eglDisplay = eglGetDisplay((EGLNativeDisplayType)m_display);
366 if(m_eglDisplay == EGL_NO_DISPLAY)
367 g_logger.fatal("EGL not supported");
368
369 if(!eglInitialize(m_eglDisplay, NULL, NULL))
370 g_logger.fatal("Unable to initialize EGL");
371#else
372 if(!glXQueryExtension(m_display, NULL, NULL))
373 g_logger.fatal("GLX not supported");
374#endif
375}
376
377void X11Window::internalChooseGLVisual()
378{
379#ifdef OPENGL_ES
380 static int attrList[] = {
381#if OPENGL_ES==2
382 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
383#else
384 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
385#endif
386 EGL_RED_SIZE, 4,
387 EGL_GREEN_SIZE, 4,
388 EGL_BLUE_SIZE, 4,
389 EGL_ALPHA_SIZE, 4,
390 EGL_NONE
391 };
392
393 EGLint numConfig;
394 XVisualInfo visTemplate;
395 int numVisuals;
396
397 if(!eglChooseConfig(m_eglDisplay, attrList, &m_eglConfig, 1, &numConfig))
398 g_logger.fatal("Failed to choose EGL config");
399
400 if(numConfig != 1)
401 g_logger.warning("Didn't got the exact EGL config");
402
403 EGLint vid;
404 if(!eglGetConfigAttrib(m_eglDisplay, m_eglConfig, EGL_NATIVE_VISUAL_ID, &vid))
405 g_logger.fatal("Unable to get visual EGL visual id");
406
407 memset(&visTemplate, 0, sizeof(visTemplate));
408 visTemplate.visualid = vid;
409 m_visual = XGetVisualInfo(m_display, VisualIDMask, &visTemplate, &numVisuals);
410 if(!m_visual)
411 g_logger.fatal("Couldn't choose RGBA, double buffered visual");
412
413 m_rootWindow = DefaultRootWindow(m_display);
414#else
415 static int attrList[] = {
416 GLX_RENDER_TYPE, GLX_RGBA_BIT,
417 GLX_DOUBLEBUFFER, True,
418 GLX_RED_SIZE, 8,
419 GLX_GREEN_SIZE, 8,
420 GLX_BLUE_SIZE, 8,
421 GLX_ALPHA_SIZE, 8,
422 None
423 };
424
425 int nelements;
426 m_fbConfig = glXChooseFBConfig(m_display, m_screen, attrList, &nelements);
427 if(!m_fbConfig)
428 g_logger.fatal("Couldn't choose RGBA, double buffered fbconfig");
429
430 m_visual = glXGetVisualFromFBConfig(m_display, *m_fbConfig);
431 if(!m_visual)
432 g_logger.fatal("Couldn't choose RGBA, double buffered visual");
433
434 m_rootWindow = RootWindow(m_display, m_visual->screen);
435#endif
436}
437
438void X11Window::internalCreateGLContext()
439{
440#ifdef OPENGL_ES
441 EGLint attrList[] = {
442#if OPENGL_ES==2
443 EGL_CONTEXT_CLIENT_VERSION, 2,
444#else
445 EGL_CONTEXT_CLIENT_VERSION, 1,
446#endif
447 EGL_NONE
448 };
449
450 m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, attrList);
451 if(m_eglContext == EGL_NO_CONTEXT )
452 g_logger.fatal(stdext::format("Unable to create EGL context: %s", eglGetError()));
453#else
454 m_glxContext = glXCreateContext(m_display, m_visual, NULL, True);
455
456 if(!m_glxContext)
457 g_logger.fatal("Unable to create GLX context");
458
459 if(!glXIsDirect(m_display, m_glxContext))
460 g_logger.warning("GL direct rendering is not possible");
461#endif
462}
463
464void X11Window::internalDestroyGLContext()
465{
466#ifdef OPENGL_ES
467 if(m_eglDisplay) {
468 if(m_eglContext) {
469 eglDestroyContext(m_eglDisplay, m_eglContext);
470 m_eglContext = 0;
471 }
472 if(m_eglSurface) {
473 eglDestroySurface(m_eglDisplay, m_eglSurface);
474 m_eglSurface = 0;
475 }
476 eglTerminate(m_eglDisplay);
477 m_eglDisplay = 0;
478 }
479#else
480 if(m_glxContext) {
481 glXMakeCurrent(m_display, None, NULL);
482 glXDestroyContext(m_display, m_glxContext);
483 m_glxContext = 0;
484 }
485#endif
486}
487
488void X11Window::internalConnectGLContext()
489{
490#ifdef OPENGL_ES
491 m_eglSurface = eglCreateWindowSurface(m_eglDisplay, m_eglConfig, m_window, NULL);
492 if(m_eglSurface == EGL_NO_SURFACE)
493 g_logger.fatal(stdext::format("Unable to create EGL surface: %s", eglGetError()));
494 if(!eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext))
495 g_logger.fatal("Unable to connect EGL context into X11 window");
496#else
497 if(!glXMakeCurrent(m_display, m_window, m_glxContext))
498 g_logger.fatal("Unable to set GLX context on X11 window");
499#endif
500}
501
502void *X11Window::getExtensionProcAddress(const char *ext)
503{
504#ifdef OPENGL_ES
505 //TODO
506 return NULL;
507#else
508 return (void *)glXGetProcAddressARB((const GLubyte*)ext);
509#endif
510}
511
512bool X11Window::isExtensionSupported(const char *ext)
513{
514#ifdef OPENGL_ES
515 //TODO
516 return false;
517#else
518 const char *exts = glXQueryExtensionsString(m_display, m_screen);
519 if(strstr(exts, ext))
520 return true;
521#endif
522 return false;
523}
524
525void X11Window::move(const Point& pos)
526{
527 if (std::this_thread::get_id() != g_mainThreadId) {
528 g_graphicsDispatcher.addEvent(std::bind(&X11Window::move, this, pos));
529 return;
530 }
531
532 m_position = pos;
533 if(m_visible) {
534 XMoveWindow(m_display, m_window, m_position.x, m_position.y);
535 XFlush(m_display);
536 }
537}
538
539void X11Window::resize(const Size& size)
540{
541 if (std::this_thread::get_id() != g_mainThreadId) {
542 g_graphicsDispatcher.addEvent(std::bind(&X11Window::resize, this, size));
543 return;
544 }
545
546 if(size.width() < m_minimumSize.width() || size.height() < m_minimumSize.height())
547 return;
548 XResizeWindow(m_display, m_window, size.width(), size.height());
549 XFlush(m_display);
550}
551
552void X11Window::show()
553{
554 if (std::this_thread::get_id() != g_mainThreadId) {
555 g_graphicsDispatcher.addEvent(std::bind(&X11Window::show, this));
556 return;
557 }
558
559 m_visible = true;
560 XMapWindow(m_display, m_window);
561 XMoveWindow(m_display, m_window, m_position.x, m_position.y);
562 XFlush(m_display);
563 if(m_maximized)
564 maximize();
565 if(m_fullscreen)
566 setFullscreen(true);
567}
568
569void X11Window::hide()
570{
571 if (std::this_thread::get_id() != g_mainThreadId) {
572 g_graphicsDispatcher.addEvent(std::bind(&X11Window::hide, this));
573 return;
574 }
575
576 m_visible = false;
577 XUnmapWindow(m_display, m_window);
578 XFlush(m_display);
579}
580
581void X11Window::minimize() {}
582
583void X11Window::maximize()
584{
585 if(m_visible) {
586 Atom wmState = XInternAtom(m_display, "_NET_WM_STATE", False);
587 Atom wmStateMaximizedVert = XInternAtom(m_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
588 Atom wmStateMaximizedHorz = XInternAtom(m_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
589
590 XEvent e = {0};
591 e.xany.type = ClientMessage;
592 e.xclient.send_event = True;
593 e.xclient.message_type = wmState;
594 e.xclient.format = 32;
595 e.xclient.window = m_window;
596 e.xclient.data.l[0] = 1;
597 e.xclient.data.l[1] = wmStateMaximizedVert;
598 e.xclient.data.l[2] = wmStateMaximizedHorz;
599 e.xclient.data.l[3] = 0;
600
601 XSendEvent(m_display, m_rootWindow, 0, SubstructureNotifyMask | SubstructureRedirectMask, &e);
602 XFlush(m_display);
603 }
604 m_maximized = true;
605}
606
607void X11Window::poll()
608{
609 AutoStat s(STATS_RENDER, "PollWindow");
610 bool needsResizeUpdate = false;
611
612 XEvent event, peekEvent;
613 while(XPending(m_display) > 0) {
614 XNextEvent(m_display, &event);
615
616 // check for repeated key releases
617 bool repatedKeyRelease = false;
618 if(event.type == KeyRelease && XPending(m_display)) {
619 XPeekEvent(m_display, &peekEvent);
620 if((peekEvent.type == KeyPress) && (peekEvent.xkey.keycode == event.xkey.keycode) && ((peekEvent.xkey.time-event.xkey.time) < 2))
621 repatedKeyRelease = true;
622 }
623
624 // process keydown and keyrelease events first
625 if(event.type == KeyPress || (event.type == KeyRelease && !repatedKeyRelease)) {
626 // remove caps lock and shift maks
627 XKeyEvent xkey = event.xkey;
628 xkey.state &= ~(ShiftMask | LockMask);
629
630 // lookup keysym and translate it
631 KeySym keysym;
632 char buf[32];
633 XLookupString(&xkey, buf, sizeof(buf), &keysym, 0);
634 Fw::Key keyCode = Fw::KeyUnknown;
635
636 if(m_keyMap.find(keysym) != m_keyMap.end())
637 keyCode = m_keyMap[keysym];
638
639 if(event.type == KeyPress)
640 processKeyDown(keyCode);
641 else if(event.type == KeyRelease)
642 processKeyUp(keyCode);
643 }
644
645 // call filter because xim will discard KeyPress events when keys still composing
646 if(XFilterEvent(&event, m_window))
647 continue;
648
649 // discard repated key releases
650 if(repatedKeyRelease)
651 continue;
652
653 switch(event.type) {
654 case ClientMessage: {
655 // close event
656 if((Atom)event.xclient.data.l[0] == m_wmDelete && m_onClose) {
657 g_dispatcher.addEvent([this] {
658 m_onClose();
659 });
660 }
661 break;
662 }
663 case ConfigureNotify: {
664 Size newSize(event.xconfigure.width, event.xconfigure.height);
665 Point newPos(event.xconfigure.x, event.xconfigure.y);
666
667 // updates window size
668 if(m_size != newSize) {
669 m_size = newSize;
670 needsResizeUpdate = true;
671 }
672
673 // checks if the window is maximized
674 if(m_visible) {
675 m_maximized = false;
676 Atom wmState = XInternAtom(m_display, "_NET_WM_STATE", False);
677 Atom wmStateMaximizedVert = XInternAtom(m_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
678 Atom wmStateMaximizedHorz = XInternAtom(m_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
679 Atom actualType;
680 ulong i, numItems, bytesAfter;
681 uchar *propertyValue = NULL;
682 int actualFormat;
683
684 if(XGetWindowProperty(m_display, m_window, wmState,
685 0, 1024, False, XA_ATOM, &actualType,
686 &actualFormat, &numItems, &bytesAfter,
687 &propertyValue) == Success) {
688 Atom *atoms = (Atom*)propertyValue;
689 int maximizedMask = 0;
690
691 for(i=0; i<numItems; ++i) {
692 if(atoms[i] == wmStateMaximizedVert)
693 maximizedMask |= 1;
694 else if(atoms[i] == wmStateMaximizedHorz)
695 maximizedMask |= 2;
696 }
697
698 if(maximizedMask == 3)
699 m_maximized = true;
700
701 XFree(propertyValue);
702 }
703 }
704
705 // updates window pos
706 if(m_visible)
707 m_position = newPos;
708 updateUnmaximizedCoords();
709 break;
710 }
711 case SelectionRequest: {
712 XEvent respond;
713 XSelectionRequestEvent *req = &(event.xselectionrequest);
714
715 Atom targets = XInternAtom(m_display, "TARGETS", False);
716 if(req->target == targets) {
717 Atom typeList[] = { XInternAtom(m_display, "UTF8_STRING", False),
718 XInternAtom(m_display, "TEXT", False),
719 XInternAtom(m_display, "STRING", False),
720 XInternAtom(m_display, "text/plain;charset=UTF-8", False),
721 XInternAtom(m_display, "text/plain", False),
722 XInternAtom(m_display, "COMPOUND_TEXT", False),
723 XA_STRING };
724
725 XChangeProperty(m_display, req->requestor,
726 req->property, req->target,
727 8, PropModeReplace,
728 (uchar *)&typeList,
729 sizeof(typeList));
730 respond.xselection.property = req->property;
731 } else {
732 std::string clipboardText = stdext::latin1_to_utf8(m_clipboardText);
733 XChangeProperty(m_display,
734 req->requestor,
735 req->property, req->target,
736 8,
737 PropModeReplace,
738 (uchar *)clipboardText.c_str(),
739 clipboardText.length());
740 respond.xselection.property = req->property;
741 }
742
743 respond.xselection.type = SelectionNotify;
744 respond.xselection.display = req->display;
745 respond.xselection.requestor = req->requestor;
746 respond.xselection.selection = req->selection;
747 respond.xselection.target = req->target;
748 respond.xselection.time = req->time;
749 XSendEvent(m_display, req->requestor, 0, 0, &respond);
750 XFlush(m_display);
751 break;
752 }
753 // process text events
754 case KeyPress: {
755 // text cant be insert while holding ctrl or alt
756 if(event.xkey.state & ControlMask || event.xkey.state & Mod1Mask)
757 break;
758
759 // process key text events
760 KeySym keysym;
761 char buf[32];
762 memset(buf, 0, 32);
763 int len;
764
765 // lookup for keyText
766 if(m_xic) { // with xim we can get latin1 input correctly
767 Status status;
768 len = XmbLookupString(m_xic, &event.xkey, buf, sizeof(buf), &keysym, &status);
769 } else { // otherwise use XLookupString, but often it doesn't work right with dead keys
770 static XComposeStatus compose = {NULL, 0};
771 len = XLookupString(&event.xkey, buf, sizeof(buf), &keysym, &compose);
772 }
773
774 // filter unwanted characters
775 if(len == 0 || (uchar)(buf[0]) < 32 || keysym == XK_BackSpace || keysym == XK_Return || keysym == XK_Delete || keysym == XK_Escape)
776 break;
777 std::string text = buf;
778
779 //g_logger.debug("char: ", buf[0], " code: ", (int)((uchar)buf[0]));
780 g_dispatcher.addEventEx("KeyPress", [&, text] {
781 if(m_onInputEvent && text.length() > 0) {
782 m_inputEvent.reset(Fw::KeyTextInputEvent);
783 m_inputEvent.keyText = text;
784 m_onInputEvent(m_inputEvent);
785 }
786 });
787 break;
788 }
789 case ButtonPress:
790 case ButtonRelease: {
791 g_dispatcher.addEventEx("ButtonRelease", [this, event] {
792 m_inputEvent.reset();
793 m_inputEvent.type = (event.type == ButtonPress) ? Fw::MousePressInputEvent : Fw::MouseReleaseInputEvent;
794 switch(event.xbutton.button) {
795 case Button1:
796 m_inputEvent.mouseButton = Fw::MouseLeftButton;
797 m_mouseButtonStates[Fw::MouseLeftButton] = (event.type == ButtonPress);
798 break;
799 case Button3:
800 m_inputEvent.mouseButton = Fw::MouseRightButton;
801 m_mouseButtonStates[Fw::MouseRightButton] = (event.type == ButtonPress);
802 break;
803 case Button2:
804 m_inputEvent.mouseButton = Fw::MouseMidButton;
805 m_mouseButtonStates[Fw::MouseMidButton] = (event.type == ButtonPress);
806 break;
807 case Button4:
808 if(event.type == ButtonPress) {
809 m_inputEvent.type = Fw::MouseWheelInputEvent;
810 m_inputEvent.mouseButton = Fw::MouseMidButton;
811 m_inputEvent.wheelDirection = Fw::MouseWheelUp;
812 } else
813 m_inputEvent.type = Fw::NoInputEvent;
814 break;
815 case Button5:
816 if(event.type == ButtonPress) {
817 m_inputEvent.type = Fw::MouseWheelInputEvent;
818 m_inputEvent.mouseButton = Fw::MouseMidButton;
819 m_inputEvent.wheelDirection = Fw::MouseWheelDown;
820 } else
821 m_inputEvent.type = Fw::NoInputEvent;
822 break;
823 default:
824 m_inputEvent.type = Fw::NoInputEvent;
825 break;
826 }
827 if(m_inputEvent.type != Fw::NoInputEvent && m_onInputEvent)
828 m_onInputEvent(m_inputEvent);
829 });
830 break;
831 }
832
833 case MotionNotify: {
834 g_dispatcher.addEventEx("MotionNotify", [this, event] {
835 m_inputEvent.reset();
836 m_inputEvent.type = Fw::MouseMoveInputEvent;
837 Point newMousePos(event.xbutton.x, event.xbutton.y);
838 m_inputEvent.mouseMoved = newMousePos - m_inputEvent.mousePos;
839 m_inputEvent.mousePos = newMousePos;
840 if(m_onInputEvent)
841 m_onInputEvent(m_inputEvent);
842 });
843 break;
844 }
845 case MapNotify:
846 m_visible = true;
847 needsResizeUpdate = true;
848 break;
849 case UnmapNotify:
850 m_visible = false;
851 releaseAllKeys();
852 break;
853 case FocusIn:
854 m_focused = true;
855 releaseAllKeys();
856 break;
857 case FocusOut:
858 m_focused = false;
859 releaseAllKeys();
860 break;
861 case Expose:
862 // window needs redraw
863 break;
864 }
865 }
866
867 if(needsResizeUpdate && m_onResize)
868 m_onResize(m_size);
869
870 fireKeysPress();
871}
872
873void X11Window::swapBuffers()
874{
875#ifdef OPENGL_ES
876 eglSwapBuffers(m_eglDisplay, m_eglSurface);
877#else
878 glXSwapBuffers(m_display, m_window);
879#endif
880}
881
882void X11Window::showMouse()
883{
884 restoreMouseCursor();
885}
886
887void X11Window::hideMouse()
888{
889 if (std::this_thread::get_id() != g_mainThreadId) {
890 g_graphicsDispatcher.addEvent(std::bind(&X11Window::hideMouse, this));
891 return;
892 }
893
894 if(m_cursor != None)
895 restoreMouseCursor();
896
897 if(m_hiddenCursor == None) {
898 char bm[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
899 Pixmap pix = XCreateBitmapFromData(m_display, m_window, bm, 8, 8);
900 XColor black;
901 memset(&black, 0, sizeof(black));
902 black.flags = DoRed | DoGreen | DoBlue;
903 m_hiddenCursor = XCreatePixmapCursor(m_display, pix, pix, &black, &black, 0, 0);
904 XFreePixmap(m_display, pix);
905 }
906
907 m_cursor = m_hiddenCursor;
908 XDefineCursor(m_display, m_window, m_cursor);
909}
910
911void X11Window::setMouseCursor(int cursorId)
912{
913 if (std::this_thread::get_id() != g_mainThreadId) {
914 g_graphicsDispatcher.addEvent(std::bind(&X11Window::setMouseCursor, this, cursorId));
915 return;
916 }
917
918 if(cursorId >= (int)m_cursors.size() || cursorId < 0)
919 return;
920
921 if(m_cursor != None)
922 restoreMouseCursor();
923
924 m_cursor = m_cursors[cursorId];
925 XDefineCursor(m_display, m_window, m_cursor);
926}
927
928void X11Window::restoreMouseCursor()
929{
930 if (std::this_thread::get_id() != g_mainThreadId) {
931 g_graphicsDispatcher.addEvent(std::bind(&X11Window::restoreMouseCursor, this));
932 return;
933 }
934
935 XUndefineCursor(m_display, m_window);
936 m_cursor = None;
937}
938
939int X11Window::internalLoadMouseCursor(const ImagePtr& image, const Point& hotSpot)
940{
941 int width = image->getWidth();
942 int height = image->getHeight();
943 int numbits = width * height;
944 int numbytes = (width * height)/8;
945
946 XColor bg, fg;
947 bg.red = 255 << 8;
948 bg.green = 255 << 8;
949 bg.blue = 255 << 8;
950 fg.red = 0;
951 fg.green = 0;
952 fg.blue = 0;
953
954 std::vector<uchar> mapBits(numbytes, 0);
955 std::vector<uchar> maskBits(numbytes, 0);
956
957 for(int i=0;i<numbits;++i) {
958 uint32 rgba = stdext::readULE32(image->getPixelData() + i*4);
959 if(rgba == 0xffffffff) { //white, background
960 LSB_BIT_SET(maskBits, i);
961 } else if(rgba == 0xff000000) { //black, foreground
962 LSB_BIT_SET(mapBits, i);
963 LSB_BIT_SET(maskBits, i);
964 } //otherwise 0x00000000 => alpha
965 }
966
967 Pixmap cp = XCreateBitmapFromData(m_display, m_window, (char*)&mapBits[0], width, height);
968 Pixmap mp = XCreateBitmapFromData(m_display, m_window, (char*)&maskBits[0], width, height);
969 Cursor cursor = XCreatePixmapCursor(m_display, cp, mp, &fg, &bg, hotSpot.x, hotSpot.y);
970 XFreePixmap(m_display, cp);
971 XFreePixmap(m_display, mp);
972
973 m_cursors.push_back(cursor);
974 return m_cursors.size()-1;
975}
976
977void X11Window::setTitle(const std::string& title)
978{
979 if (std::this_thread::get_id() != g_mainThreadId) {
980 g_graphicsDispatcher.addEvent(std::bind(&X11Window::setTitle, this, title));
981 return;
982 }
983
984 XStoreName(m_display, m_window, title.c_str());
985 XSetIconName(m_display, m_window, title.c_str());
986}
987
988void X11Window::setMinimumSize(const Size& minimumSize)
989{
990 if (std::this_thread::get_id() != g_mainThreadId) {
991 g_graphicsDispatcher.addEvent(std::bind(&X11Window::setMinimumSize, this, minimumSize));
992 return;
993 }
994
995 XSizeHints sizeHints;
996 memset(&sizeHints, 0, sizeof(sizeHints));
997 sizeHints.flags = PMinSize;
998 sizeHints.min_width = minimumSize.width();
999 sizeHints.min_height= minimumSize.height();
1000 XSetWMSizeHints(m_display, m_window, &sizeHints, XA_WM_NORMAL_HINTS);
1001}
1002
1003void X11Window::setFullscreen(bool fullscreen)
1004{
1005 if (std::this_thread::get_id() != g_mainThreadId) {
1006 g_graphicsDispatcher.addEvent(std::bind(&X11Window::setFullscreen, this, fullscreen));
1007 return;
1008 }
1009
1010 if(m_visible) {
1011 Atom wmState = XInternAtom(m_display, "_NET_WM_STATE", False);
1012 Atom wmStateFullscreen = XInternAtom(m_display, "_NET_WM_STATE_FULLSCREEN", False);
1013
1014 XEvent xev;
1015 xev.xclient.type = ClientMessage;
1016 xev.xclient.serial = 0;
1017 xev.xclient.send_event = True;
1018 xev.xclient.window = m_window;
1019 xev.xclient.message_type = wmState;
1020 xev.xclient.format = 32;
1021 xev.xclient.data.l[0] = fullscreen ? 1 : 0;
1022 xev.xclient.data.l[1] = wmStateFullscreen;
1023 xev.xclient.data.l[2] = 0;
1024
1025 XSendEvent(m_display, m_rootWindow, False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
1026 XFlush(m_display);
1027 }
1028 m_fullscreen = fullscreen;
1029}
1030
1031void X11Window::setVerticalSync(bool enable)
1032{
1033 if (std::this_thread::get_id() != g_mainThreadId) {
1034 g_graphicsDispatcher.addEvent(std::bind(&X11Window::setVerticalSync, this, enable));
1035 return;
1036 }
1037#ifdef OPENGL_ES
1038 //TODO
1039#else
1040 typedef GLint (*glSwapIntervalProc)(GLint);
1041 glSwapIntervalProc glSwapInterval = NULL;
1042
1043 if(isExtensionSupported("GLX_MESA_swap_control"))
1044 glSwapInterval = (glSwapIntervalProc)getExtensionProcAddress("glXSwapIntervalMESA");
1045 else if(isExtensionSupported("GLX_SGI_swap_control"))
1046 glSwapInterval = (glSwapIntervalProc)getExtensionProcAddress("glXSwapIntervalSGI");
1047
1048 if(glSwapInterval)
1049 glSwapInterval(enable ? 1 : 0);
1050#endif
1051}
1052
1053void X11Window::setIcon(const std::string& file)
1054{
1055 if (std::this_thread::get_id() != g_mainThreadId) {
1056 g_graphicsDispatcher.addEvent(std::bind(&X11Window::setIcon, this, file));
1057 return;
1058 }
1059
1060 ImagePtr image = Image::load(file);
1061
1062 if(!image) {
1063 g_logger.traceError(stdext::format("unable to load icon file %s", file));
1064 return;
1065 }
1066
1067 if(image->getBpp() != 4) {
1068 g_logger.error("the app icon must have 4 channels");
1069 return;
1070 }
1071
1072 int n = image->getWidth() * image->getHeight();
1073 std::vector<unsigned long int> iconData(n + 2);
1074 iconData[0] = image->getWidth();
1075 iconData[1] = image->getHeight();
1076 for(int i=0; i < n;++i) {
1077 uint8 *pixel = (uint8*)&iconData[2 + i];
1078 pixel[2] = *(image->getPixelData() + (i * 4) + 0);
1079 pixel[1] = *(image->getPixelData() + (i * 4) + 1);
1080 pixel[0] = *(image->getPixelData() + (i * 4) + 2);
1081 pixel[3] = *(image->getPixelData() + (i * 4) + 3);
1082 }
1083
1084 Atom property = XInternAtom(m_display, "_NET_WM_ICON", 0);
1085 if(!XChangeProperty(m_display, m_window, property, XA_CARDINAL, 32, PropModeReplace, (const unsigned char*)&iconData[0], iconData.size()))
1086 g_logger.error("Couldn't set app icon");
1087}
1088
1089void X11Window::setClipboardText(const std::string& text)
1090{
1091 if (std::this_thread::get_id() != g_mainThreadId) {
1092 g_graphicsDispatcher.addEvent(std::bind(&X11Window::setClipboardText, this, text));
1093 return;
1094 }
1095
1096 m_clipboardText = text;
1097 Atom clipboard = XInternAtom(m_display, "CLIPBOARD", False);
1098 XSetSelectionOwner(m_display, clipboard, m_window, CurrentTime);
1099 XFlush(m_display);
1100}
1101
1102Size X11Window::getDisplaySize()
1103{
1104 return Size(XDisplayWidth(m_display, m_screen), XDisplayHeight(m_display, m_screen));
1105}
1106
1107std::string X11Window::getClipboardText()
1108{
1109 Atom clipboard = XInternAtom(m_display, "CLIPBOARD", False);
1110 Window ownerWindow = XGetSelectionOwner(m_display, clipboard);
1111 if(ownerWindow == m_window)
1112 return m_clipboardText;
1113
1114 std::string clipboardText;
1115 if(ownerWindow != None) {
1116 XConvertSelection(m_display, clipboard, XA_STRING, XA_PRIMARY, ownerWindow, CurrentTime);
1117 XFlush(m_display);
1118
1119 // hack to wait SelectioNotify event, otherwise we will get wrong clipboard pastes
1120 // TODO: fix this in a correct way
1121 stdext::millisleep(100);
1122
1123 // check for data
1124 Atom type;
1125 int format;
1126 ulong len, bytesLeft;
1127 char *data;
1128 XGetWindowProperty(m_display, ownerWindow,
1129 XA_PRIMARY, 0, 10000000L, 0, XA_STRING,
1130 &type,
1131 &format,
1132 &len,
1133 &bytesLeft,
1134 (uchar**)&data);
1135 if(len > 0) {
1136 if(stdext::is_valid_utf8(data))
1137 clipboardText = stdext::utf8_to_latin1(data);
1138 else
1139 clipboardText = data;
1140 }
1141 }
1142
1143 return clipboardText;
1144}
1145
1146std::string X11Window::getPlatformType()
1147{
1148#if defined(__APPLE__)
1149#ifndef OPENGL_ES
1150 return "MAC_GLX";
1151#else
1152 return "MAC-EGL";
1153#endif
1154#else
1155#ifndef OPENGL_ES
1156 return "X11-GLX";
1157#else
1158 return "X11-EGL";
1159#endif
1160#endif
1161}
1162
1163void X11Window::displayFatalError(const std::string& message)
1164{
1165
1166}
1167
1168
1169#endif
1170
Top