/* $Id: framer.c,v 1.9 2009/10/12 10:17:55 khorben Exp $ */ /* Copyright (c) 2009 Pierre Pronchery */ /* This file is part of DeforaOS Desktop Framer */ /* This program 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, version 3 of the License. * * This program 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 program. If not, see . */ #include #include #include #include #include #include #include #include "framer.h" #include "../config.h" #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) /* Framer */ /* private */ /* types */ typedef enum _FramerAtom { FA_NET_ACTIVE_WINDOW = 0, FA_NET_CLIENT_LIST, FA_NET_CURRENT_DESKTOP, FA_NET_DESKTOP_GEOMETRY, FA_NET_DESKTOP_VIEWPORT, FA_NET_NUMBER_OF_DESKTOPS, FA_NET_SHOWING_DESKTOP, FA_NET_SUPPORTED, FA_NET_WM_STATE, FA_NET_WM_STATE_ADD, FA_NET_WM_STATE_FULLSCREEN, FA_NET_WM_STATE_REMOVE, FA_NET_WM_STATE_TOGGLE, FA_NET_WM_WINDOW_TYPE, FA_NET_WM_WINDOW_TYPE_DESKTOP, FA_NET_WM_WINDOW_TYPE_DOCK, FA_NET_WM_WINDOW_TYPE_NORMAL, FA_NET_WORKAREA } FramerAtom; #define FA_LAST FA_NET_WORKAREA #define FA_COUNT (FA_LAST + 1) #define FA_NET_WM_WINDOW_TYPE_FIRST FA_NET_WM_WINDOW_TYPE_DESKTOP #define FA_NET_WM_WINDOW_TYPE_LAST FA_NET_WM_WINDOW_TYPE_NORMAL struct _Framer { /* essential */ GdkDisplay * display; GdkScreen * screen; GdkWindow * root; /* screen */ int width; int height; /* atoms */ Atom atom[FA_COUNT]; /* client list */ Window * window; size_t window_cnt; }; /* constants */ static char const * _framer_atom[FA_COUNT] = { "_NET_ACTIVE_WINDOW", "_NET_CLIENT_LIST", "_NET_CURRENT_DESKTOP", "_NET_DESKTOP_GEOMETRY", "_NET_DESKTOP_VIEWPORT", "_NET_NUMBER_OF_DESKTOPS", "_NET_SHOWING_DESKTOP", "_NET_SUPPORTED", "_NET_WM_STATE", "_NET_WM_STATE_ADD", "_NET_WM_STATE_FULLSCREEN", "_NET_WM_STATE_REMOVE", "_NET_WM_STATE_TOGGLE", "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_DESKTOP", "_NET_WM_WINDOW_TYPE_DOCK", "_NET_WM_WINDOW_TYPE_NORMAL", "_NET_WORKAREA" }; /* prototypes */ static int _framer_error(char const * message, int ret); static int _framer_window_get_property(Framer * framer, Window window, FramerAtom property, Atom atom, unsigned long * cnt, unsigned char ** ret); static int _framer_window_is_manageable(Framer * framer, Window window); static int _framer_window_set_property(Framer * framer, Window window, FramerAtom property, Atom atom, unsigned long cnt, unsigned char * data); /* callbacks */ static GdkFilterReturn _framer_filter(GdkXEvent * xevent, GdkEvent * event, gpointer data); /* public */ /* functions */ /* framer_new */ static void _new_atoms(Framer * framer); static void _new_windows(Framer * framer); static void _new_ewmh(Framer * framer); Framer * framer_new(void) { Framer * framer; if((framer = malloc(sizeof(*framer))) == NULL) { _framer_error(strerror(errno), 1); return NULL; } if((framer->display = gdk_display_get_default()) == NULL) { _framer_error("There is no default display", 1); free(framer); return NULL; } framer->screen = gdk_display_get_default_screen(framer->display); framer->root = gdk_screen_get_root_window(framer->screen); framer->width = gdk_screen_get_width(framer->screen); framer->height = gdk_screen_get_height(framer->screen); _new_atoms(framer); _new_windows(framer); _new_ewmh(framer); /* register as window manager */ XSelectInput(gdk_x11_display_get_xdisplay(framer->display), GDK_WINDOW_XWINDOW(framer->root), NoEventMask | SubstructureNotifyMask | SubstructureRedirectMask); gdk_window_add_filter(NULL, _framer_filter, framer); return framer; } static void _new_atoms(Framer * framer) { size_t i; for(i = 0; i < FA_COUNT; i++) framer->atom[i] = gdk_x11_get_xatom_by_name_for_display( framer->display, _framer_atom[i]); } static void _new_windows(Framer * framer) { Window root; Window parent; Window * children = NULL; unsigned int nchildren = 0; unsigned int i; framer->window = NULL; framer->window_cnt = 0; if(XQueryTree(GDK_DISPLAY_XDISPLAY(framer->display), GDK_WINDOW_XWINDOW(framer->root), &root, &parent, &children, &nchildren) == 0) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s() failed\n", __func__); #endif return; } for(i = 0; i < nchildren; i++) framer_window_add(framer, children[i]); XFree(children); } static void _new_ewmh(Framer * framer) { long data[4]; size_t i; Atom type = XA_CARDINAL; unsigned char * p = (unsigned char *)&data; long cnt = 0; memset(&data, 0, sizeof(data)); for(i = 0; i < FA_COUNT; i++) { switch(i) { case FA_NET_ACTIVE_WINDOW: type = XA_WINDOW; data[0] = None; cnt = 1; break; case FA_NET_CLIENT_LIST: type = XA_WINDOW; p = (unsigned char *)framer->window; cnt = framer->window_cnt; break; case FA_NET_CURRENT_DESKTOP: cnt = 1; break; case FA_NET_NUMBER_OF_DESKTOPS: data[0] = 1; cnt = 1; break; case FA_NET_DESKTOP_GEOMETRY: data[0] = framer->width; data[1] = framer->height; cnt = 2; break; case FA_NET_DESKTOP_VIEWPORT: cnt = 2; break; case FA_NET_SUPPORTED: type = XA_ATOM; p = (unsigned char *)framer->atom; cnt = FA_COUNT; break; case FA_NET_WORKAREA: data[2] = framer->width; data[3] = max(framer->height - 64, 1); cnt = 4; break; default: continue; } _framer_window_set_property(framer, GDK_WINDOW_XWINDOW( framer->root), i, type, cnt, p); type = XA_CARDINAL; cnt = 0; memset(&data, 0, sizeof(data)); p = (unsigned char *)&data; } } /* framer_delete */ void framer_delete(Framer * framer) { int i; for(i = 0; i < FA_COUNT; i++) XDeleteProperty(GDK_DISPLAY_XDISPLAY(framer->display), GDK_WINDOW_XWINDOW(framer->root), framer->atom[i]); free(framer); } /* accessors */ /* framer_set_show_desktop */ void framer_set_show_desktop(Framer * framer, gboolean shown) { size_t i; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%s)\n", __func__, shown ? "TRUE" : "FALSE"); #endif if(shown != TRUE) return; for(i = 0; i < framer->window_cnt; i++) framer_window_iconify(framer, framer->window[i]); } /* framer_window_is_fullscreen */ int framer_window_is_fullscreen(Framer * framer, Window window, gboolean * fullscreen) { XWindowAttributes wa; gdk_error_trap_push(); XGetWindowAttributes(GDK_DISPLAY_XDISPLAY(framer->display), window, &wa); if(gdk_error_trap_pop() != 0) return 1; *fullscreen = (wa.x == 0 && wa.y == 0 && wa.height == framer->height && wa.width == framer->width) ? TRUE : FALSE; return 0; } /* framer_window_set_active */ int framer_window_set_active(Framer * framer, Window window) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%d)\n", __func__, (int)window); #endif XMapRaised(GDK_DISPLAY_XDISPLAY(framer->display), window); return _framer_window_set_property(framer, window, FA_NET_ACTIVE_WINDOW, XA_WINDOW, 1, (unsigned char *)&window); } /* framer_window_set_fullscreen */ int framer_window_set_fullscreen(Framer * framer, Window window, gboolean fullscreen) { XWindowChanges wc; unsigned long mask = 0; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%s)\n", __func__, fullscreen ? "TRUE" : "FALSE"); #endif memset(&wc, 0, sizeof(wc)); if(fullscreen == TRUE) { wc.width = framer->width; wc.height = framer->height; mask |= CWX | CWY | CWHeight | CWWidth; } else #ifdef EMBEDDED { wc.width = framer->width; wc.height = framer->height - 64; mask |= CWX | CWY | CWHeight | CWWidth; } #else return 1; /* FIXME implement */ #endif gdk_error_trap_push(); XConfigureWindow(GDK_DISPLAY_XDISPLAY(framer->display), window, mask, &wc); if(gdk_error_trap_pop() != 0) return 1; return 0; } /* useful */ /* framer_window_add */ int framer_window_add(Framer * framer, Window window) { size_t i; Window * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%d)\n", __func__, (int)window); #endif if(!_framer_window_is_manageable(framer, window)) return 0; for(i = 0; i < framer->window_cnt; i++) if(framer->window[i] == window) return 0; if((p = realloc(framer->window, sizeof(*p) * (framer->window_cnt + 1))) == NULL) return -1; framer->window = p; p = &framer->window[framer->window_cnt++]; *p = window; _framer_window_set_property(framer, GDK_WINDOW_XWINDOW(framer->root), FA_NET_CLIENT_LIST, XA_WINDOW, framer->window_cnt, (unsigned char *)framer->window); return 0; } /* framer_window_iconify */ int framer_window_iconify(Framer * framer, Window window) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%d)\n", __func__, (int)window); #endif gdk_error_trap_push(); XUnmapWindow(GDK_DISPLAY_XDISPLAY(framer->display), window); if(gdk_error_trap_pop() != 0) return 1; return 0; } /* framer_window_remove */ int framer_window_remove(Framer * framer, Window window) { size_t i; Window * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%d)\n", __func__, (int)window); #endif for(i = 0; i < framer->window_cnt; i++) if(framer->window[i] == window) break; if(i == framer->window_cnt) /* we don't know this window anyway */ return 0; memmove(&framer->window[i], &framer->window[i + 1], (--framer->window_cnt - i) * sizeof(*p)); if((p = realloc(framer->window, framer->window_cnt * sizeof(*p))) != NULL) /* we can ignore errors */ framer->window = p; _framer_window_set_property(framer, GDK_WINDOW_XWINDOW(framer->root), FA_NET_CLIENT_LIST, XA_WINDOW, framer->window_cnt, (unsigned char *)framer->window); return 0; } /* private */ /* functions */ /* framer_error */ static int _framer_error(char const * message, int ret) { fprintf(stderr, "%s: %s\n", PACKAGE, message); return ret; } /* framer_window_get_property */ static int _framer_window_get_property(Framer * framer, Window window, FramerAtom property, Atom atom, unsigned long * cnt, unsigned char ** ret) { int res; Atom type; int format; unsigned long bytes; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%d, %s, %lu)\n", __func__, (int)window, _framer_atom[property], atom); #endif gdk_error_trap_push(); res = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(framer->display), window, framer->atom[property], 0, G_MAXLONG, False, atom, &type, &format, cnt, &bytes, ret); if(gdk_error_trap_pop() != 0 || res != Success) return 1; if(type != atom) { if(*ret != NULL) XFree(*ret); *ret = NULL; return 1; } return 0; } /* framer_window_is_manageable */ static int _framer_window_is_manageable(Framer * framer, Window window) { Atom typehint; Atom * p = NULL; unsigned long cnt = 0; if(_framer_window_get_property(framer, window, FA_NET_WM_WINDOW_TYPE, XA_ATOM, &cnt, (void*)&p) != 0) return 0; typehint = *p; XFree(p); return typehint == framer->atom[FA_NET_WM_WINDOW_TYPE_NORMAL] ? 1 : 0; } /* framer_window_set_property */ static int _framer_window_set_property(Framer * framer, Window window, FramerAtom property, Atom atom, unsigned long cnt, unsigned char * data) { int res; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%d, %s, %lu, %lu)\n", __func__, (int)window, _framer_atom[property], atom, cnt); #endif gdk_error_trap_push(); res = XChangeProperty(GDK_DISPLAY_XDISPLAY(framer->display), window, framer->atom[property], atom, 32, PropModeReplace, data, cnt); if(gdk_error_trap_pop() != 0 || res != Success) return 1; return 0; } /* callbacks */ /* framer_filter */ static GdkFilterReturn _filter_client_message(XClientMessageEvent * xclient, Framer * framer); static GdkFilterReturn _filter_configure_notify(XConfigureEvent * xconfigure); static GdkFilterReturn _filter_configure_request( XConfigureRequestEvent * xconfigure, Framer * framer); static GdkFilterReturn _filter_create_notify(XCreateWindowEvent * xcreate, Framer * framer); static GdkFilterReturn _filter_destroy_notify(XDestroyWindowEvent * xdestroy, Framer * framer); static GdkFilterReturn _filter_leave_notify(XCrossingEvent * xleave); static GdkFilterReturn _filter_map_notify(XMapEvent * xmap); static GdkFilterReturn _filter_map_request(XMapRequestEvent * xmap); static GdkFilterReturn _filter_motion_notify(XMotionEvent * xmotion); static GdkFilterReturn _filter_property_notify(XPropertyEvent * xproperty, Framer * framer); static GdkFilterReturn _filter_unmap_notify(XUnmapEvent * xunmap); static GdkFilterReturn _framer_filter(GdkXEvent * xevent, GdkEvent * event, gpointer data) { Framer * framer = data; XEvent * xev = xevent; switch(xev->type) { case ClientMessage: return _filter_client_message(&xev->xclient, framer); case ConfigureNotify: return _filter_configure_notify(&xev->xconfigure); case ConfigureRequest: return _filter_configure_request( &xev->xconfigurerequest, framer); case CreateNotify: return _filter_create_notify(&xev->xcreatewindow, framer); case DestroyNotify: return _filter_destroy_notify(&xev->xdestroywindow, framer); case LeaveNotify: return _filter_leave_notify(&xev->xcrossing); case MapNotify: return _filter_map_notify(&xev->xmap); case MapRequest: return _filter_map_request(&xev->xmaprequest); case MotionNotify: return _filter_motion_notify(&xev->xmotion); case PropertyNotify: return _filter_property_notify(&xev->xproperty, framer); case UnmapNotify: return _filter_unmap_notify(&xev->xunmap); default: #ifdef DEBUG fprintf(stderr, "DEBUG: %s() type=%d\n", __func__, xev->type); #endif break; } return GDK_FILTER_CONTINUE; } static void _message_state(Framer * framer, Window window, unsigned long hint, unsigned long property); static GdkFilterReturn _filter_client_message(XClientMessageEvent * xclient, Framer * framer) { GdkAtom atom; size_t i; char const * name; char * p = NULL; for(i = 0; i < FA_COUNT; i++) if(xclient->message_type == framer->atom[i]) break; switch(i) { case FA_NET_ACTIVE_WINDOW: framer_window_set_active(framer, xclient->window); break; case FA_NET_SHOWING_DESKTOP: framer_set_show_desktop(framer, TRUE); break; case FA_NET_WM_STATE: _message_state(framer, xclient->window, xclient->data.l[0], xclient->data.l[1]); _message_state(framer, xclient->window, xclient->data.l[0], xclient->data.l[2]); break; default: if(i < FA_COUNT) name = _framer_atom[i]; else { atom = gdk_x11_xatom_to_atom_for_display( framer->display, xclient->message_type); p = gdk_atom_name(atom); name = p; } fprintf(stderr, "%s: %s: %s\n", PACKAGE, name, "Unsupported message"); if(p != NULL) g_free(p); break; } return GDK_FILTER_CONTINUE; } static void _message_state(Framer * framer, Window window, unsigned long hint, unsigned long property) { int i; gboolean enabled; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%ld, %ld)\n", __func__, hint, property); #endif if(property == 0) return; for(i = 0; i < FA_COUNT; i++) if(framer->atom[i] == property) break; switch(i) { case FA_NET_WM_STATE_FULLSCREEN: if(hint == framer->atom[FA_NET_WM_STATE_ADD]) enabled = TRUE; else if(hint == framer->atom[FA_NET_WM_STATE_REMOVE]) enabled = FALSE; else /* assume toggle */ { if(framer_window_is_fullscreen(framer, window, &enabled) != 0) break; enabled = !enabled; } framer_window_set_fullscreen(framer, window, enabled); break; } } static GdkFilterReturn _filter_configure_notify(XConfigureEvent * xconfigure) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif return GDK_FILTER_CONTINUE; } static GdkFilterReturn _filter_configure_request( XConfigureRequestEvent * xconfigure, Framer * framer) { FramerAtom type = FA_NET_WM_WINDOW_TYPE_NORMAL; Atom typehint; Atom * p; unsigned long cnt = 0; int i; XWindowChanges wc; unsigned long mask = xconfigure->value_mask; #ifdef DEBUG fprintf(stderr, "DEBUG: %s() (%d,%d) %dx%d %lu\n", __func__, xconfigure->x, xconfigure->y, xconfigure->width, xconfigure->height, xconfigure->value_mask); #endif if(_framer_window_get_property(framer, xconfigure->window, FA_NET_WM_WINDOW_TYPE, XA_ATOM, &cnt, (void*)&p) == 0) { typehint = *p; XFree(p); for(i = FA_NET_WM_WINDOW_TYPE_FIRST; i < FA_NET_WM_WINDOW_TYPE_LAST; i++) if(typehint == framer->atom[i]) { type = i; break; } } #ifdef DEBUG fprintf(stderr, "DEBUG: %s() %s\n", __func__, _framer_atom[type]); #endif memset(&wc, 0, sizeof(wc)); #ifndef EMBEDDED if(xconfigure->value_mask & CWX) wc.x = max(xconfigure->x, 0); if(xconfigure->value_mask & CWY) wc.y = max(xconfigure->y, 0); if(xconfigure->value_mask & CWWidth) wc.width = min(xconfigure->width, framer->width); if(xconfigure->value_mask & CWHeight) wc.height = min(xconfigure->height, framer->height - 64); #else if(type == FA_NET_WM_WINDOW_TYPE_DOCK) { wc.x = xconfigure->x; wc.y = xconfigure->y; wc.width = xconfigure->width; wc.height = xconfigure->height; } else { wc.x = 0; wc.width = framer->width; wc.y = 0; wc.height = framer->height - 64; mask |= CWX | CWWidth | CWY | CWHeight; } if(xconfigure->value_mask & CWBorderWidth) wc.border_width = 0; #endif gdk_error_trap_push(); XConfigureWindow(xconfigure->display, xconfigure->window, mask, &wc); gdk_error_trap_pop(); return GDK_FILTER_CONTINUE; } static GdkFilterReturn _filter_create_notify(XCreateWindowEvent * xcreate, Framer * framer) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif framer_window_add(framer, xcreate->window); return GDK_FILTER_CONTINUE; } static GdkFilterReturn _filter_destroy_notify(XDestroyWindowEvent * xdestroy, Framer * framer) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif framer_window_remove(framer, xdestroy->window); return GDK_FILTER_CONTINUE; } static GdkFilterReturn _filter_leave_notify(XCrossingEvent * xleave) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif return GDK_FILTER_CONTINUE; } static GdkFilterReturn _filter_map_notify(XMapEvent * xmap) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif return GDK_FILTER_CONTINUE; } static GdkFilterReturn _filter_map_request(XMapRequestEvent * xmap) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif gdk_error_trap_push(); XMapWindow(xmap->display, xmap->window); gdk_error_trap_pop(); /* we ignore errors */ return GDK_FILTER_CONTINUE; } static GdkFilterReturn _filter_motion_notify(XMotionEvent * xmotion) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif return GDK_FILTER_CONTINUE; } static GdkFilterReturn _filter_property_notify(XPropertyEvent * xproperty, Framer * framer) { GdkAtom atom; char * name; atom = gdk_x11_xatom_to_atom_for_display(framer->display, xproperty->atom); name = gdk_atom_name(atom); #ifdef DEBUG fprintf(stderr, "DEBUG: %s() %s, %s\n", __func__, name, xproperty->send_event ? "TRUE" : "FALSE"); #endif g_free(name); return GDK_FILTER_CONTINUE; } static GdkFilterReturn _filter_unmap_notify(XUnmapEvent * xunmap) { #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif return GDK_FILTER_CONTINUE; }