ecere/gui/drivers/XInterface: Centralized function to set _NET_WM_STATE hints
[sdk] / ecere / src / gui / drivers / XInterface.ec
index 1fbd0e2..bdc9585 100644 (file)
@@ -40,6 +40,7 @@ default:
 #include <X11/Xutil.h>
 #include <X11/XKBlib.h>
 #include <X11/keysym.h>
+#include <X11/cursorfont.h>
 #include <fcntl.h>
 #if !defined(ECERE_NO3D) && !defined(ECERE_NOGL)
 #include <GL/glx.h>
@@ -92,6 +93,9 @@ static bool gotAnXEvent = false;
 static XEvent xEvent;
 static int joystickFD[4];
 static X11Window activeWindow;
+static X11Cursor systemCursors[SystemCursor];
+
+static enum NETWMStateAction { remove = 0, add = 1, toggle = 2 };
 
 static enum AtomIdents
 {
@@ -102,10 +106,11 @@ static enum AtomIdents
    _net_wm_window_type_desktop, _net_wm_window_type_dialog, _net_wm_window_type_dock, _net_wm_window_type_dropdown_menu,
    _net_wm_window_type_menu, _net_wm_window_type_normal, _net_wm_window_type_popup_menu, _net_wm_window_type_splash,
    _net_wm_window_type_toolbar, _net_wm_window_type_utility, _net_workarea, _net_frame_extents, _net_request_frame_extents,
-   _net_wm_state_maximized_vert, _net_wm_state_maximized_horz, _net_wm_state_modal, app_selection
+   _net_wm_state_maximized_vert, _net_wm_state_maximized_horz, _net_wm_state_modal, app_selection, _net_supported
 };
 
 static Atom atoms[AtomIdents];
+static bool atomsSupported[AtomIdents];
 
 static const char *atomNames[AtomIdents] = {
    "CLIPBOARD", //clipboard
@@ -147,7 +152,8 @@ static const char *atomNames[AtomIdents] = {
    "_NET_WM_STATE_MAXIMIZED_VERT", // _net_wm_state_maximized_vert
    "_NET_WM_STATE_MAXIMIZED_HORZ", // _net_wm_state_maximized_horz
    "_NET_WM_STATE_MODAL", // _net_wm_state_modal
-   "APP_SELECTION"
+   "APP_SELECTION",
+   "_NET_SUPPORTED"
 };
 /*
 _NET_WM_STATE_STICKY, ATOM
@@ -170,6 +176,70 @@ PixelFormat xSystemPixelFormat;
 Visual * xSystemVisual;
 bool xSharedMemory;
 
+static void SetNETWMState(X11Window windowHandle, bool throughRoot, NETWMStateAction action, Atom atom1, Atom atom2)
+{
+   if(atomsSupported[_net_wm_state])
+   {
+      int format;
+      unsigned long count, fill;
+      Atom type;
+      char * data = null;
+      uint state = WithdrawnState;
+
+      /*if(XGetWindowProperty(xGlobalDisplay, windowHandle, atoms[wm_state], 0, 3, False,
+                 atoms[wm_state], &type, &format, &count, &fill, &data) == Success && count)
+      {
+         state = *(uint *)data;
+         XFree(data);
+      } */
+      if(!throughRoot) //state == WithdrawnState)
+      {
+         // We need to handle modifying these ourselves for withdrawn windows...
+         if(action == add)
+         {
+            Atom values[2] = { atom1, atom2 };
+            XChangeProperty(xGlobalDisplay, windowHandle, atoms[_net_wm_state], XA_ATOM,
+               32, PropModeAppend, (byte *)values, atom2 ? 2 : 1);
+         }
+         else if(XGetWindowProperty(xGlobalDisplay, windowHandle, atoms[_net_wm_state], 0, 32, False,
+             XA_ATOM, &type, &format, &count, &fill, &data) == Success)
+         {
+            Atom * values = (Atom *) data;
+            int i;
+            for (i = 0; i < count; i++)
+            {
+               if(values[i] == atom1 || (atom2 && values[i] == atom2))
+               {
+                  if(i < count - 1)
+                     memmove(values + i, values + i + 1, sizeof(Atom) * (count - i - 1));
+                  count--;
+                  i--;
+               }
+            }
+            XChangeProperty(xGlobalDisplay, windowHandle, atoms[_net_wm_state], XA_ATOM, 32, PropModeReplace, (byte *)values, (int)count);
+            XFree(data);
+         }
+      }
+      else
+      {
+         XClientMessageEvent event = { 0 };
+         event.type = ClientMessage;
+         event.message_type = atoms[_net_wm_state];
+         event.display = xGlobalDisplay;
+         event.serial = 0;
+         event.window = windowHandle;
+         event.send_event = 1;
+         event.format = 32;
+         event.data.l[0] = action;
+         event.data.l[1] = atom1;
+         event.data.l[2] = atom2;
+         XSendEvent(xGlobalDisplay, DefaultRootWindow(xGlobalDisplay), bool::false,
+            SubstructureRedirectMask | SubstructureNotifyMask, (union _XEvent *)&event);
+      }
+   }
+
+}
+
 static Time timeStamp;
 
 class XWindowData : struct
@@ -248,7 +318,7 @@ static void RepositionDesktop(bool updateChildren)
    h = XDisplayHeight(xGlobalDisplay, DefaultScreen(xGlobalDisplay));
    x_root = XRootWindowOfScreen(x_screen);
 
-   if(atoms[_net_number_of_desktops] != None)
+   if(atomsSupported[_net_number_of_desktops])
    {
       if(XGetWindowProperty(xGlobalDisplay, x_root, atoms[_net_number_of_desktops], 0, 1, False,
                             XA_CARDINAL, &type, &format, &len, &fill,
@@ -269,7 +339,7 @@ static void RepositionDesktop(bool updateChildren)
       }
    }
 
-   if(atoms[_net_current_desktop] != None)
+   if(atomsSupported[_net_current_desktop])
    {
       if(XGetWindowProperty(xGlobalDisplay, x_root, atoms[_net_current_desktop], 0, 1, False,
                             XA_CARDINAL, &type, &format, &len, &fill,
@@ -287,7 +357,7 @@ static void RepositionDesktop(bool updateChildren)
          //printf("_NET_CURRENT_DESKTOP is %d\n", current);
       }
    }
-   if(atoms[_net_workarea] != None)
+   if(atomsSupported[_net_workarea])
    {
       long *workareas;
 
@@ -891,7 +961,9 @@ static int MyXErrorHandler(X11Display * display, XErrorEvent * event)
    char buffer[1024];
    if(xGlobalDisplay)
       XGetErrorText(xGlobalDisplay, event->error_code, buffer, sizeof(buffer));
+#ifdef _DEBUG
    Logf("X Error: %s\n", buffer);
+#endif
    return 0;
 }
 
@@ -950,6 +1022,111 @@ static void WaitForViewableWindow(Window window)
    }
 }
 
+static bool RequestFrameExtents(Window window)
+{
+   if(window.nativeDecorations && frameExtentSupported != broken)
+   {
+      // Request decoration frame extents
+      XClientMessageEvent event = { 0 };
+      event.type = ClientMessage;
+      event.message_type = atoms[_net_request_frame_extents];
+      event.display = xGlobalDisplay;
+      event.serial = 0;
+      event.window = (X11Window)window.windowHandle;
+      event.send_event = 1;
+      window.windowHandle = (void *)window.windowHandle;
+      event.format = 32;
+
+      if(frameExtentSupported == unknown && !frameExtentRequest)
+      {
+         frameExtentRequest = GetTime();
+         frameExtentWindow = (X11Window)window.windowHandle;
+      }
+
+      XSendEvent(xGlobalDisplay, DefaultRootWindow(xGlobalDisplay), bool::false,
+         SubstructureRedirectMask | SubstructureNotifyMask, (union _XEvent *)&event);
+   }
+}
+
+static bool GetFrameExtents(Window window, bool update)
+{
+   XWindowData windowData = window.windowData;
+   bool result = false;
+   int format;
+   unsigned long len, fill;
+   Atom type;
+   char * data = null;
+
+   if(XGetWindowProperty(xGlobalDisplay, (X11Window)window.windowHandle,
+      atoms[_net_frame_extents], 0, 4,
+       False, XA_CARDINAL, &type, &format, &len,
+       &fill, &data) == Success && data)
+   {
+      long *extents = (long *)data;
+      bool change = extents[0] != windowData.decor.left ||
+                    extents[1] != windowData.decor.right ||
+                    extents[2] != windowData.decor.top ||
+                    extents[3] != windowData.decor.bottom;
+
+      bool hadFrameExtents = windowData.gotFrameExtents;
+      Box oldDecor = windowData.decor;
+
+      frameExtentSupported = working;
+      frameExtentWindow = 0;
+      frameExtentRequest = 0;
+
+      if(!hadFrameExtents || extents[0] || extents[1] || extents[2] || extents[3])
+      {
+         windowData.decor =
+         {
+            left = (int)extents[0], right  = (int)extents[1],
+            top  = (int)extents[2], bottom = (int)extents[3]
+         };
+         windowData.gotFrameExtents = true;
+         if(update && change && ((Window)window).clientSize.w > 0)
+         {
+            int x = window.position.x, y = window.position.y, w = window.size.w, h = window.size.h;
+            if(!hadFrameExtents && window.state != maximized)
+            {
+               window.ComputeAnchors(
+                  window.normalAnchor,
+                  window.normalSizeAnchor,
+                  &x, &y, &w, &h);
+            }
+            else
+            {
+               x += windowData.decor.left - oldDecor.left;
+               y += windowData.decor.top - oldDecor.top;
+
+               w += windowData.decor.left - oldDecor.left + windowData.decor.right - oldDecor.right;
+               h += windowData.decor.top - oldDecor.top   + windowData.decor.bottom - oldDecor.bottom;
+            }
+
+            if(window.state != maximized)
+            {
+               window.Position(x, y, w, h, true, true, true, true, false, !hadFrameExtents && window.state != maximized);
+               XInterface::UpdateRootWindow(window);
+            }
+         }
+         result = true;
+      }
+      XFree(data);
+   }
+   return result;
+}
+
+static bool WaitForFrameExtents(Window window)
+{
+   int attempts = 0;
+   //XFlush(xGlobalDisplay);
+   while(attempts++ < 10)
+   {
+      if(GetFrameExtents(window, false)) return true;
+      Sleep(1.0 / RESOLUTION);
+   }
+   return false;
+}
+
 /****************************************************************************
    /// DRIVER IMPLEMENTATION /////////////
 ****************************************************************************/
@@ -1069,6 +1246,16 @@ class XInterface : Interface
       joystickFD[2] = open("/dev/js2", O_RDONLY);
       joystickFD[3] = open("/dev/js3", O_RDONLY);
 
+      systemCursors[iBeam]    = XCreateFontCursor(xGlobalDisplay, XC_xterm);
+      systemCursors[cross]    = XCreateFontCursor(xGlobalDisplay, XC_tcross);
+      systemCursors[moving]   = XCreateFontCursor(xGlobalDisplay, XC_fleur);
+      systemCursors[sizeNESW] = XCreateFontCursor(xGlobalDisplay, XC_bottom_left_corner);
+      systemCursors[sizeNS]   = XCreateFontCursor(xGlobalDisplay, XC_sb_v_double_arrow);
+      systemCursors[sizeNWSE] = XCreateFontCursor(xGlobalDisplay, XC_bottom_right_corner);
+      systemCursors[sizeWE]   = XCreateFontCursor(xGlobalDisplay, XC_sb_h_double_arrow);
+      systemCursors[hand]     = XCreateFontCursor(xGlobalDisplay, XC_hand2);
+      systemCursors[arrow]    = XCreateFontCursor(xGlobalDisplay, XC_left_ptr);
+
       if(xGlobalDisplay)
       {
          XWindowAttributes attributes = { 0 };
@@ -1146,6 +1333,33 @@ class XInterface : Interface
 
             XInternAtoms(xGlobalDisplay, (char**)atomNames, AtomIdents::enumSize, False, atoms);
 
+            // Check which atoms are supported by the WM
+            {
+               int format;
+               unsigned long count, fill;
+               Atom type;
+               Atom * data;
+
+               if(XGetWindowProperty(xGlobalDisplay, DefaultRootWindow(xGlobalDisplay), atoms[_net_supported],
+                  0, 10000, False, XA_ATOM, &type, &format, &count, &fill, (void *)&data) == Success)
+               {
+                  int i;
+                  for (i = 0; i < count; i++)
+                  {
+                     AtomIdents j;
+                     for(j = 0; j < AtomIdents::enumSize; j++)
+                     {
+                        if(atoms[j] == data[i])
+                        {
+                           atomsSupported[j] = true;
+                           break;
+                        }
+                     }
+                  }
+                  XFree(data);
+               }
+            }
+
             {
                Atom protocols[2] = { atoms[wm_delete_window], atoms[wm_take_focus] };
 
@@ -1153,7 +1367,7 @@ class XInterface : Interface
             }
 
             /*
-            if(atoms[_net_workarea] == None)
+            if(atomsSupported[_net_workarea])
                printf("Warning: _NET_WORKAREA extension not supported\n");
             */
 
@@ -1253,6 +1467,16 @@ class XInterface : Interface
       delete timerThread;
       hiResTimer.Stop();
 
+      XFreeCursor(xGlobalDisplay, systemCursors[iBeam]);
+      XFreeCursor(xGlobalDisplay, systemCursors[cross]);
+      XFreeCursor(xGlobalDisplay, systemCursors[moving]);
+      XFreeCursor(xGlobalDisplay, systemCursors[sizeNESW]);
+      XFreeCursor(xGlobalDisplay, systemCursors[sizeNS]);
+      XFreeCursor(xGlobalDisplay, systemCursors[sizeNWSE]);
+      XFreeCursor(xGlobalDisplay, systemCursors[sizeWE]);
+      XFreeCursor(xGlobalDisplay, systemCursors[hand]);
+      XFreeCursor(xGlobalDisplay, systemCursors[arrow]);
+
       //XPutBackEvent(xGlobalDisplay, &e);
       // xThread.Wait();
       // delete xThread;
@@ -1362,6 +1586,12 @@ class XInterface : Interface
 
                   if(event->button == Button1)
                   {
+                     // Force a raise on click here to deal with confused active state preventing to bring the window up
+                     if(!atomsSupported[_net_active_window] && !window.isRemote)
+                     {
+                        XRaiseWindow(xGlobalDisplay, (X11Window)window.windowHandle);
+                        XSetInputFocus(xGlobalDisplay, (X11Window)window.windowHandle, RevertToPointerRoot, CurrentTime);
+                     }
                      button = __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonDown;
                      buttonDouble = __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftDoubleClick;
                      whichButton = 0;
@@ -1690,11 +1920,11 @@ class XInterface : Interface
                case ConfigureNotify:
                {
                   XConfigureEvent * event = (XConfigureEvent *) thisEvent;
+                  bool unmaximized = false;
                   while(XCheckIfEvent(xGlobalDisplay, (XEvent *)thisEvent, (void *)ConfigureNotifyChecker, (void *)window.windowHandle));
                   //if(event->x - desktopX != window.position.x || event->y - desktopY != window.position.y || event->width != window.size.w || event->height != window.size.h)
 
-                  // TODO: Support _NET_REQUEST_FRAME_EXTENTS message / _NET_FRAME_EXTENTS property for decoration size awareness
-                  if(window.nativeDecorations)
+                  if(atomsSupported[_net_wm_state]) //window.nativeDecorations)
                   {
                      int format;
                      unsigned long len, fill;
@@ -1720,50 +1950,93 @@ class XInterface : Interface
                         if(maxVert && maxHorz)
                         {
                            if(window.state != maximized)
+                           {
                               *&window.state = maximized;
+                              if(!window.nativeDecorations)
+                                 window.CreateSystemChildren();
+                           }
                         }
                         else if(isMinimized)
                         {
                            if(window.state != minimized)
+                           {
                               *&window.state = minimized;
+                              if(!window.nativeDecorations)
+                                 window.CreateSystemChildren();
+                           }
                         }
                         else if(window.state != normal)
+                        {
+                           if(window.state == maximized)
+                              unmaximized = true;
                            *&window.state = normal;
+                           if(!window.nativeDecorations)
+                              window.CreateSystemChildren();
+                        }
                      }
                   }
                   {
-                     int x = event->x;
-                     int y = event->y;
-                     int w = event->width, h = event->height;
-
-                     //if(event->send_event)
+                     int x, y, w, h;
+                     if(unmaximized)
                      {
-                        X11Window rootChild;
-                        int rootX, rootY;
-                        XTranslateCoordinates(xGlobalDisplay, event->window,
-                           RootWindow(xGlobalDisplay, DefaultScreen(xGlobalDisplay)), 0, 0,
-                           &rootX, &rootY, &rootChild);
-                        x = rootX;
-                        y = rootY;
+                        // Ensure we set the normal size anchor when un-maximizing
+                        if(window.nativeDecorations && RequestFrameExtents(window))
+                           WaitForFrameExtents(window);
+                        x = window.position.x, y = window.position.y, w = window.size.w, h = window.size.h;
+                        window.ComputeAnchors(window.normalAnchor, window.normalSizeAnchor, &x, &y, &w, &h);
+                     }
+                     else
+                     {
+                        x = event->x;
+                        y = event->y;
+                        w = event->width, h = event->height;
+
+                        //if(event->send_event)
+                        {
+                           X11Window rootChild;
+                           int rootX, rootY;
+                           XTranslateCoordinates(xGlobalDisplay, event->window,
+                              RootWindow(xGlobalDisplay, DefaultScreen(xGlobalDisplay)), 0, 0,
+                              &rootX, &rootY, &rootChild);
+                           x = rootX;
+                           y = rootY;
+                        }
+
+                        x -= desktopX;
+                        y -= desktopY;
+
+                        if(window.nativeDecorations && window.state != maximized)
+                        {
+                           x -= windowData.decor.left;
+                           y -= windowData.decor.top;
+                           w += windowData.decor.left + windowData.decor.right;
+                           h += windowData.decor.top + windowData.decor.bottom;
+                           /*
+                           x -= window.clientStart.x;
+                           y -= window.clientStart.y - (window.hasMenuBar ? skinMenuHeight : 0);
+                           w += window.size.w - window.clientSize.w;
+                           h += window.size.h - window.clientSize.h;
+                           */
+                        }
                      }
 
-                     x -= desktopX;
-                     y -= desktopY;
+                     // Break the anchors for moveable/resizable windows
+                     if(window.style.fixed && window.state == normal)
+                     {
+                        window.normalAnchor = Anchor { left = x, top = y };
+                        window.normalSizeAnchor = SizeAnchor { { w, h } };
+                        window.anchored = false;
+                     }
 
-                     if(window.nativeDecorations && window.state != maximized)
+                     // Break the anchors for moveable/resizable windows
+                     if(window.style.fixed && window.state == normal)
                      {
-                        x -= windowData.decor.left;
-                        y -= windowData.decor.top;
-                        w += windowData.decor.left + windowData.decor.right;
-                        h += windowData.decor.top + windowData.decor.bottom;
-                        /*
-                        x -= window.clientStart.x;
-                        y -= window.clientStart.y - (window.hasMenuBar ? skinMenuHeight : 0);
-                        w += window.size.w - window.clientSize.w;
-                        h += window.size.h - window.clientSize.h;
-                        */
+                        window.normalAnchor = Anchor { left = x, top = y };
+                        window.normalSizeAnchor = SizeAnchor { { w, h } };
+                        window.anchored = false;
                      }
-                     window.Position(x, y, w, h, true, true, true, true, false, false);
+
+                     window.Position(x, y, w, h, true, true, true, true, false, unmaximized);
                   }
                   break;
                }
@@ -1830,7 +2103,7 @@ class XInterface : Interface
                            {
                               XRaiseWindow(xGlobalDisplay, (X11Window)modalRoot.windowHandle);
                               WaitForViewableWindow(modalRoot);
-                              if(atoms[_net_active_window])
+                              if(atomsSupported[_net_active_window])
                               {
                                  XClientMessageEvent event = { 0 };
                                  event.type = ClientMessage;
@@ -1884,66 +2157,7 @@ class XInterface : Interface
                   if(event->atom == atoms[_net_frame_extents] &&
                     event->state == PropertyNewValue && windowData)
                   {
-                     int format;
-                     unsigned long len, fill;
-                     Atom type;
-                     char * data = null;
-
-                     if(XGetWindowProperty(xGlobalDisplay, (X11Window)window.windowHandle,
-                        atoms[_net_frame_extents], 0, 4,
-                         False, XA_CARDINAL, &type, &format, &len,
-                         &fill, &data) == Success && data)
-                     {
-                        long *extents = (long *)data;
-                        bool change = extents[0] != windowData.decor.left ||
-                                      extents[1] != windowData.decor.right ||
-                                      extents[2] != windowData.decor.top ||
-                                      extents[3] != windowData.decor.bottom;
-
-                        bool hadFrameExtents = windowData.gotFrameExtents;
-                        Box oldDecor = windowData.decor;
-
-                        frameExtentSupported = working;
-                        frameExtentWindow = 0;
-                        frameExtentRequest = 0;
-
-                        if(!hadFrameExtents || extents[0] || extents[1] || extents[2] || extents[3])
-                        {
-                           windowData.decor =
-                           {
-                              left = (int)extents[0], right  = (int)extents[1],
-                              top  = (int)extents[2], bottom = (int)extents[3]
-                           };
-                           windowData.gotFrameExtents = true;
-                           if(change && ((Window)window).clientSize.w > 0)
-                           {
-                              int x = window.position.x, y = window.position.y, w = window.size.w, h = window.size.h;
-                              if(!hadFrameExtents && window.state != maximized)
-                              {
-                                 window.ComputeAnchors(
-                                    window.normalAnchor,
-                                    window.normalSizeAnchor,
-                                    &x, &y, &w, &h);
-                              }
-                              else
-                              {
-                                 x += windowData.decor.left - oldDecor.left;
-                                 y += windowData.decor.top - oldDecor.top;
-
-                                 w += windowData.decor.left - oldDecor.left + windowData.decor.right - oldDecor.right;
-                                 h += windowData.decor.top - oldDecor.top   + windowData.decor.bottom - oldDecor.bottom;
-                              }
-
-                              if(window.state != maximized)
-                              {
-                                 window.Position(x, y, w, h, true, true, true, true, false, !hadFrameExtents && window.state != maximized);
-                                 UpdateRootWindow(window);
-                              }
-                           }
-                        }
-                        XFree(data);
-                     }
-                     else
+                     if(!GetFrameExtents(window, true))
                         windowData.gotFrameExtents = true; // Unsupported?
                   }
                   break;
@@ -2027,7 +2241,10 @@ class XInterface : Interface
       XIC ic = null;
       unsigned long mask = EVENT_MASK;
 
-      attributes.override_redirect = window.interim ? True : False;
+      // Old WM (e.g. TWM), use built-in decorations
+      if(!atomsSupported[_net_wm_state])
+         window.nativeDecorations = false;
+      attributes.override_redirect = (window.interim || (!atomsSupported[_net_wm_state] && !window.nativeDecorations)) ? True : False;
       attributes.event_mask = EVENT_MASK;
       //printf("%s\n", guiApp.defaultDisplayDriver);
 #if !defined(ECERE_VANILLA) && !defined(ECERE_NO3D) && !defined(ECERE_NOGL)
@@ -2162,6 +2379,14 @@ class XInterface : Interface
          else
          {
             X11Window parentWindow = (X11Window)null;
+            int x = window.position.x + desktopX, y = window.position.y + desktopY;
+            int w = window.state == normal ? Max(1, window.size.w) : Max(1, window.normalSizeAnchor.size.w);
+            int h = window.state == normal ? Max(1, window.size.h) : Max(1, window.normalSizeAnchor.size.h);
+            if(!window.nativeDecorations && window.state != normal)
+            {
+               w += window.size.w - window.clientSize.w;
+               h += window.size.h - window.clientSize.h;
+            }
 
             if(window.master.rootWindow && window.master.rootWindow != guiApp.desktop && (window._isModal || window.style.interim))
             {
@@ -2176,7 +2401,8 @@ class XInterface : Interface
                parentWindow = (X11Window)null;
 
             windowHandle = XCreateWindow(xGlobalDisplay, DefaultRootWindow(xGlobalDisplay),
-               0,0,1,1,0, depth, InputOutput, visual ? visual : CopyFromParent,
+               x, y, w, h,
+               0, depth, InputOutput, visual ? visual : CopyFromParent,
                CWEventMask | CWOverrideRedirect | (visual ? (CWColormap | CWBorderPixel) : 0), &attributes);
 
             if(parentWindow && (window.interim || window.isModal))
@@ -2187,10 +2413,7 @@ class XInterface : Interface
                //printf("Done.\n");
                //XChangeProperty(xGlobalDisplay, windowHandle, atoms[wm_transient_for], XA_WINDOW, 32, PropModeReplace, (unsigned char*)&parentWindow, 1);
                if(window.isModal)
-               {
-                  Atom hints[1] = { atoms[_net_wm_state_modal] };
-                  XChangeProperty(xGlobalDisplay, windowHandle, atoms[_net_wm_state], XA_ATOM, 32, PropModeReplace, (unsigned char*)&hints, 1);
-               }
+                  SetNETWMState(windowHandle, false, add, atoms[_net_wm_state_modal], 0);
             }
 
             {
@@ -2216,6 +2439,35 @@ class XInterface : Interface
                   Atom protocols[2] = { atoms[wm_delete_window], atoms[wm_take_focus] };
                   XSetWMProtocols(xGlobalDisplay, windowHandle, protocols, 2);
                }
+
+               // Set Normal hints for minimum/maximum size
+               if(true) //window.minSize.w || window.minSize.h || window.maxSize.w < MAXINT || window.maxSize.h < MAXINT)
+               {
+                  XSizeHints hints = { 0 };
+                  MinMaxValue mw, mh;
+                  window.SetWindowMinimum(&mw, &mh);
+                  if(window.minSize.w || window.minSize.h)
+                  {
+                     hints.min_width = Max(window.minSize.w, mw);
+                     hints.min_height = Max(window.minSize.h, mh);
+                     hints.flags |= PMinSize;
+                  }
+                  if(window.maxSize.w < MAXINT || window.minSize.h < MAXINT)
+                  {
+                     hints.max_width = window.maxSize.w;
+                     hints.max_height = window.maxSize.h;
+                     hints.flags |= PMaxSize;
+                  }
+                  hints.x = x;
+                  hints.y = y;
+                  hints.flags |= PPosition;
+
+                  hints.width = w;
+                  hints.height = h;
+                  hints.flags |= PSize;
+
+                  XSetWMNormalHints(xGlobalDisplay, windowHandle, &hints);
+               }
             }
          }
       }
@@ -2286,7 +2538,9 @@ class XInterface : Interface
             XChangeProperty(xGlobalDisplay, windowHandle, atoms[_motif_wm_hints], atoms[_motif_wm_hints], 32,
                PropModeReplace, (unsigned char*)&hints, 5);
          }
-         if(atoms[_net_wm_pid] != None)
+
+         // *** We set this for ourselves, so don't check atomsSupported !!! ***
+         if(atoms[_net_wm_pid])
          {
             int pid = getpid();
             // printf("Setting _NET_WM_PID to %d\n", pid);
@@ -2319,29 +2573,7 @@ class XInterface : Interface
          XUngrabPointer(xGlobalDisplay, CurrentTime);
       }
 
-      if(window.nativeDecorations && frameExtentSupported != broken)
-      {
-         // Request decoration frame extents
-         XClientMessageEvent event = { 0 };
-         event.type = ClientMessage;
-         event.message_type = atoms[_net_request_frame_extents];
-         event.display = xGlobalDisplay;
-         event.serial = 0;
-         event.window = (X11Window)windowHandle;
-         event.send_event = 1;
-         window.windowHandle = (void *)windowHandle;
-         event.format = 32;
-
-         if(frameExtentSupported == unknown && !frameExtentRequest)
-         {
-            frameExtentRequest = GetTime();
-            frameExtentWindow = windowHandle;
-         }
-
-         XSendEvent(xGlobalDisplay, DefaultRootWindow(xGlobalDisplay), bool::false,
-            SubstructureRedirectMask | SubstructureNotifyMask, (union _XEvent *)&event);
-      }
-      else
+      if(!window.nativeDecorations || !RequestFrameExtents(window))
          ((XWindowData)window.windowData).gotFrameExtents = true;
       return (void *)windowHandle;
    }
@@ -2391,7 +2623,7 @@ class XInterface : Interface
          bool visible = window.visible;
          if(window.visible && window.created)
             XMapWindow(xGlobalDisplay, (X11Window)window.windowHandle);
-         if(window.state == minimized) return;
+         if(window.state == minimized && atomsSupported[_net_wm_state]) return;
 
          if(window.nativeDecorations)
          {
@@ -2400,7 +2632,17 @@ class XInterface : Interface
             // Was commenting this out was part of #700/#795 fix, but this causes jumping of e.g. About box after getting frame extents PropertyNotify
 
                // && window.state != maximized -- required for Cinnamon on Mint 14/15
-            if(!windowData.gotFrameExtents && window.state != maximized) return;
+            if(!windowData.gotFrameExtents && window.state != maximized)
+            {
+               if(WaitForFrameExtents(window))
+               {
+                  x += windowData.decor.left;
+                  y += windowData.decor.top ;
+
+                  w += windowData.decor.left + windowData.decor.right;
+                  h += windowData.decor.top  + windowData.decor.bottom;
+               }
+            }
 
             x -= windowData.decor.left;
             y -= windowData.decor.top;
@@ -2409,22 +2651,25 @@ class XInterface : Interface
             h -= windowData.decor.top + windowData.decor.bottom;
 
             // Tweak for first unmaximize on Unity on Ubuntu 11.10
-            if(window.state == maximized && (desktopX + w > desktopW || desktopY + h > desktopH))
+            /*if(window.state == maximized && (desktopX + w > desktopW || desktopY + h > desktopH))
             {
                w -= 40;
                h -= 40;
-            }
+            }*/
          }
 
          x += desktopX;
          y += desktopY;
 
-         if(move && resize)
-            XMoveResizeWindow(xGlobalDisplay, (X11Window)window.windowHandle, x, y, w, h);
-         else if(move)
-            XMoveWindow(xGlobalDisplay, (X11Window)window.windowHandle, x, y);
-         else if(resize)
-            XResizeWindow(xGlobalDisplay, (X11Window)window.windowHandle, w, h);
+         if(!atomsSupported[_net_wm_state] || window.state != maximized)
+         {
+            if(move && resize)
+               XMoveResizeWindow(xGlobalDisplay, (X11Window)window.windowHandle, x, y, w, h);
+            else if(move)
+               XMoveWindow(xGlobalDisplay, (X11Window)window.windowHandle, x, y);
+            else if(resize)
+               XResizeWindow(xGlobalDisplay, (X11Window)window.windowHandle, w, h);
+         }
 #if defined(__APPLE__)
 //         if(window.created && !visible)
   //          XUnmapWindow(xGlobalDisplay, (X11Window)window.windowHandle);
@@ -2469,9 +2714,11 @@ class XInterface : Interface
       }
    }
 
-
    void SetRootWindowState(Window window, WindowState state, bool visible)
    {
+      // Old WM (e.g. TWM), use built-in decorations
+      if(!atomsSupported[_net_wm_state])
+         window.nativeDecorations = false;
       if(!window.parent || !window.parent.display)
       {
          //Logf("Set root window state %d %s\n", state, window.name);
@@ -2482,13 +2729,11 @@ class XInterface : Interface
             if(window.creationActivation == activate && state != minimized)
                ActivateRootWindow(window);
 
-            if(state == minimized)
+            if(state == minimized && atomsSupported[_net_wm_state])
             {
                uint iconic = IconicState;
-               /*
-               XChangeProperty(xGlobalDisplay, window.windowHandle, atoms[_net_wm_state], XA_ATOM, 32,
-                  PropModeReplace, (unsigned char*)&atoms[_net_wm_state_hidden], 1);
-               */
+
+               // SetNETWMState(window.windowHandle, true, add, atoms[_net_wm_state_hidden], null);
                /*
                XChangeProperty(xGlobalDisplay, window.windowHandle, atoms[wm_state], XA_CARDINAL, 32,
                   PropModeReplace, &iconic, 1);
@@ -2497,19 +2742,6 @@ class XInterface : Interface
                /*
                XClientMessageEvent event = { 0 };
                event.type = ClientMessage;
-               event.message_type = atoms[_net_wm_state];
-               event.display = xGlobalDisplay;
-               event.serial = 0;
-               event.window = window.windowHandle;
-               event.send_event = 1;
-               event.format = 32;
-               event.data.l[0] = 2; // 1;
-               event.data.l[1] = atoms[_net_wm_state_hidden];
-               XSendEvent(xGlobalDisplay, DefaultRootWindow(xGlobalDisplay), bool::false, SubstructureRedirectMask | SubstructureNotifyMask, &event);
-               */
-               /*
-               XClientMessageEvent event = { 0 };
-               event.type = ClientMessage;
                event.message_type = atoms[wm_state];
                event.display = xGlobalDisplay;
                event.window = window.windowHandle;
@@ -2524,48 +2756,47 @@ class XInterface : Interface
             }
             else
             {
-               if(!window.nativeDecorations || (!((XWindowData)window.windowData).gotFrameExtents && window.state == maximized)) //((XWindowData)window.windowData).gotFrameExtents && (!window.nativeDecorations || window.state == state))
+               //((XWindowData)window.windowData).gotFrameExtents && (!window.nativeDecorations || window.state == state))
+               if(!atomsSupported[_net_wm_state] || (!((XWindowData)window.windowData).gotFrameExtents && window.state == maximized))
                {
-                  int x = window.position.x;
-                  int y = window.position.y;
-                  int w = window.size.w;
-                  int h = window.size.h;
-
-                  if(window.nativeDecorations)
+                  // Running this block avoids the initial IDE maximized->unmaximized flicker
+                  //if(window.state != maximized || !atomsSupported[_net_wm_state] || window.nativeDecorations)
                   {
-                     XWindowData windowData = window.windowData;
-                     x -= windowData.decor.left;
-                     y -= windowData.decor.top;
+                     int x = window.position.x;
+                     int y = window.position.y;
+                     int w = window.size.w;
+                     int h = window.size.h;
+
+                     if(window.nativeDecorations)
+                     {
+                        XWindowData windowData = window.windowData;
+                        x -= windowData.decor.left;
+                        y -= windowData.decor.top;
+
+                        w -= windowData.decor.left + windowData.decor.right;
+                        h -= windowData.decor.top + windowData.decor.bottom;
+                     }
+                     x += desktopX;
+                     y += desktopY;
 
-                     w -= windowData.decor.left + windowData.decor.right;
-                     h -= windowData.decor.top + windowData.decor.bottom;
+                     XMoveResizeWindow(xGlobalDisplay,
+                        (X11Window)window.windowHandle,
+                        x, y, w, h);
                   }
-                  x += desktopX;
-                  y += desktopY;
-
-                  // With native decorations, we do it the first time
-                  // or the WM (Gnome) is sticking it to the top/right!
-                  XMoveResizeWindow(xGlobalDisplay,
-                     (X11Window)window.windowHandle,
-                     x, y, w, h);
                   UpdateRootWindow(window);
                }
-               if(window.nativeDecorations)
+               if(atomsSupported[_net_wm_state])
                {
                   // Maximize / Restore the window
-                  XClientMessageEvent event = { 0 };
-                  event.type = ClientMessage;
-                  event.message_type = atoms[_net_wm_state];
-                  event.display = xGlobalDisplay;
-                  event.serial = 0;
-                  event.window = (X11Window)window.windowHandle;
-                  event.send_event = 1;
-                  event.format = 32;
-                  event.data.l[0] = (state == maximized) ? 1 : 0;
-                  event.data.l[1] = atoms[_net_wm_state_maximized_vert];
-                  event.data.l[2] = atoms[_net_wm_state_maximized_horz];
-                  XSendEvent(xGlobalDisplay, DefaultRootWindow(xGlobalDisplay), bool::false,
-                     SubstructureRedirectMask | SubstructureNotifyMask, (union _XEvent *)&event);
+                  SetNETWMState((X11Window)window.windowHandle, true, state == maximized ? add: remove,
+                     atoms[_net_wm_state_maximized_vert], atoms[_net_wm_state_maximized_horz]);
+                  if(state == maximized)
+                  {
+                     // Prevent the code in ConfigureNotify to think the window has been unmaximized
+                     // if the Window Manager hasn't set the hints yet.
+                     XFlush(xGlobalDisplay);
+                     Sleep(0.01);
+                  }
                }
             }
          }
@@ -2577,18 +2808,8 @@ class XInterface : Interface
 
    void FlashRootWindow(Window window)
    {
-      XClientMessageEvent event = { 0 };
       // printf("Attempting to flash root window\n");
-      event.type = ClientMessage;
-      event.message_type = atoms[_net_wm_state];
-      event.display = xGlobalDisplay;
-      event.serial = 0;
-      event.window = (X11Window)window.windowHandle;
-      event.send_event = 1;
-      event.format = 32;
-      event.data.l[0] = 1;
-      event.data.l[1] = atoms[_net_wm_state_demands_attention];
-      XSendEvent(xGlobalDisplay, DefaultRootWindow(xGlobalDisplay), bool::false, SubstructureRedirectMask | SubstructureNotifyMask, (union _XEvent *)&event);
+      SetNETWMState((X11Window)window.windowHandle, true, add, atoms[_net_wm_state_demands_attention], 0);
    }
 
    void ActivateRootWindow(Window window)
@@ -2601,7 +2822,7 @@ class XInterface : Interface
             XRaiseWindow(xGlobalDisplay, (X11Window)window.windowHandle);
             XMapWindow(xGlobalDisplay, (X11Window)window.windowHandle);
             WaitForViewableWindow(window);
-            if(atoms[_net_active_window])
+            if(atomsSupported[_net_active_window])
             {
                XClientMessageEvent event = { 0 };
                event.type = ClientMessage;
@@ -2738,14 +2959,10 @@ class XInterface : Interface
 
    // -- Mouse cursor ---
 
-   void SetMouseCursor(int cursor)
+   void SetMouseCursor(Window window, int cursor)
    {
-      //*XLockDisplay(xGlobalDisplay);
-      if(cursor == -1)
-      {
-         XDefineCursor(xGlobalDisplay, (X11Window) guiApp.desktop.windowHandle, nullCursor);
-      }
-      //*XUnlockDisplay(xGlobalDisplay);
+      XDefineCursor(xGlobalDisplay, (X11Window) window.rootWindow.windowHandle,
+         cursor == -1 ? (X11Cursor)0 : systemCursors[(SystemCursor)cursor]);
    }
 
    // --- Caret ---