Add support for GHOST_kWindowState* to Linux.
authorDiego Borghetti <bdiego@gmail.com>
Thu, 3 Jan 2008 21:04:16 +0000 (21:04 +0000)
committerDiego Borghetti <bdiego@gmail.com>
Thu, 3 Jan 2008 21:04:16 +0000 (21:04 +0000)
This add support for the different window state in Linux/X11.
Two think to take care:
1) m_post_*
2) motifFullScreen

1) This is only need if we want start a window in FullScreen or Maximized
state. The basic problem is that we can't set this property to a window
until it's really map, so i add a "post init" proccess for this two
special case (check the code for more info).

2) The Motif Hint isn't really a "FullScreen" mode, it's only a
"NO DECORATIONS" options, so if the window manager don't support WM-spec
this function only remove the border of the window, don't make it
FullScreen. A simple fix (hack) is check if the Window Manager support
WM-spec, in case that not, remove the decorations and move/resize the
window.

Test in different WM (gnome, kde, etc) and comment are welcome :)

intern/ghost/intern/GHOST_SystemX11.cpp
intern/ghost/intern/GHOST_SystemX11.h
intern/ghost/intern/GHOST_WindowX11.cpp
intern/ghost/intern/GHOST_WindowX11.h

index 14383ad36244ea78e830b1e0243c5f6fdc131562..e02131498e439833d8213bc22ec176a30aaaf816 100644 (file)
@@ -113,9 +113,26 @@ GHOST_SystemX11(
          = XSGIFastInternAtom(m_display,
                               "WM_DELETE_WINDOW", 
                               SGI_XA_WM_DELETE_WINDOW, False);
+       /* Some one with SGI can tell me about this ? */
+       m_wm_state= None;
+       m_wm_change_state= None;
+       m_net_state= None;
+       m_net_max_horz= None;
+       m_net_max_vert= None;
+       m_net_fullscreen= None;
+       m_motif = None;
 #else
-       m_delete_window_atom 
-         = XInternAtom(m_display, "WM_DELETE_WINDOW", True);
+       m_delete_window_atom= XInternAtom(m_display, "WM_DELETE_WINDOW", False);
+       m_wm_state= XInternAtom(m_display, "WM_STATE", False);
+       m_wm_change_state= XInternAtom(m_display, "WM_CHANGE_STATE", False);
+       m_net_state= XInternAtom(m_display, "_NET_WM_STATE", False);
+       m_net_max_horz= XInternAtom(m_display,
+                                       "_NET_WM_STATE_MAXIMIZED_HORZ", False);
+       m_net_max_vert= XInternAtom(m_display,
+                                       "_NET_WM_STATE_MAXIMIZED_VERT", False);
+       m_net_fullscreen= XInternAtom(m_display,
+                                       "_NET_WM_STATE_FULLSCREEN", False);
+       m_motif= XInternAtom(m_display, "_MOTIF_WM_HINTS", False);
 #endif
 
        // compute the initial time
@@ -499,6 +516,24 @@ GHOST_SystemX11::processEvent(XEvent *xe)
                        // XCrossingEvents pointer leave enter window.
                        break;
                case MapNotify:
+                       /*
+                        * From ICCCM:
+                        * [ Clients can select for StructureNotify on their
+                        *   top-level windows to track transition between
+                        *   Normal and Iconic states. Receipt of a MapNotify
+                        *   event will indicate a transition to the Normal
+                        *   state, and receipt of an UnmapNotify event will
+                        *   indicate a transition to the Iconic state. ]
+                        */
+                       if (window->m_post_init == True) {
+                               /*
+                                * Now we are sure that the window is
+                                * mapped, so only need change the state.
+                                */
+                               window->setState (window->m_post_state);
+                               window->m_post_init = False;
+                       }
+                       break;
                case UnmapNotify:
                        break;
                case MappingNotify:
index c8d8d73404a531fbf62a373c9c84e5f320bdf168..0763238ea6192e0a5c09bd998fd404defb977013 100644 (file)
@@ -196,6 +196,19 @@ public:
                return m_display;
        }       
 
+       /**
+        * Atom used for ICCCM, WM-spec and Motif.
+        * We only need get this atom at the start, it's relative
+        * to the display not the window and are public for every
+        * window that need it.
+        */
+       Atom m_wm_state;
+       Atom m_wm_change_state;
+       Atom m_net_state;
+       Atom m_net_max_horz;
+       Atom m_net_max_vert;
+       Atom m_net_fullscreen;
+       Atom m_motif;
        
 private :
 
index 544222e814fccf8d26c8f7fd1e8376f44cac41c6..f40f70eb24df3e39e5e23c3fb3768faf906667ca 100644 (file)
@@ -55,6 +55,16 @@ typedef struct {
 
 #define MWM_HINTS_DECORATIONS         (1L << 1)
 
+/*
+ * A Client can't change the window property, that is
+ * the work of the window manager. In case, we send
+ * a ClientMessage to the RootWindow with the property
+ * and the Action (WM-spec define this):
+ */
+#define _NET_WM_STATE_REMOVE 0
+#define _NET_WM_STATE_ADD 1
+#define _NET_WM_STATE_TOGGLE 2
+
 /*
 import bpy
 I = bpy.data.images['blender.png'] # the 48x48 icon
@@ -222,49 +232,25 @@ GHOST_WindowX11(
                        &xattributes
                );
        
-       
-       // Are we in fullscreen mode - then include
-       // some obscure blut code to remove decorations.
-
-       if (state == GHOST_kWindowStateFullScreen) {
-
-               MotifWmHints hints;
-               Atom atom;
-                                       
-               atom = XInternAtom(m_display, "_MOTIF_WM_HINTS", False);
-               
-               if (atom == None) {
-                       GHOST_PRINT("Could not intern X atom for _MOTIF_WM_HINTS.\n");
-               } else {
-                       hints.flags = MWM_HINTS_DECORATIONS;
-                       hints.decorations = 0;  /* Absolutely no decorations. */
-                       // other hints.decorations make no sense
-                       // you can't select individual decorations
-
-                       XChangeProperty(m_display, m_window,
-                               atom, atom, 32,
-                               PropModeReplace, (unsigned char *) &hints, 4);
-               }
-       } else if (state == GHOST_kWindowStateMaximized) {
-               // With this, xprop should report the following just after launch
-               // _NET_WM_STATE(ATOM) = _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_MAXIMIZED_HORZ
-               // After demaximization the right side is empty, though (maybe not the most correct then?)
-               Atom state, atomh, atomv;
-
-               state = XInternAtom(m_display, "_NET_WM_STATE", False);
-               atomh = XInternAtom(m_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
-               atomv = XInternAtom(m_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
-               if (state == None ) {
-                       GHOST_PRINT("Atom _NET_WM_STATE requested but not avaliable nor created.\n");
-               } else {
-                       XChangeProperty(m_display, m_window,
-                               state, XA_ATOM, 32,
-                               PropModeAppend, (unsigned char *) &atomh, 1);
-                       XChangeProperty(m_display, m_window,
-                               state, XA_ATOM, 32,
-                               PropModeAppend, (unsigned char *) &atomv, 1);
-               }
-       }
+       /*
+        * One of the problem with WM-spec is that can't set a property
+        * to a window that isn't mapped. That is why we can't "just
+        * call setState" here.
+        *
+        * To fix this, we first need know that the window is really
+        * map waiting for the MapNotify event.
+        *
+        * So, m_post_init indicate that we need wait for the MapNotify
+        * event and then set the Window state to the m_post_state.
+        */
+       if ((state != GHOST_kWindowStateNormal) && (state != GHOST_kWindowStateMinimized)) {
+               m_post_init = True;
+               m_post_state = state;
+       }
+       else {
+               m_post_init = False;
+               m_post_state = GHOST_kWindowStateNormal;
+       }
        
        // Create some hints for the window manager on how
        // we want this window treated. 
@@ -609,28 +595,299 @@ clientToScreen(
        outY = ay;
 }
 
+void GHOST_WindowX11::icccmSetState(int state)
+{
+       XEvent xev;
 
-       GHOST_TWindowState 
-GHOST_WindowX11::
-getState(
-) const {
-       //FIXME 
-       return GHOST_kWindowStateNormal;
+       if (state != IconicState)
+               return;
+
+       xev.xclient.type = ClientMessage;
+       xev.xclient.serial = 0;
+       xev.xclient.send_event = True;
+       xev.xclient.display = m_display;
+       xev.xclient.window = m_window;
+       xev.xclient.format = 32;
+       xev.xclient.message_type = m_system->m_wm_change_state;
+       xev.xclient.data.l[0] = state;
+       XSendEvent (m_display, RootWindow(m_display, DefaultScreen(m_display)),
+               False, SubstructureNotifyMask | SubstructureRedirectMask, &xev);
 }
 
-       GHOST_TSuccess 
-GHOST_WindowX11::
-setState(
-       GHOST_TWindowState state
-){
-       //TODO
+int GHOST_WindowX11::icccmGetState(void) const
+{
+       unsigned char *prop_ret;
+       unsigned long bytes_after, num_ret;
+       Atom type_ret;
+       int format_ret, st;
+
+       prop_ret = NULL;
+       st = XGetWindowProperty(m_display, m_window, m_system->m_wm_state, 0,
+                       0x7fffffff, False, m_system->m_wm_state, &type_ret,
+                       &format_ret, &num_ret, &bytes_after, &prop_ret);
+
+       if ((st == Success) && (prop_ret) && (num_ret == 2))
+               st = prop_ret[0];
+       else
+               st = NormalState;
+
+       if (prop_ret)
+               XFree(prop_ret);
+       return (st);
+}
+
+void GHOST_WindowX11::netwmMaximized(bool set)
+{
+       XEvent xev;
+
+       xev.xclient.type = ClientMessage;
+       xev.xclient.serial = 0;
+       xev.xclient.send_event = True;
+       xev.xclient.window = m_window;
+       xev.xclient.message_type = m_system->m_net_state;
+       xev.xclient.format = 32;
+
+       if (set == True)
+               xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
+       else
+               xev.xclient.data.l[0] = _NET_WM_STATE_REMOVE;
+
+       xev.xclient.data.l[1] = m_system->m_net_max_horz;
+       xev.xclient.data.l[2] = m_system->m_net_max_vert;
+       xev.xclient.data.l[3] = 0;
+       xev.xclient.data.l[4] = 0;
+       XSendEvent(m_display, RootWindow(m_display, DefaultScreen(m_display)),
+               False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+}
+
+bool GHOST_WindowX11::netwmIsMaximized(void) const
+{
+       unsigned char *prop_ret;
+       unsigned long bytes_after, num_ret;
+       Atom type_ret;
+       bool st;
+       int format_ret, count, i;
+
+       prop_ret = NULL;
+       st = False;
+       i = XGetWindowProperty(m_display, m_window, m_system->m_net_state, 0,
+                       0x7fffffff, False, XA_ATOM, &type_ret, &format_ret,
+                       &num_ret, &bytes_after, &prop_ret);
+       if ((i == Success) && (prop_ret) && (format_ret == 32)) {
+               count = 0;
+               for (i = 0; i < num_ret; i++) {
+                       if (((unsigned long *) prop_ret)[i] == m_system->m_net_max_horz)
+                               count++;
+                       if (((unsigned long *) prop_ret)[i] == m_system->m_net_max_vert)
+                               count++;
+                       if (count == 2) {
+                               st = True;
+                               break;
+                       }
+               }
+       }
+
+       if (prop_ret)
+               XFree(prop_ret);
+       return (st);
+}
+
+void GHOST_WindowX11::netwmFullScreen(bool set)
+{
+       XEvent xev;
+
+       xev.xclient.type = ClientMessage;
+       xev.xclient.serial = 0;
+       xev.xclient.send_event = True;
+       xev.xclient.window = m_window;
+       xev.xclient.message_type = m_system->m_net_state;
+       xev.xclient.format = 32;
+
+       if (set == True)
+               xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
+       else
+               xev.xclient.data.l[0] = _NET_WM_STATE_REMOVE;
+
+       xev.xclient.data.l[1] = m_system->m_net_fullscreen;
+       xev.xclient.data.l[2] = 0;
+       xev.xclient.data.l[3] = 0;
+       xev.xclient.data.l[4] = 0;
+       XSendEvent(m_display, RootWindow(m_display, DefaultScreen(m_display)),
+               False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+}
+
+bool GHOST_WindowX11::netwmIsFullScreen(void) const
+{
+       unsigned char *prop_ret;
+       unsigned long bytes_after, num_ret;
+       Atom type_ret;
+       bool st;
+       int format_ret, i;
+
+       prop_ret = NULL;
+       st = False;
+       i = XGetWindowProperty(m_display, m_window, m_system->m_net_state, 0,
+                       0x7fffffff, False, XA_ATOM, &type_ret, &format_ret,
+                       &num_ret, &bytes_after, &prop_ret);
+       if ((i == Success) && (prop_ret) && (format_ret == 32)) {
+               for (i = 0; i < num_ret; i++) {
+                       if (((unsigned long *) prop_ret)[i] == m_system->m_net_fullscreen) {
+                               st = True;
+                               break;
+                       }
+               }
+       }
+
+       if (prop_ret)
+               XFree(prop_ret);
+       return (st);
+}
+
+void GHOST_WindowX11::motifFullScreen(bool set)
+{
+       MotifWmHints hints;
+
+       hints.flags = MWM_HINTS_DECORATIONS;
+       if (set == True)
+               hints.decorations = 0;
+       else
+               hints.decorations = 1;
+
+       XChangeProperty(m_display, m_window, m_system->m_motif,
+                       m_system->m_motif, 32, PropModeReplace,
+                       (unsigned char *) &hints, 4);
+}
+
+bool GHOST_WindowX11::motifIsFullScreen(void) const
+{
+       unsigned char *prop_ret;
+       unsigned long bytes_after, num_ret;
+       MotifWmHints *hints;
+       Atom type_ret;
+       bool state;
+       int format_ret, st;
+
+       prop_ret = NULL;
+       state = False;
+       st = XGetWindowProperty(m_display, m_window, m_system->m_motif, 0,
+                               0x7fffffff, False, m_system->m_motif,
+                               &type_ret, &format_ret, &num_ret,
+                               &bytes_after, &prop_ret);
+       if ((st == Success) && (prop_ret)) {
+               hints = (MotifWmHints *) prop_ret;
+               if (hints->flags & MWM_HINTS_DECORATIONS) {
+                       if (!hints->decorations)
+                               state = True;
+               }
+       }
+
+       if (prop_ret)
+               XFree(prop_ret);
+       return (state);
+}
 
-        if (state == (int)getState()) {
+GHOST_TWindowState GHOST_WindowX11::getState() const
+{
+       GHOST_TWindowState state_ret;
+       int state;
+
+       state_ret = GHOST_kWindowStateNormal;
+       state = icccmGetState();
+       /*
+        * In the Iconic and Withdrawn state, the window
+        * is unmaped, so only need return a Minimized state.
+        */
+       if ((state == IconicState) || (state == WithdrawnState))
+               state_ret = GHOST_kWindowStateMinimized;
+       else if (netwmIsMaximized() == True)
+               state_ret = GHOST_kWindowStateMaximized;
+       else if (netwmIsFullScreen() == True)
+               state_ret = GHOST_kWindowStateFullScreen;
+       else if (motifIsFullScreen() == True)
+               state_ret = GHOST_kWindowStateFullScreen;
+       return (state_ret);
+}
+
+GHOST_TSuccess GHOST_WindowX11::setState(GHOST_TWindowState state)
+{
+       GHOST_TWindowState cur_state;
+       bool is_max, is_full, is_motif_full;
+       int icccm_state;
+
+       cur_state = getState();
+        if (state == (int)cur_state)
                return GHOST_kSuccess;
-       } else {
-               return GHOST_kFailure;
+
+       if (cur_state != GHOST_kWindowStateMinimized) {
+               /*
+                * The window don't have this property's
+                * if it's not mapped.
+                */
+               is_max = netwmIsMaximized();
+               is_full = netwmIsFullScreen();
+       }
+       else {
+               is_max = False;
+               is_full = False;
+       }
+
+       is_motif_full = motifIsFullScreen();
+
+       if (state == GHOST_kWindowStateNormal) {
+               if (is_max == True)
+                       netwmMaximized(False);
+               if (is_full == True)
+                       netwmFullScreen(False);
+               if (is_motif_full == True)
+                       motifFullScreen(False);
+               icccmSetState(NormalState);
+               return (GHOST_kSuccess);
+       }
+
+       if (state == GHOST_kWindowStateFullScreen) {
+               /*
+                * We can't change to full screen if the window
+                * isn't mapped.
+                */
+               if (cur_state == GHOST_kWindowStateMinimized)
+                       return (GHOST_kFailure);
+
+               if (is_max == True)
+                       netwmMaximized(False);
+               if (is_full == False)
+                       netwmFullScreen(True);
+               if (is_motif_full == False)
+                       motifFullScreen(True);
+               return (GHOST_kSuccess);
+       }
+
+       if (state == GHOST_kWindowStateMaximized) {
+               /*
+                * We can't change to Maximized if the window
+                * isn't mapped.
+                */
+               if (cur_state == GHOST_kWindowStateMinimized)
+                       return (GHOST_kFailure);
+
+               if (is_full == True)
+                       netwmFullScreen(False);
+               if (is_motif_full == True)
+                       motifFullScreen(False);
+               if (is_max == False)
+                       netwmMaximized(True);
+               return (GHOST_kSuccess);
+       }
+
+       if (state == GHOST_kWindowStateMinimized) {
+               /*
+                * The window manager need save the current state of
+                * the window (maximized, full screen, etc).
+                */
+               icccmSetState(IconicState);
+               return (GHOST_kSuccess);
        }
 
+       return (GHOST_kFailure);
 }
 
 #include <iostream>
index 0de4f65acd180245abe874dee339a036ee2dc2b7..0acf55328295069f66a35c174044887f9e9c5fd7 100644 (file)
@@ -213,6 +213,15 @@ public:
 
        const GHOST_TabletData* GetTabletData()
        { return &m_xtablet.CommonData; }
+
+       /*
+        * Need this in case that we want start the window
+        * in FullScree or Maximized state.
+        * Check GHOST_WindowX11.cpp
+        */
+       bool m_post_init;
+       GHOST_TWindowState m_post_state;
+
 protected:
        /**
         * Tries to install a rendering context in this window.
@@ -328,6 +337,18 @@ private :
 
        /* Tablet devices */
        XTablet m_xtablet;
+
+       void icccmSetState(int state);
+       int icccmGetState() const;
+
+       void netwmMaximized(bool set);
+       bool netwmIsMaximized() const;
+
+       void netwmFullScreen(bool set);
+       bool netwmIsFullScreen() const;
+
+       void motifFullScreen(bool set);
+       bool motifIsFullScreen() const;
 };