[Test] Added tests for CUtil::SplitParams
[xbmc.git] / xbmc / windowing / X11 / WinEventsX11.cpp
blob756532c2c8a4b9752a070aa73f926a4e7f8ad60b
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "WinEventsX11.h"
11 #include "ServiceBroker.h"
12 #include "application/AppInboundProtocol.h"
13 #include "application/Application.h"
14 #include "cores/AudioEngine/Interfaces/AE.h"
15 #include "guilib/GUIComponent.h"
16 #include "guilib/GUIWindowManager.h"
17 #include "input/InputManager.h"
18 #include "input/mouse/MouseStat.h"
19 #include "messaging/ApplicationMessenger.h"
20 #include "utils/CharsetConverter.h"
21 #include "utils/log.h"
22 #include "windowing/WinEvents.h"
23 #include "windowing/X11/WinSystemX11.h"
25 #include <stdexcept>
27 #include <X11/XF86keysym.h>
28 #include <X11/Xlib.h>
29 #include <X11/extensions/Xrandr.h>
30 #include <X11/keysymdef.h>
32 using namespace KODI::WINDOWING::X11;
34 static uint32_t SymMappingsX11[][2] =
36 {XK_BackSpace, XBMCK_BACKSPACE}
37 , {XK_Tab, XBMCK_TAB}
38 , {XK_Clear, XBMCK_CLEAR}
39 , {XK_Return, XBMCK_RETURN}
40 , {XK_Pause, XBMCK_PAUSE}
41 , {XK_Escape, XBMCK_ESCAPE}
42 , {XK_Delete, XBMCK_DELETE}
43 // multi-media keys
44 , {XF86XK_Back, XBMCK_BROWSER_BACK}
45 , {XF86XK_Forward, XBMCK_BROWSER_FORWARD}
46 , {XF86XK_Refresh, XBMCK_BROWSER_REFRESH}
47 , {XF86XK_Stop, XBMCK_BROWSER_STOP}
48 , {XF86XK_Search, XBMCK_BROWSER_SEARCH}
49 , {XF86XK_Favorites, XBMCK_BROWSER_FAVORITES}
50 , {XF86XK_HomePage, XBMCK_BROWSER_HOME}
51 , {XF86XK_AudioMute, XBMCK_VOLUME_MUTE}
52 , {XF86XK_AudioLowerVolume, XBMCK_VOLUME_DOWN}
53 , {XF86XK_AudioRaiseVolume, XBMCK_VOLUME_UP}
54 , {XF86XK_AudioNext, XBMCK_MEDIA_NEXT_TRACK}
55 , {XF86XK_AudioPrev, XBMCK_MEDIA_PREV_TRACK}
56 , {XF86XK_AudioStop, XBMCK_MEDIA_STOP}
57 , {XF86XK_AudioPause, XBMCK_MEDIA_PLAY_PAUSE}
58 , {XF86XK_Mail, XBMCK_LAUNCH_MAIL}
59 , {XF86XK_Select, XBMCK_LAUNCH_MEDIA_SELECT}
60 , {XF86XK_Launch0, XBMCK_LAUNCH_APP1}
61 , {XF86XK_Launch1, XBMCK_LAUNCH_APP2}
62 , {XF86XK_WWW, XBMCK_LAUNCH_FILE_BROWSER}
63 , {XF86XK_AudioMedia, XBMCK_LAUNCH_MEDIA_CENTER }
64 // Numeric keypad
65 , {XK_KP_0, XBMCK_KP0}
66 , {XK_KP_1, XBMCK_KP1}
67 , {XK_KP_2, XBMCK_KP2}
68 , {XK_KP_3, XBMCK_KP3}
69 , {XK_KP_4, XBMCK_KP4}
70 , {XK_KP_5, XBMCK_KP5}
71 , {XK_KP_6, XBMCK_KP6}
72 , {XK_KP_7, XBMCK_KP7}
73 , {XK_KP_8, XBMCK_KP8}
74 , {XK_KP_9, XBMCK_KP9}
75 , {XK_KP_Separator, XBMCK_KP_PERIOD}
76 , {XK_KP_Divide, XBMCK_KP_DIVIDE}
77 , {XK_KP_Multiply, XBMCK_KP_MULTIPLY}
78 , {XK_KP_Subtract, XBMCK_KP_MINUS}
79 , {XK_KP_Add, XBMCK_KP_PLUS}
80 , {XK_KP_Enter, XBMCK_KP_ENTER}
81 , {XK_KP_Equal, XBMCK_KP_EQUALS}
82 // Arrows + Home/End pad
83 , {XK_Up, XBMCK_UP}
84 , {XK_Down, XBMCK_DOWN}
85 , {XK_Right, XBMCK_RIGHT}
86 , {XK_Left, XBMCK_LEFT}
87 , {XK_Insert, XBMCK_INSERT}
88 , {XK_Home, XBMCK_HOME}
89 , {XK_End, XBMCK_END}
90 , {XK_Page_Up, XBMCK_PAGEUP}
91 , {XK_Page_Down, XBMCK_PAGEDOWN}
92 // Function keys
93 , {XK_F1, XBMCK_F1}
94 , {XK_F2, XBMCK_F2}
95 , {XK_F3, XBMCK_F3}
96 , {XK_F4, XBMCK_F4}
97 , {XK_F5, XBMCK_F5}
98 , {XK_F6, XBMCK_F6}
99 , {XK_F7, XBMCK_F7}
100 , {XK_F8, XBMCK_F8}
101 , {XK_F9, XBMCK_F9}
102 , {XK_F10, XBMCK_F10}
103 , {XK_F11, XBMCK_F11}
104 , {XK_F12, XBMCK_F12}
105 , {XK_F13, XBMCK_F13}
106 , {XK_F14, XBMCK_F14}
107 , {XK_F15, XBMCK_F15}
108 // Key state modifier keys
109 , {XK_Num_Lock, XBMCK_NUMLOCK}
110 , {XK_Caps_Lock, XBMCK_CAPSLOCK}
111 , {XK_Scroll_Lock, XBMCK_SCROLLOCK}
112 , {XK_Shift_R, XBMCK_RSHIFT}
113 , {XK_Shift_L, XBMCK_LSHIFT}
114 , {XK_Control_R, XBMCK_RCTRL}
115 , {XK_Control_L, XBMCK_LCTRL}
116 , {XK_Alt_R, XBMCK_RALT}
117 , {XK_Alt_L, XBMCK_LALT}
118 , {XK_Meta_R, XBMCK_RMETA}
119 , {XK_Meta_L, XBMCK_LMETA}
120 , {XK_Super_L, XBMCK_LSUPER}
121 , {XK_Super_R, XBMCK_RSUPER}
122 , {XK_Mode_switch, XBMCK_MODE}
123 , {XK_Multi_key, XBMCK_COMPOSE}
124 // Miscellaneous function keys
125 , {XK_Help, XBMCK_HELP}
126 , {XK_Print, XBMCK_PRINT}
127 //, {0, XBMCK_SYSREQ}
128 , {XK_Break, XBMCK_BREAK}
129 , {XK_Menu, XBMCK_MENU}
130 , {XF86XK_PowerOff, XBMCK_POWER}
131 , {XF86XK_Sleep, XBMCK_SLEEP}
132 , {XK_EcuSign, XBMCK_EURO}
133 , {XK_Undo, XBMCK_UNDO}
134 /* Media keys */
135 , {XF86XK_Eject, XBMCK_EJECT}
136 , {XF86XK_Stop, XBMCK_STOP}
137 , {XF86XK_AudioRecord, XBMCK_RECORD}
138 , {XF86XK_AudioRewind, XBMCK_REWIND}
139 , {XF86XK_Phone, XBMCK_PHONE}
140 , {XF86XK_AudioPlay, XBMCK_PLAY}
141 , {XF86XK_AudioRandomPlay, XBMCK_SHUFFLE}
142 , {XF86XK_AudioForward, XBMCK_FASTFORWARD}
145 CWinEventsX11::CWinEventsX11(CWinSystemX11& winSystem) : m_winSystem(winSystem)
149 CWinEventsX11::~CWinEventsX11()
151 Quit();
154 bool CWinEventsX11::Init(Display *dpy, Window win)
156 if (m_display)
157 return true;
159 m_display = dpy;
160 m_window = win;
161 m_keybuf_len = 32*sizeof(char);
162 m_keybuf = (char*)malloc(m_keybuf_len);
163 m_keymodState = 0;
164 m_wmDeleteMessage = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
165 m_structureChanged = false;
166 m_xrrEventPending = false;
168 // open input method
169 char *old_locale = NULL, *old_modifiers = NULL;
170 char res_name[8];
171 const char *p;
173 // set resource name to xbmc, not used
174 strcpy(res_name, "xbmc");
176 // save current locale, this should be "C"
177 p = setlocale(LC_ALL, NULL);
178 if (p)
180 old_locale = (char*)malloc(strlen(p) +1);
181 strcpy(old_locale, p);
183 p = XSetLocaleModifiers(NULL);
184 if (p)
186 old_modifiers = (char*)malloc(strlen(p) +1);
187 strcpy(old_modifiers, p);
190 // set users preferences and open input method
191 p = setlocale(LC_ALL, "");
192 XSetLocaleModifiers("");
193 m_xim = XOpenIM(m_display, NULL, res_name, res_name);
195 // restore old locale
196 if (old_locale)
198 setlocale(LC_ALL, old_locale);
199 free(old_locale);
201 if (old_modifiers)
203 XSetLocaleModifiers(old_modifiers);
204 free(old_modifiers);
207 m_xic = NULL;
208 if (m_xim)
210 m_xic = XCreateIC(m_xim,
211 XNClientWindow, m_window,
212 XNFocusWindow, m_window,
213 XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
214 XNResourceName, res_name,
215 XNResourceClass, res_name,
216 nullptr);
219 if (!m_xic)
220 CLog::Log(LOGWARNING,"CWinEventsX11::Init - no input method found");
222 // build Keysym lookup table
223 for (const auto& symMapping : SymMappingsX11)
225 m_symLookupTable[symMapping[0]] = symMapping[1];
228 // register for xrandr events
229 int iReturn;
230 XRRQueryExtension(m_display, &m_RREventBase, &iReturn);
231 int numScreens = XScreenCount(m_display);
232 for (int i = 0; i < numScreens; i++)
234 XRRSelectInput(m_display, RootWindow(m_display, i), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask | RROutputPropertyNotifyMask);
237 return true;
240 void CWinEventsX11::Quit()
242 free(m_keybuf);
243 m_keybuf = nullptr;
245 if (m_xic)
247 XUnsetICFocus(m_xic);
248 XDestroyIC(m_xic);
249 m_xic = nullptr;
252 if (m_xim)
254 XCloseIM(m_xim);
255 m_xim = nullptr;
258 m_symLookupTable.clear();
260 m_display = nullptr;
263 bool CWinEventsX11::HasStructureChanged()
265 if (!m_display)
266 return false;
268 bool ret = m_structureChanged;
269 m_structureChanged = false;
270 return ret;
273 void CWinEventsX11::SetXRRFailSafeTimer(std::chrono::milliseconds duration)
275 if (!m_display)
276 return;
278 m_xrrFailSafeTimer.Set(duration);
279 m_xrrEventPending = true;
282 bool CWinEventsX11::MessagePump()
284 if (!m_display)
285 return false;
287 bool ret = false;
288 XEvent xevent;
289 unsigned long serial = 0;
290 std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
292 while (m_display && XPending(m_display))
294 memset(&xevent, 0, sizeof (XEvent));
295 XNextEvent(m_display, &xevent);
297 if (m_display && (xevent.type == m_RREventBase + RRScreenChangeNotify))
299 if (xevent.xgeneric.serial == serial)
300 continue;
302 if (m_xrrEventPending)
304 m_winSystem.NotifyXRREvent();
305 m_xrrEventPending = false;
306 serial = xevent.xgeneric.serial;
309 continue;
311 else if (m_display && (xevent.type == m_RREventBase + RRNotify))
313 if (xevent.xgeneric.serial == serial)
314 continue;
316 XRRNotifyEvent* rrEvent = reinterpret_cast<XRRNotifyEvent*>(&xevent);
317 if (rrEvent->subtype == RRNotify_OutputChange)
319 XRROutputChangeNotifyEvent* changeEvent = reinterpret_cast<XRROutputChangeNotifyEvent*>(&xevent);
320 if (changeEvent->connection == RR_Connected ||
321 changeEvent->connection == RR_Disconnected)
323 m_winSystem.NotifyXRREvent();
324 CServiceBroker::GetActiveAE()->DeviceChange();
325 serial = xevent.xgeneric.serial;
329 continue;
332 if (XFilterEvent(&xevent, None))
333 continue;
335 switch (xevent.type)
337 case MapNotify:
339 if (appPort)
340 appPort->SetRenderGUI(true);
341 break;
344 case UnmapNotify:
346 if (appPort)
347 appPort->SetRenderGUI(false);
348 break;
351 case FocusIn:
353 if (m_xic)
354 XSetICFocus(m_xic);
355 g_application.m_AppFocused = true;
356 m_keymodState = 0;
357 if (serial == xevent.xfocus.serial)
358 break;
359 m_winSystem.NotifyAppFocusChange(g_application.m_AppFocused);
360 break;
363 case FocusOut:
365 if (m_xic)
366 XUnsetICFocus(m_xic);
367 g_application.m_AppFocused = false;
368 m_winSystem.NotifyAppFocusChange(g_application.m_AppFocused);
369 serial = xevent.xfocus.serial;
370 break;
373 case Expose:
375 CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
376 break;
379 case ConfigureNotify:
381 if (xevent.xconfigure.window != m_window)
382 break;
384 m_structureChanged = true;
385 XBMC_Event newEvent = {};
386 newEvent.type = XBMC_VIDEORESIZE;
387 newEvent.resize.w = xevent.xconfigure.width;
388 newEvent.resize.h = xevent.xconfigure.height;
389 if (appPort)
390 ret |= appPort->OnEvent(newEvent);
391 CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
392 break;
395 case ClientMessage:
397 if ((unsigned int)xevent.xclient.data.l[0] == m_wmDeleteMessage)
398 if (!g_application.m_bStop)
399 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
400 break;
403 case KeyPress:
405 XBMC_Event newEvent = {};
406 newEvent.type = XBMC_KEYDOWN;
407 KeySym xkeysym;
409 // fallback if we have no IM
410 if (!m_xic)
412 static XComposeStatus state;
413 char keybuf[32];
414 XLookupString(&xevent.xkey, NULL, 0, &xkeysym, NULL);
415 newEvent.key.keysym.sym = LookupXbmcKeySym(xkeysym);
416 newEvent.key.keysym.scancode = xevent.xkey.keycode;
417 if (XLookupString(&xevent.xkey, keybuf, sizeof(keybuf), NULL, &state))
419 newEvent.key.keysym.unicode = keybuf[0];
421 ret |= ProcessKey(newEvent);
422 break;
425 Status status;
426 int len;
427 len = Xutf8LookupString(m_xic, &xevent.xkey,
428 m_keybuf, m_keybuf_len,
429 &xkeysym, &status);
430 if (status == XBufferOverflow)
432 m_keybuf_len = len;
433 m_keybuf = (char*)realloc(m_keybuf, m_keybuf_len);
434 if (m_keybuf == nullptr)
435 throw std::runtime_error("Failed to realloc memory, insufficient memory available");
436 len = Xutf8LookupString(m_xic, &xevent.xkey,
437 m_keybuf, m_keybuf_len,
438 &xkeysym, &status);
440 switch (status)
442 case XLookupNone:
443 break;
444 case XLookupChars:
445 case XLookupBoth:
447 std::string data(m_keybuf, len);
448 std::wstring keys;
449 g_charsetConverter.utf8ToW(data, keys, false);
451 if (keys.length() == 0)
453 break;
456 for (unsigned int i = 0; i < keys.length() - 1; i++)
458 newEvent.key.keysym.sym = XBMCK_UNKNOWN;
459 newEvent.key.keysym.unicode = keys[i];
460 ret |= ProcessKey(newEvent);
462 if (keys.length() > 0)
464 newEvent.key.keysym.scancode = xevent.xkey.keycode;
465 XLookupString(&xevent.xkey, NULL, 0, &xkeysym, NULL);
466 newEvent.key.keysym.sym = LookupXbmcKeySym(xkeysym);
467 newEvent.key.keysym.unicode = keys[keys.length() - 1];
469 ret |= ProcessKey(newEvent);
471 break;
474 case XLookupKeySym:
476 newEvent.key.keysym.scancode = xevent.xkey.keycode;
477 newEvent.key.keysym.sym = LookupXbmcKeySym(xkeysym);
478 ret |= ProcessKey(newEvent);
479 break;
482 }// switch status
483 break;
484 } //KeyPress
486 case KeyRelease:
488 // if we have a queued press directly after, this is a repeat
489 if (XEventsQueued(m_display, QueuedAfterReading))
491 XEvent next_event;
492 XPeekEvent(m_display, &next_event);
493 if (next_event.type == KeyPress &&
494 next_event.xkey.window == xevent.xkey.window &&
495 next_event.xkey.keycode == xevent.xkey.keycode &&
496 (next_event.xkey.time - xevent.xkey.time < 2))
497 continue;
500 XBMC_Event newEvent = {};
501 KeySym xkeysym;
502 newEvent.type = XBMC_KEYUP;
503 xkeysym = XLookupKeysym(&xevent.xkey, 0);
504 newEvent.key.keysym.scancode = xevent.xkey.keycode;
505 newEvent.key.keysym.sym = LookupXbmcKeySym(xkeysym);
506 ret |= ProcessKey(newEvent);
507 break;
510 case EnterNotify:
512 break;
515 // lose mouse coverage
516 case LeaveNotify:
518 CServiceBroker::GetInputManager().SetMouseActive(false);
519 break;
522 case MotionNotify:
524 if (xevent.xmotion.window != m_window)
525 break;
526 XBMC_Event newEvent = {};
527 newEvent.type = XBMC_MOUSEMOTION;
528 newEvent.motion.x = (int16_t)xevent.xmotion.x;
529 newEvent.motion.y = (int16_t)xevent.xmotion.y;
530 if (appPort)
531 ret |= appPort->OnEvent(newEvent);
532 break;
535 case ButtonPress:
537 XBMC_Event newEvent = {};
538 newEvent.type = XBMC_MOUSEBUTTONDOWN;
539 newEvent.button.button = (unsigned char)xevent.xbutton.button;
540 newEvent.button.x = (int16_t)xevent.xbutton.x;
541 newEvent.button.y = (int16_t)xevent.xbutton.y;
542 if (appPort)
543 ret |= appPort->OnEvent(newEvent);
544 break;
547 case ButtonRelease:
549 XBMC_Event newEvent = {};
550 newEvent.type = XBMC_MOUSEBUTTONUP;
551 newEvent.button.button = (unsigned char)xevent.xbutton.button;
552 newEvent.button.x = (int16_t)xevent.xbutton.x;
553 newEvent.button.y = (int16_t)xevent.xbutton.y;
554 if (appPort)
555 ret |= appPort->OnEvent(newEvent);
556 break;
559 default:
561 break;
563 }// switch event.type
564 }// while
566 if (m_display && m_xrrEventPending && m_xrrFailSafeTimer.IsTimePast())
568 CLog::Log(LOGERROR,"CWinEventsX11::MessagePump - missed XRR Events");
569 m_winSystem.NotifyXRREvent();
570 m_xrrEventPending = false;
573 return ret;
576 bool CWinEventsX11::ProcessKey(XBMC_Event &event)
578 if (event.type == XBMC_KEYDOWN)
580 // check key modifiers
581 switch(event.key.keysym.sym)
583 case XBMCK_LSHIFT:
584 m_keymodState |= XBMCKMOD_LSHIFT;
585 break;
586 case XBMCK_RSHIFT:
587 m_keymodState |= XBMCKMOD_RSHIFT;
588 break;
589 case XBMCK_LCTRL:
590 m_keymodState |= XBMCKMOD_LCTRL;
591 break;
592 case XBMCK_RCTRL:
593 m_keymodState |= XBMCKMOD_RCTRL;
594 break;
595 case XBMCK_LALT:
596 m_keymodState |= XBMCKMOD_LALT;
597 break;
598 case XBMCK_RALT:
599 m_keymodState |= XBMCKMOD_RCTRL;
600 break;
601 case XBMCK_LMETA:
602 m_keymodState |= XBMCKMOD_LMETA;
603 break;
604 case XBMCK_RMETA:
605 m_keymodState |= XBMCKMOD_RMETA;
606 break;
607 case XBMCK_MODE:
608 m_keymodState |= XBMCKMOD_MODE;
609 break;
610 default:
611 break;
613 event.key.keysym.mod = (XBMCMod)m_keymodState;
615 else if (event.type == XBMC_KEYUP)
617 switch(event.key.keysym.sym)
619 case XBMCK_LSHIFT:
620 m_keymodState &= ~XBMCKMOD_LSHIFT;
621 break;
622 case XBMCK_RSHIFT:
623 m_keymodState &= ~XBMCKMOD_RSHIFT;
624 break;
625 case XBMCK_LCTRL:
626 m_keymodState &= ~XBMCKMOD_LCTRL;
627 break;
628 case XBMCK_RCTRL:
629 m_keymodState &= ~XBMCKMOD_RCTRL;
630 break;
631 case XBMCK_LALT:
632 m_keymodState &= ~XBMCKMOD_LALT;
633 break;
634 case XBMCK_RALT:
635 m_keymodState &= ~XBMCKMOD_RCTRL;
636 break;
637 case XBMCK_LMETA:
638 m_keymodState &= ~XBMCKMOD_LMETA;
639 break;
640 case XBMCK_RMETA:
641 m_keymodState &= ~XBMCKMOD_RMETA;
642 break;
643 case XBMCK_MODE:
644 m_keymodState &= ~XBMCKMOD_MODE;
645 break;
646 default:
647 break;
649 event.key.keysym.mod = (XBMCMod)m_keymodState;
652 std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
653 if (appPort)
654 appPort->OnEvent(event);
655 return true;
658 XBMCKey CWinEventsX11::LookupXbmcKeySym(KeySym keysym)
660 // try direct mapping first
661 std::map<uint32_t, uint32_t>::iterator it;
662 it = m_symLookupTable.find(keysym);
663 if (it != m_symLookupTable.end())
665 return (XBMCKey)(it->second);
668 // try ascii mappings
669 if (keysym>>8 == 0x00)
670 return (XBMCKey)tolower(keysym & 0xFF);
672 return (XBMCKey)keysym;