Merge pull request #4775 from jmarshallnz/empty_episode_playcount
[vuplus_xbmc] / xbmc / windowing / WinEventsSDL.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "system.h"
22 #ifdef HAS_SDL_WIN_EVENTS
23
24 #include "WinEvents.h"
25 #include "WinEventsSDL.h"
26 #include "Application.h"
27 #include "ApplicationMessenger.h"
28 #include "GUIUserMessages.h"
29 #include "settings/DisplaySettings.h"
30 #include "guilib/GUIWindowManager.h"
31 #include "guilib/Key.h"
32 #ifdef HAS_SDL_JOYSTICK
33 #include "input/SDLJoystick.h"
34 #endif
35 #include "input/MouseStat.h"
36 #include "WindowingFactory.h"
37 #if defined(TARGET_DARWIN)
38 #include "osx/CocoaInterface.h"
39 #endif
40
41 #if defined(TARGET_POSIX) && !defined(TARGET_DARWIN) && !defined(TARGET_ANDROID)
42 #include <X11/Xlib.h>
43 #include <X11/XKBlib.h>
44 #include "input/XBMC_keysym.h"
45 #include "utils/log.h"
46 #endif
47
48 #if defined(TARGET_POSIX) && !defined(TARGET_DARWIN)
49 // The following chunk of code is Linux specific. For keys that have
50 // with keysym.sym set to zero it checks the scan code, and sets the sym
51 // for some known scan codes. This is mostly the multimedia keys.
52 // Note that the scan code to sym mapping is different with and without
53 // the evdev driver so we need to check if evdev is loaded.
54
55 // m_bEvdev == true if evdev is loaded
56 static bool m_bEvdev = true;
57 // We need to initialise some local storage once. Set m_bEvdevInit to true
58 // once the initialisation has been done
59 static bool m_bEvdevInit = false;
60
61 // Mappings of scancodes to syms for the evdev driver
62 static uint16_t SymMappingsEvdev[][2] =
63 { { 121, XBMCK_VOLUME_MUTE }         // Volume mute
64 , { 122, XBMCK_VOLUME_DOWN }         // Volume down
65 , { 123, XBMCK_VOLUME_UP }           // Volume up
66 , { 124, XBMCK_POWER }               // Power button on PC case
67 , { 127, XBMCK_SPACE }               // Pause
68 , { 135, XBMCK_MENU }                // Right click
69 , { 136, XBMCK_MEDIA_STOP }          // Stop
70 , { 138, 0x69 /* 'i' */}             // Info
71 , { 147, 0x6d /* 'm' */}             // Menu
72 , { 148, XBMCK_LAUNCH_APP2 }         // Launch app 2
73 , { 150, XBMCK_SLEEP }               // Sleep
74 , { 152, XBMCK_LAUNCH_APP1 }         // Launch app 1
75 , { 163, XBMCK_LAUNCH_MAIL }         // Launch Mail
76 , { 164, XBMCK_BROWSER_FAVORITES }   // Browser favorites
77 , { 166, XBMCK_BROWSER_BACK }        // Back
78 , { 167, XBMCK_BROWSER_FORWARD }     // Browser forward
79 , { 171, XBMCK_MEDIA_NEXT_TRACK }    // Next track
80 , { 172, XBMCK_MEDIA_PLAY_PAUSE }    // Play_Pause
81 , { 173, XBMCK_MEDIA_PREV_TRACK }    // Prev track
82 , { 174, XBMCK_MEDIA_STOP }          // Stop
83 , { 176, 0x72 /* 'r' */}             // Rewind
84 , { 179, XBMCK_LAUNCH_MEDIA_SELECT } // Launch media select
85 , { 180, XBMCK_BROWSER_HOME }        // Browser home
86 , { 181, XBMCK_BROWSER_REFRESH }     // Browser refresh
87 , { 214, XBMCK_ESCAPE }              // Close
88 , { 215, XBMCK_MEDIA_PLAY_PAUSE }    // Play_Pause
89 , { 216, 0x66 /* 'f' */}             // Forward
90 //, {167, 0xb3 } // Record
91 };
92
93 // The non-evdev mappings need to be checked. At the moment XBMC will never
94 // use them anyway.
95 static uint16_t SymMappingsUbuntu[][2] =
96 { { 0xf5, XBMCK_F13 } // F13 Launch help browser
97 , { 0x87, XBMCK_F14 } // F14 undo
98 , { 0x8a, XBMCK_F15 } // F15 redo
99 , { 0x89, 0x7f } // F16 new
100 , { 0xbf, 0x80 } // F17 open
101 , { 0xaf, 0x81 } // F18 close
102 , { 0xe4, 0x82 } // F19 reply
103 , { 0x8e, 0x83 } // F20 forward
104 , { 0xda, 0x84 } // F21 send
105 //, { 0x, 0x85 } // F22 check spell (doesn't work for me with ubuntu)
106 , { 0xd5, 0x86 } // F23 save
107 , { 0xb9, 0x87 } // 0x2a?? F24 print
108         // end of special keys above F1 till F12
109
110 , { 234,  XBMCK_BROWSER_BACK } // Browser back
111 , { 233,  XBMCK_BROWSER_FORWARD } // Browser forward
112 , { 231,  XBMCK_BROWSER_REFRESH } // Browser refresh
113 //, { , XBMCK_BROWSER_STOP } // Browser stop
114 , { 122,  XBMCK_BROWSER_SEARCH } // Browser search
115 , { 0xe5, XBMCK_BROWSER_SEARCH } // Browser search
116 , { 230,  XBMCK_BROWSER_FAVORITES } // Browser favorites
117 , { 130,  XBMCK_BROWSER_HOME } // Browser home
118 , { 0xa0, XBMCK_VOLUME_MUTE } // Volume mute
119 , { 0xae, XBMCK_VOLUME_DOWN } // Volume down
120 , { 0xb0, XBMCK_VOLUME_UP } // Volume up
121 , { 0x99, XBMCK_MEDIA_NEXT_TRACK } // Next track
122 , { 0x90, XBMCK_MEDIA_PREV_TRACK } // Prev track
123 , { 0xa4, XBMCK_MEDIA_STOP } // Stop
124 , { 0xa2, XBMCK_MEDIA_PLAY_PAUSE } // Play_Pause
125 , { 0xec, XBMCK_LAUNCH_MAIL } // Launch mail
126 , { 129,  XBMCK_LAUNCH_MEDIA_SELECT } // Launch media_select
127 , { 198,  XBMCK_LAUNCH_APP1 } // Launch App1/PC icon
128 , { 0xa1, XBMCK_LAUNCH_APP2 } // Launch App2/Calculator
129 , { 34, 0x3b /* vkey 0xba */} // OEM 1: [ on us keyboard
130 , { 51, 0x2f /* vkey 0xbf */} // OEM 2: additional key on european keyboards between enter and ' on us keyboards
131 , { 47, 0x60 /* vkey 0xc0 */} // OEM 3: ; on us keyboards
132 , { 20, 0xdb } // OEM 4: - on us keyboards (between 0 and =)
133 , { 49, 0xdc } // OEM 5: ` on us keyboards (below ESC)
134 , { 21, 0xdd } // OEM 6: =??? on us keyboards (between - and backspace)
135 , { 48, 0xde } // OEM 7: ' on us keyboards (on right side of ;)
136 //, { 0, 0xdf } // OEM 8
137 , { 94, 0xe2 } // OEM 102: additional key on european keyboards between left shift and z on us keyboards
138 //, { 0xb2, 0x } // Ubuntu default setting for launch browser
139 //, { 0x76, 0x } // Ubuntu default setting for launch music player
140 //, { 0xcc, 0x } // Ubuntu default setting for eject
141 , { 117, 0x5d } // right click
142 };
143
144 // Called once. Checks whether evdev is loaded and sets m_bEvdev accordingly.
145 static void InitEvdev(void)
146 {
147   // Set m_bEvdevInit to indicate we have been initialised
148   m_bEvdevInit = true;
149
150   Display* dpy = XOpenDisplay(NULL);
151   if (!dpy)
152   {
153     CLog::Log(LOGERROR, "CWinEventsSDL::CWinEventsSDL - XOpenDisplay failed");
154     return;
155   }
156
157   XkbDescPtr desc;
158   char* symbols;
159
160   desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
161   if(!desc)
162   {
163     XCloseDisplay(dpy);
164     CLog::Log(LOGERROR, "CWinEventsSDL::CWinEventsSDL - XkbGetKeyboard failed");
165     return;
166   }
167
168   symbols = XGetAtomName(dpy, desc->names->symbols);
169   if(symbols)
170   {
171     CLog::Log(LOGDEBUG, "CWinEventsSDL::CWinEventsSDL - XKb symbols %s", symbols);
172     if(strstr(symbols, "(evdev)"))
173       m_bEvdev = true;
174     else
175       m_bEvdev = false;
176   }
177
178   XFree(symbols);
179   XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
180   XCloseDisplay(dpy);
181
182   CLog::Log(LOGDEBUG, "CWinEventsSDL::CWinEventsSDL - m_bEvdev = %d", m_bEvdev);
183 }
184
185 // Check the scan code and return the matching sym, or zero if the scan code
186 // is unknown.
187 static uint16_t SymFromScancode(uint16_t scancode)
188 {
189   unsigned int i;
190
191   // We need to initialise m_bEvdev once
192   if (!m_bEvdevInit)
193     InitEvdev();
194
195   // If evdev is loaded look up the scan code in SymMappingsEvdev
196   if (m_bEvdev)
197   {
198     for (i = 0; i < sizeof(SymMappingsEvdev)/4; i++)
199       if (scancode == SymMappingsEvdev[i][0])
200         return SymMappingsEvdev[i][1];
201   }
202
203   // If evdev is not loaded look up the scan code in SymMappingsUbuntu
204   else
205   {
206     for (i = 0; i < sizeof(SymMappingsUbuntu)/4; i++)
207       if (scancode == SymMappingsUbuntu[i][0])
208         return SymMappingsUbuntu[i][1];
209   }
210
211   // Scan code wasn't found, return zero
212   return 0;
213 }
214 #endif // End of checks for keysym.sym == 0
215
216 bool CWinEventsSDL::MessagePump()
217 {
218   SDL_Event event;
219   bool ret = false;
220
221   while (SDL_PollEvent(&event))
222   {
223     switch(event.type)
224     {
225       case SDL_QUIT:
226         if (!g_application.m_bStop) 
227           CApplicationMessenger::Get().Quit();
228         break;
229
230 #ifdef HAS_SDL_JOYSTICK
231       case SDL_JOYBUTTONUP:
232       case SDL_JOYBUTTONDOWN:
233       case SDL_JOYAXISMOTION:
234       case SDL_JOYBALLMOTION:
235       case SDL_JOYHATMOTION:
236         g_Joystick.Update(event);
237         ret = true;
238         break;
239 #endif
240
241       case SDL_ACTIVEEVENT:
242         //If the window was inconified or restored
243         if( event.active.state & SDL_APPACTIVE )
244         {
245           g_application.SetRenderGUI(event.active.gain != 0);
246           g_Windowing.NotifyAppActiveChange(g_application.GetRenderGUI());
247         }
248         else if (event.active.state & SDL_APPINPUTFOCUS)
249       {
250         g_application.m_AppFocused = event.active.gain != 0;
251         g_Windowing.NotifyAppFocusChange(g_application.m_AppFocused);
252       }
253       break;
254
255       case SDL_KEYDOWN:
256       {
257         // process any platform specific shortcuts before handing off to XBMC
258 #ifdef TARGET_DARWIN_OSX
259         if (ProcessOSXShortcuts(event))
260         {
261           ret = true;
262           break;
263         }
264 #endif
265
266         XBMC_Event newEvent;
267         newEvent.type = XBMC_KEYDOWN;
268         newEvent.key.keysym.scancode = event.key.keysym.scancode;
269         newEvent.key.keysym.sym = (XBMCKey) event.key.keysym.sym;
270         newEvent.key.keysym.unicode = event.key.keysym.unicode;
271         newEvent.key.state = event.key.state;
272         newEvent.key.type = event.key.type;
273         newEvent.key.which = event.key.which;
274
275         // Check if the Windows keys are down because SDL doesn't flag this.
276         uint16_t mod = event.key.keysym.mod;
277         uint8_t* keystate = SDL_GetKeyState(NULL);
278         if (keystate[SDLK_LSUPER] || keystate[SDLK_RSUPER])
279           mod |= XBMCKMOD_LSUPER;
280         newEvent.key.keysym.mod = (XBMCMod) mod;
281
282 #if defined(TARGET_POSIX) && !defined(TARGET_DARWIN)
283         // If the keysym.sym is zero try to get it from the scan code
284         if (newEvent.key.keysym.sym == 0)
285           newEvent.key.keysym.sym = (XBMCKey) SymFromScancode(newEvent.key.keysym.scancode);
286 #endif
287
288         // don't handle any more messages in the queue until we've handled keydown,
289         // if a keyup is in the queue it will reset the keypress before it is handled.
290         ret |= g_application.OnEvent(newEvent);
291         break;
292       }
293
294       case SDL_KEYUP:
295       {
296         XBMC_Event newEvent;
297         newEvent.type = XBMC_KEYUP;
298         newEvent.key.keysym.scancode = event.key.keysym.scancode;
299         newEvent.key.keysym.sym = (XBMCKey) event.key.keysym.sym;
300         newEvent.key.keysym.mod =(XBMCMod) event.key.keysym.mod;
301         newEvent.key.keysym.unicode = event.key.keysym.unicode;
302         newEvent.key.state = event.key.state;
303         newEvent.key.type = event.key.type;
304         newEvent.key.which = event.key.which;
305
306         ret |= g_application.OnEvent(newEvent);
307         break;
308       }
309
310       case SDL_MOUSEBUTTONDOWN:
311       {
312         XBMC_Event newEvent;
313         newEvent.type = XBMC_MOUSEBUTTONDOWN;
314         newEvent.button.button = event.button.button;
315         newEvent.button.state = event.button.state;
316         newEvent.button.type = event.button.type;
317         newEvent.button.which = event.button.which;
318         newEvent.button.x = event.button.x;
319         newEvent.button.y = event.button.y;
320
321         ret |= g_application.OnEvent(newEvent);
322         break;
323       }
324
325       case SDL_MOUSEBUTTONUP:
326       {
327         XBMC_Event newEvent;
328         newEvent.type = XBMC_MOUSEBUTTONUP;
329         newEvent.button.button = event.button.button;
330         newEvent.button.state = event.button.state;
331         newEvent.button.type = event.button.type;
332         newEvent.button.which = event.button.which;
333         newEvent.button.x = event.button.x;
334         newEvent.button.y = event.button.y;
335
336         ret |= g_application.OnEvent(newEvent);
337         break;
338       }
339
340       case SDL_MOUSEMOTION:
341       {
342         if (0 == (SDL_GetAppState() & SDL_APPMOUSEFOCUS))
343         {
344           g_Mouse.SetActive(false);
345 #if defined(TARGET_DARWIN_OSX)
346           // See CApplication::ProcessSlow() for a description as to why we call Cocoa_HideMouse.
347           // this is here to restore the pointer when toggling back to window mode from fullscreen.
348           Cocoa_ShowMouse();
349 #endif
350           break;
351         }
352         XBMC_Event newEvent;
353         newEvent.type = XBMC_MOUSEMOTION;
354         newEvent.motion.xrel = event.motion.xrel;
355         newEvent.motion.yrel = event.motion.yrel;
356         newEvent.motion.state = event.motion.state;
357         newEvent.motion.type = event.motion.type;
358         newEvent.motion.which = event.motion.which;
359         newEvent.motion.x = event.motion.x;
360         newEvent.motion.y = event.motion.y;
361
362         ret |= g_application.OnEvent(newEvent);
363         break;
364       }
365       case SDL_VIDEORESIZE:
366       {
367         // Under linux returning from fullscreen, SDL sends an extra event to resize to the desktop
368         // resolution causing the previous window dimensions to be lost. This is needed to rectify
369         // that problem.
370         if(!g_Windowing.IsFullScreen())
371         {
372           int RES_SCREEN = g_Windowing.DesktopResolution(g_Windowing.GetCurrentScreen());
373           if((event.resize.w == CDisplaySettings::Get().GetResolutionInfo(RES_SCREEN).iWidth) &&
374               (event.resize.h == CDisplaySettings::Get().GetResolutionInfo(RES_SCREEN).iHeight))
375             break;
376         }
377         XBMC_Event newEvent;
378         newEvent.type = XBMC_VIDEORESIZE;
379         newEvent.resize.w = event.resize.w;
380         newEvent.resize.h = event.resize.h;
381         ret |= g_application.OnEvent(newEvent);
382         g_windowManager.MarkDirty();
383         break;
384       }
385       case SDL_USEREVENT:
386       {
387         XBMC_Event newEvent;
388         newEvent.type = XBMC_USEREVENT;
389         newEvent.user.code = event.user.code;
390         ret |= g_application.OnEvent(newEvent);
391         break;
392       }
393       case SDL_VIDEOEXPOSE:
394         g_windowManager.MarkDirty();
395         break;
396     }
397     memset(&event, 0, sizeof(SDL_Event));
398   }
399
400   return ret;
401 }
402
403 size_t CWinEventsSDL::GetQueueSize()
404 {
405   int ret;
406   SDL_Event event;
407
408   if (-1 == (ret = SDL_PeepEvents(&event, 0, SDL_PEEKEVENT, ~0)))
409     ret = 0;
410
411   return ret;
412 }
413
414 #ifdef TARGET_DARWIN_OSX
415 bool CWinEventsSDL::ProcessOSXShortcuts(SDL_Event& event)
416 {
417   static bool shift = false, cmd = false;
418
419   cmd   = !!(SDL_GetModState() & (KMOD_LMETA  | KMOD_RMETA ));
420   shift = !!(SDL_GetModState() & (KMOD_LSHIFT | KMOD_RSHIFT));
421
422   if (cmd && event.key.type == SDL_KEYDOWN)
423   {
424     switch(event.key.keysym.sym)
425     {
426     case SDLK_q:  // CMD-q to quit
427       if (!g_application.m_bStop)
428         CApplicationMessenger::Get().Quit();
429       return true;
430
431     case SDLK_f: // CMD-f to toggle fullscreen
432       g_application.OnAction(CAction(ACTION_TOGGLE_FULLSCREEN));
433       return true;
434
435     case SDLK_s: // CMD-3 to take a screenshot
436       g_application.OnAction(CAction(ACTION_TAKE_SCREENSHOT));
437       return true;
438
439     case SDLK_h: // CMD-h to hide
440       g_Windowing.Hide();
441       return true;
442
443     case SDLK_m: // CMD-m to minimize
444       CApplicationMessenger::Get().Minimize();
445       return true;
446
447     case SDLK_v: // CMD-v to paste clipboard text
448       if (g_Windowing.IsTextInputEnabled())
449       {
450         const char *szStr = Cocoa_Paste();
451         if (szStr)
452         {
453           CGUIMessage msg(GUI_MSG_INPUT_TEXT, 0, 0);
454           msg.SetLabel(szStr);
455           g_windowManager.SendMessage(msg, g_windowManager.GetFocusedWindow());
456         }
457       }
458       return true;
459
460     default:
461       return false;
462     }
463   }
464
465   return false;
466 }
467
468 #elif defined(TARGET_POSIX)
469
470 bool CWinEventsSDL::ProcessLinuxShortcuts(SDL_Event& event)
471 {
472   bool alt = false;
473
474   alt = !!(SDL_GetModState() & (XBMCKMOD_LALT  | XBMCKMOD_RALT));
475
476   if (alt && event.key.type == SDL_KEYDOWN)
477   {
478     switch(event.key.keysym.sym)
479     {
480       case SDLK_TAB:  // ALT+TAB to minimize/hide
481         g_application.Minimize();
482         return true;
483       default:
484         break;
485     }
486   }
487   return false;
488 }
489 #endif
490
491 #endif