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.
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"
27 #include <X11/XF86keysym.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
}
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
}
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
}
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
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
}
90 , {XK_Page_Up
, XBMCK_PAGEUP
}
91 , {XK_Page_Down
, XBMCK_PAGEDOWN
}
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
}
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()
154 bool CWinEventsX11::Init(Display
*dpy
, Window win
)
161 m_keybuf_len
= 32*sizeof(char);
162 m_keybuf
= (char*)malloc(m_keybuf_len
);
164 m_wmDeleteMessage
= XInternAtom(dpy
, "WM_DELETE_WINDOW", False
);
165 m_structureChanged
= false;
166 m_xrrEventPending
= false;
169 char *old_locale
= NULL
, *old_modifiers
= NULL
;
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
);
180 old_locale
= (char*)malloc(strlen(p
) +1);
181 strcpy(old_locale
, p
);
183 p
= XSetLocaleModifiers(NULL
);
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
198 setlocale(LC_ALL
, old_locale
);
203 XSetLocaleModifiers(old_modifiers
);
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
,
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
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
);
240 void CWinEventsX11::Quit()
247 XUnsetICFocus(m_xic
);
258 m_symLookupTable
.clear();
263 bool CWinEventsX11::HasStructureChanged()
268 bool ret
= m_structureChanged
;
269 m_structureChanged
= false;
273 void CWinEventsX11::SetXRRFailSafeTimer(std::chrono::milliseconds duration
)
278 m_xrrFailSafeTimer
.Set(duration
);
279 m_xrrEventPending
= true;
282 bool CWinEventsX11::MessagePump()
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
)
302 if (m_xrrEventPending
)
304 m_winSystem
.NotifyXRREvent();
305 m_xrrEventPending
= false;
306 serial
= xevent
.xgeneric
.serial
;
311 else if (m_display
&& (xevent
.type
== m_RREventBase
+ RRNotify
))
313 if (xevent
.xgeneric
.serial
== serial
)
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
;
332 if (XFilterEvent(&xevent
, None
))
340 appPort
->SetRenderGUI(true);
347 appPort
->SetRenderGUI(false);
355 g_application
.m_AppFocused
= true;
357 if (serial
== xevent
.xfocus
.serial
)
359 m_winSystem
.NotifyAppFocusChange(g_application
.m_AppFocused
);
366 XUnsetICFocus(m_xic
);
367 g_application
.m_AppFocused
= false;
368 m_winSystem
.NotifyAppFocusChange(g_application
.m_AppFocused
);
369 serial
= xevent
.xfocus
.serial
;
375 CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
379 case ConfigureNotify
:
381 if (xevent
.xconfigure
.window
!= m_window
)
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
;
390 ret
|= appPort
->OnEvent(newEvent
);
391 CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
397 if ((unsigned int)xevent
.xclient
.data
.l
[0] == m_wmDeleteMessage
)
398 if (!g_application
.m_bStop
)
399 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT
);
405 XBMC_Event newEvent
= {};
406 newEvent
.type
= XBMC_KEYDOWN
;
409 // fallback if we have no IM
412 static XComposeStatus state
;
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
);
427 len
= Xutf8LookupString(m_xic
, &xevent
.xkey
,
428 m_keybuf
, m_keybuf_len
,
430 if (status
== XBufferOverflow
)
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
,
447 std::string
data(m_keybuf
, len
);
449 g_charsetConverter
.utf8ToW(data
, keys
, false);
451 if (keys
.length() == 0)
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
);
476 newEvent
.key
.keysym
.scancode
= xevent
.xkey
.keycode
;
477 newEvent
.key
.keysym
.sym
= LookupXbmcKeySym(xkeysym
);
478 ret
|= ProcessKey(newEvent
);
488 // if we have a queued press directly after, this is a repeat
489 if (XEventsQueued(m_display
, QueuedAfterReading
))
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))
500 XBMC_Event newEvent
= {};
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
);
515 // lose mouse coverage
518 CServiceBroker::GetInputManager().SetMouseActive(false);
524 if (xevent
.xmotion
.window
!= m_window
)
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
;
531 ret
|= appPort
->OnEvent(newEvent
);
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
;
543 ret
|= appPort
->OnEvent(newEvent
);
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
;
555 ret
|= appPort
->OnEvent(newEvent
);
563 }// switch event.type
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;
576 bool CWinEventsX11::ProcessKey(XBMC_Event
&event
)
578 if (event
.type
== XBMC_KEYDOWN
)
580 // check key modifiers
581 switch(event
.key
.keysym
.sym
)
584 m_keymodState
|= XBMCKMOD_LSHIFT
;
587 m_keymodState
|= XBMCKMOD_RSHIFT
;
590 m_keymodState
|= XBMCKMOD_LCTRL
;
593 m_keymodState
|= XBMCKMOD_RCTRL
;
596 m_keymodState
|= XBMCKMOD_LALT
;
599 m_keymodState
|= XBMCKMOD_RCTRL
;
602 m_keymodState
|= XBMCKMOD_LMETA
;
605 m_keymodState
|= XBMCKMOD_RMETA
;
608 m_keymodState
|= XBMCKMOD_MODE
;
613 event
.key
.keysym
.mod
= (XBMCMod
)m_keymodState
;
615 else if (event
.type
== XBMC_KEYUP
)
617 switch(event
.key
.keysym
.sym
)
620 m_keymodState
&= ~XBMCKMOD_LSHIFT
;
623 m_keymodState
&= ~XBMCKMOD_RSHIFT
;
626 m_keymodState
&= ~XBMCKMOD_LCTRL
;
629 m_keymodState
&= ~XBMCKMOD_RCTRL
;
632 m_keymodState
&= ~XBMCKMOD_LALT
;
635 m_keymodState
&= ~XBMCKMOD_RCTRL
;
638 m_keymodState
&= ~XBMCKMOD_LMETA
;
641 m_keymodState
&= ~XBMCKMOD_RMETA
;
644 m_keymodState
&= ~XBMCKMOD_MODE
;
649 event
.key
.keysym
.mod
= (XBMCMod
)m_keymodState
;
652 std::shared_ptr
<CAppInboundProtocol
> appPort
= CServiceBroker::GetAppPort();
654 appPort
->OnEvent(event
);
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
;