1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "Image/BsPixelData.h"
4#include "Image/BsPixelUtil.h"
5#include "String/BsUnicode.h"
6#include "Private/Linux/BsLinuxInput.h"
7#include "Private/Linux/BsLinuxPlatform.h"
8#include "Private/Linux/BsLinuxWindow.h"
9#include "Private/Linux/BsLinuxDropTarget.h"
10#include "RenderAPI/BsRenderWindow.h"
11#include "BsCoreApplication.h"
12#include <X11/X.h>
13#include <X11/Xatom.h>
14#include <X11/Xcursor/Xcursor.h>
15#include <X11/Xlib.h>
16#include <X11/XKBlib.h>
17#include <X11/extensions/XInput2.h>
18#include <pwd.h>
19
20namespace bs
21{
22 Event<void(const Vector2I&, const OSPointerButtonStates&)> Platform::onCursorMoved;
23 Event<void(const Vector2I&, OSMouseButton button, const OSPointerButtonStates&)> Platform::onCursorButtonPressed;
24 Event<void(const Vector2I&, OSMouseButton button, const OSPointerButtonStates&)> Platform::onCursorButtonReleased;
25 Event<void(const Vector2I&, const OSPointerButtonStates&)> Platform::onCursorDoubleClick;
26 Event<void(InputCommandType)> Platform::onInputCommand;
27 Event<void(float)> Platform::onMouseWheelScrolled;
28 Event<void(UINT32)> Platform::onCharInput;
29
30 Event<void()> Platform::onMouseCaptureChanged;
31
32 Mutex LinuxPlatform::eventLock;
33 Queue<LinuxButtonEvent> LinuxPlatform::buttonEvents;
34 LinuxMouseMotionEvent LinuxPlatform::mouseMotionEvent;
35
36 enum class X11CursorType
37 {
38 Arrow,
39 ArrowDrag,
40 ArrowLeftRight,
41 Wait,
42 IBeam,
43 SizeTopLeft,
44 SizeTopRight,
45 SizeBotLeft,
46 SizeBotRight,
47 SizeLeft,
48 SizeRight,
49 SizeTop,
50 SizeBottom,
51 Deny,
52
53 Count
54 };
55
56 struct Platform::Pimpl
57 {
58 ::Display* xDisplay = nullptr;
59 ::Window mainXWindow = 0;
60 ::Window fullscreenXWindow = 0;
61 UnorderedMap<::Window, LinuxWindow*> windowMap;
62 Mutex lock;
63
64 XIM IM;
65 XIC IC;
66 ::Time lastButtonPressTime;
67
68 Atom atomDeleteWindow;
69 Atom atomWmState;
70 Atom atomWmStateHidden;
71 Atom atomWmStateMaxVert;
72 Atom atomWmStateMaxHorz;
73
74 // X11 Event handling
75 int xInput2Opcode;
76 UnorderedMap<String, KeyCode> keyNameMap; /**< Maps X11 key name (e.g. "TAB") to system-specific X11 KeyCode. */
77 Vector<ButtonCode> keyCodeMap; /**< Maps system-specific X11 KeyCode to Banshee ButtonCode. */
78
79 // Clipboard
80 String clipboardData;
81
82 // Cursor
83 ::Cursor currentCursor = None;
84 ::Cursor emptyCursor = None;
85 bool isCursorHidden = false;
86
87 Rect2I cursorClipRect;
88 LinuxWindow* cursorClipWindow = nullptr;
89 bool cursorClipEnabled = false;
90 };
91
92 static const UINT32 DOUBLE_CLICK_MS = 500;
93
94 Vector2I _getCursorPosition(Platform::Pimpl* data)
95 {
96 Vector2I pos;
97 UINT32 screenCount = (UINT32)XScreenCount(data->xDisplay);
98
99 for (UINT32 i = 0; i < screenCount; ++i)
100 {
101 ::Window outRoot, outChild;
102 INT32 childX, childY;
103 UINT32 mask;
104 if(XQueryPointer(data->xDisplay, XRootWindow(data->xDisplay, i), &outRoot, &outChild, &pos.x,
105 &pos.y, &childX, &childY, &mask))
106 break;
107 }
108
109 return pos;
110 }
111
112 void _setCursorPosition(Platform::Pimpl* data, const Vector2I& screenPos)
113 {
114 UINT32 screenCount = (UINT32)XScreenCount(data->xDisplay);
115
116 // Note assuming screens are laid out horizontally left to right
117 INT32 screenX = 0;
118 for(UINT32 i = 0; i < screenCount; ++i)
119 {
120 ::Window root = XRootWindow(data->xDisplay, i);
121 INT32 screenXEnd = screenX + XDisplayWidth(data->xDisplay, i);
122
123 if(screenPos.x >= screenX && screenPos.x < screenXEnd)
124 {
125 XWarpPointer(data->xDisplay, None, root, 0, 0, 0, 0, screenPos.x, screenPos.y);
126 XFlush(data->xDisplay);
127 return;
128 }
129
130 screenX = screenXEnd;
131 }
132 }
133
134 void applyCurrentCursor(Platform::Pimpl* data, ::Window window)
135 {
136 if(data->isCursorHidden)
137 XDefineCursor(data->xDisplay, window, data->emptyCursor);
138 else
139 {
140 if (data->currentCursor != None)
141 XDefineCursor(data->xDisplay, window, data->currentCursor);
142 else
143 XUndefineCursor(data->xDisplay, window);
144 }
145 }
146
147 void updateClipBounds(Platform::Pimpl* data, LinuxWindow* window)
148 {
149 if(!data->cursorClipEnabled || data->cursorClipWindow != window)
150 return;
151
152 data->cursorClipRect.x = window->getLeft();
153 data->cursorClipRect.y = window->getTop();
154 data->cursorClipRect.width = window->getWidth();
155 data->cursorClipRect.height = window->getHeight();
156 }
157
158 bool clipCursor(Platform::Pimpl* data, Vector2I& pos)
159 {
160 if(!data->cursorClipEnabled)
161 return false;
162
163 INT32 clippedX = pos.x - data->cursorClipRect.x;
164 INT32 clippedY = pos.y - data->cursorClipRect.y;
165
166 if(clippedX < 0)
167 clippedX = 0;
168 else if(clippedX >= (INT32)data->cursorClipRect.width)
169 clippedX = data->cursorClipRect.width > 0 ? data->cursorClipRect.width - 1 : 0;
170
171 if(clippedY < 0)
172 clippedY = 0;
173 else if(clippedY >= (INT32)data->cursorClipRect.height)
174 clippedY = data->cursorClipRect.height > 0 ? data->cursorClipRect.height - 1 : 0;
175
176 clippedX += data->cursorClipRect.x;
177 clippedY += data->cursorClipRect.y;
178
179 if(clippedX != pos.x || clippedY != pos.y)
180 {
181 pos.x = clippedX;
182 pos.y = clippedY;
183
184 return true;
185 }
186
187 return false;
188 }
189
190 void clipCursorDisable(Platform::Pimpl* data)
191 {
192 data->cursorClipEnabled = false;
193 data->cursorClipWindow = None;
194 }
195
196 void setCurrentCursor(Platform::Pimpl* data, ::Cursor cursor)
197 {
198 if(data->currentCursor)
199 XFreeCursor(data->xDisplay, data->currentCursor);
200
201 data->currentCursor = cursor;
202 for(auto& entry : data->windowMap)
203 applyCurrentCursor(data, entry.first);
204 }
205
206 /**
207 * Searches the window hierarchy, from top to bottom, looking for the top-most window that contains the specified
208 * point. Returns 0 if one is not found.
209 */
210 ::Window getWindowUnderPoint(::Display* display, ::Window rootWindow, ::Window window, const Vector2I& screenPos)
211 {
212 ::Window outRoot, outParent;
213 ::Window* children;
214 UINT32 numChildren;
215 XQueryTree(display, window, &outRoot, &outParent, &children, &numChildren);
216
217 if(children == nullptr || numChildren == 0)
218 return window;
219
220 for(UINT32 j = 0; j < numChildren; j++)
221 {
222 ::Window curWindow = children[numChildren - j - 1];
223
224 XWindowAttributes xwa;
225 XGetWindowAttributes(display, curWindow, &xwa);
226
227 if(xwa.map_state != IsViewable || xwa.c_class != InputOutput)
228 continue;
229
230 // Get position in root window coordinates
231 ::Window outChild;
232 Vector2I pos;
233 if(!XTranslateCoordinates(display, curWindow, rootWindow, 0, 0, &pos.x, &pos.y, &outChild))
234 continue;
235
236 Rect2I area(pos.x, pos.y, (UINT32)xwa.width, (UINT32)xwa.height);
237 if(area.contains(screenPos))
238 {
239 XFree(children);
240 return getWindowUnderPoint(display, rootWindow, curWindow, screenPos);
241 }
242 }
243
244 XFree(children);
245 return 0;
246 }
247
248 int x11ErrorHandler(::Display* display, XErrorEvent* event)
249 {
250 // X11 by default crashes the app on error, even though some errors can be just fine. So we provide our own handler.
251
252 char buffer[256];
253 XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
254 LOGWRN("X11 error: " + String(buffer));
255
256 return 0;
257 }
258
259 Platform::Pimpl* Platform::mData = bs_new<Platform::Pimpl>();
260
261 Platform::~Platform()
262 { }
263
264 Vector2I Platform::getCursorPosition()
265 {
266 Lock lock(mData->lock);
267 return _getCursorPosition(mData);
268 }
269
270 void Platform::setCursorPosition(const Vector2I& screenPos)
271 {
272 Lock lock(mData->lock);
273
274 _setCursorPosition(mData, screenPos);
275 }
276
277 void Platform::captureMouse(const RenderWindow& window)
278 {
279 Lock lock(mData->lock);
280
281 LinuxWindow* linuxWindow;
282 window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
283
284 UINT32 mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask;
285 XGrabPointer(mData->xDisplay, linuxWindow->_getXWindow(), False, mask, GrabModeAsync,
286 GrabModeAsync, None, None, CurrentTime);
287 XSync(mData->xDisplay, False);
288 }
289
290 void Platform::releaseMouseCapture()
291 {
292 Lock lock(mData->lock);
293
294 XUngrabPointer(mData->xDisplay, CurrentTime);
295 XSync(mData->xDisplay, False);
296 }
297
298 bool Platform::isPointOverWindow(const RenderWindow& window, const Vector2I& screenPos)
299 {
300 Lock lock(mData->lock);
301
302 LinuxWindow* linuxWindow;
303 window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
304 ::Window xWindow = linuxWindow->_getXWindow();
305
306 UINT32 screenCount = (UINT32)XScreenCount(mData->xDisplay);
307
308 for (UINT32 i = 0; i < screenCount; ++i)
309 {
310 ::Window rootWindow = XRootWindow(mData->xDisplay, i);
311
312 ::Window curWindow = getWindowUnderPoint(mData->xDisplay, rootWindow, rootWindow, screenPos);
313 return curWindow == xWindow;
314 }
315
316 return false;
317 }
318
319 void Platform::hideCursor()
320 {
321 Lock lock(mData->lock);
322 mData->isCursorHidden = true;
323
324 for(auto& entry : mData->windowMap)
325 applyCurrentCursor(mData, entry.first);
326 }
327
328 void Platform::showCursor()
329 {
330 Lock lock(mData->lock);
331 mData->isCursorHidden = false;
332
333 for(auto& entry : mData->windowMap)
334 applyCurrentCursor(mData, entry.first);
335 }
336
337 bool Platform::isCursorHidden()
338 {
339 Lock lock(mData->lock);
340 return mData->isCursorHidden;
341 }
342
343 void Platform::clipCursorToWindow(const RenderWindow& window)
344 {
345 Lock lock(mData->lock);
346
347 LinuxWindow* linuxWindow;
348 window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
349
350 mData->cursorClipEnabled = true;
351 mData->cursorClipWindow = linuxWindow;
352
353 updateClipBounds(mData, linuxWindow);
354
355 Vector2I pos = _getCursorPosition(mData);
356
357 if(clipCursor(mData, pos))
358 _setCursorPosition(mData, pos);
359 }
360
361 void Platform::clipCursorToRect(const Rect2I& screenRect)
362 {
363 Lock lock(mData->lock);
364
365 mData->cursorClipEnabled = true;
366 mData->cursorClipRect = screenRect;
367 mData->cursorClipWindow = nullptr;
368
369 Vector2I pos = _getCursorPosition(mData);
370
371 if(clipCursor(mData, pos))
372 _setCursorPosition(mData, pos);
373 }
374
375 void Platform::clipCursorDisable()
376 {
377 Lock lock(mData->lock);
378
379 bs::clipCursorDisable(mData);
380 }
381
382 void Platform::setCursor(PixelData& pixelData, const Vector2I& hotSpot)
383 {
384 SPtr<PixelData> bgraData = PixelData::create(pixelData.getWidth(), pixelData.getHeight(), 1, PF_BGRA8);
385 PixelUtil::bulkPixelConversion(pixelData, *bgraData);
386
387 Lock lock(mData->lock);
388
389 XcursorImage* image = XcursorImageCreate((int)bgraData->getWidth(), (int)bgraData->getHeight());
390 image->xhot = (XcursorDim)hotSpot.x;
391 image->yhot = (XcursorDim)hotSpot.y;
392 image->delay = 0;
393
394 memcpy(image->pixels, bgraData->getData(), bgraData->getSize());
395
396 ::Cursor cursor = XcursorImageLoadCursor(mData->xDisplay, image);
397 XcursorImageDestroy(image);
398
399 setCurrentCursor(mData, cursor);
400 }
401
402 void Platform::setIcon(const PixelData& pixelData)
403 {
404 if(!mData->mainXWindow)
405 return;
406
407 auto iterFind = mData->windowMap.find(mData->mainXWindow);
408 if(iterFind == mData->windowMap.end())
409 return;
410
411 LinuxWindow* mainLinuxWindow = iterFind->second;
412
413 Lock lock(mData->lock);
414 mainLinuxWindow->setIcon(pixelData);
415 }
416
417 void Platform::setCaptionNonClientAreas(const ct::RenderWindow& window, const Vector<Rect2I>& nonClientAreas)
418 {
419 if(nonClientAreas.size() == 0)
420 return;
421
422 Lock lock(mData->lock);
423
424 LinuxWindow* linuxWindow;
425 window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
426
427 linuxWindow->_setDragZones(nonClientAreas);
428 }
429
430 void Platform::setResizeNonClientAreas(const ct::RenderWindow& window, const Vector<NonClientResizeArea>& nonClientAreas)
431 {
432 // Do nothing, resize areas not supported on Linux (but they are provided even on undecorated windows by the WM)
433 }
434
435 void Platform::resetNonClientAreas(const ct::RenderWindow& window)
436 {
437 Lock lock(mData->lock);
438
439 LinuxWindow* linuxWindow;
440 window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
441
442 linuxWindow->_setDragZones({});
443 }
444
445 void Platform::sleep(UINT32 duration)
446 {
447 usleep(duration * 1000);
448 }
449
450 void Platform::copyToClipboard(const String& string)
451 {
452 Lock lock(mData->lock);
453 mData->clipboardData = string;
454
455 Atom clipboardAtom = XInternAtom(mData->xDisplay, "CLIPBOARD", 0);
456 XSetSelectionOwner(mData->xDisplay, clipboardAtom, mData->mainXWindow, CurrentTime);
457 }
458
459 String Platform::copyFromClipboard()
460 {
461 Lock lock(mData->lock);
462 Atom clipboardAtom = XInternAtom(mData->xDisplay, "CLIPBOARD", 0);
463 ::Window selOwner = XGetSelectionOwner(mData->xDisplay, clipboardAtom);
464
465 if(selOwner == None)
466 return "";
467
468 if(selOwner == mData->mainXWindow)
469 return mData->clipboardData;
470
471 XConvertSelection(mData->xDisplay, clipboardAtom, XA_STRING, clipboardAtom, mData->mainXWindow,
472 CurrentTime);
473 XFlush(mData->xDisplay);
474
475 // Note: This might discard events if there are any in between the one we need. Ideally we let the
476 // processEvents() handle them
477 while(true)
478 {
479 XEvent event;
480 XNextEvent(mData->xDisplay, &event);
481
482 if(event.type == SelectionNotify && event.xselection.requestor == mData->mainXWindow)
483 break;
484 }
485
486 Atom actualType;
487 INT32 actualFormat;
488 unsigned long length;
489 unsigned long bytesRemaining;
490 UINT8* data;
491 XGetWindowProperty(mData->xDisplay, mData->mainXWindow, clipboardAtom,
492 0, 0, False, AnyPropertyType, &actualType, &actualFormat, &length, &bytesRemaining, &data);
493
494 if(bytesRemaining > 0)
495 {
496 unsigned long unused;
497 INT32 result = XGetWindowProperty(mData->xDisplay, mData->mainXWindow, clipboardAtom,
498 0, bytesRemaining, False, AnyPropertyType, &actualType, &actualFormat, &length,
499 &unused, &data);
500
501 if(result == Success)
502 return String((const char*)data);
503
504 XFree(data);
505 }
506
507 return "";
508 }
509
510 /** Maps X11 mouse button codes to Banshee button codes. */
511 ButtonCode xButtonToButtonCode(int button)
512 {
513 switch (button)
514 {
515 case Button1:
516 return BC_MOUSE_LEFT;
517 case Button2:
518 return BC_MOUSE_MIDDLE;
519 case Button3:
520 return BC_MOUSE_RIGHT;
521 default:
522 return (ButtonCode)(BC_MOUSE_LEFT + button - 1);
523 }
524 }
525
526 /** Maps Banshee button codes to X11 names for physical key locations. */
527 const char* buttonCodeToKeyName(ButtonCode code)
528 {
529 switch(code)
530 {
531 // Row #1
532 case BC_ESCAPE: return "ESC";
533 case BC_F1: return "FK01";
534 case BC_F2: return "FK02";
535 case BC_F3: return "FK03";
536 case BC_F4: return "FK04";
537 case BC_F5: return "FK05";
538 case BC_F6: return "FK06";
539 case BC_F7: return "FK07";
540 case BC_F8: return "FK08";
541 case BC_F9: return "FK09";
542 case BC_F10: return "FK10";
543 case BC_F11: return "FK11";
544 case BC_F12: return "FK12";
545 case BC_F13: return "FK13";
546 case BC_F14: return "FK14";
547 case BC_F15: return "FK15";
548
549 // Row #2
550 case BC_GRAVE: return "TLDE";
551 case BC_1: return "AE01";
552 case BC_2: return "AE02";
553 case BC_3: return "AE03";
554 case BC_4: return "AE04";
555 case BC_5: return "AE05";
556 case BC_6: return "AE06";
557 case BC_7: return "AE07";
558 case BC_8: return "AE08";
559 case BC_9: return "AE09";
560 case BC_0: return "AE10";
561 case BC_MINUS: return "AE11";
562 case BC_EQUALS: return "AE12";
563 case BC_BACK: return "BKSP";
564
565 // Row #3
566 case BC_TAB: return "TAB";
567 case BC_Q: return "AD01";
568 case BC_W: return "AD02";
569 case BC_E: return "AD03";
570 case BC_R: return "AD04";
571 case BC_T: return "AD05";
572 case BC_Y: return "AD06";
573 case BC_U: return "AD07";
574 case BC_I: return "AD08";
575 case BC_O: return "AD09";
576 case BC_P: return "AD10";
577 case BC_LBRACKET: return "AD11";
578 case BC_RBRACKET: return "AD12";
579 case BC_RETURN: return "RTRN";
580
581 // Row #4
582 case BC_CAPITAL: return "CAPS";
583 case BC_A: return "AC01";
584 case BC_S: return "AC02";
585 case BC_D: return "AC03";
586 case BC_F: return "AC04";
587 case BC_G: return "AC05";
588 case BC_H: return "AC06";
589 case BC_J: return "AC07";
590 case BC_K: return "AC08";
591 case BC_L: return "AC09";
592 case BC_SEMICOLON: return "AC10";
593 case BC_APOSTROPHE: return "AC11";
594 case BC_BACKSLASH: return "BKSL";
595
596 // Row #5
597 case BC_LSHIFT: return "LFSH";
598 case BC_Z: return "AB01";
599 case BC_X: return "AB02";
600 case BC_C: return "AB03";
601 case BC_V: return "AB04";
602 case BC_B: return "AB05";
603 case BC_N: return "AB06";
604 case BC_M: return "AB07";
605 case BC_COMMA: return "AB08";
606 case BC_PERIOD: return "AB09";
607 case BC_SLASH: return "AB10";
608 case BC_RSHIFT: return "RTSH";
609
610 // Row #6
611 case BC_LCONTROL: return "LCTL";
612 case BC_LWIN: return "LWIN";
613 case BC_LMENU: return "LALT";
614 case BC_SPACE: return "SPCE";
615 case BC_RMENU: return "RALT";
616 case BC_RWIN: return "RWIN";
617 case BC_RCONTROL: return "RCTL";
618
619 // Keypad
620 case BC_NUMPAD0: return "KP0";
621 case BC_NUMPAD1: return "KP1";
622 case BC_NUMPAD2: return "KP2";
623 case BC_NUMPAD3: return "KP3";
624 case BC_NUMPAD4: return "KP4";
625 case BC_NUMPAD5: return "KP5";
626 case BC_NUMPAD6: return "KP6";
627 case BC_NUMPAD7: return "KP7";
628 case BC_NUMPAD8: return "KP8";
629 case BC_NUMPAD9: return "KP9";
630
631 case BC_NUMLOCK: return "NMLK";
632 case BC_DIVIDE: return "KPDV";
633 case BC_MULTIPLY: return "KPMU";
634 case BC_SUBTRACT: return "KPSU";
635 case BC_ADD: return "KPAD";
636 case BC_DECIMAL: return "KPDL";
637 case BC_NUMPADENTER: return "KPEN";
638 case BC_NUMPADEQUALS: return "KPEQ";
639
640 // Special keys
641 case BC_SCROLL: return "SCLK";
642 case BC_PAUSE: return "PAUS";
643
644 case BC_INSERT: return "INS";
645 case BC_HOME: return "HOME";
646 case BC_PGUP: return "PGUP";
647 case BC_DELETE: return "DELE";
648 case BC_END: return "END";
649 case BC_PGDOWN: return "PGDN";
650
651 case BC_UP: return "UP";
652 case BC_LEFT: return "LEFT";
653 case BC_DOWN: return "DOWN";
654 case BC_RIGHT: return "RGHT";
655
656 case BC_MUTE: return "MUTE";
657 case BC_VOLUMEDOWN: return "VOL-";
658 case BC_VOLUMEUP: return "VOL+";
659 case BC_POWER: return "POWR";
660
661 // International keys
662 case BC_OEM_102: return "LSGT"; // German keyboard: < > |
663 case BC_KANA: return "AB11"; // Taking a guess here, many layouts map <AB11> to "kana_RO"
664 case BC_YEN: return "AE13"; // Taking a guess, often mapped to yen
665
666 default:
667 // Missing Japanese (?): KATA, HIRA, HENK, MUHE, JPCM
668 // Missing Korean (?): HNGL, HJCV
669 // Missing because it's not clear which BC_ is correct: PRSC (print screen), LVL3 (AltGr), MENU
670 // Misc: LNFD (line feed), I120, I126, I128, I129, COMP, STOP, AGAI (redo), PROP, UNDO, FRNT, COPY, OPEN, PAST
671 // FIND, CUT, HELP, I147-I190, FK16-FK24, MDSW (mode switch), ALT, META, SUPR, HYPR, I208-I253
672 break;
673 }
674
675 return nullptr;
676 }
677
678 String Platform::keyCodeToUnicode(UINT32 buttonCode)
679 {
680 Lock lock(mData->lock);
681
682 const char* keyName = buttonCodeToKeyName((ButtonCode)buttonCode);
683 if(keyName == nullptr)
684 {
685 // Not a printable key
686 return "";
687 }
688
689 auto iterFind = mData->keyNameMap.find(String(keyName));
690 if(iterFind == mData->keyNameMap.end())
691 {
692 // Cannot find mapping, although this shouldn't really happen
693 return "";
694 }
695
696 XKeyPressedEvent event;
697 bs_zero_out(event);
698 event.type = KeyPress;
699 event.keycode = iterFind->second;
700 event.display = mData->xDisplay;
701 event.time = CurrentTime;
702 event.window = mData->mainXWindow;
703 event.root = RootWindow(mData->xDisplay, XDefaultScreen(mData->xDisplay));
704
705 Status status;
706 char buffer[16];
707
708 INT32 length = Xutf8LookupString(mData->IC, &event, buffer, sizeof(buffer), nullptr, &status);
709 if(length > 0)
710 {
711 buffer[length] = '\0';
712
713 return String(buffer);
714 }
715
716 return "";
717 }
718
719 void Platform::openFolder(const Path& path)
720 {
721 String pathString = path.toString();
722
723 const char* commandPattern = "xdg-open '%s'";
724
725 char* commandStr = (char*)bs_stack_alloc((UINT32)pathString.size() + (UINT32)strlen(commandPattern) + 1);
726 sprintf(commandStr, commandPattern, pathString.c_str());
727
728 if(system(commandStr)){};
729 bs_stack_free(commandStr);
730 }
731
732 /**
733 * Converts an X11 KeySym code into an input command, if possible. Returns true if conversion was done.
734 *
735 * @param[in] keySym KeySym to try to translate to a command.
736 * @param[in] shift True if the shift key was held down when the key was pressed.
737 * @param[out] command Input command. Only valid if function returns true.
738 * @return True if the KeySym is an input command.
739 */
740 bool parseInputCommand(KeySym keySym, bool shift, InputCommandType& command)
741 {
742 switch (keySym)
743 {
744 case XK_Left:
745 command = shift ? InputCommandType::SelectLeft : InputCommandType::CursorMoveLeft;
746 return true;
747 case XK_Right:
748 command = shift ? InputCommandType::SelectRight : InputCommandType::CursorMoveRight;
749 return true;
750 case XK_Up:
751 command = shift ? InputCommandType::SelectUp : InputCommandType::CursorMoveUp;
752 return true;
753 case XK_Down:
754 command = shift ? InputCommandType::SelectDown : InputCommandType::CursorMoveDown;
755 return true;
756 case XK_Escape:
757 command = InputCommandType::Escape;
758 return true;
759 case XK_Return:
760 command = shift ? InputCommandType::Return : InputCommandType::Confirm;
761 return true;
762 case XK_BackSpace:
763 command = InputCommandType::Backspace;
764 return true;
765 case XK_Delete:
766 command = InputCommandType::Delete;
767 return true;
768 case XK_Tab:
769 command = InputCommandType::Tab;
770 return true;
771 }
772
773 return false;
774 }
775
776 /** Returns a LinuxWindow from a native X11 window handle. */
777 LinuxWindow* getLinuxWindow(LinuxPlatform::Pimpl* data, ::Window xWindow)
778 {
779 auto iterFind = data->windowMap.find(xWindow);
780 if (iterFind != data->windowMap.end())
781 {
782 LinuxWindow* window = iterFind->second;
783 return window;
784 }
785
786 return nullptr;
787 }
788
789 /** Returns a RenderWindow from a native X11 window handle. Returns null if the window isn't a RenderWindow */
790 ct::RenderWindow* getRenderWindow(LinuxPlatform::Pimpl* data, ::Window xWindow)
791 {
792 LinuxWindow* linuxWindow = getLinuxWindow(data, xWindow);
793 if(linuxWindow != nullptr)
794 return (ct::RenderWindow*)linuxWindow->_getUserData();
795
796 return nullptr;
797 }
798
799 /**
800 * Enqueue a button press/release event to be handled by the main thread
801 *
802 * @param bc ButtonCode for the button that was pressed or released
803 * @param pressed true if the button was pressed, false if it was released
804 * @param timestamp Time when the event happened
805 */
806 void enqueueButtonEvent(ButtonCode bc, bool pressed, UINT64 timestamp)
807 {
808 if (bc == BC_UNASSIGNED)
809 return;
810
811 Lock eventLock(LinuxPlatform::eventLock);
812
813 LinuxButtonEvent event;
814 event.button = bc;
815 event.pressed = pressed;
816 event.timestamp = timestamp;
817 LinuxPlatform::buttonEvents.push(event);
818 }
819
820 void Platform::_messagePump()
821 {
822 while(true)
823 {
824 Lock lock(mData->lock);
825
826 if(XPending(mData->xDisplay) <= 0)
827 break;
828
829 XEvent event;
830 XNextEvent(mData->xDisplay, &event);
831
832 XGenericEventCookie* cookie = &event.xcookie;
833 if (cookie->type == GenericEvent && cookie->extension == mData->xInput2Opcode)
834 {
835 XGetEventData(mData->xDisplay, cookie);
836 XIRawEvent* xInput2Event = (XIRawEvent*) cookie->data;
837 switch (xInput2Event->evtype)
838 {
839 case XI_RawMotion:
840 if (xInput2Event->valuators.mask_len > 0)
841 {
842 // Assume X/Y delta is stored in valuators 0/1 and vertical scroll in valuator 3.
843 // While there is an API that reliably tells us the valuator index for vertical scroll, there's
844 // nothing "more reliable" for X/Y axes, as the only way to possibly identify them from device
845 // info is by axis name, so we can use the axis index directly just as well. GDK seems to assume
846 // 0 for x and 1 for y too, so that's hopefully safe, and 3 appears to be common for the scroll
847 // wheel.
848 float deltas[4] = {0};
849 int currentValuesIndex = 0;
850 for (unsigned int valuator = 0; valuator < 4; valuator++)
851 if (XIMaskIsSet(xInput2Event->valuators.mask, valuator))
852 deltas[valuator] = xInput2Event->raw_values[currentValuesIndex++];
853
854 Lock eventLock(LinuxPlatform::eventLock);
855 LinuxPlatform::mouseMotionEvent.deltaX += deltas[0];
856 LinuxPlatform::mouseMotionEvent.deltaY += deltas[1];
857 LinuxPlatform::mouseMotionEvent.deltaZ += deltas[3]; // Not a typo - 2 is for horizontal scroll.
858 }
859 break;
860 }
861
862 XFreeEventData(mData->xDisplay, cookie);
863 }
864
865
866 switch (event.type)
867 {
868 case ClientMessage:
869 {
870 if(LinuxDragAndDrop::handleClientMessage(event.xclient))
871 break;
872
873 // User requested the window to close
874 if((Atom)event.xclient.data.l[0] == mData->atomDeleteWindow)
875 {
876 LinuxWindow* window = getLinuxWindow(mData, event.xclient.window);
877 if(window != nullptr)
878 {
879 // If it's a render window we allow the client code to handle the message
880 ct::RenderWindow* renderWindow = (ct::RenderWindow*)window->_getUserData();
881 if(renderWindow != nullptr)
882 renderWindow->_notifyWindowEvent(WindowEventType::CloseRequested);
883 else // If not, we just destroy the window
884 window->_destroy();
885 }
886 }
887 }
888 break;
889 case KeyPress:
890 {
891 XKeyPressedEvent* keyEvent = (XKeyPressedEvent*) &event;
892 enqueueButtonEvent(mData->keyCodeMap[keyEvent->keycode], true, (UINT64) keyEvent->time);
893
894 // Process text input
895 KeySym keySym = XkbKeycodeToKeysym(mData->xDisplay, (KeyCode)event.xkey.keycode, 0, 0);
896
897 // Handle input commands
898 InputCommandType command = InputCommandType::Backspace;
899 bool shift = (event.xkey.state & ShiftMask) != 0;
900
901 bool isInputCommand = parseInputCommand(keySym, shift, command);
902
903 // Check if input manager wants this event. If not, we process it.
904 if(XFilterEvent(&event, None) == False && !isInputCommand)
905 {
906 // Send a text input event
907 Status status;
908 char buffer[16];
909
910 INT32 length = Xutf8LookupString(mData->IC, &event.xkey, buffer, sizeof(buffer), nullptr,
911 &status);
912
913 if (length > 0)
914 {
915 buffer[length] = '\0';
916
917 U32String utfStr = UTF8::toUTF32(String(buffer));
918 if (utfStr.length() > 0)
919 onCharInput((UINT32) utfStr[0]);
920 }
921 }
922
923 // Send an input command event
924 if(isInputCommand)
925 {
926 if(!onInputCommand.empty())
927 onInputCommand(command);
928 }
929 }
930 break;
931 case KeyRelease:
932 {
933 XKeyReleasedEvent* keyEvent = (XKeyReleasedEvent*) &event;
934 enqueueButtonEvent(mData->keyCodeMap[keyEvent->keycode], false, (UINT64) keyEvent->time);
935 }
936 break;
937 case ButtonPress:
938 {
939 XButtonPressedEvent* buttonEvent = (XButtonPressedEvent*) &event;
940 UINT32 button = event.xbutton.button;
941 enqueueButtonEvent(xButtonToButtonCode(button), true, (UINT64) buttonEvent->time);
942
943 OSPointerButtonStates btnStates;
944 btnStates.mouseButtons[0] = (event.xbutton.state & Button1Mask) != 0;
945 btnStates.mouseButtons[1] = (event.xbutton.state & Button2Mask) != 0;
946 btnStates.mouseButtons[2] = (event.xbutton.state & Button3Mask) != 0;
947
948 OSMouseButton mouseButton;
949 bool validPress = false;
950 switch(button)
951 {
952 case Button1:
953 mouseButton = OSMouseButton::Left;
954 btnStates.mouseButtons[0] = true;
955 validPress = true;
956 break;
957 case Button2:
958 mouseButton = OSMouseButton::Middle;
959 btnStates.mouseButtons[1] = true;
960 validPress = true;
961 break;
962 case Button3:
963 mouseButton = OSMouseButton::Right;
964 btnStates.mouseButtons[2] = true;
965 validPress = true;
966 break;
967
968 default:
969 break;
970 }
971
972 if(validPress)
973 {
974 // Send event
975 Vector2I pos;
976 pos.x = event.xbutton.x_root;
977 pos.y = event.xbutton.y_root;
978
979 btnStates.ctrl = (event.xbutton.state & ControlMask) != 0;
980 btnStates.shift = (event.xbutton.state & ShiftMask) != 0;
981
982 onCursorButtonPressed(pos, mouseButton, btnStates);
983
984 // Handle double-click
985 if(button == Button1)
986 {
987 if (event.xbutton.time < (mData->lastButtonPressTime + DOUBLE_CLICK_MS))
988 {
989 onCursorDoubleClick(pos, btnStates);
990 mData->lastButtonPressTime = 0;
991 }
992 else
993 mData->lastButtonPressTime = event.xbutton.time;
994 }
995 }
996
997 // Handle window dragging for windows without a title bar
998 if(button == Button1)
999 {
1000 LinuxWindow* window = getLinuxWindow(mData, event.xbutton.window);
1001 if(window != nullptr)
1002 window->_dragStart(event.xbutton);
1003 }
1004
1005 break;
1006 }
1007 case ButtonRelease:
1008 {
1009 XButtonReleasedEvent* buttonEvent = (XButtonReleasedEvent*) &event;
1010 UINT32 button = event.xbutton.button;
1011 enqueueButtonEvent(xButtonToButtonCode(button), false, (UINT64) buttonEvent->time);
1012
1013 Vector2I pos;
1014 pos.x = event.xbutton.x_root;
1015 pos.y = event.xbutton.y_root;
1016
1017 OSPointerButtonStates btnStates;
1018 btnStates.ctrl = (event.xbutton.state & ControlMask) != 0;
1019 btnStates.shift = (event.xbutton.state & ShiftMask) != 0;
1020 btnStates.mouseButtons[0] = (event.xbutton.state & Button1Mask) != 0;
1021 btnStates.mouseButtons[1] = (event.xbutton.state & Button2Mask) != 0;
1022 btnStates.mouseButtons[2] = (event.xbutton.state & Button3Mask) != 0;
1023
1024 switch(button)
1025 {
1026 case Button1:
1027 btnStates.mouseButtons[0] = false;
1028 onCursorButtonReleased(pos, OSMouseButton::Left, btnStates);
1029 break;
1030 case Button2:
1031 btnStates.mouseButtons[1] = false;
1032 onCursorButtonReleased(pos, OSMouseButton::Middle, btnStates);
1033 break;
1034 case Button3:
1035 btnStates.mouseButtons[2] = false;
1036 onCursorButtonReleased(pos, OSMouseButton::Right, btnStates);
1037 break;
1038 case Button4: // Vertical mouse wheel
1039 case Button5:
1040 {
1041 INT32 delta = button == Button4 ? 1 : -1;
1042 onMouseWheelScrolled((float)delta);
1043 }
1044 break;
1045 default:
1046 break;
1047 }
1048
1049 // Handle window dragging for windows without a title bar
1050 if(button == Button1)
1051 {
1052 LinuxWindow* window = getLinuxWindow(mData, event.xbutton.window);
1053 if(window != nullptr)
1054 window->_dragEnd();
1055 }
1056
1057 break;
1058 }
1059 case MotionNotify:
1060 {
1061 Vector2I pos;
1062 pos.x = event.xmotion.x_root;
1063 pos.y = event.xmotion.y_root;
1064
1065 // Handle clipping if enabled
1066 if(clipCursor(mData, pos))
1067 _setCursorPosition(mData, pos);
1068
1069 // Send event
1070 OSPointerButtonStates btnStates;
1071 btnStates.ctrl = (event.xmotion.state & ControlMask) != 0;
1072 btnStates.shift = (event.xmotion.state & ShiftMask) != 0;
1073 btnStates.mouseButtons[0] = (event.xmotion.state & Button1Mask) != 0;
1074 btnStates.mouseButtons[1] = (event.xmotion.state & Button2Mask) != 0;
1075 btnStates.mouseButtons[2] = (event.xmotion.state & Button3Mask) != 0;
1076
1077 onCursorMoved(pos, btnStates);
1078 }
1079 break;
1080 case EnterNotify:
1081 // Do nothing
1082 break;
1083 case LeaveNotify:
1084 {
1085 if (event.xcrossing.mode == NotifyNormal)
1086 {
1087 Vector2I pos;
1088 pos.x = event.xcrossing.x_root;
1089 pos.y = event.xcrossing.y_root;
1090
1091 if (clipCursor(mData, pos))
1092 _setCursorPosition(mData, pos);
1093 }
1094
1095 ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xcrossing.window);
1096 if(renderWindow != nullptr)
1097 renderWindow->_notifyWindowEvent(WindowEventType::MouseLeft);
1098 }
1099 break;
1100 case ConfigureNotify:
1101 {
1102 LinuxWindow* window = getLinuxWindow(mData, event.xconfigure.window);
1103 if(window != nullptr)
1104 {
1105 updateClipBounds(mData, window);
1106
1107 ct::RenderWindow* renderWindow = (ct::RenderWindow*)window->_getUserData();
1108 if(renderWindow != nullptr)
1109 {
1110 renderWindow->_notifyWindowEvent(WindowEventType::Resized);
1111 renderWindow->_notifyWindowEvent(WindowEventType::Moved);
1112 }
1113 }
1114 }
1115 break;
1116 case FocusIn:
1117 {
1118 // Update input context focus
1119 XSetICFocus(mData->IC);
1120
1121 // Send event to render window
1122 ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xfocus.window);
1123
1124 // Not a render window, so it doesn't care about these events
1125 if (renderWindow != nullptr)
1126 {
1127 if (!renderWindow->getProperties().hasFocus)
1128 renderWindow->_notifyWindowEvent(WindowEventType::FocusReceived);
1129 }
1130 }
1131 break;
1132 case FocusOut:
1133 {
1134 // Update input context focus
1135 XUnsetICFocus(mData->IC);
1136
1137 // Send event to render window
1138 ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xfocus.window);
1139
1140 // Not a render window, so it doesn't care about these events
1141 if (renderWindow != nullptr)
1142 {
1143 if (renderWindow->getProperties().hasFocus)
1144 renderWindow->_notifyWindowEvent(WindowEventType::FocusLost);
1145 }
1146 }
1147 break;
1148 case SelectionNotify:
1149 LinuxDragAndDrop::handleSelectionNotify(event.xselection);
1150 break;
1151 case SelectionRequest:
1152 {
1153 // Send the data saved by the last clipboard copy operation
1154 Atom compoundTextAtom = XInternAtom(mData->xDisplay, "COMPOUND_TEXT", 0);
1155 Atom utf8StringAtom = XInternAtom(mData->xDisplay, "UTF8_STRING", 0);
1156 Atom targetsAtom = XInternAtom(mData->xDisplay, "TARGETS", 0);
1157
1158 XSelectionRequestEvent& selReq = event.xselectionrequest;
1159 XEvent response;
1160 if(selReq.target == XA_STRING || selReq.target == compoundTextAtom || selReq.target == utf8StringAtom)
1161 {
1162 String utf8data = mData->clipboardData;
1163
1164 const UINT8* data = (const UINT8*)utf8data.c_str();
1165 INT32 dataLength = (INT32)utf8data.length();
1166
1167 XChangeProperty(mData->xDisplay, selReq.requestor, selReq.property,
1168 selReq.target, 8, PropModeReplace, data, dataLength);
1169
1170 response.xselection.property = selReq.property;
1171 }
1172 else if(selReq.target == targetsAtom)
1173 {
1174 Atom data[2];
1175 data[0] = utf8StringAtom;
1176 data[1] = XA_STRING;
1177 XChangeProperty (mData->xDisplay, selReq.requestor, selReq.property, selReq.target,
1178 8, PropModeReplace, (unsigned char*)&data, sizeof (data));
1179
1180 response.xselection.property = selReq.property;
1181 }
1182 else
1183 {
1184 response.xselection.property = None;
1185 }
1186
1187 response.xselection.type = SelectionNotify;
1188 response.xselection.display = selReq.display;
1189 response.xselection.requestor = selReq.requestor;
1190 response.xselection.selection = selReq.selection;
1191 response.xselection.target = selReq.target;
1192 response.xselection.time = selReq.time;
1193
1194 XSendEvent (mData->xDisplay, selReq.requestor, 0, 0, &response);
1195 XFlush (mData->xDisplay);
1196 }
1197 break;
1198 case PropertyNotify:
1199 // Report minimize, maximize and restore events
1200 if(event.xproperty.atom == mData->atomWmState)
1201 {
1202 // Check that the window hasn't been destroyed
1203 if(getLinuxWindow(mData, event.xproperty.window) == nullptr)
1204 break;
1205
1206 Atom type;
1207 INT32 format;
1208 unsigned long count, bytesRemaining;
1209 UINT8* data = nullptr;
1210
1211 INT32 result = XGetWindowProperty(mData->xDisplay, event.xproperty.window, mData->atomWmState,
1212 0, 1024, False, AnyPropertyType, &type, &format,
1213 &count, &bytesRemaining, &data);
1214
1215 if (result == Success)
1216 {
1217 ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xproperty.window);
1218
1219 // Not a render window, so it doesn't care about these events
1220 if(renderWindow == nullptr)
1221 continue;
1222
1223 Atom* atoms = (Atom*)data;
1224
1225 bool foundHorz = false;
1226 bool foundVert = false;
1227 for (unsigned long i = 0; i < count; i++)
1228 {
1229 if (atoms[i] == mData->atomWmStateMaxHorz) foundHorz = true;
1230 if (atoms[i] == mData->atomWmStateMaxVert) foundVert = true;
1231
1232 if (foundVert && foundHorz)
1233 {
1234 if(event.xproperty.state == PropertyNewValue)
1235 renderWindow->_notifyWindowEvent(WindowEventType::Maximized);
1236 else
1237 renderWindow->_notifyWindowEvent(WindowEventType::Restored);
1238 }
1239
1240 if(atoms[i] == mData->atomWmStateHidden)
1241 {
1242 if(event.xproperty.state == PropertyNewValue)
1243 renderWindow->_notifyWindowEvent(WindowEventType::Minimized);
1244 else
1245 renderWindow->_notifyWindowEvent(WindowEventType::Restored);
1246 }
1247 }
1248
1249 XFree(atoms);
1250 }
1251 }
1252 break;
1253 default:
1254 break;
1255 }
1256 }
1257 }
1258
1259 void Platform::_startUp()
1260 {
1261 Lock lock(mData->lock);
1262 mData->xDisplay = XOpenDisplay(nullptr);
1263 XSetErrorHandler(x11ErrorHandler);
1264
1265 // For raw, relative mouse motion events, XInput2 extension is required
1266 int firstEvent;
1267 int firstError;
1268 if (!XQueryExtension(mData->xDisplay, "XInputExtension", &mData->xInput2Opcode, &firstEvent, &firstError))
1269 BS_EXCEPT(InternalErrorException, "X Server doesn't support the XInput extension");
1270
1271 int majorVersion = 2;
1272 int minorVersion = 0;
1273 if (XIQueryVersion(mData->xDisplay, &majorVersion, &minorVersion) != Success)
1274 BS_EXCEPT(InternalErrorException, "X Server doesn't support at least the XInput 2.0 extension");
1275
1276 // Let XInput know we are interested in raw mouse movement events
1277 constexpr int maskLen = XIMaskLen(XI_LASTEVENT);
1278 XIEventMask mask;
1279 mask.deviceid = XIAllDevices;
1280 mask.mask_len = maskLen;
1281
1282 unsigned char maskBuffer[maskLen] = {0};
1283 mask.mask = maskBuffer;
1284 XISetMask(mask.mask, XI_RawMotion);
1285
1286 // "RawEvents are sent exclusively to all root windows", so this should receive all events, even though we only
1287 // select on one display's root window (untested for lack of second screen).
1288 XISelectEvents(mData->xDisplay, XRootWindow(mData->xDisplay, DefaultScreen(mData->xDisplay)), &mask, 1);
1289 XFlush(mData->xDisplay);
1290
1291 if(XSupportsLocale())
1292 {
1293 XSetLocaleModifiers("@im=none");
1294 mData->IM = XOpenIM(mData->xDisplay, nullptr, nullptr, nullptr);
1295
1296 // Note: Currently our windows don't support pre-edit and status areas, which are used for more complex types
1297 // of character input. Later on it might be beneficial to support them.
1298 mData->IC = XCreateIC(mData->IM, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, nullptr);
1299 }
1300
1301 mData->atomDeleteWindow = XInternAtom(mData->xDisplay, "WM_DELETE_WINDOW", False);
1302 mData->atomWmState = XInternAtom(mData->xDisplay, "_NET_WM_STATE", False);
1303 mData->atomWmStateHidden = XInternAtom(mData->xDisplay, "_NET_WM_STATE_HIDDEN", False);
1304 mData->atomWmStateMaxHorz = XInternAtom(mData->xDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
1305 mData->atomWmStateMaxVert = XInternAtom(mData->xDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", False);
1306
1307 // Drag and drop
1308 LinuxDragAndDrop::startUp(mData->xDisplay);
1309
1310 // Create empty cursor
1311 char data[1];
1312 memset(data, 0, sizeof(data));
1313
1314 Pixmap pixmap = XCreateBitmapFromData(mData->xDisplay, DefaultRootWindow(mData->xDisplay), data, 1, 1);
1315
1316 XColor color;
1317 color.red = color.green = color.blue = 0;
1318 mData->emptyCursor = XCreatePixmapCursor(mData->xDisplay, pixmap, pixmap, &color, &color, 0, 0);
1319
1320 XFreePixmap(mData->xDisplay, pixmap);
1321
1322 // Initialize "unique X11 keyname" -> "X11 keycode" map
1323 char name[XkbKeyNameLength + 1];
1324
1325 XkbDescPtr desc = XkbGetMap(mData->xDisplay, 0, XkbUseCoreKbd);
1326 XkbGetNames(mData->xDisplay, XkbKeyNamesMask, desc);
1327
1328 for (UINT32 keyCode = desc->min_key_code; keyCode <= desc->max_key_code; keyCode++)
1329 {
1330 memcpy(name, desc->names->keys[keyCode].name, XkbKeyNameLength);
1331 name[XkbKeyNameLength] = '\0';
1332
1333 mData->keyNameMap[String(name)] = keyCode;
1334 }
1335
1336 // Initialize "X11 keycode" -> "Banshee ButtonCode" map, based on the keyNameMap and keyCodeToKeyName()
1337 mData->keyCodeMap.resize(desc->max_key_code + 1, BC_UNASSIGNED);
1338
1339 XkbFreeNames(desc, XkbKeyNamesMask, True);
1340 XkbFreeKeyboard(desc, 0, True);
1341
1342 for (UINT32 buttonCodeNum = BC_UNASSIGNED; buttonCodeNum <= BC_NumKeys; buttonCodeNum++)
1343 {
1344 ButtonCode buttonCode = (ButtonCode) buttonCodeNum;
1345 const char* keyNameCStr = buttonCodeToKeyName(buttonCode);
1346
1347 if (keyNameCStr != nullptr)
1348 {
1349 String keyName = String(keyNameCStr);
1350 auto iterFind = mData->keyNameMap.find(keyName);
1351 if (iterFind != mData->keyNameMap.end())
1352 {
1353 KeyCode keyCode = iterFind->second;
1354 mData->keyCodeMap[keyCode] = buttonCode;
1355 }
1356 }
1357 }
1358 }
1359
1360 void Platform::_update()
1361 {
1362 LinuxDragAndDrop::update();
1363 }
1364
1365 void Platform::_coreUpdate()
1366 {
1367 _messagePump();
1368 }
1369
1370 void Platform::_shutDown()
1371 {
1372 Lock lock(mData->lock);
1373
1374 // Free empty cursor
1375 XFreeCursor(mData->xDisplay, mData->emptyCursor);
1376 mData->emptyCursor = None;
1377
1378 // Shutdown drag and drop
1379 LinuxDragAndDrop::shutDown();
1380
1381 if(mData->IC)
1382 {
1383 XDestroyIC(mData->IC);
1384 mData->IC = 0;
1385 }
1386
1387 if(mData->IM)
1388 {
1389 XCloseIM(mData->IM);
1390 mData->IM = 0;
1391 }
1392
1393 XCloseDisplay(mData->xDisplay);
1394 mData->xDisplay = nullptr;
1395
1396 bs_delete(mData);
1397 mData = nullptr;
1398 }
1399
1400 ::Display* LinuxPlatform::getXDisplay()
1401 {
1402 return mData->xDisplay;
1403 }
1404
1405 ::Window LinuxPlatform::getMainXWindow()
1406 {
1407 return mData->mainXWindow;
1408 }
1409
1410 Path LinuxPlatform::getHomeDir()
1411 {
1412 const char* homeDir = getenv("HOME");
1413 if(!homeDir)
1414 homeDir = getpwuid(getuid())->pw_dir;
1415
1416 return Path(homeDir);
1417 }
1418
1419 void LinuxPlatform::lockX()
1420 {
1421 mData->lock.lock();
1422 }
1423
1424 void LinuxPlatform::unlockX()
1425 {
1426 mData->lock.unlock();
1427 }
1428
1429 void LinuxPlatform::_registerWindow(::Window xWindow, LinuxWindow* window)
1430 {
1431 // First window is assumed to be the main
1432 if(mData->mainXWindow == 0)
1433 {
1434 mData->mainXWindow = xWindow;
1435
1436 // Input context client window must be set before use
1437 XSetICValues(mData->IC,
1438 XNClientWindow, xWindow,
1439 XNFocusWindow, xWindow,
1440 nullptr);
1441 }
1442
1443 mData->windowMap[xWindow] = window;
1444
1445 applyCurrentCursor(mData, xWindow);
1446 }
1447
1448 void LinuxPlatform::_unregisterWindow(::Window xWindow)
1449 {
1450 auto iterFind = mData->windowMap.find(xWindow);
1451 if(iterFind != mData->windowMap.end())
1452 {
1453 if(mData->cursorClipEnabled && mData->cursorClipWindow == iterFind->second)
1454 bs::clipCursorDisable(mData);
1455
1456 mData->windowMap.erase(iterFind);
1457 }
1458
1459 if(mData->mainXWindow == xWindow)
1460 mData->mainXWindow = 0;
1461 }
1462
1463 Pixmap LinuxPlatform::createPixmap(const PixelData& data, UINT32 depth)
1464 {
1465 // Premultiply alpha
1466 Vector<Color> colors = data.getColors();
1467 for(auto& color : colors)
1468 {
1469 color.r *= color.a;
1470 color.g *= color.a;
1471 color.b *= color.a;
1472 }
1473
1474 // Convert to BGRA
1475 SPtr<PixelData> bgraData = PixelData::create(data.getWidth(), data.getHeight(), 1, PF_BGRA8);
1476 bgraData->setColors(colors);
1477
1478 XImage* image = XCreateImage(mData->xDisplay, CopyFromParent, depth, ZPixmap, 0,
1479 (char*)bgraData->getData(), data.getWidth(), data.getHeight(), 32, 0);
1480
1481 Pixmap pixmap = XCreatePixmap(mData->xDisplay, XDefaultRootWindow(mData->xDisplay),
1482 data.getWidth(), data.getHeight(), depth);
1483
1484 XGCValues gcValues;
1485 GC gc = XCreateGC(mData->xDisplay, pixmap, 0, &gcValues);
1486 XPutImage(mData->xDisplay, pixmap, gc, image, 0, 0, 0, 0, data.getWidth(), data.getHeight());
1487 XFreeGC(mData->xDisplay, gc);
1488
1489 // Make sure XDestroyImage doesn't free the data pointed to by 'data.bytes'
1490 image->data = nullptr;
1491 XDestroyImage(image);
1492
1493 return pixmap;
1494 }
1495}
1496