Blob Blame History Raw
diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx
index 47fe8f8..8f387dc 100644
--- a/vncviewer/DesktopWindow.cxx
+++ b/vncviewer/DesktopWindow.cxx
@@ -1,16 +1,16 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
  * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
- * 
+ *
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
- * 
+ *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public License
  * along with this software; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
@@ -662,20 +662,16 @@ int DesktopWindow::fltkHandle(int event, Fl_Window *win)
 
   if (dw && fullscreenSystemKeys) {
     switch (event) {
+    // Focus might not stay with us just because we have grabbed the
+    // keyboard. E.g. we might have sub windows, or we're not using
+    // all monitors and the user clicked on another application.
+    // Make sure we update our grabs with the focus changes.
     case FL_FOCUS:
-      // FIXME: We reassert the keyboard grabbing on focus as FLTK there are
-      //        some issues we need to work around:
-      //        a) Fl::grab(0) on X11 will release the keyboard grab for us.
-      //        b) Gaining focus on the system level causes FLTK to switch
-      //           window level on OS X.
       if (dw->fullscreen_active())
         dw->grabKeyboard();
       break;
 
     case FL_UNFOCUS:
-      // FIXME: We need to relinquish control when the entire window loses
-      //        focus as it is very tied to this specific window on some
-      //        platforms and we want to be able to open subwindows.
       dw->ungrabKeyboard();
       break;
     }
@@ -729,6 +725,23 @@ void DesktopWindow::fullscreen_on()
   fullscreen();
 }
 
+#if !defined(WIN32) && !defined(__APPLE__)
+Bool eventIsFocusWithSerial(Display *display, XEvent *event, XPointer arg)
+{
+  unsigned long serial;
+
+  serial = *(unsigned long*)arg;
+
+  if (event->xany.serial != serial)
+    return False;
+
+  if ((event->type != FocusIn) && (event->type != FocusOut))
+   return False;
+
+  return True;
+}
+#endif
+
 void DesktopWindow::grabKeyboard()
 {
   // Grabbing the keyboard is fairly safe as FLTK reroutes events to the
@@ -739,19 +752,24 @@ void DesktopWindow::grabKeyboard()
 
 #if defined(WIN32)
   int ret;
-  
+
   ret = win32_enable_lowlevel_keyboard(fl_xid(this));
   if (ret != 0)
     vlog.error(_("Failure grabbing keyboard"));
 #elif defined(__APPLE__)
   int ret;
-  
+
   ret = cocoa_capture_display(this, fullScreenAllMonitors);
   if (ret != 0)
     vlog.error(_("Failure grabbing keyboard"));
 #else
   int ret;
 
+  XEvent xev;
+  unsigned long serial;
+
+  serial = XNextRequest(fl_display);
+
   ret = XGrabKeyboard(fl_display, fl_xid(this), True,
                       GrabModeAsync, GrabModeAsync, CurrentTime);
   if (ret) {
@@ -774,6 +792,16 @@ void DesktopWindow::grabKeyboard()
                      None, None, CurrentTime);
   if (ret)
     vlog.error(_("Failure grabbing mouse"));
+
+  // Xorg 1.20+ generates FocusIn/FocusOut even when there is no actual
+  // change of focus. This causes us to get stuck in an endless loop
+  // grabbing and ungrabbing the keyboard. Avoid this by filtering out
+  // any focus events generated by XGrabKeyboard().
+  XSync(fl_display, False);
+  while (XCheckIfEvent(fl_display, &xev, &eventIsFocusWithSerial,
+                       (XPointer)&serial) == True) {
+    vlog.debug("Ignored synthetic focus event cause by grab change");
+  }
 #endif
 }
 
@@ -791,8 +819,20 @@ void DesktopWindow::ungrabKeyboard()
   if (Fl::grab())
     return;
 
+  XEvent xev;
+  unsigned long serial;
+
+  serial = XNextRequest(fl_display);
+
   XUngrabPointer(fl_display, fl_event_time);
   XUngrabKeyboard(fl_display, fl_event_time);
+
+  // See grabKeyboard()
+  XSync(fl_display, False);
+  while (XCheckIfEvent(fl_display, &xev, &eventIsFocusWithSerial,
+                       (XPointer)&serial) == True) {
+    vlog.debug("Ignored synthetic focus event cause by grab change");
+  }
 #endif
 }