From: Dana Jansens Date: Sun, 16 Mar 2003 21:11:39 +0000 (+0000) Subject: merge the C branch into HEAD X-Git-Tag: gl~968 X-Git-Url: http://git.openbox.org/?p=dana%2Fopenbox.git;a=commitdiff_plain;h=f8a47de5ec444c452093371e3db16857eb39a490 merge the C branch into HEAD --- diff --git a/Makefile.am b/Makefile.am index 2491616..aff7de3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = po data doc otk wrap src scripts tools +SUBDIRS = po data themes doc render kernel engines python MAINTAINERCLEANFILES = aclocal.m4 config.h.in configure Makefile.in stamp-h.in .PHONY: doc diff --git a/README.CVS b/README.CVS index 2f8dcbe..893da87 100644 --- a/README.CVS +++ b/README.CVS @@ -1,7 +1,6 @@ To build Openbox from CVS you need: -A C++ Compiler (GNU GCC/G++ 3.2+ suggested, 2.9x is not supported as it is not - very compliant with the ISO C++ standards) +A C Compiler (GNU GCC 3.2+ suggested) GNU Gettext 0.11.5 GNU Autoconf 2.50+ GNU Automake 1.7.1+ (1.7.1 has a bug fix for py-compile) @@ -9,7 +8,7 @@ GNU Libtool Xft2 library/headers (devel package) (http://www.fontconfig.org) Xlib library/headers (devel package) Python library/headers (devel package) (http://www.python.org) -SWIG 1.3.14+ (http://www.swig.org) +Pkg-Config I recommend the latest version of all these packages. @@ -19,10 +18,6 @@ Do the following to build and install Openbox in CVS: % ./configure % make all install -When building the SWIG generated wrappers (*_wrap.cc), a huge number of -compiler warnings will be generated. Don't be phased by this, it doesn't mean -anything is broken unless you get an actual error. - Don't try running it from the src/ directory without installing, it won't work. It needs to be installed before it is run. @@ -32,15 +27,14 @@ The installed binary is 'openbox3'. ---- Here's the packages from Debian (sid): - g++ + gcc gettext automake1.7 autoconf libtool + pkg-config libxft2-dev python-dev - swig1.3 - ---- Check out the Wiki for how to configure Openbox 3: diff --git a/bootstrap b/bootstrap index f0e2b2b..288bd69 100755 --- a/bootstrap +++ b/bootstrap @@ -7,7 +7,7 @@ sh() { sh autopoint --force # for GNU gettext sh libtoolize --copy --force --automake sh aclocal -I m4 -sh autoheader +#sh autoheader sh autoconf sh automake --foreign --include-deps --add-missing --copy diff --git a/c/.cvsignore b/c/.cvsignore new file mode 100644 index 0000000..250363e --- /dev/null +++ b/c/.cvsignore @@ -0,0 +1,5 @@ +ob3 +Makefile.in +.libs +.deps +Makefile diff --git a/c/Makefile.am b/c/Makefile.am new file mode 100644 index 0000000..cfde7b6 --- /dev/null +++ b/c/Makefile.am @@ -0,0 +1,34 @@ +localedir=$(datadir)/locale +scriptdir = $(libdir)/openbox/python + +CPPFLAGS=$(PYTHON_CFLAGS) $(GLIB_CFLAGS) @CPPFLAGS@ \ +-DLOCALEDIR=\"$(localedir)\" \ +-DSCRIPTDIR=\"$(scriptdir)\" \ +-DG_LOG_DOMAIN=\"Openbox\" + +LIBS=$(PYTHON_LIBS) $(GLIB_LIBS) @LIBS@ + +bin_PROGRAMS= ob3 + +ob3_LDADD=@LIBINTL@ +ob3_LDFLAGS=-export-dynamic +ob3_SOURCES=client.c event.c extensions.c focus.c frame.c openbox.c prop.c \ + python.c screen.c stacking.c xerror.c hooks.c eventdata.c obexport.c \ + clientwrap.c screenwrap.c kbind.c mbind.c + +noinst_HEADERS=client.h event.h extensions.h focus.h frame.h geom.h gettext.h \ + openbox.h prop.h python.h screen.h stacking.h xerror.h hooks.h \ + eventdata.h clientwrap.h obexport.h screenwrap.h kbind.h mbind.h + +MAINTAINERCLEANFILES= Makefile.in + +#if CVS +#ob3.i: $(wildcard *.h) +# @touch $@ + +#ob3_wrap.c: ob3.i +# $(SWIG) $(SWIG_PYTHON_OPT) $(filter -I%,$(CPPFLAGS)) -o $@ $< +#endif + +distclean-local: + $(RM) *\~ *.orig *.rej .\#* diff --git a/c/client.c b/c/client.c new file mode 100644 index 0000000..995cba9 --- /dev/null +++ b/c/client.c @@ -0,0 +1,1869 @@ +#include "client.h" +#include "screen.h" +#include "prop.h" +#include "extensions.h" +#include "frame.h" +#include "event.h" +#include "focus.h" +#include "clientwrap.h" +#include "stacking.h" +#include "hooks.h" +#include "mbind.h" +#include + +/*! The event mask to grab on client windows */ +#define CLIENT_EVENTMASK (PropertyChangeMask | FocusChangeMask | \ + StructureNotifyMask) + +#define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \ + ButtonMotionMask) + +GSList *client_list = NULL; +GHashTable *client_map = NULL; + +static void client_get_all(Client *self); +static void client_toggle_border(Client *self, gboolean show); +static void client_get_area(Client *self); +static void client_get_desktop(Client *self); +static void client_get_state(Client *self); +static void client_get_shaped(Client *self); +static void client_get_mwm_hints(Client *self); +static void client_get_gravity(Client *self); +static void client_change_allowed_actions(Client *self); +static void client_change_state(Client *self); +static Client *search_focus_tree(Client *node, Client *skip); +static void client_apply_startup_state(Client *self); +static Client *search_modal_tree(Client *node, Client *skip); + +static guint map_hash(Window w) { return w; } +static gboolean map_key_comp(Window w1, Window w2) { return w1 == w2; } + +void client_startup() +{ + client_map = g_hash_table_new((GHashFunc)map_hash, + (GEqualFunc)map_key_comp); + client_set_list(); +} + +void client_shutdown() +{ + g_hash_table_destroy(client_map); +} + +void client_set_list() +{ + Window *windows, *win_it; + GSList *it; + guint size = g_slist_length(client_list); + + /* create an array of the window ids */ + if (size > 0) { + windows = g_new(Window, size); + win_it = windows; + for (it = client_list; it != NULL; it = it->next, ++win_it) + *win_it = ((Client*)it->data)->window; + } else + windows = NULL; + + PROP_SET32A(ob_root, net_client_list, window, windows, size); + + if (windows) + g_free(windows); + + stacking_set_list(); +} + +void client_manage_all() +{ + unsigned int i, j, nchild; + Window w, *children; + XWMHints *wmhints; + XWindowAttributes attrib; + + XQueryTree(ob_display, ob_root, &w, &w, &children, &nchild); + + /* remove all icon windows from the list */ + for (i = 0; i < nchild; i++) { + if (children[i] == None) continue; + wmhints = XGetWMHints(ob_display, children[i]); + if (wmhints) { + if ((wmhints->flags & IconWindowHint) && + (wmhints->icon_window != children[i])) + for (j = 0; j < nchild; j++) + if (children[j] == wmhints->icon_window) { + children[j] = None; + break; + } + XFree(wmhints); + } + } + + for (i = 0; i < nchild; ++i) { + if (children[i] == None) + continue; + if (XGetWindowAttributes(ob_display, children[i], &attrib)) { + if (attrib.override_redirect) continue; + + if (attrib.map_state != IsUnmapped) + client_manage(children[i]); + } + } + XFree(children); +} + +void client_manage(Window window) +{ + Client *client; + XEvent e; + XWindowAttributes attrib; + XSetWindowAttributes attrib_set; + XWMHints *wmhint; + + XGrabServer(ob_display); + XSync(ob_display, FALSE); + + /* check if it has already been unmapped by the time we started mapping + the grab does a sync so we don't have to here */ + if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) || + XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e)) { + XPutBackEvent(ob_display, &e); + + XUngrabServer(ob_display); + XFlush(ob_display); + return; /* don't manage it */ + } + + /* make sure it isn't an override-redirect window */ + if (!XGetWindowAttributes(ob_display, window, &attrib) || + attrib.override_redirect) { + XUngrabServer(ob_display); + XFlush(ob_display); + return; /* don't manage it */ + } + + /* is the window a docking app */ + if ((wmhint = XGetWMHints(ob_display, window))) { + if ((wmhint->flags & StateHint) && + wmhint->initial_state == WithdrawnState) { + /* XXX: make dock apps work! */ + XUngrabServer(ob_display); + XFlush(ob_display); + XFree(wmhint); + return; + } + XFree(wmhint); + } + + /* choose the events we want to receive on the CLIENT window */ + attrib_set.event_mask = CLIENT_EVENTMASK; + attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK; + XChangeWindowAttributes(ob_display, window, + CWEventMask|CWDontPropagate, &attrib_set); + + + /* create the Client struct, and populate it from the hints on the + window */ + client = g_new(Client, 1); + client->window = window; + client_get_all(client); + + /* remove the client's border (and adjust re gravity) */ + client_toggle_border(client, FALSE); + + /* specify that if we exit, the window should not be destroyed and should + be reparented back to root automatically */ + XChangeSaveSet(ob_display, window, SetModeInsert); + + /* create the decoration frame for the client window */ + client->frame = frame_new(client); + + frame_grab_client(client->frame); + + client_apply_startup_state(client); + + XUngrabServer(ob_display); + XFlush(ob_display); + + client_list = g_slist_append(client_list, client); + stacking_list = g_list_append(stacking_list, client); + g_hash_table_insert(client_map, (gpointer)window, client); + + stacking_raise(client); + + screen_update_struts(); + + LOGICALHOOK(NewWindow, g_quark_try_string("client"), client); + + client_showhide(client); + + /* grab all mouse bindings */ + mbind_grab_all(client, TRUE); + + /* update the list hints */ + client_set_list(); + + g_message("Managed window 0x%lx frame 0x%lx", window, + client->frame->window); +} + +void client_unmanage_all() +{ + while (client_list != NULL) { + client_unmanage(client_list->data); + } +} + +void client_unmanage(Client *client) +{ + int j; + GSList *it; + + g_message("Unmanaging window: %lx", client->window); + + LOGICALHOOK(CloseWindow, g_quark_try_string("client"), client); + + /* remove the window from our save set */ + XChangeSaveSet(ob_display, client->window, SetModeDelete); + + /* we dont want events no more */ + XSelectInput(ob_display, client->window, NoEventMask); + + /* ungrab any mouse bindings */ + mbind_grab_all(client, FALSE); + + frame_hide(client->frame); + + /* give the client its border back */ + client_toggle_border(client, TRUE); + + /* reparent the window out of the frame */ + frame_release_client(client->frame); + + client_list = g_slist_remove(client_list, client); + stacking_list = g_list_remove(stacking_list, client); + g_hash_table_remove(client_map, (gpointer)client->window); + + /* once the client is out of the list, update the struts to remove it's + influence */ + screen_update_struts(); + + frame_free(client->frame); + + /* notify the wrapper that its useless now */ + if (client->wrap != NULL) + client->wrap->client = NULL; + + /* tell our parent that we're gone */ + if (client->transient_for != NULL) + client->transient_for->transients = + g_slist_remove(client->transient_for->transients, client); + + /* tell our transients that we're gone */ + for (it = client->transients; it != NULL; it = it->next) { + Client *t = ((Client*)it->data)->transient_for; + ((Client*)it->data)->transient_for = NULL; + if (t != NULL) + client_calc_layer(it->data); + } + + /* unfocus the client (calls the focus callbacks) (we're out of the + transient lists already, so being modal doesn't matter) */ + if (client->focused) + client_unfocus(client); + + if (ob_state != State_Exiting) { + /* these values should not be persisted across a window + unmapping/mapping */ + prop_erase(client->window, prop_atoms.net_wm_desktop); + prop_erase(client->window, prop_atoms.net_wm_state); + } else { + /* if we're left in an iconic state, the client wont be mapped. this is + bad, since we will no longer be managing the window on restart */ + if (client->iconic) + XMapWindow(ob_display, client->window); + } + + /* free all data allocated in the client struct */ + g_slist_free(client->transients); + for (j = 0; j < client->nicons; ++j) + g_free(client->icons[j].data); + if (client->nicons > 0) + g_free(client->icons); + g_free(client->title); + g_free(client->icon_title); + g_free(client->res_name); + g_free(client->res_class); + g_free(client->role); + g_free(client); + + /* update the list hints */ + client_set_list(); +} + +static void client_toggle_border(Client *self, gboolean show) +{ + /* adjust our idea of where the client is, based on its border. When the + border is removed, the client should now be considered to be in a + different position. + when re-adding the border to the client, the same operation needs to be + reversed. */ + int oldx = self->area.x, oldy = self->area.y; + int x = oldx, y = oldy; + switch(self->gravity) { + default: + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + if (show) x -= self->border_width * 2; + else x += self->border_width * 2; + break; + case NorthGravity: + case SouthGravity: + case CenterGravity: + case ForgetGravity: + case StaticGravity: + if (show) x -= self->border_width; + else x += self->border_width; + break; + } + switch(self->gravity) { + default: + case NorthWestGravity: + case NorthGravity: + case NorthEastGravity: + break; + case SouthWestGravity: + case SouthGravity: + case SouthEastGravity: + if (show) y -= self->border_width * 2; + else y += self->border_width * 2; + break; + case WestGravity: + case EastGravity: + case CenterGravity: + case ForgetGravity: + case StaticGravity: + if (show) y -= self->border_width; + else y += self->border_width; + break; + } + self->area.x = x; + self->area.y = y; + + if (show) { + XSetWindowBorderWidth(ob_display, self->window, self->border_width); + + /* move the client so it is back it the right spot _with_ its + border! */ + if (x != oldx || y != oldy) + XMoveWindow(ob_display, self->window, x, y); + } else + XSetWindowBorderWidth(ob_display, self->window, 0); +} + + +static void client_get_all(Client *self) +{ + /* update EVERYTHING!! */ + + self->ignore_unmaps = 0; + + /* defaults */ + self->frame = NULL; + self->title = self->icon_title = NULL; + self->res_name = self->res_class = self->role = NULL; + self->wmstate = NormalState; + self->focused = FALSE; + self->transient = FALSE; + self->transients = NULL; + self->transient_for = NULL; + self->layer = -1; + self->urgent = FALSE; + self->positioned = FALSE; + self->disabled_decorations = 0; + self->group = None; + self->nicons = 0; + self->wrap = NULL; + + client_get_area(self); + client_get_desktop(self); + client_get_state(self); + client_get_shaped(self); + + client_update_transient_for(self); + client_get_mwm_hints(self); + client_get_type(self);/* this can change the mwmhints for special cases */ + + client_update_protocols(self); + + client_get_gravity(self); /* get the attribute gravity */ + client_update_normal_hints(self); /* this may override the attribute + gravity */ + + /* got the type, the mwmhints, the protocols, and the normal hints + (min/max sizes), so we're ready to set up the decorations/functions */ + client_setup_decor_and_functions(self); + + client_update_wmhints(self); + client_update_title(self); + client_update_icon_title(self); + client_update_class(self); + client_update_strut(self); + client_update_icons(self); + client_update_kwm_icon(self); + + /* this makes sure that these windows appear on all desktops */ + if (self->type == Type_Desktop) + self->desktop = DESKTOP_ALL; + + /* set the desktop hint, to make sure that it always exists, and to + reflect any changes we've made here */ + PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop); + + client_change_state(self); +} + +static void client_get_area(Client *self) +{ + XWindowAttributes wattrib; + Status ret; + + ret = XGetWindowAttributes(ob_display, self->window, &wattrib); + g_assert(ret != BadWindow); + + RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height); + self->border_width = wattrib.border_width; +} + +static void client_get_desktop(Client *self) +{ + unsigned int d; + + if (PROP_GET32(self->window, net_wm_desktop, cardinal, d)) { + if (d >= screen_desktop && d != DESKTOP_ALL) + d = screen_desktop - 1; + self->desktop = d; + } else { + /* defaults to the current desktop */ + self->desktop = screen_desktop; + } +} + +static void client_get_state(Client *self) +{ + gulong *state; + gulong num; + + self->modal = self->shaded = self->max_horz = self->max_vert = + self->fullscreen = self->above = self->below = self->iconic = + self->skip_taskbar = self->skip_pager = FALSE; + + if (PROP_GET32U(self->window, net_wm_state, atom, state, num)) { + gulong i; + for (i = 0; i < num; ++i) { + if (state[i] == prop_atoms.net_wm_state_modal) + self->modal = TRUE; + else if (state[i] == prop_atoms.net_wm_state_shaded) + self->shaded = TRUE; + else if (state[i] == prop_atoms.net_wm_state_hidden) + self->iconic = TRUE; + else if (state[i] == prop_atoms.net_wm_state_skip_taskbar) + self->skip_taskbar = TRUE; + else if (state[i] == prop_atoms.net_wm_state_skip_pager) + self->skip_pager = TRUE; + else if (state[i] == prop_atoms.net_wm_state_fullscreen) + self->fullscreen = TRUE; + else if (state[i] == prop_atoms.net_wm_state_maximized_vert) + self->max_vert = TRUE; + else if (state[i] == prop_atoms.net_wm_state_maximized_horz) + self->max_horz = TRUE; + else if (state[i] == prop_atoms.net_wm_state_above) + self->above = TRUE; + else if (state[i] == prop_atoms.net_wm_state_below) + self->below = TRUE; + } + + g_free(state); + } +} + +static void client_get_shaped(Client *self) +{ + self->shaped = FALSE; +#ifdef SHAPE + if (extensions_shape) { + int foo; + guint ufoo; + int s; + + XShapeSelectInput(ob_display, self->window, ShapeNotifyMask); + + XShapeQueryExtents(ob_display, self->window, &s, &foo, + &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, + &ufoo); + self->shaped = (s != 0); + } +#endif +} + +void client_update_transient_for(Client *self) +{ + Window t = None; + Client *c = NULL; + + if (XGetTransientForHint(ob_display, self->window, &t) && + t != self->window) { /* cant be transient to itself! */ + self->transient = TRUE; + c = g_hash_table_lookup(client_map, (gpointer)t); + g_assert(c != self);/* if this happens then we need to check for it*/ + + if (!c /*XXX: && _group*/) { + /* not transient to a client, see if it is transient for a + group */ + if (/*t == _group->leader() || */ + t == None || + t == ob_root) { + /* window is a transient for its group! */ + /* XXX: for now this is treated as non-transient. + this needs to be fixed! */ + } + } + } else + self->transient = FALSE; + + /* if anything has changed... */ + if (c != self->transient_for) { + if (self->transient_for) + /* remove from old parent */ + g_slist_remove(self->transient_for->transients, self); + self->transient_for = c; + if (self->transient_for) + /* add to new parent */ + g_slist_append(self->transient_for->transients, self); + } +} + +static void client_get_mwm_hints(Client *self) +{ + unsigned long num; + unsigned long *hints; + + self->mwmhints.flags = 0; /* default to none */ + + if (PROP_GET32U(self->window, motif_wm_hints, motif_wm_hints, hints, num)) { + if (num >= MWM_ELEMENTS) { + self->mwmhints.flags = hints[0]; + self->mwmhints.functions = hints[1]; + self->mwmhints.decorations = hints[2]; + } + g_free(hints); + } +} + +void client_get_type(Client *self) +{ + gulong *val, num, i; + + self->type = -1; + + if (PROP_GET32U(self->window, net_wm_window_type, atom, val, num)) { + /* use the first value that we know about in the array */ + for (i = 0; i < num; ++i) { + if (val[i] == prop_atoms.net_wm_window_type_desktop) + self->type = Type_Desktop; + else if (val[i] == prop_atoms.net_wm_window_type_dock) + self->type = Type_Dock; + else if (val[i] == prop_atoms.net_wm_window_type_toolbar) + self->type = Type_Toolbar; + else if (val[i] == prop_atoms.net_wm_window_type_menu) + self->type = Type_Menu; + else if (val[i] == prop_atoms.net_wm_window_type_utility) + self->type = Type_Utility; + else if (val[i] == prop_atoms.net_wm_window_type_splash) + self->type = Type_Splash; + else if (val[i] == prop_atoms.net_wm_window_type_dialog) + self->type = Type_Dialog; + else if (val[i] == prop_atoms.net_wm_window_type_normal) + self->type = Type_Normal; + else if (val[i] == prop_atoms.kde_net_wm_window_type_override) { + /* prevent this window from getting any decor or + functionality */ + self->mwmhints.flags &= (MwmFlag_Functions | + MwmFlag_Decorations); + self->mwmhints.decorations = 0; + self->mwmhints.functions = 0; + } + if (self->type != (WindowType) -1) + break; /* grab the first legit type */ + } + g_free(val); + } + + if (self->type == (WindowType) -1) { + /*the window type hint was not set, which means we either classify + ourself as a normal window or a dialog, depending on if we are a + transient. */ + if (self->transient) + self->type = Type_Dialog; + else + self->type = Type_Normal; + } +} + +void client_update_protocols(Client *self) +{ + Atom *proto; + int num_return = 0, i; + + self->focus_notify = FALSE; + self->delete_window = FALSE; + + if (XGetWMProtocols(ob_display, self->window, &proto, &num_return)) { + for (i = 0; i < num_return; ++i) { + if (proto[i] == prop_atoms.wm_delete_window) { + /* this means we can request the window to close */ + self->delete_window = TRUE; + } else if (proto[i] == prop_atoms.wm_take_focus) + /* if this protocol is requested, then the window will be + notified whenever we want it to receive focus */ + self->focus_notify = TRUE; + } + XFree(proto); + } +} + +static void client_get_gravity(Client *self) +{ + XWindowAttributes wattrib; + Status ret; + + ret = XGetWindowAttributes(ob_display, self->window, &wattrib); + g_assert(ret != BadWindow); + self->gravity = wattrib.win_gravity; +} + +void client_update_normal_hints(Client *self) +{ + XSizeHints size; + long ret; + int oldgravity = self->gravity; + + /* defaults */ + self->min_ratio = 0.0f; + self->max_ratio = 0.0f; + SIZE_SET(self->size_inc, 1, 1); + SIZE_SET(self->base_size, 0, 0); + SIZE_SET(self->min_size, 0, 0); + SIZE_SET(self->max_size, G_MAXINT, G_MAXINT); + + /* get the hints from the window */ + if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) { + self->positioned = (size.flags & (PPosition|USPosition)); + + if (size.flags & PWinGravity) { + self->gravity = size.win_gravity; + + /* if the client has a frame, i.e. has already been mapped and + is changing its gravity */ + if (self->frame && self->gravity != oldgravity) { + /* move our idea of the client's position based on its new + gravity */ + self->area.x = self->frame->area.x; + self->area.y = self->frame->area.y; + frame_frame_gravity(self->frame, + &self->area.x, &self->area.y); + } + } + + if (size.flags & PAspect) { + if (size.min_aspect.y) + self->min_ratio = size.min_aspect.x / size.min_aspect.y; + if (size.max_aspect.y) + self->max_ratio = size.max_aspect.x / size.max_aspect.y; + } + + if (size.flags & PMinSize) + SIZE_SET(self->min_size, size.min_width, size.min_height); + + if (size.flags & PMaxSize) + SIZE_SET(self->max_size, size.max_width, size.max_height); + + if (size.flags & PBaseSize) + SIZE_SET(self->base_size, size.base_width, size.base_height); + + if (size.flags & PResizeInc) + SIZE_SET(self->size_inc, size.width_inc, size.height_inc); + } +} + +void client_setup_decor_and_functions(Client *self) +{ + /* start with everything (cept fullscreen) */ + self->decorations = Decor_Titlebar | Decor_Handle | Decor_Border | + Decor_Icon | Decor_AllDesktops | Decor_Iconify | Decor_Maximize; + self->functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize | + Func_Shade; + if (self->delete_window) { + self->decorations |= Decor_Close; + self->functions |= Func_Close; + } + + if (!(self->min_size.width < self->max_size.width || + self->min_size.height < self->max_size.height)) { + self->decorations &= ~(Decor_Maximize | Decor_Handle); + self->functions &= ~(Func_Resize | Func_Maximize); + } + + switch (self->type) { + case Type_Normal: + /* normal windows retain all of the possible decorations and + functionality, and are the only windows that you can fullscreen */ + self->functions |= Func_Fullscreen; + break; + + case Type_Dialog: + /* dialogs cannot be maximized */ + self->decorations &= ~Decor_Maximize; + self->functions &= ~Func_Maximize; + break; + + case Type_Menu: + case Type_Toolbar: + case Type_Utility: + /* these windows get less functionality */ + self->decorations &= ~(Decor_Iconify | Decor_Handle); + self->functions &= ~(Func_Iconify | Func_Resize); + break; + + case Type_Desktop: + case Type_Dock: + case Type_Splash: + /* none of these windows are manipulated by the window manager */ + self->decorations = 0; + self->functions = 0; + break; + } + + /* Mwm Hints are applied subtractively to what has already been chosen for + decor and functionality */ + if (self->mwmhints.flags & MwmFlag_Decorations) { + if (! (self->mwmhints.decorations & MwmDecor_All)) { + if (! (self->mwmhints.decorations & MwmDecor_Border)) + self->decorations &= ~Decor_Border; + if (! (self->mwmhints.decorations & MwmDecor_Handle)) + self->decorations &= ~Decor_Handle; + if (! (self->mwmhints.decorations & MwmDecor_Title)) + self->decorations &= ~Decor_Titlebar; + if (! (self->mwmhints.decorations & MwmDecor_Iconify)) + self->decorations &= ~Decor_Iconify; + if (! (self->mwmhints.decorations & MwmDecor_Maximize)) + self->decorations &= ~Decor_Maximize; + } + } + + if (self->mwmhints.flags & MwmFlag_Functions) { + if (! (self->mwmhints.functions & MwmFunc_All)) { + if (! (self->mwmhints.functions & MwmFunc_Resize)) + self->functions &= ~Func_Resize; + if (! (self->mwmhints.functions & MwmFunc_Move)) + self->functions &= ~Func_Move; + if (! (self->mwmhints.functions & MwmFunc_Iconify)) + self->functions &= ~Func_Iconify; + if (! (self->mwmhints.functions & MwmFunc_Maximize)) + self->functions &= ~Func_Maximize; + /* dont let mwm hints kill the close button + if (! (self->mwmhints.functions & MwmFunc_Close)) + self->functions &= ~Func_Close; */ + } + } + + /* can't maximize without moving/resizing */ + if (!((self->functions & Func_Move) && (self->functions & Func_Resize))) + self->functions &= ~Func_Maximize; + + /* finally, user specified disabled decorations are applied to subtract + decorations */ + if (self->disabled_decorations & Decor_Titlebar) + self->decorations &= ~Decor_Titlebar; + if (self->disabled_decorations & Decor_Handle) + self->decorations &= ~Decor_Handle; + if (self->disabled_decorations & Decor_Border) + self->decorations &= ~Decor_Border; + if (self->disabled_decorations & Decor_Iconify) + self->decorations &= ~Decor_Iconify; + if (self->disabled_decorations & Decor_Maximize) + self->decorations &= ~Decor_Maximize; + if (self->disabled_decorations & Decor_AllDesktops) + self->decorations &= ~Decor_AllDesktops; + if (self->disabled_decorations & Decor_Close) + self->decorations &= ~Decor_Close; + + /* if we don't have a titlebar, then we cannot shade! */ + if (!(self->decorations & Decor_Titlebar)) + self->functions &= ~Func_Shade; + + client_change_allowed_actions(self); + + if (self->frame) { + /* change the decors on the frame */ + frame_adjust_size(self->frame); + /* with more/less decorations, we may need to be repositioned */ + frame_adjust_position(self->frame); + /* with new decor, the window's maximized size may change */ + client_remaximize(self); + } +} + +static void client_change_allowed_actions(Client *self) +{ + Atom actions[9]; + int num = 0; + + actions[num++] = prop_atoms.net_wm_action_change_desktop; + + if (self->functions & Func_Shade) + actions[num++] = prop_atoms.net_wm_action_shade; + if (self->functions & Func_Close) + actions[num++] = prop_atoms.net_wm_action_close; + if (self->functions & Func_Move) + actions[num++] = prop_atoms.net_wm_action_move; + if (self->functions & Func_Iconify) + actions[num++] = prop_atoms.net_wm_action_minimize; + if (self->functions & Func_Resize) + actions[num++] = prop_atoms.net_wm_action_resize; + if (self->functions & Func_Fullscreen) + actions[num++] = prop_atoms.net_wm_action_fullscreen; + if (self->functions & Func_Maximize) { + actions[num++] = prop_atoms.net_wm_action_maximize_horz; + actions[num++] = prop_atoms.net_wm_action_maximize_vert; + } + + PROP_SET32A(self->window, net_wm_allowed_actions, atom, actions, num); + + /* make sure the window isn't breaking any rules now */ + + if (!(self->functions & Func_Shade) && self->shaded) { + if (self->frame) client_shade(self, FALSE); + else self->shaded = FALSE; + } + if (!(self->functions & Func_Iconify) && self->iconic) { + if (self->frame) client_iconify(self, FALSE, TRUE); + else self->iconic = FALSE; + } + if (!(self->functions & Func_Fullscreen) && self->fullscreen) { + if (self->frame) client_fullscreen(self, FALSE, TRUE); + else self->fullscreen = FALSE; + } + if (!(self->functions & Func_Maximize) && (self->max_horz || + self->max_vert)) { + if (self->frame) client_maximize(self, FALSE, 0, TRUE); + else self->max_vert = self->max_horz = FALSE; + } +} + +void client_remaximize(Client *self) +{ + int dir; + if (self->max_horz && self->max_vert) + dir = 0; + else if (self->max_horz) + dir = 1; + else if (self->max_vert) + dir = 2; + else + return; /* not maximized */ + self->max_horz = self->max_vert = FALSE; + client_maximize(self, TRUE, dir, FALSE); +} + +void client_update_wmhints(Client *self) +{ + XWMHints *hints; + gboolean ur = FALSE; + + /* assume a window takes input if it doesnt specify */ + self->can_focus = TRUE; + + if ((hints = XGetWMHints(ob_display, self->window)) != NULL) { + if (hints->flags & InputHint) + self->can_focus = hints->input; + + /* only do this when starting! */ + if (ob_state == State_Starting && (hints->flags & StateHint)) + self->iconic = hints->initial_state == IconicState; + + if (hints->flags & XUrgencyHint) + ur = TRUE; + + if (hints->flags & WindowGroupHint) { + if (hints->window_group != self->group) { + /* XXX: remove from the old group if there was one */ + self->group = hints->window_group; + /* XXX: do stuff with the group */ + } + } else /* no group! */ + self->group = None; + + if (hints->flags & IconPixmapHint) { + client_update_kwm_icon(self); + /* try get the kwm icon first, this is a fallback only */ + if (self->pixmap_icon == None) { + self->pixmap_icon = hints->icon_pixmap; + if (hints->flags & IconMaskHint) + self->pixmap_icon_mask = hints->icon_mask; + else + self->pixmap_icon_mask = None; + + if (self->frame) + frame_adjust_icon(self->frame); + } + } + + XFree(hints); + } + + if (ur != self->urgent) { + self->urgent = ur; + g_message("Urgent Hint for 0x%lx: %s\n", self->window, + ur ? "ON" : "OFF"); + /* fire the urgent callback if we're mapped, otherwise, wait until + after we're mapped */ + if (self->frame) + LOGICALHOOK(UrgentWindow, g_quark_try_string("client"), self); + } +} + +void client_update_title(Client *self) +{ + gchar *data = NULL; + + if (self->title != NULL) + g_free(self->title); + + /* try netwm */ + if (!PROP_GETS(self->window, net_wm_name, utf8, data)) { + /* try old x stuff */ + if (PROP_GETS(self->window, wm_name, string, data)) { + /* convert it to UTF-8 */ + gsize r, w; + gchar *u; + + u = g_locale_to_utf8(data, -1, &r, &w, NULL); + if (u == NULL) { + g_warning("Unable to convert string to UTF-8"); + } else { + g_free(data); + data = u; + } + } + } + + if (data == NULL) + data = g_strdup("Unnamed Window"); + + self->title = data; + + if (self->frame) + frame_adjust_title(self->frame); +} + +void client_update_icon_title(Client *self) +{ + gchar *data = NULL; + + if (self->icon_title != NULL) + g_free(self->icon_title); + + /* try netwm */ + if (!PROP_GETS(self->window, net_wm_icon_name, utf8, data)) { + /* try old x stuff */ + if (PROP_GETS(self->window, wm_icon_name, string, data)) { + /* convert it to UTF-8 */ + gsize r, w; + gchar *u; + + u = g_locale_to_utf8(data, -1, &r, &w, NULL); + if (u == NULL) { + g_warning("Unable to convert string to UTF-8"); + } else { + g_free(data); + data = u; + } + } + } + + if (data == NULL) + data = g_strdup(self->title); + + self->icon_title = data; +} + +void client_update_class(Client *self) +{ + GPtrArray *data; + gchar *s; + guint i; + + if (self->res_name) g_free(self->res_name); + if (self->res_class) g_free(self->res_class); + if (self->role) g_free(self->role); + + self->res_name = self->res_class = self->role = NULL; + + data = g_ptr_array_new(); + + if (PROP_GETSA(self->window, wm_class, string, data)) { + if (data->len > 0) + self->res_name = g_strdup(g_ptr_array_index(data, 0)); + if (data->len > 1) + self->res_class = g_strdup(g_ptr_array_index(data, 1)); + } + + for (i = 0; i < data->len; ++i) + g_free(g_ptr_array_index(data, i)); + g_ptr_array_free(data, TRUE); + + if (PROP_GETS(self->window, wm_window_role, string, s)) + self->role = g_strdup(s); + + if (self->res_name == NULL) self->res_name = g_strdup(""); + if (self->res_class == NULL) self->res_class = g_strdup(""); + if (self->role == NULL) self->role = g_strdup(""); +} + +void client_update_strut(Client *self) +{ + gulong num = 4; + gulong *data; + + if (PROP_GET32A(self->window, net_wm_strut, cardinal, data, num)) { + STRUT_SET(self->strut, data[0], data[1], data[2], data[3]); + g_free(data); + } else + STRUT_SET(self->strut, 0, 0, 0, 0); + + /* updating here is pointless while we're being mapped cuz we're not in + the client list yet */ + if (self->frame) + screen_update_struts(); +} + +void client_update_icons(Client *self) +{ + unsigned long num; + unsigned long *data; + unsigned long w, h, i; + int j; + + for (j = 0; j < self->nicons; ++j) + g_free(self->icons[j].data); + if (self->nicons > 0) + g_free(self->icons); + self->nicons = 0; + + if (PROP_GET32U(self->window, net_wm_icon, cardinal, data, num)) { + /* figure out how many valid icons are in here */ + i = 0; + while (num - i > 2) { + w = data[i++]; + h = data[i++]; + i += w * h; + if (i > num) break; + ++self->nicons; + } + + self->icons = g_new(Icon, self->nicons); + + /* store the icons */ + i = 0; + for (j = 0; j < self->nicons; ++j) { + w = self->icons[j].w = data[i++]; + h = self->icons[j].h = data[i++]; + self->icons[j].data = + g_memdup(&data[i], w * h * sizeof(gulong)); + i += w * h; + g_assert(i <= num); + } + + g_free(data); + } + + if (self->nicons <= 0) { + self->nicons = 1; + self->icons = g_new0(Icon, 1); + } + + if (self->frame) + frame_adjust_icon(self->frame); +} + +void client_update_kwm_icon(Client *self) +{ + Pixmap *data; + + if (PROP_GET32A(self->window, kwm_win_icon, kwm_win_icon, data, 2)) { + self->pixmap_icon = data[0]; + self->pixmap_icon_mask = data[1]; + g_free(data); + } else { + self->pixmap_icon = self->pixmap_icon_mask = None; + } + if (self->frame) + frame_adjust_icon(self->frame); +} + +static void client_change_state(Client *self) +{ + unsigned long state[2]; + Atom netstate[10]; + int num; + + state[0] = self->wmstate; + state[1] = None; + PROP_SET32A(self->window, wm_state, wm_state, state, 2); + + num = 0; + if (self->modal) + netstate[num++] = prop_atoms.net_wm_state_modal; + if (self->shaded) + netstate[num++] = prop_atoms.net_wm_state_shaded; + if (self->iconic) + netstate[num++] = prop_atoms.net_wm_state_hidden; + if (self->skip_taskbar) + netstate[num++] = prop_atoms.net_wm_state_skip_taskbar; + if (self->skip_pager) + netstate[num++] = prop_atoms.net_wm_state_skip_pager; + if (self->fullscreen) + netstate[num++] = prop_atoms.net_wm_state_fullscreen; + if (self->max_vert) + netstate[num++] = prop_atoms.net_wm_state_maximized_vert; + if (self->max_horz) + netstate[num++] = prop_atoms.net_wm_state_maximized_horz; + if (self->above) + netstate[num++] = prop_atoms.net_wm_state_above; + if (self->below) + netstate[num++] = prop_atoms.net_wm_state_below; + PROP_SET32A(self->window, net_wm_state, atom, netstate, num); + + client_calc_layer(self); + + if (self->frame) + frame_adjust_state(self->frame); +} + +static Client *search_focus_tree(Client *node, Client *skip) +{ + GSList *it; + Client *ret; + + for (it = node->transients; it != NULL; it = g_slist_next(it)) { + Client *c = it->data; + if (c == skip) continue; /* circular? */ + if ((ret = search_focus_tree(c, skip))) return ret; + if (c->focused) return c; + } + return NULL; +} + +void client_calc_layer(Client *self) +{ + StackLayer l; + gboolean fs; + Client *c; + + /* are we fullscreen, or do we have a fullscreen transient parent? */ + c = self; + fs = FALSE; + while (c) { + if (c->fullscreen) { + fs = TRUE; + break; + } + c = c->transient_for; + } + if (!fs && self->fullscreen) { + /* is one of our transients focused? */ + c = search_focus_tree(self, self); + if (c != NULL) fs = TRUE; + } + + if (self->iconic) l = Layer_Icon; + else if (fs) l = Layer_Fullscreen; + else if (self->type == Type_Desktop) l = Layer_Desktop; + else if (self->type == Type_Dock) { + if (!self->below) l = Layer_Top; + else l = Layer_Normal; + } + else if (self->above) l = Layer_Above; + else if (self->below) l = Layer_Below; + else l = Layer_Normal; + + if (l != self->layer) { + self->layer = l; + if (self->frame) + stacking_raise(self); + } +} + +void client_showhide(Client *self) +{ + gboolean show; + + if (self->iconic) show = FALSE; + else if (!(self->desktop == screen_desktop || + self->desktop == DESKTOP_ALL)) show = FALSE; + else if (client_normal(self) && screen_showing_desktop) show = FALSE; + else show = TRUE; + + if (show) frame_show(self->frame); + else frame_hide(self->frame); +} + +gboolean client_normal(Client *self) { + return ! (self->type == Type_Desktop || self->type == Type_Dock || + self->type == Type_Splash); +} + +static void client_apply_startup_state(Client *self) +{ + /* these are in a carefully crafted order.. */ + + if (self->iconic) { + self->iconic = FALSE; + client_iconify(self, TRUE, FALSE); + } + if (self->fullscreen) { + self->fullscreen = FALSE; + client_fullscreen(self, TRUE, FALSE); + } + if (self->shaded) { + self->shaded = FALSE; + client_shade(self, TRUE); + } + if (self->urgent) + LOGICALHOOK(UrgentWindow, g_quark_try_string("client"), self); + + if (self->max_vert && self->max_horz) { + self->max_vert = self->max_horz = FALSE; + client_maximize(self, TRUE, 0, FALSE); + } else if (self->max_vert) { + self->max_vert = FALSE; + client_maximize(self, TRUE, 2, FALSE); + } else if (self->max_horz) { + self->max_horz = FALSE; + client_maximize(self, TRUE, 1, FALSE); + } + + /* nothing to do for the other states: + skip_taskbar + skip_pager + modal + above + below + */ +} + +void client_configure(Client *self, Corner anchor, int x, int y, int w, int h, + gboolean user, gboolean final) +{ + w -= self->base_size.width; + h -= self->base_size.height; + + if (user) { + /* for interactive resizing. have to move half an increment in each + direction. */ + + /* how far we are towards the next size inc */ + int mw = w % self->size_inc.width; + int mh = h % self->size_inc.height; + /* amount to add */ + int aw = self->size_inc.width / 2; + int ah = self->size_inc.height / 2; + /* don't let us move into a new size increment */ + if (mw + aw >= self->size_inc.width) + aw = self->size_inc.width - mw - 1; + if (mh + ah >= self->size_inc.height) + ah = self->size_inc.height - mh - 1; + w += aw; + h += ah; + + /* if this is a user-requested resize, then check against min/max + sizes and aspect ratios */ + + /* smaller than min size or bigger than max size? */ + if (w > self->max_size.width) w = self->max_size.width; + if (w < self->min_size.width) w = self->min_size.width; + if (h > self->max_size.height) h = self->max_size.height; + if (h < self->min_size.height) h = self->min_size.height; + + /* adjust the height ot match the width for the aspect ratios */ + if (self->min_ratio) + if (h * self->min_ratio > w) h = (int)(w / self->min_ratio); + if (self->max_ratio) + if (h * self->max_ratio < w) h = (int)(w / self->max_ratio); + } + + /* keep to the increments */ + w /= self->size_inc.width; + h /= self->size_inc.height; + + /* you cannot resize to nothing */ + if (w < 1) w = 1; + if (h < 1) h = 1; + + /* store the logical size */ + SIZE_SET(self->logical_size, w, h); + + w *= self->size_inc.width; + h *= self->size_inc.height; + + w += self->base_size.width; + h += self->base_size.height; + + switch (anchor) { + case Corner_TopLeft: + break; + case Corner_TopRight: + x -= w - self->area.width; + break; + case Corner_BottomLeft: + y -= h - self->area.height; + break; + case Corner_BottomRight: + x -= w - self->area.width; + y -= h - self->area.height; + break; + } + + RECT_SET(self->area, x, y, w, h); + + XResizeWindow(ob_display, self->window, w, h); + + /* move/resize the frame to match the request */ + if (self->frame) { + /* Adjust the size and then the position, as required by the EWMH */ + frame_adjust_size(self->frame); + frame_adjust_position(self->frame); + + if (!user || final) { + XEvent event; + event.type = ConfigureNotify; + event.xconfigure.display = ob_display; + event.xconfigure.event = self->window; + event.xconfigure.window = self->window; + + /* root window coords with border in mind */ + event.xconfigure.x = x - self->border_width + + self->frame->size.left; + event.xconfigure.y = y - self->border_width + + self->frame->size.top; + + event.xconfigure.width = self->area.width; + event.xconfigure.height = self->area.height; + event.xconfigure.border_width = self->border_width; + event.xconfigure.above = self->frame->plate; + event.xconfigure.override_redirect = FALSE; + XSendEvent(event.xconfigure.display, event.xconfigure.window, + FALSE, StructureNotifyMask, &event); + } + } +} + +void client_fullscreen(Client *self, gboolean fs, gboolean savearea) +{ + static int saved_func, saved_decor; + int x, y, w, h; + + if (!(self->functions & Func_Fullscreen) || /* can't */ + self->fullscreen == fs) return; /* already done */ + + self->fullscreen = fs; + client_change_state(self); /* change the state hints on the client */ + + if (fs) { + /* save the functions and remove them */ + saved_func = self->functions; + self->functions &= (Func_Close | Func_Fullscreen | + Func_Iconify); + /* save the decorations and remove them */ + saved_decor = self->decorations; + self->decorations = 0; + if (savearea) { + long dimensions[4]; + dimensions[0] = self->area.x; + dimensions[1] = self->area.y; + dimensions[2] = self->area.width; + dimensions[3] = self->area.height; + + PROP_SET32A(self->window, openbox_premax, cardinal, + dimensions, 4); + } + x = 0; + y = 0; + w = screen_physical_size.width; + h = screen_physical_size.height; + } else { + long *dimensions; + + self->functions = saved_func; + self->decorations = saved_decor; + + if (PROP_GET32A(self->window, openbox_premax, cardinal, + dimensions, 4)) { + x = dimensions[0]; + y = dimensions[1]; + w = dimensions[2]; + h = dimensions[3]; + g_free(dimensions); + } else { + /* pick some fallbacks... */ + x = screen_area(self->desktop)->x + + screen_area(self->desktop)->width / 4; + y = screen_area(self->desktop)->y + + screen_area(self->desktop)->height / 4; + w = screen_area(self->desktop)->width / 2; + h = screen_area(self->desktop)->height / 2; + } + } + + client_change_allowed_actions(self); /* based on the new _functions */ + + /* when fullscreening, don't obey things like increments, fill the + screen */ + client_configure(self, Corner_TopLeft, x, y, w, h, !fs, TRUE); + + /* raise (back) into our stacking layer */ + stacking_raise(self); + + /* try focus us when we go into fullscreen mode */ + client_focus(self); +} + +void client_iconify(Client *self, gboolean iconic, gboolean curdesk) +{ + if (self->iconic == iconic) return; /* nothing to do */ + + g_message("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"), + self->window); + + self->iconic = iconic; + + if (iconic) { + self->wmstate = IconicState; + self->ignore_unmaps++; + /* we unmap the client itself so that we can get MapRequest events, + and because the ICCCM tells us to! */ + XUnmapWindow(ob_display, self->window); + } else { + if (curdesk) + client_set_desktop(self, screen_desktop); + self->wmstate = self->shaded ? IconicState : NormalState; + XMapWindow(ob_display, self->window); + } + client_change_state(self); + client_showhide(self); + screen_update_struts(); +} + +void client_maximize(Client *self, gboolean max, int dir, gboolean savearea) +{ + int x, y, w, h; + + g_assert(dir == 0 || dir == 1 || dir == 2); + if (!(self->functions & Func_Maximize)) return; /* can't */ + + /* check if already done */ + if (max) { + if (dir == 0 && self->max_horz && self->max_vert) return; + if (dir == 1 && self->max_horz) return; + if (dir == 2 && self->max_vert) return; + } else { + if (dir == 0 && !self->max_horz && !self->max_vert) return; + if (dir == 1 && !self->max_horz) return; + if (dir == 2 && !self->max_vert) return; + } + + x = self->frame->area.x; + y = self->frame->area.y; + w = self->frame->area.width; + h = self->frame->area.height; + + if (max) { + if (savearea) { + long dimensions[4]; + long *readdim; + + dimensions[0] = x; + dimensions[1] = y; + dimensions[2] = w; + dimensions[3] = h; + + /* get the property off the window and use it for the dimensions + we are already maxed on */ + if (PROP_GET32A(self->window, openbox_premax, cardinal, + readdim, 4)) { + if (self->max_horz) { + dimensions[0] = readdim[0]; + dimensions[2] = readdim[2]; + } + if (self->max_vert) { + dimensions[1] = readdim[1]; + dimensions[3] = readdim[3]; + } + g_free(readdim); + } + + PROP_SET32A(self->window, openbox_premax, cardinal, + dimensions, 4); + } + if (dir == 0 || dir == 1) { /* horz */ + x = screen_area(self->desktop)->x; + w = screen_area(self->desktop)->x + + screen_area(self->desktop)->width; + } + if (dir == 0 || dir == 2) { /* vert */ + y = screen_area(self->desktop)->y; + h = screen_area(self->desktop)->y + + screen_area(self->desktop)->height - + self->frame->size.top - self->frame->size.bottom; + } + } else { + long *dimensions; + + if (PROP_GET32A(self->window, openbox_premax, cardinal, + dimensions, 4)) { + if (dir == 0 || dir == 1) { /* horz */ + x = dimensions[0]; + w = dimensions[2]; + } + if (dir == 0 || dir == 2) { /* vert */ + y = dimensions[1]; + h = dimensions[3]; + } + g_free(dimensions); + } else { + /* pick some fallbacks... */ + if (dir == 0 || dir == 1) { /* horz */ + x = screen_area(self->desktop)->x + + screen_area(self->desktop)->width / 4; + w = screen_area(self->desktop)->width / 2; + } + if (dir == 0 || dir == 2) { /* vert */ + y = screen_area(self->desktop)->y + + screen_area(self->desktop)->height / 4; + h = screen_area(self->desktop)->height / 2; + } + } + } + + if (dir == 0 || dir == 1) /* horz */ + self->max_horz = max; + if (dir == 0 || dir == 2) /* vert */ + self->max_vert = max; + + if (!self->max_horz && !self->max_vert) + PROP_ERASE(self->window, openbox_premax); + + client_change_state(self); /* change the state hints on the client */ + + /* figure out where the client should be going */ + frame_frame_gravity(self->frame, &x, &y); + client_configure(self, Corner_TopLeft, x, y, w, h, TRUE, TRUE); +} + +void client_shade(Client *self, gboolean shade) +{ + if (!(self->functions & Func_Shade) || /* can't */ + self->shaded == shade) return; /* already done */ + + /* when we're iconic, don't change the wmstate */ + if (!self->iconic) + self->wmstate = shade ? IconicState : NormalState; + self->shaded = shade; + client_change_state(self); + frame_adjust_size(self->frame); +} + +void client_close(Client *self) +{ + XEvent ce; + + if (!(self->functions & Func_Close)) return; + + /* + XXX: itd be cool to do timeouts and shit here for killing the client's + process off + like... if the window is around after 5 seconds, then the close button + turns a nice red, and if this function is called again, the client is + explicitly killed. + */ + + ce.xclient.type = ClientMessage; + ce.xclient.message_type = prop_atoms.wm_protocols; + ce.xclient.display = ob_display; + ce.xclient.window = self->window; + ce.xclient.format = 32; + ce.xclient.data.l[0] = prop_atoms.wm_delete_window; + ce.xclient.data.l[1] = CurrentTime; + ce.xclient.data.l[2] = 0l; + ce.xclient.data.l[3] = 0l; + ce.xclient.data.l[4] = 0l; + XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce); +} + +void client_set_desktop(Client *self, unsigned int target) +{ + if (target == self->desktop) return; + + g_message("Setting desktop %u\n", target); + + if (!(target < screen_num_desktops || + target == DESKTOP_ALL)) + return; + + self->desktop = target; + PROP_SET32(self->window, net_wm_desktop, cardinal, target); + /* the frame can display the current desktop state */ + frame_adjust_state(self->frame); + /* 'move' the window to the new desktop */ + client_showhide(self); + screen_update_struts(); +} + +static Client *search_modal_tree(Client *node, Client *skip) +{ + GSList *it; + Client *ret; + + for (it = node->transients; it != NULL; it = it->next) { + Client *c = it->data; + if (c == skip) continue; /* circular? */ + if ((ret = search_modal_tree(c, skip))) return ret; + if (c->modal) return c; + } + return NULL; +} + +Client *client_find_modal_child(Client *self) +{ + return search_modal_tree(self, self); +} + +gboolean client_validate(Client *self) +{ + XEvent e; + + XSync(ob_display, FALSE); /* get all events on the server */ + + if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) || + XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) { + XPutBackEvent(ob_display, &e); + return FALSE; + } + + return FALSE; +} + +void client_set_wm_state(Client *self, long state) +{ + if (state == self->wmstate) return; /* no change */ + + switch (state) { + case IconicState: + client_iconify(self, TRUE, TRUE); + break; + case NormalState: + client_iconify(self, FALSE, TRUE); + break; + } +} + +void client_set_state(Client *self, Atom action, long data1, long data2) +{ + gboolean shaded = self->shaded; + gboolean fullscreen = self->fullscreen; + gboolean max_horz = self->max_horz; + gboolean max_vert = self->max_vert; + int i; + + if (!(action == prop_atoms.net_wm_state_add || + action == prop_atoms.net_wm_state_remove || + action == prop_atoms.net_wm_state_toggle)) + /* an invalid action was passed to the client message, ignore it */ + return; + + for (i = 0; i < 2; ++i) { + Atom state = i == 0 ? data1 : data2; + + if (!state) continue; + + /* if toggling, then pick whether we're adding or removing */ + if (action == prop_atoms.net_wm_state_toggle) { + if (state == prop_atoms.net_wm_state_modal) + action = self->modal ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_maximized_vert) + action = self->max_vert ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_maximized_horz) + action = self->max_horz ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_shaded) + action = self->shaded ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_skip_taskbar) + action = self->skip_taskbar ? + prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_skip_pager) + action = self->skip_pager ? + prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_fullscreen) + action = self->fullscreen ? + prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_above) + action = self->above ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_below) + action = self->below ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + } + + if (action == prop_atoms.net_wm_state_add) { + if (state == prop_atoms.net_wm_state_modal) { + /* XXX raise here or something? */ + self->modal = TRUE; + } else if (state == prop_atoms.net_wm_state_maximized_vert) { + max_vert = TRUE; + } else if (state == prop_atoms.net_wm_state_maximized_horz) { + max_horz = TRUE; + } else if (state == prop_atoms.net_wm_state_shaded) { + shaded = TRUE; + } else if (state == prop_atoms.net_wm_state_skip_taskbar) { + self->skip_taskbar = TRUE; + } else if (state == prop_atoms.net_wm_state_skip_pager) { + self->skip_pager = TRUE; + } else if (state == prop_atoms.net_wm_state_fullscreen) { + fullscreen = TRUE; + } else if (state == prop_atoms.net_wm_state_above) { + self->above = TRUE; + } else if (state == prop_atoms.net_wm_state_below) { + self->below = TRUE; + } + + } else { /* action == prop_atoms.net_wm_state_remove */ + if (state == prop_atoms.net_wm_state_modal) { + self->modal = FALSE; + } else if (state == prop_atoms.net_wm_state_maximized_vert) { + max_vert = FALSE; + } else if (state == prop_atoms.net_wm_state_maximized_horz) { + max_horz = FALSE; + } else if (state == prop_atoms.net_wm_state_shaded) { + shaded = FALSE; + } else if (state == prop_atoms.net_wm_state_skip_taskbar) { + self->skip_taskbar = FALSE; + } else if (state == prop_atoms.net_wm_state_skip_pager) { + self->skip_pager = FALSE; + } else if (state == prop_atoms.net_wm_state_fullscreen) { + fullscreen = FALSE; + } else if (state == prop_atoms.net_wm_state_above) { + self->above = FALSE; + } else if (state == prop_atoms.net_wm_state_below) { + self->below = FALSE; + } + } + } + if (max_horz != self->max_horz || max_vert != self->max_vert) { + if (max_horz != self->max_horz && max_vert != self->max_vert) { + /* toggling both */ + if (max_horz == max_vert) { /* both going the same way */ + client_maximize(self, max_horz, 0, TRUE); + } else { + client_maximize(self, max_horz, 1, TRUE); + client_maximize(self, max_vert, 2, TRUE); + } + } else { + /* toggling one */ + if (max_horz != self->max_horz) + client_maximize(self, max_horz, 1, TRUE); + else + client_maximize(self, max_vert, 2, TRUE); + } + } + /* change fullscreen state before shading, as it will affect if the window + can shade or not */ + if (fullscreen != self->fullscreen) + client_fullscreen(self, fullscreen, TRUE); + if (shaded != self->shaded) + client_shade(self, shaded); + client_calc_layer(self); + client_change_state(self); /* change the hint to relect these changes */ +} + +gboolean client_focus(Client *self) +{ + XEvent ev; + Client *child; + + /* if we have a modal child, then focus it, not us */ + child = client_find_modal_child(self); + if (child) + return client_focus(child); + + /* won't try focus if the client doesn't want it, or if the window isn't + visible on the screen */ + if (!(self->frame->visible && + (self->can_focus || self->focus_notify))) + return FALSE; + + /* do a check to see if the window has already been unmapped or destroyed + do this intelligently while watching out for unmaps we've generated + (ignore_unmaps > 0) */ + if (XCheckTypedWindowEvent(ob_display, self->window, + DestroyNotify, &ev)) { + XPutBackEvent(ob_display, &ev); + return FALSE; + } + while (XCheckTypedWindowEvent(ob_display, self->window, + UnmapNotify, &ev)) { + if (self->ignore_unmaps) { + self->ignore_unmaps--; + } else { + XPutBackEvent(ob_display, &ev); + return FALSE; + } + } + + if (self->can_focus) + XSetInputFocus(ob_display, self->window, RevertToNone, CurrentTime); + + if (self->focus_notify) { + XEvent ce; + ce.xclient.type = ClientMessage; + ce.xclient.message_type = prop_atoms.wm_protocols; + ce.xclient.display = ob_display; + ce.xclient.window = self->window; + ce.xclient.format = 32; + ce.xclient.data.l[0] = prop_atoms.wm_take_focus; + ce.xclient.data.l[1] = event_lasttime; + ce.xclient.data.l[2] = 0l; + ce.xclient.data.l[3] = 0l; + ce.xclient.data.l[4] = 0l; + XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce); + } + + /*XSync(ob_display, FALSE); XXX Why sync? */ + return TRUE; +} + +void client_unfocus(Client *self) +{ + g_assert(focus_client == self); + focus_set_client(NULL); +} diff --git a/c/client.h b/c/client.h new file mode 100644 index 0000000..d0f5266 --- /dev/null +++ b/c/client.h @@ -0,0 +1,401 @@ +#ifndef __client_h +#define __client_h + +#include "geom.h" +#include "obexport.h" +#include +#include + +struct ClientWrap; +struct Frame; + + +/*! Holds an icon in ARGB format */ +typedef struct Icon { + unsigned long w, h; + unsigned long *data; +} Icon; + +/*! The MWM Hints as retrieved from the window property + This structure only contains 3 elements, even though the Motif 2.0 + structure contains 5. We only use the first 3, so that is all gets + defined. +*/ +typedef struct MwmHints { + /*! A bitmask of Client::MwmFlags values */ + unsigned long flags; + /*! A bitmask of Client::MwmFunctions values */ + unsigned long functions; + /*! A bitmask of Client::MwmDecorations values */ + unsigned long decorations; +} MwmHints; +/*! The number of elements in the Client::MwmHints struct */ +#define MWM_ELEMENTS 3 + +/*! Possible flags for MWM Hints (defined by Motif 2.0) */ +typedef enum { + MwmFlag_Functions = 1 << 0, /*!< The MMW Hints define funcs */ + MwmFlag_Decorations = 1 << 1 /*!< The MWM Hints define decor */ +} MwmFlags; + +/*! Possible functions for MWM Hints (defined by Motif 2.0) */ +typedef enum { + MwmFunc_All = 1 << 0, /*!< All functions */ + MwmFunc_Resize = 1 << 1, /*!< Allow resizing */ + MwmFunc_Move = 1 << 2, /*!< Allow moving */ + MwmFunc_Iconify = 1 << 3, /*!< Allow to be iconfied */ + MwmFunc_Maximize = 1 << 4 /*!< Allow to be maximized */ + /*MwmFunc_Close = 1 << 5 /!< Allow to be closed */ +} MwmFunctions; + +/*! Possible decorations for MWM Hints (defined by Motif 2.0) */ +typedef enum { + MwmDecor_All = 1 << 0, /*!< All decorations */ + MwmDecor_Border = 1 << 1, /*!< Show a border */ + MwmDecor_Handle = 1 << 2, /*!< Show a handle (bottom) */ + MwmDecor_Title = 1 << 3, /*!< Show a titlebar */ + /*MwmDecor_Menu = 1 << 4, /!< Show a menu */ + MwmDecor_Iconify = 1 << 5, /*!< Show an iconify button */ + MwmDecor_Maximize = 1 << 6 /*!< Show a maximize button */ +} MemDecorations; + + +typedef struct Client { + Window window; + + struct Frame *frame; + + /*! The number of unmap events to ignore on the window */ + int ignore_unmaps; + + /*! The id of the group the window belongs to */ + Window group; + /*! Whether or not the client is a transient window. This is guaranteed to + be TRUE if transient_for != NULL, but not guaranteed to be FALSE if + transient_for == NULL. */ + gboolean transient; + /*! The client which this client is a transient (child) for */ + struct Client *transient_for; + /*! The clients which are transients (children) of this client */ + GSList *transients; + /*! The desktop on which the window resides (0xffffffff for all + desktops) */ + unsigned int desktop; + + /*! Normal window title */ + gchar *title; + /*! Window title when iconified */ + gchar *icon_title; + + /*! The application that created the window */ + gchar *res_name; + /*! The class of the window, can used for grouping */ + gchar *res_class; + /*! The specified role of the window, used for identification */ + gchar *role; + + /*! The type of window (what its function is) */ + WindowType type; + + /*! Position and size of the window + This will not always be the actual position of the window on screen, it + is, rather, the position requested by the client, to which the window's + gravity is applied. + */ + Rect area; + + /*! The window's strut + The strut defines areas of the screen that are marked off-bounds for + window placement. In theory, where this window exists. + */ + Strut strut; + + /*! The logical size of the window + The "logical" size of the window is refers to the user's perception of + the size of the window, and is the value that should be displayed to the + user. For example, with xterms, this value it the number of characters + being displayed in the terminal, instead of the number of pixels. + */ + Size logical_size; + + /*! Width of the border on the window. + The window manager will set this to 0 while the window is being managed, + but needs to restore it afterwards, so it is saved here. + */ + guint border_width; + + /*! The minimum aspect ratio the client window can be sized to. + A value of 0 means this is ignored. + */ + float min_ratio; + /*! The maximum aspect ratio the client window can be sized to. + A value of 0 means this is ignored. + */ + float max_ratio; + + /*! The minimum size of the client window + If the min is > the max, then the window is not resizable + */ + Size min_size; + /*! The maximum size of the client window + If the min is > the max, then the window is not resizable + */ + Size max_size; + /*! The size of increments to resize the client window by */ + Size size_inc; + /*! The base size of the client window + This value should be subtracted from the window's actual size when + displaying its size to the user, or working with its min/max size + */ + Size base_size; + + /*! Window decoration and functionality hints */ + MwmHints mwmhints; + + /*! Where to place the decorated window in relation to the undecorated + window */ + int gravity; + + /*! The state of the window, one of WithdrawnState, IconicState, or + NormalState */ + long wmstate; + + /*! True if the client supports the delete_window protocol */ + gboolean delete_window; + + /*! Was the window's position requested by the application? if not, we + should place the window ourselves when it first appears */ + gboolean positioned; + + /*! Can the window receive input focus? */ + gboolean can_focus; + /*! Urgency flag */ + gboolean urgent; + /*! Notify the window when it receives focus? */ + gboolean focus_notify; + /*! Does the client window have the input focus? */ + gboolean focused; + + /*! The window uses shape extension to be non-rectangular? */ + gboolean shaped; + + /*! The window is modal, so it must be processed before any windows it is + related to can be focused */ + gboolean modal; + /*! Only the window's titlebar is displayed */ + gboolean shaded; + /*! The window is iconified */ + gboolean iconic; + /*! The window is maximized to fill the screen vertically */ + gboolean max_vert; + /*! The window is maximized to fill the screen horizontally */ + gboolean max_horz; + /*! The window should not be displayed by pagers */ + gboolean skip_pager; + /*! The window should not be displayed by taskbars */ + gboolean skip_taskbar; + /*! The window is a 'fullscreen' window, and should be on top of all + others */ + gboolean fullscreen; + /*! The window should be on top of other windows of the same type */ + gboolean above; + /*! The window should be underneath other windows of the same type */ + gboolean below; + + /*! The layer in which the window will be stacked, windows in lower layers + are always below windows in higher layers. */ + StackLayer layer; + + /*! A bitmask of values in the Decoration enum + The values in the variable are the decorations that the client wants to + be displayed around it. + */ + int decorations; + + /*! A bitmask of values in the Decoration enum. + Specifies the decorations that should NOT be displayed on the client. + */ + int disabled_decorations; + + /*! A bitmask of values in the Function enum + The values in the variable specify the ways in which the user is allowed + to modify this window. + */ + int functions; + + /*! Icons for the client as specified on the client window */ + Icon *icons; + /*! The number of icons in icons */ + int nicons; + + /*! The icon for the client specified in the WMHints or the KWM hints */ + Pixmap pixmap_icon; + /*! The mask for the pixmap_icon, or None if its not masked */ + Pixmap pixmap_icon_mask; + + /* The instance of the wrapper class if one exists */ + struct ClientWrap *wrap; +} Client; + +extern GSList *client_list; +extern GHashTable *client_map; + +void client_startup(); +void client_shutdown(); + +/*! Manages all existing windows */ +void client_manage_all(); +/*! Manages a given window */ +void client_manage(Window win); +/*! Unmanages all managed windows */ +void client_unmanage_all(); +/*! Unmanages a given client */ +void client_unmanage(Client *client); + +/*! Sets the client list on the root window from the client_list */ +void client_set_list(); + +/*! Reapplies the maximized state to the window + Use this to make the window readjust its maximized size to new + surroundings (struts, etc). */ +void client_remaximize(Client *self); + +/*! Shows the window if it should be shown, or hides it + Used when changing desktops, the window's state, etc. */ +void client_showhide(Client *self); + +/*! Returns if the window should be treated as a normal window. + Some windows (desktops, docks, splash screens) have special rules applied + to them in a number of places regarding focus or user interaction. */ +gboolean client_normal(Client *self); + +/*! Internal version of the Client::resize function + This also maintains things like the client's minsize, and size increments. + @param anchor The corner to keep in the same position when resizing. + @param x The x coordiante of the new position for the client. + @param y The y coordiante of the new position for the client. + @param w The width component of the new size for the client. + @param h The height component of the new size for the client. + @param user Specifies whether this is a user-requested change or a + program requested change. For program requested changes, the + constraints are not checked. + @param final If user is true, then this should specify if this is a final + configuration. e.g. Final should be FALSE if doing an + interactive move/resize, and then be TRUE for the last call + only. +*/ +void client_configure(Client *self, Corner anchor, int x, int y, int w, int h, + gboolean user, gboolean final); + +/*! Fullscreen's or unfullscreen's the client window + @param fs true if the window should be made fullscreen; false if it should + be returned to normal state. + @param savearea true to have the client's current size and position saved; + otherwise, they are not. You should not save when mapping a + new window that is set to fullscreen. This has no effect + when restoring a window from fullscreen. +*/ +void client_fullscreen(Client *self, gboolean fs, gboolean savearea); + +/*! Iconifies or uniconifies the client window + @param iconic true if the window should be iconified; false if it should be + restored. + @param curdesk If iconic is FALSE, then this determines if the window will + be uniconified to the current viewable desktop (true) or to + its previous desktop (false) +*/ +void client_iconify(Client *self, gboolean iconic, gboolean curdesk); + +/*! Maximize or unmaximize the client window + @param max true if the window should be maximized; false if it should be + returned to normal size. + @param dir 0 to set both horz and vert, 1 to set horz, 2 to set vert. + @param savearea true to have the client's current size and position saved; + otherwise, they are not. You should not save when mapping a + new window that is set to fullscreen. This has no effect + when unmaximizing a window. +*/ +void client_maximize(Client *self, gboolean max, int dir, + gboolean savearea); + +/*! Shades or unshades the client window + @param shade true if the window should be shaded; false if it should be + unshaded. +*/ +void client_shade(Client *self, gboolean shade); + +/*! Request the client to close its window. */ +void client_close(Client *self); + +/*! Sends the window to the specified desktop */ +void client_set_desktop(Client *self, unsigned int target); + +/*! Return a modal child of the client window + @return A modal child of the client window, or 0 if none was found. +*/ +Client *client_find_modal_child(Client *self); + +/*! Validate client, by making sure no Destroy or Unmap events exist in + the event queue for the window. + @return true if the client is valid; false if the client has already + been unmapped/destroyed, and so is invalid. +*/ +gboolean client_validate(Client *self); + +/*! Sets the wm_state to the specified value */ +void client_set_wm_state(Client *self, long state); + +/*! Adjusts the window's net_state + This should not be called as part of the window mapping process! It is for + use when updating the state post-mapping.
+ client_apply_startup_state is used to do the same things during the mapping + process. +*/ +void client_set_state(Client *self, Atom action, long data1, long data2); + +/*! Attempt to focus the client window */ +gboolean client_focus(Client *self); + +/*! Remove focus from the client window */ +void client_unfocus(Client *self); + +/*! Calculates the stacking layer for the client window */ +void client_calc_layer(Client *self); + +/*! Updates the window's transient status, and any parents of it */ +void client_update_transient_for(Client *self); +/*! Update the protocols that the window supports and adjusts things if they + change */ +void client_update_protocols(Client *self); +/*! Updates the WMNormalHints and adjusts things if they change */ +void client_update_normal_hints(Client *self); + +/*! Updates the WMHints and adjusts things if they change + @param initstate Whether to read the initial_state property from the + WMHints. This should only be used during the mapping + process. +*/ +void client_update_wmhints(Client *self); +/*! Updates the window's title */ +void client_update_title(Client *self); +/*! Updates the window's icon title */ +void client_update_icon_title(Client *self); +/*! Updates the window's application name and class */ +void client_update_class(Client *self); +/*! Updates the strut for the client */ +void client_update_strut(Client *self); +/*! Updates the window's icons */ +void client_update_icons(Client *self); +/*! Updates the window's kwm icon */ +void client_update_kwm_icon(Client *self); + +/*! Set up what decor should be shown on the window and what functions should + be allowed (Client::_decorations and Client::_functions). + This also updates the NET_WM_ALLOWED_ACTIONS hint. +*/ +void client_setup_decor_and_functions(Client *self); + +/*! Retrieves the window's type and sets Client->type */ +void client_get_type(Client *self); + +#endif diff --git a/c/clientwrap.c b/c/clientwrap.c new file mode 100644 index 0000000..1ea5c9f --- /dev/null +++ b/c/clientwrap.c @@ -0,0 +1,792 @@ +#include "clientwrap.h" +#include "client.h" +#include "frame.h" +#include "stacking.h" +#include "focus.h" +#include + +/*************************************************************************** + + Define the type 'ClientWrap' + + ***************************************************************************/ + +#define IS_CWRAP(v) ((v)->ob_type == &ClientWrapType) +#define IS_VALID_CWRAP(v) ((v)->client != NULL) +#define CHECK_CWRAP(self, funcname) { \ + if (!IS_CWRAP(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'Client' " \ + "object"); \ + return NULL; \ + } \ + if (!IS_VALID_CWRAP(self)) { \ + PyErr_SetString(PyExc_ValueError, \ + "This 'Client' is wrapping a client which no longer "\ + "exists."); \ + return NULL; \ + } \ +} + + +staticforward PyTypeObject ClientWrapType; + +/*************************************************************************** + + Attribute methods + + ***************************************************************************/ + +static PyObject *cwrap_window(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "window"); + if (!PyArg_ParseTuple(args, ":window")) + return NULL; + return PyInt_FromLong(self->client->window); +} + +static PyObject *cwrap_group(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "group"); + if (!PyArg_ParseTuple(args, ":group")) + return NULL; + return PyInt_FromLong(self->client->group); +} + +static PyObject *cwrap_parent(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "parent"); + if (!PyArg_ParseTuple(args, ":parent")) + return NULL; + if (self->client->transient_for != NULL) + return clientwrap_new(self->client->transient_for); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_children(ClientWrap *self, PyObject *args) +{ + PyObject *list; + GSList *it; + guint i, s; + + CHECK_CWRAP(self, "children"); + if (!PyArg_ParseTuple(args, ":children")) + return NULL; + s = g_slist_length(self->client->transients); + list = PyList_New(s); + for (i = 0, it = self->client->transients; i < s; ++i, it = it->next) + PyList_SET_ITEM(list, i, clientwrap_new(it->data)); + return list; +} + +static PyObject *cwrap_desktop(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "desktop"); + if (!PyArg_ParseTuple(args, ":desktop")) + return NULL; + return PyInt_FromLong(self->client->desktop); +} + +static PyObject *cwrap_title(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "title"); + if (!PyArg_ParseTuple(args, ":title")) + return NULL; + return PyString_FromString(self->client->title); +} + +static PyObject *cwrap_iconTitle(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "iconTitle"); + if (!PyArg_ParseTuple(args, ":iconTitle")) + return NULL; + return PyString_FromString(self->client->icon_title); +} + +static PyObject *cwrap_resName(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "resName"); + if (!PyArg_ParseTuple(args, ":resName")) + return NULL; + return PyString_FromString(self->client->res_name); +} + +static PyObject *cwrap_resClass(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "resClass"); + if (!PyArg_ParseTuple(args, ":resClass")) + return NULL; + return PyString_FromString(self->client->res_class); +} + +static PyObject *cwrap_role(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "role"); + if (!PyArg_ParseTuple(args, ":role")) + return NULL; + return PyString_FromString(self->client->role); +} + +static PyObject *cwrap_type(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "type"); + if (!PyArg_ParseTuple(args, ":type")) + return NULL; + return PyInt_FromLong(self->client->type); +} + +static PyObject *cwrap_area(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "area"); + if (!PyArg_ParseTuple(args, ":area")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->area.x)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->area.y)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(self->client->area.width)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(self->client->area.height)); + return tuple; +} + +static PyObject *cwrap_screenArea(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "screenArea"); + if (!PyArg_ParseTuple(args, ":screenArea")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->frame->area.x)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->frame->area.y)); + PyTuple_SET_ITEM(tuple, 2, + PyInt_FromLong(self->client->frame->area.width)); + PyTuple_SET_ITEM(tuple, 3, + PyInt_FromLong(self->client->frame->area.height)); + return tuple; +} + +static PyObject *cwrap_strut(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "strut"); + if (!PyArg_ParseTuple(args, ":strut")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->strut.left)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->strut.top)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(self->client->strut.right)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(self->client->strut.bottom)); + return tuple; +} + +static PyObject *cwrap_logicalSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "logicalSize"); + if (!PyArg_ParseTuple(args, ":logicalSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, + PyInt_FromLong(self->client->logical_size.width)); + PyTuple_SET_ITEM(tuple, 1, + PyInt_FromLong(self->client->logical_size.height)); + return tuple; +} + +static PyObject *cwrap_minRatio(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "minRatio"); + if (!PyArg_ParseTuple(args, ":minRatio")) + return NULL; + return PyFloat_FromDouble(self->client->min_ratio); +} + +static PyObject *cwrap_maxRatio(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maxRatio"); + if (!PyArg_ParseTuple(args, ":maxRatio")) + return NULL; + return PyFloat_FromDouble(self->client->max_ratio); +} + +static PyObject *cwrap_minSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "minSize"); + if (!PyArg_ParseTuple(args, ":minSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->min_size.width)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->min_size.height)); + return tuple; +} + +static PyObject *cwrap_maxSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "maxSize"); + if (!PyArg_ParseTuple(args, ":maxSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->max_size.width)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->max_size.height)); + return tuple; +} + +static PyObject *cwrap_sizeIncrement(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "sizeIncrement"); + if (!PyArg_ParseTuple(args, ":sizeIncrement")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->size_inc.width)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->size_inc.height)); + return tuple; +} + +static PyObject *cwrap_baseSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "baseSize"); + if (!PyArg_ParseTuple(args, ":baseSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->base_size.width)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->base_size.height)); + return tuple; +} + +static PyObject *cwrap_gravity(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "gravity"); + if (!PyArg_ParseTuple(args, ":gravity")) + return NULL; + return PyInt_FromLong(self->client->gravity); +} + +static PyObject *cwrap_canClose(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "canClose"); + if (!PyArg_ParseTuple(args, ":canClose")) + return NULL; + return PyInt_FromLong(self->client->delete_window ? 1 : 0); +} + +static PyObject *cwrap_positionRequested(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "positionRequested"); + if (!PyArg_ParseTuple(args, ":positionRequested")) + return NULL; + return PyInt_FromLong(self->client->positioned ? 1 : 0); +} + +static PyObject *cwrap_canFocus(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "canFocus"); + if (!PyArg_ParseTuple(args, ":canFocus")) + return NULL; + return PyInt_FromLong(self->client->can_focus || + self->client->focus_notify); +} + +static PyObject *cwrap_urgent(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "urgent"); + if (!PyArg_ParseTuple(args, ":urgent")) + return NULL; + return PyInt_FromLong(self->client->urgent ? 1 : 0); +} + +static PyObject *cwrap_focused(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "focused"); + if (!PyArg_ParseTuple(args, ":focused")) + return NULL; + return PyInt_FromLong(self->client->focused ? 1 : 0); +} + +static PyObject *cwrap_modal(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "modal"); + if (!PyArg_ParseTuple(args, ":modal")) + return NULL; + return PyInt_FromLong(self->client->modal ? 1 : 0); +} + +static PyObject *cwrap_shaded(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "shaded"); + if (!PyArg_ParseTuple(args, ":shaded")) + return NULL; + return PyInt_FromLong(self->client->shaded ? 1 : 0); +} + +static PyObject *cwrap_iconic(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "iconic"); + if (!PyArg_ParseTuple(args, ":iconc")) + return NULL; + return PyInt_FromLong(self->client->iconic ? 1 : 0); +} + +static PyObject *cwrap_maximizedVertical(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maximizedVertical"); + if (!PyArg_ParseTuple(args, ":maximizedVertical")) + return NULL; + return PyInt_FromLong(self->client->max_vert ? 1 : 0); +} + +static PyObject *cwrap_maximizedHorizontal(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maximizedHorizontal"); + if (!PyArg_ParseTuple(args, ":maximizedHorizontal")) + return NULL; + return PyInt_FromLong(self->client->max_horz ? 1 : 0); +} + +static PyObject *cwrap_skipPager(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "skipPager"); + if (!PyArg_ParseTuple(args, ":skipPager")) + return NULL; + return PyInt_FromLong(self->client->skip_pager ? 1 : 0); +} + +static PyObject *cwrap_skipTaskbar(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "skipTaskbar"); + if (!PyArg_ParseTuple(args, ":skipTaskbar")) + return NULL; + return PyInt_FromLong(self->client->skip_taskbar ? 1 : 0); +} + +static PyObject *cwrap_fullscreen(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "fullscreen"); + if (!PyArg_ParseTuple(args, ":fullscreen")) + return NULL; + return PyInt_FromLong(self->client->fullscreen ? 1 : 0); +} + +static PyObject *cwrap_above(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "above"); + if (!PyArg_ParseTuple(args, ":above")) + return NULL; + return PyInt_FromLong(self->client->above ? 1 : 0); +} + +static PyObject *cwrap_below(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "below"); + if (!PyArg_ParseTuple(args, ":below")) + return NULL; + return PyInt_FromLong(self->client->below ? 1 : 0); +} + +static PyObject *cwrap_layer(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "layer"); + if (!PyArg_ParseTuple(args, ":layer")) + return NULL; + return PyInt_FromLong(self->client->layer); +} + +static PyObject *cwrap_decorations(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "decorations"); + if (!PyArg_ParseTuple(args, ":decorations")) + return NULL; + return PyInt_FromLong(self->client->decorations); +} + +static PyObject *cwrap_disabledDecorations(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "disabledDecorations"); + if (!PyArg_ParseTuple(args, ":disabledDecorations")) + return NULL; + return PyInt_FromLong(self->client->disabled_decorations); +} + +static PyObject *cwrap_functions(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "functions"); + if (!PyArg_ParseTuple(args, ":functions")) + return NULL; + return PyInt_FromLong(self->client->functions); +} + +static PyObject *cwrap_visible(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "visible"); + if (!PyArg_ParseTuple(args, ":visible")) + return NULL; + return PyInt_FromLong(self->client->frame->visible ? 1 : 0); +} + +static PyObject *cwrap_decorationSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "decorationSize"); + if (!PyArg_ParseTuple(args, ":decorationSize")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->frame->size.left)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->frame->size.top)); + PyTuple_SET_ITEM(tuple, 2, + PyInt_FromLong(self->client->frame->size.right)); + PyTuple_SET_ITEM(tuple, 3, + PyInt_FromLong(self->client->frame->size.bottom)); + return tuple; +} + +static PyObject *cwrap_normal(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "normal"); + if (!PyArg_ParseTuple(args, ":normal")) + return NULL; + return PyInt_FromLong(client_normal(self->client) ? 1 : 0); +} + +static PyObject *cwrap_setVisible(ClientWrap *self, PyObject *args) +{ + int i; + + CHECK_CWRAP(self, "setVisible"); + if (!PyArg_ParseTuple(args, "i:setVisible", &i)) + return NULL; + if (i) + frame_show(self->client->frame); + else + frame_hide(self->client->frame); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_raiseWindow(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "raiseWindow"); + if (!PyArg_ParseTuple(args, ":raiseWindow")) + return NULL; + stacking_raise(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_lowerWindow(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "lowerWindow"); + if (!PyArg_ParseTuple(args, ":lowerWindow")) + return NULL; + stacking_lower(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_focus(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "focus"); + if (!PyArg_ParseTuple(args, ":focus")) + return NULL; + return PyInt_FromLong(client_focus(self->client) ? 1 : 0); +} + +static PyObject *cwrap_unfocus(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "unfocus"); + if (!PyArg_ParseTuple(args, ":unfocus")) + return NULL; + client_unfocus(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_move(ClientWrap *self, PyObject *args) +{ + int x, y; + int final = TRUE; + CHECK_CWRAP(self, "move"); + if (!PyArg_ParseTuple(args, "ii|i:unfocus", &x, &y, &final)) + return NULL; + /* get the client's position based on x,y for the frame */ + frame_frame_gravity(self->client->frame, &x, &y); + + client_configure(self->client, Corner_TopLeft, x, y, + self->client->area.width, self->client->area.height, + TRUE, final); + Py_INCREF(Py_None); + return Py_None; +} + +#define ATTRMETH(n, d) {#n, (PyCFunction)cwrap_##n, METH_VARARGS, #d} + +static PyMethodDef ClientWrapMethods[] = { + ATTRMETH(window, + "c.window() -- Returns the window id for the Client."), + ATTRMETH(group, + "c.group() -- Returns the group id for the Client."), + ATTRMETH(parent, + "c.parent() -- Returns the parent Client for the Client, or the " + "Client for which this Client is a transient. Returns None if it " + "is not a transient."), + ATTRMETH(children, + "c.parent() -- Returns a list of child Clients for the Client, " + "or the Clients transients."), + ATTRMETH(desktop, + "c.desktop() -- Returns the desktop on which the Client resides, " + "or 0xffffffff for 'all desktops'."), + ATTRMETH(title, + "c.title() -- Returns the Client's title string. This is in " + "UTF-8 encoding."), + ATTRMETH(iconTitle, + "c.iconTitle() -- Returns the Client's icon title string. This " + "is in UTF-8 encoding."), + ATTRMETH(resName, + "c.resName() -- Returns the application's specified resource " + "name."), + ATTRMETH(resClass, + "c.resClass() -- Returns the application's specified resource " + "class."), + ATTRMETH(role, + "c.role() -- Returns the window's role, which should be unique " + "for every window of an application, if it is not empty."), + ATTRMETH(type, + "c.type() -- Returns the window's type, one of the ob.Type_ " + "constants. This is the logical type of the window."), + ATTRMETH(area, + "c.area() -- Returns the area rectangle for the Client in a " + "tuple. The tuple's format is (x, y, width, height). This is " + "not the area on-screen that the Client and frame occupies, but " + "rather, the position and size that the Client has requested, " + "before its gravity() and decorations have been applied. You " + "should use the c.screenArea() to get the actual on-screen area " + "for the Client."), + ATTRMETH(screenArea, + "c.screenArea() -- Returns the on-screen area rectangle for the " + "Client in a tuple. The tuple's format is (x, y, width, height). " + "This is the actual position and size of the Client plus its " + "decorations."), + ATTRMETH(strut, + "c.strut() -- Returns the strut requested by the Client in a " + "tuple. The format of the tuple is (left, top, right, bottom)."), + ATTRMETH(logicalSize, + "c.logicalSize() -- Returns the logical size of the Client. This " + "is the 'user friendly' size for the Client, such as for an " + "xterm, it will be the number of characters in the xterm " + "instead of the number of pixels it takes up."), + ATTRMETH(minRatio, + "c.minRatio() -- Returns the minimum width:height ratio for " + "the Client. A value of 0 implies no ratio enforcement."), + ATTRMETH(maxRatio, + "c.maxRatio() -- Returns the maximum width:height ratio for " + "the Client. A value of 0 implies no ratio enforcement."), + ATTRMETH(minSize, + "c.minSize() -- Returns the minimum size of the Client."), + ATTRMETH(maxSize, + "c.maxSize() -- Returns the maximum size of the Client."), + ATTRMETH(sizeIncrement, + "c.sizeIncrement() -- Returns the size increments in which the " + "Client must be resized."), + ATTRMETH(baseSize, + "c.baseSize() -- Returns the base size of the Client, which is " + "subtracted from the Client's size before comparing to its " + "various sizing constraints."), + ATTRMETH(gravity, + "c.gravity() -- Returns the gravity for the Client. One of the " + "ob.Gravity_ constants."), + ATTRMETH(canClose, + "c.canClose() -- Returns whether or not the Client provides a " + "means for Openbox to request that it close."), + ATTRMETH(positionRequested, + "c.positionRequested() -- Returns whether or not the Client has " + "requested a specified position on screen. When it has it should " + "probably not be placed using an algorithm when it is managed."), + ATTRMETH(canFocus, + "c.canFocus() -- Returns whether or not the Client can be " + "given input focus."), + ATTRMETH(urgent, + "c.urgent() -- Returns the urgent state of the window (on/off)."), + ATTRMETH(focused, + "c.focused() -- Returns whether or not the Client has the input " + "focus."), + ATTRMETH(modal, + "c.modal() -- Returns whether or not the Client is modal. A " + "modal Client implies that it needs to be closed before its " + "parent() can be used (focsed) again."), + ATTRMETH(shaded, + "c.shaded() -- Returns whether or not the Client is shaded. " + "Shaded clients are hidden except for their titlebar."), + ATTRMETH(iconic, + "c.iconic() -- Returns whether or not the Client is iconic. " + "Iconic windows are represented only by icons, or possibly " + "hidden entirely."), + ATTRMETH(maximizedVertical, + "c.maximizedVertical() -- Returns whether or not the Client is " + "maxized vertically. When a Client is maximized it will expand " + "to fill as much of the screen as it can in that direction."), + ATTRMETH(maximizedHorizontal, + "c.maximizedHorizontal() -- Returns whether or not the Client is " + "maxized horizontally. When a Client is maximized it will expand " + "to fill as much of the screen as it can in that direction."), + ATTRMETH(skipPager, + "c.skipPager() -- Returns whether the Client as requested to be " + "skipped by pagers."), + ATTRMETH(skipTaskbar, + "c.skipTaskbar() -- Returns whether the Client as requested to " + "be skipped by taskbars."), + ATTRMETH(fullscreen, + "c.fullscreen() -- Returns whether the Client is in fullscreen " + "mode."), + ATTRMETH(above, + "c.above() -- Returns whether the Client should be stacked above " + "other windows of the same type."), + ATTRMETH(below, + "c.below() -- Returns whether the Client should be stacked below " + "other windows of the same type."), + ATTRMETH(layer, + "c.layer() -- Returns the layer in which the window should be " + "stacked. This is one of the ob.Layer_ constants. Windows in " + "layers with higher values should be kept above windows in lower " + "valued layers."), + ATTRMETH(decorations, + "c.decorations() -- Returns a mask of decorations which the " + "Client will be given. It is made up of the ob.Decor_ constants. " + "These can be turned off with the " + " disabledDecorations()."), + ATTRMETH(disabledDecorations, + "c.disabledDecorations() -- returns a mask of decorations which " + "are disabled on the Client. This is made up of the ob.Decor_ " + "constants."), + ATTRMETH(functions, + "ob.functions() -- Returns the list of functionality for the " + "Client, in a mask made up of the ob.Func_ constants."), + ATTRMETH(visible, + "ob.visible() -- Returns if the client is currently visible " + "or hidden."), + ATTRMETH(decorationSize, + "c.decorationSize() -- Returns the size of the Client's " + "decorations around the Client window, in a tuple. The format of " + "the tuple is (left, top, right, bottom)."), + ATTRMETH(normal, + "c.normal() -- Returns if the window should be treated as a " + "normal window. Some windows (desktops, docks, splash screens) " + "should have special rules applied to them in a number of " + "places regarding focus or user interaction."), + ATTRMETH(setVisible, + "c.setVisible(show) -- Shows or hides the Client."), + ATTRMETH(raiseWindow, + "c.raiseWindow() -- Raises the Client to the top of its layer."), + ATTRMETH(lowerWindow, + "c.lowerWindow() -- Lowers the Client to the bottom of its " + "layer."), + ATTRMETH(focus, + "c.focus() -- Focuses the Client. Returns 1 if the Client will " + "be focused, or 0 if it will not."), + ATTRMETH(unfocus, + "c.unfocus() -- Unfocuses the Client, leaving nothing focused."), + ATTRMETH(move, + "c.move(x, y) -- Moves the Client to the specified position. The " + "top left corner of the Client's decorations is positioned at " + "the given x, y."), + { NULL, NULL, 0, NULL } +}; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +/*static PyObject *cwrap_getattr(ClientWrap *self, char *name) +{ + CHECK_CWRAP(self, "getattr"); + return Py_FindMethod(ClientWrapAttributeMethods, (PyObject*)self, name); +}*/ + +static void cwrap_dealloc(ClientWrap *self) +{ + if (self->client != NULL) + self->client->wrap = NULL; + PyObject_Del((PyObject*) self); +} + +static PyObject *cwrap_repr(ClientWrap *self) +{ + CHECK_CWRAP(self, "repr"); + return PyString_FromFormat("0x%x", (guint)self->client->window); +} + +static int cwrap_compare(ClientWrap *self, ClientWrap *other) +{ + Window w1, w2; + if (!IS_VALID_CWRAP(self)) { + PyErr_SetString(PyExc_ValueError, + "This 'Client' is wrapping a client which no longer " + "exists."); + } + + w1 = self->client->window; + w2 = self->client->window; + return w1 > w2 ? 1 : w1 < w2 ? -1 : 0; +} + +static PyTypeObject ClientWrapType = { + PyObject_HEAD_INIT(NULL) + 0, + "Client", + sizeof(ClientWrap), + 0, + (destructor) cwrap_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + (cmpfunc) cwrap_compare, /*tp_compare*/ + (reprfunc) cwrap_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/*************************************************************************** + + External methods + + ***************************************************************************/ + +void clientwrap_startup() +{ + ClientWrapType.ob_type = &PyType_Type; + ClientWrapType.tp_methods = ClientWrapMethods; + PyType_Ready(&ClientWrapType); +} + +void clientwrap_shutdown() +{ +} + +PyObject *clientwrap_new(Client *client) +{ + g_assert(client != NULL); + + if (client->wrap != NULL) { + /* already has a wrapper! */ + Py_INCREF((PyObject*) client->wrap); + } else { + client->wrap = PyObject_New(ClientWrap, &ClientWrapType); + client->wrap->client = client; + } + return (PyObject*) client->wrap; +} diff --git a/c/clientwrap.h b/c/clientwrap.h new file mode 100644 index 0000000..b20ca91 --- /dev/null +++ b/c/clientwrap.h @@ -0,0 +1,19 @@ +#ifndef __clientwrap_h +#define __clientwrap_h + +#include + +struct Client; + +/* ClientWrap is a PyObject */ +typedef struct ClientWrap { + PyObject_HEAD + struct Client *client; +} ClientWrap; + +void clientwrap_startup(); +void clientwrap_shutdown(); + +PyObject *clientwrap_new(struct Client *client); + +#endif diff --git a/c/event.c b/c/event.c new file mode 100644 index 0000000..32d8734 --- /dev/null +++ b/c/event.c @@ -0,0 +1,593 @@ +#include "openbox.h" +#include "client.h" +#include "xerror.h" +#include "prop.h" +#include "screen.h" +#include "frame.h" +#include "focus.h" +#include "hooks.h" +#include "stacking.h" +#include "kbind.h" +#include "mbind.h" + +#include +#include +#include + +static void event_process(XEvent *e); +static void event_handle_root(XEvent *e); +static void event_handle_client(Client *c, XEvent *e); + +Time event_lasttime = 0; + +/*! A list of all possible combinations of keyboard lock masks */ +static unsigned int mask_list[8]; +/*! The value of the mask for the NumLock modifier */ +static unsigned int NumLockMask; +/*! The value of the mask for the ScrollLock modifier */ +static unsigned int ScrollLockMask; +/*! The key codes for the modifier keys */ +static XModifierKeymap *modmap; +/*! Table of the constant modifier masks */ +static const int mask_table[] = { + ShiftMask, LockMask, ControlMask, Mod1Mask, + Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask +}; +static int mask_table_size; + +void event_startup() +{ + mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]); + + /* get lock masks that are defined by the display (not constant) */ + modmap = XGetModifierMapping(ob_display); + g_assert(modmap); + if (modmap && modmap->max_keypermod > 0) { + size_t cnt; + const size_t size = mask_table_size * modmap->max_keypermod; + /* get the values of the keyboard lock modifiers + Note: Caps lock is not retrieved the same way as Scroll and Num + lock since it doesn't need to be. */ + const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock); + const KeyCode scroll_lock = XKeysymToKeycode(ob_display, + XK_Scroll_Lock); + + for (cnt = 0; cnt < size; ++cnt) { + if (! modmap->modifiermap[cnt]) continue; + + if (num_lock == modmap->modifiermap[cnt]) + NumLockMask = mask_table[cnt / modmap->max_keypermod]; + if (scroll_lock == modmap->modifiermap[cnt]) + ScrollLockMask = mask_table[cnt / modmap->max_keypermod]; + } + } + + mask_list[0] = 0; + mask_list[1] = LockMask; + mask_list[2] = NumLockMask; + mask_list[3] = LockMask | NumLockMask; + mask_list[4] = ScrollLockMask; + mask_list[5] = ScrollLockMask | LockMask; + mask_list[6] = ScrollLockMask | NumLockMask; + mask_list[7] = ScrollLockMask | LockMask | NumLockMask; +} + +void event_shutdown() +{ + XFreeModifiermap(modmap); +} + +void event_loop() +{ + fd_set selset; + XEvent e; + int x_fd; + + while (TRUE) { + /* + There are slightly different event retrieval semantics here for + local (or high bandwidth) versus remote (or low bandwidth) + connections to the display/Xserver. + */ + if (ob_remote) { + if (!XPending(ob_display)) + break; + } else { + /* + This XSync allows for far more compression of events, which + makes things like Motion events perform far far better. Since + it also means network traffic for every event instead of every + X events (where X is the number retrieved at a time), it + probably should not be used for setups where Openbox is + running on a remote/low bandwidth display/Xserver. + */ + XSync(ob_display, FALSE); + if (!XEventsQueued(ob_display, QueuedAlready)) + break; + } + XNextEvent(ob_display, &e); + + event_process(&e); + } + + x_fd = ConnectionNumber(ob_display); + FD_ZERO(&selset); + FD_SET(x_fd, &selset); + select(x_fd + 1, &selset, NULL, NULL, NULL); +} + +void event_process(XEvent *e) +{ + XEvent ce; + KeyCode *kp; + Window window; + int i, k; + Client *client; + GQuark context; + static guint motion_button = 0; + + /* pick a window */ + switch (e->type) { + case UnmapNotify: + window = e->xunmap.window; + break; + case DestroyNotify: + window = e->xdestroywindow.window; + break; + case ConfigureRequest: + window = e->xconfigurerequest.window; + break; + default: + window = e->xany.window; + } + + /* grab the lasttime and hack up the state */ + switch (e->type) { + case ButtonPress: + case ButtonRelease: + event_lasttime = e->xbutton.time; + e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + break; + case KeyPress: + event_lasttime = e->xkey.time; + e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* add to the state the mask of the modifier being pressed, if it is + a modifier key being pressed (this is a little ugly..) */ +/* I'm commenting this out cuz i don't want "C-Control_L" being returned. */ +/* kp = modmap->modifiermap;*/ +/* for (i = 0; i < mask_table_size; ++i) {*/ +/* for (k = 0; k < modmap->max_keypermod; ++k) {*/ +/* if (*kp == e->xkey.keycode) {*/ /* found the keycode */ + /* add the mask for it */ +/* e->xkey.state |= mask_table[i];*/ + /* cause the first loop to break; */ +/* i = mask_table_size;*/ +/* break;*/ /* get outta here! */ +/* }*/ +/* ++kp;*/ +/* }*/ +/* }*/ + + break; + case KeyRelease: + event_lasttime = e->xkey.time; + e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* remove from the state the mask of the modifier being released, if + it is a modifier key being released (this is a little ugly..) */ + kp = modmap->modifiermap; + for (i = 0; i < mask_table_size; ++i) { + for (k = 0; k < modmap->max_keypermod; ++k) { + if (*kp == e->xkey.keycode) { /* found the keycode */ + /* remove the mask for it */ + e->xkey.state &= ~mask_table[i]; + /* cause the first loop to break; */ + i = mask_table_size; + break; /* get outta here! */ + } + ++kp; + } + } + break; + case MotionNotify: + event_lasttime = e->xmotion.time; + e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* compress events */ + while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) { + e->xmotion.x_root = ce.xmotion.x_root; + e->xmotion.y_root = ce.xmotion.y_root; + } + break; + case PropertyNotify: + event_lasttime = e->xproperty.time; + break; + case FocusIn: + case FocusOut: + if (e->xfocus.mode == NotifyGrab) + /*|| e.xfocus.mode == NotifyUngrab ||*/ + + /* From Metacity, from WindowMaker, ignore all funky pointer + root events. Its commented out cuz I don't think we need this + at all. If problems arise we can look into it */ + /*e.xfocus.detail > NotifyNonlinearVirtual) */ + return; /* skip me! */ + if (e->type == FocusOut) { + /* FocusOut events just make us look for FocusIn events. They + are mostly ignored otherwise. */ + XEvent fi; + if (XCheckTypedEvent(ob_display, FocusIn, &fi)) { + event_process(&fi); + /* dont unfocus the window we just focused! */ + if (fi.xfocus.window == e->xfocus.window) + return; + } + } + break; + case EnterNotify: + case LeaveNotify: + event_lasttime = e->xcrossing.time; + if (e->xcrossing.mode != NotifyNormal) + return; /* skip me! */ + break; + } + + client = g_hash_table_lookup(client_map, (gpointer)window); + if (client) { + event_handle_client(client, e); + } else if (window == ob_root) + event_handle_root(e); + else if (e->type == ConfigureRequest) { + /* unhandled configure requests must be used to configure the + window directly */ + XWindowChanges xwc; + + xwc.x = e->xconfigurerequest.x; + xwc.y = e->xconfigurerequest.y; + xwc.width = e->xconfigurerequest.width; + xwc.height = e->xconfigurerequest.height; + xwc.border_width = e->xconfigurerequest.border_width; + xwc.sibling = e->xconfigurerequest.above; + xwc.stack_mode = e->xconfigurerequest.detail; + + g_message("Proxying configure event for 0x%lx\n", window); + + /* we are not to be held responsible if someone sends us an + invalid request! */ + xerror_set_ignore(TRUE); + XConfigureWindow(ob_display, window, + e->xconfigurerequest.value_mask, &xwc); + xerror_set_ignore(FALSE); + } + + /* dispatch Crossing, Pointer and Key events to the hooks */ + switch(e->type) { + case EnterNotify: + context = frame_get_context(client, window); + LOGICALHOOK(EnterWindow, context, client); + break; + case LeaveNotify: + context = frame_get_context(client, window); + LOGICALHOOK(LeaveWindow, context, client); + break; + case ButtonPress: + if (!motion_button) motion_button = e->xbutton.button; + context = frame_get_context(client, window); + mbind_fire(e->xbutton.state, e->xbutton.button, context, + Pointer_Press, client, e->xbutton.x_root, + e->xbutton.y_root); + break; + case ButtonRelease: + if (motion_button == e->xbutton.button) motion_button = 0; + context = frame_get_context(client, window); + mbind_fire(e->xbutton.state, e->xbutton.button, context, + Pointer_Release, client, e->xbutton.x_root, + e->xbutton.y_root); + break; + case MotionNotify: + context = frame_get_context(client, window); + mbind_fire(e->xkey.state, motion_button, context, Pointer_Motion, + client, e->xmotion.x_root, e->xmotion.y_root); + break; + case KeyPress: + kbind_fire(e->xkey.state, e->xkey.keycode, TRUE); + break; + case KeyRelease: + kbind_fire(e->xkey.state, e->xkey.keycode, FALSE); + break; + } +} + +static void event_handle_root(XEvent *e) +{ + Atom msgtype; + + switch(e->type) { + case MapRequest: + g_message("MapRequest on root"); + client_manage(e->xmap.window); + break; + case ClientMessage: + if (e->xclient.format != 32) break; + + msgtype = e->xclient.message_type; + if (msgtype == prop_atoms.net_current_desktop) { + unsigned int d = e->xclient.data.l[0]; + if (d <= screen_num_desktops) + screen_set_desktop(d); + } else if (msgtype == prop_atoms.net_number_of_desktops) { + unsigned int d = e->xclient.data.l[0]; + if (d > 0) + screen_set_num_desktops(d); + } else if (msgtype == prop_atoms.net_showing_desktop) { + screen_show_desktop(e->xclient.data.l[0] != 0); + } + break; + case PropertyNotify: + if (e->xproperty.atom == prop_atoms.net_desktop_names) + screen_update_desktop_names(); + else if (e->xproperty.atom == prop_atoms.net_desktop_layout) + screen_update_layout(); + break; + } +} + +static void event_handle_client(Client *client, XEvent *e) +{ + XEvent ce; + Atom msgtype; + + switch (e->type) { + case FocusIn: + client->focused = TRUE; + frame_adjust_focus(client->frame); + + /* focus state can affect the stacking layer */ + client_calc_layer(client); + + focus_set_client(client); + break; + case FocusOut: + client->focused = FALSE; + frame_adjust_focus(client->frame); + + /* focus state can affect the stacking layer */ + client_calc_layer(client); + + if (focus_client == client) + focus_set_client(NULL); + break; + case ConfigureRequest: + g_message("ConfigureRequest for window %lx", client->window); + /* compress these */ + while (XCheckTypedWindowEvent(ob_display, client->window, + ConfigureRequest, &ce)) { + /* XXX if this causes bad things.. we can compress config req's + with the same mask. */ + e->xconfigurerequest.value_mask |= + ce.xconfigurerequest.value_mask; + if (ce.xconfigurerequest.value_mask & CWX) + e->xconfigurerequest.x = ce.xconfigurerequest.x; + if (ce.xconfigurerequest.value_mask & CWY) + e->xconfigurerequest.y = ce.xconfigurerequest.y; + if (ce.xconfigurerequest.value_mask & CWWidth) + e->xconfigurerequest.width = ce.xconfigurerequest.width; + if (ce.xconfigurerequest.value_mask & CWHeight) + e->xconfigurerequest.height = ce.xconfigurerequest.height; + if (ce.xconfigurerequest.value_mask & CWBorderWidth) + e->xconfigurerequest.border_width = + ce.xconfigurerequest.border_width; + if (ce.xconfigurerequest.value_mask & CWStackMode) + e->xconfigurerequest.detail = ce.xconfigurerequest.detail; + } + + /* if we are iconic (or shaded (fvwm does this)) ignore the event */ + if (client->iconic || client->shaded) return; + + if (e->xconfigurerequest.value_mask & CWBorderWidth) + client->border_width = e->xconfigurerequest.border_width; + + /* resize, then move, as specified in the EWMH section 7.7 */ + if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight | + CWX | CWY)) { + int x, y, w, h; + Corner corner; + + x = (e->xconfigurerequest.value_mask & CWX) ? + e->xconfigurerequest.x : client->area.x; + y = (e->xconfigurerequest.value_mask & CWY) ? + e->xconfigurerequest.y : client->area.y; + w = (e->xconfigurerequest.value_mask & CWWidth) ? + e->xconfigurerequest.width : client->area.width; + h = (e->xconfigurerequest.value_mask & CWHeight) ? + e->xconfigurerequest.height : client->area.height; + + switch (client->gravity) { + case NorthEastGravity: + case EastGravity: + corner = Corner_TopRight; + break; + case SouthWestGravity: + case SouthGravity: + corner = Corner_BottomLeft; + break; + case SouthEastGravity: + corner = Corner_BottomRight; + break; + default: /* NorthWest, Static, etc */ + corner = Corner_TopLeft; + } + + client_configure(client, corner, x, y, w, h, FALSE, FALSE); + } + + if (e->xconfigurerequest.value_mask & CWStackMode) { + switch (e->xconfigurerequest.detail) { + case Below: + case BottomIf: + stacking_lower(client); + break; + + case Above: + case TopIf: + default: + stacking_raise(client); + break; + } + } + break; + case UnmapNotify: + if (client->ignore_unmaps) { + client->ignore_unmaps--; + break; + } + g_message("UnmapNotify for %lx", client->window); + client_unmanage(client); + break; + case DestroyNotify: + g_message("DestroyNotify for %lx", client->window); + client_unmanage(client); + break; + case ReparentNotify: + /* this is when the client is first taken captive in the frame */ + if (e->xreparent.parent == client->frame->plate) break; + + /* + This event is quite rare and is usually handled in unmapHandler. + However, if the window is unmapped when the reparent event occurs, + the window manager never sees it because an unmap event is not sent + to an already unmapped window. + */ + + /* we don't want the reparent event, put it back on the stack for the + X server to deal with after we unmanage the window */ + XPutBackEvent(ob_display, e); + + client_unmanage(client); + break; + case MapRequest: + /* we shouldn't be able to get this unless we're iconic */ + g_assert(client->iconic); + + LOGICALHOOK(RequestActivate, g_quark_try_string("client"), client); + break; + case ClientMessage: + /* validate cuz we query stuff off the client here */ + if (!client_validate(client)) break; + + if (e->xclient.format != 32) return; + + msgtype = e->xclient.message_type; + if (msgtype == prop_atoms.wm_change_state) { + /* compress changes into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL messages of a + type, not just messages in a row without other + message types between. */ + if (ce.xclient.message_type != msgtype) { + XPutBackEvent(ob_display, &ce); + break; + } + e->xclient = ce.xclient; + } + client_set_wm_state(client, e->xclient.data.l[0]); + } else if (msgtype == prop_atoms.net_wm_desktop) { + /* compress changes into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL messages of a + type, not just messages in a row without other + message types between. */ + if (ce.xclient.message_type != msgtype) { + XPutBackEvent(ob_display, &ce); + break; + } + e->xclient = ce.xclient; + } + client_set_desktop(client, e->xclient.data.l[0]); + } else if (msgtype == prop_atoms.net_wm_state) { + /* can't compress these */ + g_message("net_wm_state %s %ld %ld for 0x%lx\n", + (e->xclient.data.l[0] == 0 ? "Remove" : + e->xclient.data.l[0] == 1 ? "Add" : + e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"), + e->xclient.data.l[1], e->xclient.data.l[2], + client->window); + client_set_state(client, e->xclient.data.l[0], + e->xclient.data.l[1], e->xclient.data.l[2]); + } else if (msgtype == prop_atoms.net_close_window) { + g_message("net_close_window for 0x%lx\n", client->window); + client_close(client); + } else if (msgtype == prop_atoms.net_active_window) { + g_message("net_active_window for 0x%lx\n", client->window); + if (screen_showing_desktop) + screen_show_desktop(FALSE); + if (client->iconic) + client_iconify(client, FALSE, TRUE); + else if (!client->frame->visible) + /* if its not visible for other reasons, then don't mess + with it */ + return; + LOGICALHOOK(RequestActivate, g_quark_try_string("client"), client); + } + break; + case PropertyNotify: + /* validate cuz we query stuff off the client here */ + if (!client_validate(client)) break; + + /* compress changes to a single property into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL changes to a property, + not just changes in a row without other props between. */ + if (ce.xproperty.atom != e->xproperty.atom) { + XPutBackEvent(ob_display, &ce); + break; + } + } + + msgtype = e->xproperty.atom; + if (msgtype == XA_WM_NORMAL_HINTS) { + client_update_normal_hints(client); + /* normal hints can make a window non-resizable */ + client_setup_decor_and_functions(client); + } else if (msgtype == XA_WM_HINTS) + client_update_wmhints(client); + else if (msgtype == XA_WM_TRANSIENT_FOR) { + client_update_transient_for(client); + client_get_type(client); + /* type may have changed, so update the layer */ + client_calc_layer(client); + client_setup_decor_and_functions(client); + } + else if (msgtype == prop_atoms.net_wm_name || + msgtype == prop_atoms.wm_name) + client_update_title(client); + else if (msgtype == prop_atoms.net_wm_icon_name || + msgtype == prop_atoms.wm_icon_name) + client_update_icon_title(client); + else if (msgtype == prop_atoms.wm_class) + client_update_class(client); + else if (msgtype == prop_atoms.wm_protocols) { + client_update_protocols(client); + client_setup_decor_and_functions(client); + } + else if (msgtype == prop_atoms.net_wm_strut) + client_update_strut(client); + else if (msgtype == prop_atoms.net_wm_icon) + client_update_icons(client); + else if (msgtype == prop_atoms.kwm_win_icon) + client_update_kwm_icon(client); + } +} diff --git a/c/event.h b/c/event.h new file mode 100644 index 0000000..9153116 --- /dev/null +++ b/c/event.h @@ -0,0 +1,12 @@ +#ifndef __events_h +#define __events_h + +/*! Time at which the last event with a timestamp occured. */ +extern Time event_lasttime; + +void event_startup(); +void event_shutdown(); + +void event_loop(); + +#endif diff --git a/c/eventdata.c b/c/eventdata.c new file mode 100644 index 0000000..e3bf15e --- /dev/null +++ b/c/eventdata.c @@ -0,0 +1,433 @@ +#include "eventdata.h" +#include "openbox.h" +#include "event.h" +#include "clientwrap.h" +#include + +/* + * + * Define the type 'EventData' + * + */ + +#define IS_EVENTDATA(v) ((v)->ob_type == &EventDataType) +#define CHECK_EVENTDATA(self, funcname) { \ + if (!IS_EVENTDATA(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires an 'EventData' " \ + "object"); \ + return NULL; \ + } \ +} + +staticforward PyTypeObject EventDataType; + +static PyObject *eventdata_type(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "type"); + if (!PyArg_ParseTuple(args, ":type")) + return NULL; + return PyInt_FromLong(self->type); +} + +static PyObject *eventdata_time(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "time"); + if (!PyArg_ParseTuple(args, ":time")) + return NULL; + return PyInt_FromLong(event_lasttime); +} + +static PyObject *eventdata_context(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "context"); + if (!PyArg_ParseTuple(args, ":context")) + return NULL; + return PyString_FromString(self->context); +} + +static PyObject *eventdata_client(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "client"); + if (!PyArg_ParseTuple(args, ":client")) + return NULL; + if (self->client == NULL) { + Py_INCREF(Py_None); + return Py_None; + } else { + return clientwrap_new(self->client); + } +} + +static PyObject *eventdata_keycode(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "keycode"); + if (!PyArg_ParseTuple(args, ":keycode")) + return NULL; + switch (self->type) { + case Key_Press: + case Key_Release: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Key event"); + return NULL; + } + return PyInt_FromLong(self->details.key->keycode); +} + +static PyObject *eventdata_modifiers(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "key"); + if (!PyArg_ParseTuple(args, ":key")) + return NULL; + switch (self->type) { + case Key_Press: + case Key_Release: + case Pointer_Press: + case Pointer_Release: + case Pointer_Motion: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Key or Pointer event"); + return NULL; + } + return PyInt_FromLong(self->details.key->modifiers); +} + +static PyObject *eventdata_keyName(EventData *self, PyObject *args) +{ + GList *it; + PyObject *tuple; + int i; + + CHECK_EVENTDATA(self, "keyName"); + if (!PyArg_ParseTuple(args, ":keyName")) + return NULL; + switch (self->type) { + case Key_Press: + case Key_Release: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Key event"); + return NULL; + } + + if (self->details.key->keylist != NULL) { + tuple = PyTuple_New(g_list_length(self->details.key->keylist)); + for (i = 0, it = self->details.key->keylist; it != NULL; + it = it->next, ++i) + PyTuple_SET_ITEM(tuple, i, PyString_FromString(it->data)); + return tuple; + } else { + GString *str = g_string_sized_new(0); + KeySym sym; + + if (self->details.key->modifiers & ControlMask) + g_string_append(str, "C-"); + if (self->details.key->modifiers & ShiftMask) + g_string_append(str, "S-"); + if (self->details.key->modifiers & Mod1Mask) + g_string_append(str, "Mod1-"); + if (self->details.key->modifiers & Mod2Mask) + g_string_append(str, "Mod2-"); + if (self->details.key->modifiers & Mod3Mask) + g_string_append(str, "Mod3-"); + if (self->details.key->modifiers & Mod4Mask) + g_string_append(str, "Mod4-"); + if (self->details.key->modifiers & Mod5Mask) + g_string_append(str, "Mod5-"); + + sym = XKeycodeToKeysym(ob_display, self->details.key->keycode, 0); + if (sym == NoSymbol) + g_string_append(str, "NoSymbol"); + else { + char *name = XKeysymToString(sym); + if (name == NULL) + name = "Undefined"; + g_string_append(str, name); + } + + tuple = PyTuple_New(1); + PyTuple_SET_ITEM(tuple, 0, PyString_FromString(str->str)); + g_string_free(str, TRUE); + + return tuple; + } +} + +static PyObject *eventdata_button(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "button"); + if (!PyArg_ParseTuple(args, ":button")) + return NULL; + switch (self->type) { + case Pointer_Press: + case Pointer_Release: + case Pointer_Motion: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Pointer event"); + return NULL; + } + return PyInt_FromLong(self->details.pointer->button); +} + +static PyObject *eventdata_buttonName(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "buttonName"); + if (!PyArg_ParseTuple(args, ":buttonName")) + return NULL; + switch (self->type) { + case Pointer_Press: + case Pointer_Release: + case Pointer_Motion: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Pointer event"); + return NULL; + } + + if (self->details.pointer->name != NULL) { + return PyString_FromString(self->details.pointer->name); + } else { + PyObject *pystr; + GString *str = g_string_sized_new(0); + + if (self->details.pointer->modifiers & ControlMask) + g_string_append(str, "C-"); + if (self->details.pointer->modifiers & ShiftMask) + g_string_append(str, "S-"); + if (self->details.pointer->modifiers & Mod1Mask) + g_string_append(str, "Mod1-"); + if (self->details.pointer->modifiers & Mod2Mask) + g_string_append(str, "Mod2-"); + if (self->details.pointer->modifiers & Mod3Mask) + g_string_append(str, "Mod3-"); + if (self->details.pointer->modifiers & Mod4Mask) + g_string_append(str, "Mod4-"); + if (self->details.pointer->modifiers & Mod5Mask) + g_string_append(str, "Mod5-"); + + g_string_append_printf(str, "%d", self->details.pointer->button); + + pystr = PyString_FromString(str->str); + + g_string_free(str, TRUE); + + return pystr; + } +} + +static PyObject *eventdata_position(EventData *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_EVENTDATA(self, "position"); + if (!PyArg_ParseTuple(args, ":position")) + return NULL; + switch (self->type) { + case Pointer_Press: + case Pointer_Release: + case Pointer_Motion: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Pointer event"); + return NULL; + } + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->details.pointer->xroot)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->details.pointer->yroot)); + return tuple; +} + +static PyMethodDef EventDataAttributeMethods[] = { + {"type", (PyCFunction)eventdata_type, METH_VARARGS, + "data.type() -- Return the event type"}, + {"context", (PyCFunction)eventdata_context, METH_VARARGS, + "data.context() -- Return the context for the event. If it is " + "\"client\", then data.client() can be used to find out the " + "client."}, + {"client", (PyCFunction)eventdata_client, METH_VARARGS, + "data.client() -- Return the client for the event. This may be None if " + "there is no client, even if data.context() gives Context_Client."}, + {"time", (PyCFunction)eventdata_time, METH_VARARGS, + "data.time() -- Return the time at which the last X event occured with " + "a timestamp. Should be the time at which this event, or the event that " + "caused this event to occur happened."}, + {"modifiers", (PyCFunction)eventdata_modifiers, METH_VARARGS, + "data.modifiers() -- Return the modifier keymask that was pressed " + "when the event occured. A bitmask of ShiftMask, LockMask, ControlMask, " + "Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, and Mod5Mask. Cannot be used " + "when the data.type() is not a Key_* or Pointer_* event type."}, + {"keycode", (PyCFunction)eventdata_keycode, METH_VARARGS, + "data.keycode() -- Return the keycode for the key which generated the " + "event. Cannot be used when the data.type() is not a Key_* event type."}, + {"keyName", (PyCFunction)eventdata_keyName, METH_VARARGS, + "data.keyName() -- Return a tuple of the string names of the key which " + "generated the event. Cannot be used when the data.type() is not a Key_* " + "event " + "type."}, + {"button", (PyCFunction)eventdata_button, METH_VARARGS, + "data.button() -- Return the pointer button which generated the event. " + "Cannot be used when the data.type() is not a Pointer_* event type."}, + {"buttonName", (PyCFunction)eventdata_keyName, METH_VARARGS, + "data.buttonName() -- Return the name of the button which generated the " + "event. Cannot be used when the data.type() is not a Pointer_* event " + "type."}, + {"position", (PyCFunction)eventdata_position, METH_VARARGS, + "data.position() -- Returns the current position of the pointer on the " + "root window when the event was generated. Gives the position in a tuple " + "with a format of (x, y). Cannot be used when the data.type() is not a " + "Pointer_* event type."}, + { NULL, NULL, 0, NULL } +}; + +static void data_dealloc(EventData *self) +{ + GList *it; + + switch(self->type) { + case Logical_EnterWindow: + case Logical_LeaveWindow: + case Logical_NewWindow: + case Logical_CloseWindow: + case Logical_Startup: + case Logical_Shutdown: + case Logical_RequestActivate: + case Logical_WindowShow: + case Logical_WindowHide: + case Logical_Focus: + case Logical_Bell: + case Logical_UrgentWindow: + g_free(self->details.logical); + break; + case Pointer_Press: + case Pointer_Release: + case Pointer_Motion: + if (self->details.pointer->name != NULL) + g_free(self->details.pointer->name); + g_free(self->details.pointer); + break; + case Key_Press: + case Key_Release: + for (it = self->details.key->keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(self->details.key->keylist); + g_free(self->details.key); + break; + default: + g_assert_not_reached(); + } + PyObject_Del((PyObject*) self); +} + +static PyObject *eventdata_getattr(EventData *self, char *name) +{ + return Py_FindMethod(EventDataAttributeMethods, (PyObject*)self, name); +} + +static PyTypeObject EventDataType = { + PyObject_HEAD_INIT(NULL) + 0, + "EventData", + sizeof(EventData), + 0, + (destructor) data_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc) eventdata_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + + + +void eventdata_startup() +{ + EventDataType.ob_type = &PyType_Type; + PyType_Ready(&EventDataType); +} + +void eventdata_shutdown() +{ +} + +void eventdata_free(EventData *data) +{ + Py_DECREF(data); +} + +EventData *eventdata_new_logical(EventType type, GQuark context, + struct Client *client) +{ + EventData *data; + + g_assert(type < Pointer_Press); + + data = PyObject_New(EventData, &EventDataType); + data->type = type; + data->context = g_quark_to_string(context); + data->client = client; + data->details.logical = g_new(LogicalEvent, 1); + return data; +} + +EventData *eventdata_new_pointer(EventType type, GQuark context, + struct Client *client, guint modifiers, + guint button, char *name, + int xroot, int yroot) +{ + EventData *data; + + g_assert(type >= Pointer_Press && type < Key_Press); + + data = PyObject_New(EventData, &EventDataType); + data->type = type; + data->context = g_quark_to_string(context); + data->client = client; + data->details.pointer = g_new(PointerEvent, 1); + data->details.pointer->modifiers = modifiers; + data->details.pointer->button = button; + data->details.pointer->name = name == NULL ? name : g_strdup(name); + data->details.pointer->xroot = xroot; + data->details.pointer->yroot = yroot; + return data; +} + +EventData *eventdata_new_key(EventType type, GQuark context, + struct Client *client, guint modifiers, + guint keycode, GList *keylist) +{ + EventData *data; + GList *mykeylist, *it; + + g_assert(type >= Key_Press); + + data = PyObject_New(EventData, &EventDataType); + data->type = type; + data->context = g_quark_to_string(context); + data->client = client; + data->details.key = g_new(KeyEvent, 1); + + /* make a copy of the keylist. + If the user were to clear the key bindings, then the keylist given here + would no longer point at valid memory.*/ + mykeylist = g_list_copy(keylist); /* shallow copy */ + for (it = mykeylist; it != NULL; it = it->next) /* deep copy */ + it->data = g_strdup(it->data); + + data->details.key->keylist = mykeylist; + data->details.key->keycode = keycode; + data->details.key->modifiers = modifiers; + return data; +} diff --git a/c/eventdata.h b/c/eventdata.h new file mode 100644 index 0000000..ef6beab --- /dev/null +++ b/c/eventdata.h @@ -0,0 +1,74 @@ +#ifndef __eventdata_h +#define __eventdata_h + +#include "obexport.h" +#include +#include + +struct Client; + +typedef struct { + int temp:1; /* just a placeholder to kill warnings for now.. */ +} LogicalEvent; + +typedef struct { + /*! The button which generated the event */ + guint button; + /*! The pointer's x position on the root window when the event occured */ + int xroot; + /*! The pointer's y position on the root window when the event occured */ + int yroot; + /*! The modifiers that were pressed when the event occured. A bitmask of: + ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask, + Mod4Mask, Mod5Mask */ + guint modifiers; + /*! The name of the button/modifier combination being pressed, + eg "Mod1-1" */ + char *name; +} PointerEvent; + +typedef struct { + /*! The keycode of the key which generated the event */ + guint keycode; + /*! The modifiers that were pressed when the event occured. A bitmask of: + ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask, + Mod4Mask, Mod5Mask */ + guint modifiers; + /* The list of strings which make up the chain that fired, + eg ("Mod1-a", "a") */ + GList *keylist; +} KeyEvent; + +/* EventData is a PyObject */ +typedef struct EventData { + PyObject_HEAD + /* The type of event which occured */ + EventType type; + /*! The context in which the event occured, the type of window it occured + for. */ + const char *context; + /* The Client on which the event occured, or NULL */ + struct Client *client; + + union EventDetails { + LogicalEvent *logical; + PointerEvent *pointer; + KeyEvent *key; + } details; +} EventData; + +void eventdata_startup(); +void eventdata_shutdown(); + +EventData *eventdata_new_logical(EventType type, GQuark context, + struct Client *client); +EventData *eventdata_new_pointer(EventType type, GQuark context, + struct Client *client, guint modifiers, + guint button, char *name, + int xroot, int yroot); +EventData *eventdata_new_key(EventType type, GQuark context, + struct Client *client, guint modifiers, + guint keycode, GList *keylist); +void eventdata_free(EventData *data); + +#endif diff --git a/c/extensions.c b/c/extensions.c new file mode 100644 index 0000000..3fe4319 --- /dev/null +++ b/c/extensions.c @@ -0,0 +1,34 @@ +#include "openbox.h" +#include "extensions.h" + +gboolean extensions_xkb = FALSE; +int extensions_xkb_event_basep; +gboolean extensions_shape = FALSE; +int extensions_shape_event_basep; +gboolean extensions_xinerama = FALSE; +int extensions_xinerama_event_basep; + + +void extensions_query_all() +{ + int junk; + (void)junk; + +#ifdef XKB + extensions_xkb = + XkbQueryExtension(ob_display, &junk, &extensions_xkb_event_basep, + &junk, NULL, NULL); +#endif + +#ifdef SHAPE + extensions_shape = + XShapeQueryExtension(ob_display, &extensions_shape_event_basep, + &junk); +#endif + +#ifdef XINERAMA + extensions_xinerama = + XineramaQueryExtension(ob_display, &extensions_xinerama_event_basep, + &junk); +#endif +} diff --git a/c/extensions.h b/c/extensions.h new file mode 100644 index 0000000..3c11076 --- /dev/null +++ b/c/extensions.h @@ -0,0 +1,33 @@ +#ifndef __extensions_h +#define __extensions_h + +#include +#ifdef XKB +#include +#endif +#ifdef SHAPE +#include +#endif +#ifdef XINERAMA +#include +#endif +#include + +/*! Does the display have the XKB extension? */ +extern gboolean extensions_xkb; +/*! Base for events for the XKB extension */ +extern int extensions_xkb_event_basep; + +/*! Does the display have the Shape extension? */ +extern gboolean extensions_shape; +/*! Base for events for the Shape extension */ +extern int extensions_shape_event_basep; + +/*! Does the display have the Xinerama extension? */ +extern gboolean extensions_xinerama; +/*! Base for events for the Xinerama extension */ +extern int extensions_xinerama_event_basep; + +void extensions_query_all(); + +#endif diff --git a/c/focus.c b/c/focus.c new file mode 100644 index 0000000..329a4a9 --- /dev/null +++ b/c/focus.c @@ -0,0 +1,56 @@ +#include "openbox.h" +#include "client.h" +#include "screen.h" +#include "prop.h" +#include "hooks.h" +#include + +Client *focus_client = NULL; + +Window focus_backup = None; + +void focus_set_client(Client *client); + +void focus_startup() +{ + /* create the window which gets focus when no clients get it. Have to + make it override-redirect so we don't try manage it, since it is + mapped. */ + XSetWindowAttributes attrib; + + attrib.override_redirect = TRUE; + focus_backup = XCreateWindow(ob_display, ob_root, + -100, -100, 1, 1, 0, 0, InputOnly, + CopyFromParent, CWOverrideRedirect, &attrib); + XMapRaised(ob_display, focus_backup); + + /* start with nothing focused */ + focus_set_client(NULL); +} + +void focus_set_client(Client *client) +{ + Window active; + + /* sometimes this is called with the already-focused window, this is + important for the python scripts to work (eg, c = 0 twice). don't just + return if _focused_client == c */ + + /* uninstall the old colormap, and install the new one */ + screen_install_colormap(focus_client, FALSE); + screen_install_colormap(client, TRUE); + + + if (client == NULL) { + /* when nothing will be focused, send focus to the backup target */ + XSetInputFocus(ob_display, focus_backup, RevertToNone, CurrentTime); + } + + focus_client = client; + + /* set the NET_ACTIVE_WINDOW hint */ + active = client ? client->window : None; + PROP_SET32(ob_root, net_active_window, window, active); + + LOGICALHOOK(Focus, g_quark_try_string("client"), client); +} diff --git a/c/focus.h b/c/focus.h new file mode 100644 index 0000000..9db5202 --- /dev/null +++ b/c/focus.h @@ -0,0 +1,20 @@ +#ifndef __focus_h +#define __focus_h + +#include + +struct Client; + +/*! The window which gets focus when nothing else will be focused */ +extern Window focus_backup; + +/*! The client which is currently focused */ +extern struct Client *focus_client; + +void focus_startup(); + +/*! Specify which client is currently focused, this doesn't actually + send focus anywhere, its called by the Focus event handlers */ +void focus_set_client(struct Client *client); + +#endif diff --git a/c/frame.c b/c/frame.c new file mode 100644 index 0000000..536b4cd --- /dev/null +++ b/c/frame.c @@ -0,0 +1,533 @@ +#include "openbox.h" +#include "frame.h" +#include "extensions.h" +#include "hooks.h" + +#define PLATE_EVENTMASK (SubstructureRedirectMask | ButtonPressMask) +#define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask) + +static Window createWindow(Window parent, unsigned long mask, + XSetWindowAttributes *attrib) +{ + /* XXX DONT USE THE DEFAULT SHIT */ + return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0, + DefaultDepth(ob_display, ob_screen), InputOutput, + DefaultVisual(ob_display, ob_screen), + mask, attrib); + +} + +Frame *frame_new(Client *client) +{ + XSetWindowAttributes attrib; + unsigned long mask; + Frame *self; + + self = g_new(Frame, 1); + + self->client = client; + self->visible = FALSE; + + /* create all of the decor windows */ + mask = CWOverrideRedirect | CWEventMask; + attrib.event_mask = FRAME_EVENTMASK; + attrib.override_redirect = TRUE; + self->window = createWindow(ob_root, mask, &attrib); + + mask = 0; + self->plate = createWindow(self->window, mask, &attrib); + mask = CWEventMask; + attrib.event_mask = (ButtonPressMask | ButtonReleaseMask | + ButtonMotionMask | ExposureMask); + self->title = createWindow(self->window, mask, &attrib); + self->label = createWindow(self->title, mask, &attrib); + self->max = createWindow(self->title, mask, &attrib); + self->close = createWindow(self->title, mask, &attrib); + self->desk = createWindow(self->title, mask, &attrib); + self->icon = createWindow(self->title, mask, &attrib); + self->iconify = createWindow(self->title, mask, &attrib); + self->handle = createWindow(self->window, mask, &attrib); + mask |= CWCursor; + attrib.cursor = ob_cursors.ll_angle; + self->lgrip = createWindow(self->handle, mask, &attrib); + attrib.cursor = ob_cursors.lr_angle; + self->rgrip = createWindow(self->handle, mask, &attrib); + + /* the other stuff is shown based on decor settings */ + XMapWindow(ob_display, self->plate); + XMapWindow(ob_display, self->lgrip); + XMapWindow(ob_display, self->rgrip); + XMapWindow(ob_display, self->label); + + + /* XXX TEMPORARY OF COURSE!@&*(@! */ + + XSetWindowBackground(ob_display, self->title, 0x3333aa); + XSetWindowBackground(ob_display, self->handle, 0x3333aa); + XSetWindowBackground(ob_display, self->lgrip, 0x2233aa); + XSetWindowBackground(ob_display, self->rgrip, 0x2233aa); + + XSetWindowBorder(ob_display, self->window, 0); + XSetWindowBorder(ob_display, self->label, 0); + XSetWindowBorder(ob_display, self->rgrip, 0); + XSetWindowBorder(ob_display, self->lgrip, 0); + XSetWindowBorder(ob_display, self->plate, 0x771122); + + /* XXX /TEMPORARY OF COURSE!@&*(@! */ + + /* set all the windows for the frame in the client_map */ + g_hash_table_insert(client_map, (gpointer)self->window, self->client); + g_hash_table_insert(client_map, (gpointer)self->plate, self->client); + g_hash_table_insert(client_map, (gpointer)self->title, self->client); + g_hash_table_insert(client_map, (gpointer)self->label, self->client); + g_hash_table_insert(client_map, (gpointer)self->max, self->client); + g_hash_table_insert(client_map, (gpointer)self->close, self->client); + g_hash_table_insert(client_map, (gpointer)self->desk, self->client); + g_hash_table_insert(client_map, (gpointer)self->icon, self->client); + g_hash_table_insert(client_map, (gpointer)self->iconify, self->client); + g_hash_table_insert(client_map, (gpointer)self->handle, self->client); + g_hash_table_insert(client_map, (gpointer)self->lgrip, self->client); + g_hash_table_insert(client_map, (gpointer)self->rgrip, self->client); + + return self; +} + +void frame_free(Frame *self) +{ + /* remove all the windows for the frame from the client_map */ + g_hash_table_remove(client_map, (gpointer)self->window); + g_hash_table_remove(client_map, (gpointer)self->plate); + g_hash_table_remove(client_map, (gpointer)self->title); + g_hash_table_remove(client_map, (gpointer)self->label); + g_hash_table_remove(client_map, (gpointer)self->max); + g_hash_table_remove(client_map, (gpointer)self->close); + g_hash_table_remove(client_map, (gpointer)self->desk); + g_hash_table_remove(client_map, (gpointer)self->icon); + g_hash_table_remove(client_map, (gpointer)self->iconify); + g_hash_table_remove(client_map, (gpointer)self->handle); + g_hash_table_remove(client_map, (gpointer)self->lgrip); + g_hash_table_remove(client_map, (gpointer)self->rgrip); + + XDestroyWindow(ob_display, self->window); + + g_free(self); +} + +void frame_grab_client(Frame *self) +{ + /* reparent the client to the frame */ + XReparentWindow(ob_display, self->client->window, self->plate, 0, 0); + /* + When reparenting the client window, it is usually not mapped yet, since + this occurs from a MapRequest. However, in the case where Openbox is + starting up, the window is already mapped, so we'll see unmap events for + it. There are 2 unmap events generated that we see, one with the 'event' + member set the root window, and one set to the client, but both get + handled and need to be ignored. + */ + if (ob_state == State_Starting) + self->client->ignore_unmaps += 2; + + /* select the event mask on the client's parent (to receive config/map + req's) the ButtonPress is to catch clicks on the client border */ + XSelectInput(ob_display, self->plate, PLATE_EVENTMASK); + + /* map the client so it maps when the frame does */ + XMapWindow(ob_display, self->client->window); + + frame_adjust_size(self); + frame_adjust_position(self); +} + +void frame_release_client(Frame *self) +{ + XEvent ev; + + /* check if the app has already reparented its window away */ + if (XCheckTypedWindowEvent(ob_display, self->client->window, + ReparentNotify, &ev)) { + XPutBackEvent(ob_display, &ev); + /* re-map the window since the unmanaging process unmaps it */ + XMapWindow(ob_display, self->client->window); + } else { + /* according to the ICCCM - if the client doesn't reparent itself, + then we will reparent the window to root for them */ + XReparentWindow(ob_display, self->client->window, ob_root, + self->client->area.x, self->client->area.y); + } +} + +void frame_show(Frame *self) +{ + if (!self->visible) { + self->visible = TRUE; + XMapWindow(ob_display, self->window); + LOGICALHOOK(WindowShow, g_quark_try_string("client"), self->client); + } +} + +void frame_hide(Frame *self) +{ + if (self->visible) { + self->visible = FALSE; + self->client->ignore_unmaps++; + XUnmapWindow(ob_display, self->window); + LOGICALHOOK(WindowHide, g_quark_try_string("client"), self->client); + } +} + +void frame_adjust_size(Frame *self) +{ + self->decorations = self->client->decorations; + + /* XXX set shit from the style */ + self->geom.font_height = 10; + self->geom.bevel = 1; + self->geom.button_size = self->geom.font_height - 2; + self->geom.handle_height = 2; + self->geom.grip_width = self->geom.button_size * 2; + XResizeWindow(ob_display, self->lgrip, self->geom.grip_width, + self->geom.handle_height); + XResizeWindow(ob_display, self->rgrip, self->geom.grip_width, + self->geom.handle_height); + + + + + if (self->decorations & Decor_Border) { + self->geom.bwidth = 1;/*XXX style->frameBorderWidth(); */ + self->geom.cbwidth = 1; /*XXX style->clientBorderWidth(); */ + } else { + self->geom.bwidth = self->geom.cbwidth = 0; + } + STRUT_SET(self->innersize, self->geom.cbwidth, self->geom.cbwidth, + self->geom.cbwidth, self->geom.cbwidth); + self->geom.width = self->client->area.width + self->geom.cbwidth * 2; + g_assert(self->geom.width > 0); + + /* set border widths */ + XSetWindowBorderWidth(ob_display, self->plate, self->geom.cbwidth); + XSetWindowBorderWidth(ob_display, self->window, self->geom.bwidth); + XSetWindowBorderWidth(ob_display, self->title, self->geom.bwidth); + XSetWindowBorderWidth(ob_display, self->handle, self->geom.bwidth); + XSetWindowBorderWidth(ob_display, self->lgrip, self->geom.bwidth); + XSetWindowBorderWidth(ob_display, self->rgrip, self->geom.bwidth); + + /* position/size and map/unmap all the windows */ + + if (self->decorations & Decor_Titlebar) { + self->geom.title_height = self->geom.font_height + + self->geom.bevel * 2; + XMoveResizeWindow(ob_display, self->title, + -self->geom.bwidth, -self->geom.bwidth, + self->geom.width, self->geom.title_height); + self->innersize.top += self->geom.title_height + self->geom.bwidth; + XMapWindow(ob_display, self->title); + + /* layout the title bar elements */ + /*XXX layoutTitle(); */ + } else { + XUnmapWindow(ob_display, self->title); + /* make all the titlebar stuff not render */ + self->decorations &= ~(Decor_Icon | Decor_Iconify | + Decor_Maximize | Decor_Close | + Decor_AllDesktops); + } + + if (self->decorations & Decor_Handle) { + self->geom.handle_y = self->innersize.top + + self->client->area.height + self->geom.cbwidth; + XMoveResizeWindow(ob_display, self->handle, + -self->geom.bwidth, self->geom.handle_y, + self->geom.width, self->geom.handle_height); + XMoveWindow(ob_display, self->lgrip, + -self->geom.bwidth, -self->geom.bwidth); + XMoveWindow(ob_display, self->rgrip, + -self->geom.bwidth + self->geom.width - + self->geom.grip_width, -self->geom.bwidth); + self->innersize.bottom += self->geom.handle_height + + self->geom.bwidth; + XMapWindow(ob_display, self->handle); + } else + XUnmapWindow(ob_display, self->handle); + + XResizeWindow(ob_display, self->window, self->geom.width, + (self->client->shaded ? self->geom.title_height : + self->innersize.top + self->innersize.bottom + + self->client->area.height)); + + /* do this in two steps because clients whose gravity is set to + 'Static' don't end up getting moved at all with an XMoveResizeWindow */ + XMoveWindow(ob_display, self->plate, + self->innersize.left - self->geom.cbwidth, + self->innersize.top - self->geom.cbwidth); + XResizeWindow(ob_display, self->plate, self->client->area.width, + self->client->area.height); + + STRUT_SET(self->size, + self->innersize.left + self->geom.bwidth, + self->innersize.right + self->geom.bwidth, + self->innersize.top + self->geom.bwidth, + self->innersize.bottom + self->geom.bwidth); + + RECT_SET_SIZE(self->area, + self->client->area.width + + self->size.left + self->size.right, + self->client->area.height + + self->size.top + self->size.bottom); + + /* + // render all the elements + int screen = _client->screen(); + bool focus = _client->focused(); + if (_decorations & Client::Decor_Titlebar) { + render(screen, otk::Size(geom.width, geom.title_height()), _title, + &_title_sur, *(focus ? style->titlebarFocusBackground() : + style->titlebarUnfocusBackground()), false); + + renderLabel(); + renderMax(); + renderDesk(); + renderIconify(); + renderIcon(); + renderClose(); + } + + if (_decorations & Client::Decor_Handle) { + render(screen, otk::Size(geom.width, geom.handle_height), _handle, + &_handle_sur, *(focus ? style->handleFocusBackground() : + style->handleUnfocusBackground())); + render(screen, otk::Size(geom.grip_width(), geom.handle_height), _lgrip, + &_grip_sur, *(focus ? style->gripFocusBackground() : + style->gripUnfocusBackground())); + if ((focus ? style->gripFocusBackground() : + style->gripUnfocusBackground())->parentRelative()) + XSetWindowBackgroundPixmap(**otk::display, _rgrip, ParentRelative); + else { + XSetWindowBackgroundPixmap(**otk::display, _rgrip, _grip_sur->pixmap()); + } + XClearWindow(**otk::display, _rgrip); + } + + XSetWindowBorder(**otk::display, _plate, + focus ? style->clientBorderFocusColor()->pixel() : + style->clientBorderUnfocusColor()->pixel()); + + */ + + frame_adjust_shape(self); +} + +void frame_adjust_position(Frame *self) +{ + self->area.x = self->client->area.x; + self->area.y = self->client->area.y; + frame_client_gravity(self, &self->area.x, &self->area.y); + XMoveWindow(ob_display, self->window, self->area.x, self->area.y); +} + +void frame_adjust_shape(Frame *self) +{ +#ifdef SHAPE + int num; + XRectangle xrect[2]; + + if (!self->client->shaped) { + /* clear the shape on the frame window */ + XShapeCombineMask(ob_display, self->window, ShapeBounding, + self->innersize.left, + self->innersize.top, + None, ShapeSet); + } else { + /* make the frame's shape match the clients */ + XShapeCombineShape(ob_display, self->window, ShapeBounding, + self->innersize.left, + self->innersize.top, + self->client->window, ShapeBounding, ShapeSet); + + num = 0; + if (self->decorations & Decor_Titlebar) { + xrect[0].x = -self->geom.bevel; + xrect[0].y = -self->geom.bevel; + xrect[0].width = self->geom.width + self->geom.bwidth * 2; + xrect[0].height = self->geom.title_height + + self->geom.bwidth * 2; + ++num; + } + + if (self->decorations & Decor_Handle) { + xrect[1].x = -self->geom.bevel; + xrect[1].y = self->geom.handle_y; + xrect[1].width = self->geom.width + self->geom.bwidth * 2; + xrect[1].height = self->geom.handle_height + + self->geom.bwidth * 2; + ++num; + } + + XShapeCombineRectangles(ob_display, self->window, + ShapeBounding, 0, 0, xrect, num, + ShapeUnion, Unsorted); + } +#endif +} + +void frame_client_gravity(Frame *self, int *x, int *y) +{ + /* horizontal */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case SouthWestGravity: + case WestGravity: + break; + + case NorthGravity: + case SouthGravity: + case CenterGravity: + *x -= (self->size.left + self->size.right) / 2; + break; + + case NorthEastGravity: + case SouthEastGravity: + case EastGravity: + *x -= self->size.left + self->size.right; + break; + + case ForgetGravity: + case StaticGravity: + *x -= self->size.left; + break; + } + + /* vertical */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case NorthEastGravity: + case NorthGravity: + break; + + case CenterGravity: + case EastGravity: + case WestGravity: + *y -= (self->size.top + self->size.bottom) / 2; + break; + + case SouthWestGravity: + case SouthEastGravity: + case SouthGravity: + *y -= self->size.top + self->size.bottom; + break; + + case ForgetGravity: + case StaticGravity: + *y -= self->size.top; + break; + } +} + +void frame_frame_gravity(Frame *self, int *x, int *y) +{ + /* horizontal */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + break; + case NorthGravity: + case CenterGravity: + case SouthGravity: + *x += (self->size.left + self->size.right) / 2; + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + *x += self->size.left + self->size.right; + break; + case StaticGravity: + case ForgetGravity: + x += self->size.left; + break; + } + + /* vertical */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + break; + case NorthGravity: + case CenterGravity: + case SouthGravity: + *y += (self->size.top + self->size.bottom) / 2; + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + *y += self->size.top + self->size.bottom; + break; + case StaticGravity: + case ForgetGravity: + *y += self->size.top; + break; + } +} + +void frame_adjust_state(Frame *self) +{ + /* XXX do shit.. buttons? */ +} + +void frame_adjust_focus(Frame *self) +{ + /* XXX optimizations later... */ + frame_adjust_size(self); +} + +void frame_adjust_title(Frame *self) +{ + /* XXX optimizations later... */ + frame_adjust_size(self); +} + +void frame_adjust_icon(Frame *self) +{ + /* XXX render icon */ +} + +GQuark frame_get_context(Client *client, Window win) +{ + Frame *self; + + if (win == ob_root) return g_quark_try_string("root"); + if (client == NULL) return g_quark_try_string("none"); + if (win == client->window) return g_quark_try_string("client"); + + self = client->frame; + if (win == self->window) return g_quark_try_string("frame"); + if (win == self->plate) return g_quark_try_string("frame"); + if (win == self->title) return g_quark_try_string("titlebar"); + if (win == self->label) return g_quark_try_string("titlebar"); + if (win == self->handle) return g_quark_try_string("handle"); + if (win == self->lgrip) return g_quark_try_string("blcorner"); + if (win == self->rgrip) return g_quark_try_string("brcorner"); + + return g_quark_try_string("none"); +} + +void frame_startup(void) +{ + g_quark_from_string("none"); + g_quark_from_string("root"); + g_quark_from_string("client"); + g_quark_from_string("titlebar"); + g_quark_from_string("handle"); + g_quark_from_string("frame"); + g_quark_from_string("blcorner"); + g_quark_from_string("brcorner"); + g_quark_from_string("tlcorner"); + g_quark_from_string("trcorner"); + g_quark_from_string("foo"); +} diff --git a/c/frame.h b/c/frame.h new file mode 100644 index 0000000..7d84167 --- /dev/null +++ b/c/frame.h @@ -0,0 +1,101 @@ +#ifndef __frame_h +#define __frame_h + +#include +#include "geom.h" +#include "client.h" + +/*! Varius geometry settings in the frame decorations */ +typedef struct { + int width; /* title and handle */ + int font_height; +/* int title_height() { return font_height + bevel*2; } */ + int title_height; + int label_width; +/* int label_height() { return font_height; } */ + int handle_height; /* static, from the style */ + int icon_x; /* x-position of the window icon button */ + int title_x; /* x-position of the window title */ + int iconify_x; /* x-position of the window iconify button */ + int desktop_x; /* x-position of the window all-desktops button */ + int max_x; /* x-position of the window maximize button */ + int close_x; /* x-position of the window close button */ + int handle_y; + int button_size; /* static, from the style */ +/* int grip_width() { return button_size * 2; } */ + int grip_width; + int bevel; /* static, from the style */ + int bwidth; /* frame elements' border width */ + int cbwidth; /* client border width */ +} FrameGeometry; + +typedef struct Frame { + Window window; + Window plate; + Window title; + Window label; + Window max; + Window close; + Window desk; + Window icon; + Window iconify; + Window handle; + Window lgrip; + Window rgrip; + + Strut size; + Strut innersize; + Rect area; + FrameGeometry geom; + + Client *client; + int decorations; + + gboolean visible; +} Frame; + +Frame *frame_new(struct Client *client); +void frame_free(Frame *self); + +void frame_grab_client(Frame *self); +void frame_release_client(Frame *self); + +/*! Update the frame's size to match the client */ +void frame_adjust_size(Frame *self); +/*! Update the frame's position to match the client */ +void frame_adjust_position(Frame *self); +/*! Shape the frame window to the client window */ +void frame_adjust_shape(Frame *self); +/*! Update the frame to match the client's new state (for things like toggle + buttons, focus, and the title) XXX break this up */ +void frame_adjust_state(Frame *self); +/*! Update the frame to match the client's focused state */ +void frame_adjust_focus(Frame *self); +/*! Update the frame to display the client's current title */ +void frame_adjust_title(Frame *self); +/*! Update the frame to display the client's current icon */ +void frame_adjust_icon(Frame *self); + +/*! Applies gravity to the client's position to find where the frame should + be positioned. + @return The proper coordinates for the frame, based on the client. +*/ +void frame_client_gravity(Frame *self, int *x, int *y); + +/*! Reversly applies gravity to the frame's position to find where the client + should be positioned. + @return The proper coordinates for the client, based on the frame. +*/ +void frame_frame_gravity(Frame *self, int *x, int *y); + +/*! Shows the frame */ +void frame_show(Frame *self); +/*! Hides the frame */ +void frame_hide(Frame *self); + +/*! inits quarks - this will go in engines later */ +void frame_startup(void); + +GQuark frame_get_context(Client *client, Window win); + +#endif diff --git a/c/geom.h b/c/geom.h new file mode 100644 index 0000000..aa763db --- /dev/null +++ b/c/geom.h @@ -0,0 +1,53 @@ +#ifndef __geom_h +#define __geom_h + +#ifdef HAVE_ASSERT_H +# include +#endif + +typedef struct Point { + int x; + int y; +} Point; + +#define POINT_SET(pt, nx, ny) {pt.x = nx; pt.y = ny;} + +typedef struct Size { + int width; + int height; +} Size; + +#define SIZE_SET(sz, w, h) {sz.width = w; sz.height = h;} + +typedef struct Rect { + int x; + int y; + int width; + int height; +} Rect; + +#define RECT_SET_POINT(r, nx, ny) \ + {r.x = ny; r.y = ny;} +#define RECT_SET_SIZE(r, w, h) \ + {r.width = w; r.height = h;} +#define RECT_SET(r, nx, ny, w, h) \ + {r.x = nx; r.y = ny; r.width = w; r.height = h;} + +#define RECT_EQUAL(r1, r2) (r1.x == r2.x && r1.y == r2.y && \ + r1.width == r2.width && r1.height == r2.height) + +typedef struct Strut { + int left; + int top; + int right; + int bottom; +} Strut; + +#define STRUT_SET(s, l, t, r, b) \ + {s.left = l; s.top = t; s.right = r; s.bottom = b; } + +#define STRUT_ADD(s1, s2) \ + {s1.left = MAX(s1.left, s2.left); s1.right = MAX(s1.right, s2.right); \ + s1.top = MAX(s1.top, s2.top); s1.bottom = MAX(s1.bottom, s2.bottom); } + +#endif diff --git a/c/gettext.h b/c/gettext.h new file mode 100644 index 0000000..7bbc6a9 --- /dev/null +++ b/c/gettext.h @@ -0,0 +1,73 @@ +/* Convenience header for conditional use of GNU . + Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published + by the Free Software Foundation; either version 2, or (at your option) + any later version. + + 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. */ + +#ifndef _LIBGETTEXT_H +#define _LIBGETTEXT_H 1 + + +/* NLS can be disabled through the configure --disable-nls option. */ +#if ENABLE_NLS + +/* Get declarations of GNU message catalog functions. */ +# include + +#else + +/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which + chokes if dcgettext is defined as a macro. So include it now, to make + later inclusions of a NOP. We don't include + as well because people using "gettext.h" will not include , + and also including would fail on SunOS 4, whereas + is OK. */ +#if defined(__sun) +# include +#endif + +/* Disabled NLS. + The casts to 'const char *' serve the purpose of producing warnings + for invalid uses of the value returned from these functions. + On pre-ANSI systems without 'const', the config.h file is supposed to + contain "#define const". */ +# define gettext(Msgid) ((const char *) (Msgid)) +# define dgettext(Domainname, Msgid) ((const char *) (Msgid)) +# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid)) +# define ngettext(Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dngettext(Domainname, Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define textdomain(Domainname) ((const char *) (Domainname)) +# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) +# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset)) + +#endif + +/* A pseudo function call that serves as a marker for the automated + extraction of messages, but does not call gettext(). The run-time + translation is done at a different place in the code. + The argument, String, should be a literal string. Concatenated strings + and other string expressions won't work. + The macro's expansion is not parenthesized, so that it is suitable as + initializer for static 'char[]' or 'const char[]' variables. */ +#define gettext_noop(String) String + +/* Custom macro to make life easier */ +#define _(str) gettext(str) + +#endif /* _LIBGETTEXT_H */ diff --git a/c/hooks.c b/c/hooks.c new file mode 100644 index 0000000..6bf15d2 --- /dev/null +++ b/c/hooks.c @@ -0,0 +1,285 @@ +#include "hooks.h" +#include +#include + +/* the 'hooks' module and its dictionary */ +static PyObject *hooks = NULL, *hooksdict = NULL; + +/* + * + * Define the type 'Hook' + * + */ +#define IS_HOOK(v) ((v)->ob_type == &HookType) + +staticforward PyTypeObject HookType; + +typedef struct { + PyObject_HEAD + GSList *funcs; +} HookObject; + +static PyObject *create_Hook(PyObject *self, PyObject *args) +{ + HookObject *hook; + char *name; + int ret; + + (void) self; + + if (!PyArg_ParseTuple(args, "s:Hook", &name)) + return NULL; + + hook = PyObject_New(HookObject, &HookType); + hook->funcs = NULL; + + /* add it to the hooks module */ + ret = PyDict_SetItemString(hooksdict, name, (PyObject*) hook); + Py_DECREF(hook); + + if (ret == -1) { + char *s = g_strdup_printf( + "Failed to add the hook '%s' to the 'hooks' module", name); + PyErr_SetString(PyExc_RuntimeError, s); + g_free(s); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static void hook_dealloc(HookObject *self) +{ + GSList *it; + + for (it = self->funcs; it != NULL; it = it->next) + Py_DECREF((PyObject*) it->data); + + PyObject_Del((PyObject*) self); +} + +static PyObject *hook_fire(HookObject *self, PyObject *args) +{ + GSList *it; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'fire' requires a 'Hook' object"); + return NULL; + } + + for (it = self->funcs; it != NULL; it = it->next) { + PyObject *ret = PyObject_CallObject(it->data, args); + if (ret == NULL) + return NULL; + Py_DECREF(ret); + } + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *hook_add(HookObject *self, PyObject *args) +{ + PyObject *func; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'add' requires a 'Hook' object"); + return NULL; + } + if (!PyArg_ParseTuple(args, "O:add", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'add' requires a callable argument"); + return NULL; + } + self->funcs = g_slist_append(self->funcs, func); + Py_INCREF(func); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *hook_remove(HookObject *self, PyObject *args) +{ + PyObject *func; + GSList *it; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'remove' requires a 'Hook' object"); + return NULL; + } + if (!PyArg_ParseTuple(args, "O:remove", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'remove' requires a callable argument"); + return NULL; + } + it = g_slist_find(self->funcs, func); + if (it != NULL) { + self->funcs = g_slist_delete_link(self->funcs, it); + Py_DECREF(func); + + Py_INCREF(Py_None); + return Py_None; + } + PyErr_SetString(PyExc_TypeError, + "given callable object was not found in Hook"); + return NULL; +} + +static PyObject *hook_count(HookObject *self, PyObject *args) +{ + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'fire' requires a 'Hook' object"); + return NULL; + } + if (!PyArg_ParseTuple(args, ":count")) + return NULL; + + return PyInt_FromLong(g_slist_length(self->funcs)); +} + +static PyTypeObject HookType = { + PyObject_HEAD_INIT(NULL) + 0, + "Hook", + sizeof(HookObject), + 0, + (destructor) hook_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +static PyMethodDef HookMethods[] = { + {"fire", (PyCFunction)hook_fire, METH_VARARGS, + "hook.fire() -- Fire the added hook functions for the Hook."}, + {"add", (PyCFunction)hook_add, METH_VARARGS, + "hook.add(func) -- Add a function to the hook." }, + {"remove", (PyCFunction)hook_remove, METH_VARARGS, + "hook.remove(func) -- Remove a function from the hook." }, + {"count", (PyCFunction)hook_count, METH_VARARGS, + "hook.count() -- Return the number of functions in the hook." }, + + { NULL, NULL, 0, NULL } +}; + + +/* + * + * Module initialization/finalization + * + */ + +/* the "events" hook */ +static HookObject *events_hook = NULL, *keyboard_hook = NULL, + *pointer_hook = NULL; + +static PyMethodDef HooksMethods[] = { + {"create", create_Hook, METH_VARARGS, + "hooks.create('name') -- Add a hook called 'name' to the hooks module."}, + + { NULL, NULL, 0, NULL } +}; + +void hooks_startup() +{ + int ret; + + HookType.ob_type = &PyType_Type; + HookType.tp_methods = HookMethods; + PyType_Ready(&HookType); + + Py_InitModule("hooks", HooksMethods); + + /* get the hooks module/dict */ + hooks = PyImport_ImportModule("hooks"); /* new */ + g_assert(hooks != NULL); + hooksdict = PyModule_GetDict(hooks); /* borrowed */ + g_assert(hooksdict != NULL); + + /* create the "events" hook */ + events_hook = PyObject_New(HookObject, &HookType); + events_hook->funcs = NULL; + + /* add it to the hooks module */ + ret = PyDict_SetItemString(hooksdict, "events", (PyObject*) events_hook); + g_assert(ret == 0); + + /* create the "keyboard" hook */ + keyboard_hook = PyObject_New(HookObject, &HookType); + keyboard_hook->funcs = NULL; + + /* add it to the hooks module */ + ret = PyDict_SetItemString(hooksdict, "keyboard", + (PyObject*) keyboard_hook); + g_assert(ret == 0); + + /* create the "pointer" hook */ + pointer_hook = PyObject_New(HookObject, &HookType); + pointer_hook->funcs = NULL; + + /* add it to the hooks module */ + ret = PyDict_SetItemString(hooksdict, "pointer", (PyObject*) pointer_hook); + g_assert(ret == 0); +} + +void hooks_shutdown() +{ + Py_DECREF(pointer_hook); + Py_DECREF(keyboard_hook); + Py_DECREF(events_hook); + Py_DECREF(hooks); +} + +void hooks_fire(EventData *data) +{ + PyObject *ret, *args; + + g_assert(events_hook != NULL); + + args = Py_BuildValue("(O)", data); + ret = hook_fire(events_hook, args); + Py_DECREF(args); + if (ret == NULL) + PyErr_Print(); +} + +void hooks_fire_keyboard(EventData *data) +{ + PyObject *ret, *args; + + g_assert(events_hook != NULL); + + args = Py_BuildValue("(O)", data); + ret = hook_fire(keyboard_hook, args); + Py_DECREF(args); + if (ret == NULL) + PyErr_Print(); +} + +void hooks_fire_pointer(EventData *data) +{ + PyObject *ret, *args; + + g_assert(events_hook != NULL); + + args = Py_BuildValue("(O)", data); + ret = hook_fire(pointer_hook, args); + Py_DECREF(args); + if (ret == NULL) + PyErr_Print(); +} diff --git a/c/hooks.h b/c/hooks.h new file mode 100644 index 0000000..477c812 --- /dev/null +++ b/c/hooks.h @@ -0,0 +1,23 @@ +#ifndef __hooks_h +#define __hooks_h + +#include "eventdata.h" + +void hooks_startup(); +void hooks_shutdown(); + +void hooks_fire(EventData *data); + +void hooks_fire_keyboard(EventData *data); + +void hooks_fire_pointer(EventData *data); + +#define LOGICALHOOK(type, context, client) \ +{ EventData *data = eventdata_new_logical(Logical_##type, \ + context, client); \ + g_assert(data != NULL); \ + hooks_fire(data); \ + eventdata_free(data); \ +} + +#endif diff --git a/c/kbind.c b/c/kbind.c new file mode 100644 index 0000000..399ec83 --- /dev/null +++ b/c/kbind.c @@ -0,0 +1,354 @@ +#include "focus.h" +#include "openbox.h" +#include "hooks.h" +#include "kbind.h" + +#include +#ifdef HAVE_STRING_H +# include +#endif + +typedef struct KeyBindingTree { + guint state; + guint key; + GList *keylist; + + /* the next binding in the tree at the same level */ + struct KeyBindingTree *next_sibling; + /* the first child of this binding (next binding in a chained sequence).*/ + struct KeyBindingTree *first_child; +} KeyBindingTree; + + +static KeyBindingTree *firstnode, *curpos; +static guint reset_key, reset_state; +static gboolean grabbed, user_grabbed; + +guint kbind_translate_modifier(char *str) +{ + if (!strcmp("Mod1", str)) return Mod1Mask; + else if (!strcmp("Mod2", str)) return Mod2Mask; + else if (!strcmp("Mod3", str)) return Mod3Mask; + else if (!strcmp("Mod4", str)) return Mod4Mask; + else if (!strcmp("Mod5", str)) return Mod5Mask; + else if (!strcmp("C", str)) return ControlMask; + else if (!strcmp("S", str)) return ShiftMask; + g_warning("Invalid modifier '%s' in binding.", str); + return 0; +} + +static gboolean translate(char *str, guint *state, guint *keycode) +{ + char **parsed; + char *l; + int i; + gboolean ret = FALSE; + KeySym sym; + + parsed = g_strsplit(str, "-", -1); + + /* first, find the key (last token) */ + l = NULL; + for (i = 0; parsed[i] != NULL; ++i) + l = parsed[i]; + if (l == NULL) + goto translation_fail; + + /* figure out the mod mask */ + *state = 0; + for (i = 0; parsed[i] != l; ++i) { + guint m = kbind_translate_modifier(parsed[i]); + if (!m) goto translation_fail; + *state |= m; + } + + /* figure out the keycode */ + sym = XStringToKeysym(l); + if (sym == NoSymbol) { + g_warning("Invalid key name '%s' in key binding.", l); + goto translation_fail; + } + *keycode = XKeysymToKeycode(ob_display, sym); + if (!keycode) { + g_warning("Key '%s' does not exist on the display.", l); + goto translation_fail; + } + + ret = TRUE; + +translation_fail: + g_strfreev(parsed); + return ret; +} + +static void destroytree(KeyBindingTree *tree) +{ + KeyBindingTree *c; + + while (tree) { + destroytree(tree->next_sibling); + c = tree->first_child; + if (c == NULL) { + GList *it; + for (it = tree->keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(tree->keylist); + } + g_free(tree); + tree = c; + } +} + +static KeyBindingTree *buildtree(GList *keylist) +{ + GList *it; + KeyBindingTree *ret = NULL, *p; + + if (g_list_length(keylist) <= 0) + return NULL; /* nothing in the list.. */ + + for (it = g_list_last(keylist); it != NULL; it = it->prev) { + p = ret; + ret = g_new(KeyBindingTree, 1); + ret->next_sibling = NULL; + if (p == NULL) { + GList *it; + + /* this is the first built node, the bottom node of the tree */ + ret->keylist = g_list_copy(keylist); /* shallow copy */ + for (it = ret->keylist; it != NULL; it = it->next) /* deep copy */ + it->data = g_strdup(it->data); + } + ret->first_child = p; + if (!translate(it->data, &ret->state, &ret->key)) { + destroytree(ret); + return NULL; + } + } + return ret; +} + +static void assimilate(KeyBindingTree *node) +{ + KeyBindingTree *a, *b, *tmp, *last; + + if (firstnode == NULL) { + /* there are no nodes at this level yet */ + firstnode = node; + } else { + a = firstnode; + last = a; + b = node; + while (a) { + last = a; + if (!(a->state == b->state && a->key == b->key)) { + a = a->next_sibling; + } else { + tmp = b; + b = b->first_child; + g_free(tmp); + a = a->first_child; + } + } + if (!(last->state == b->state && last->key == a->key)) + last->next_sibling = b; + else { + last->first_child = b->first_child; + g_free(b); + } + } +} + +KeyBindingTree *find(KeyBindingTree *search, gboolean *conflict) +{ + KeyBindingTree *a, *b; + + *conflict = FALSE; + + a = firstnode; + b = search; + while (a && b) { + if (!(a->state == b->state && a->key == b->key)) { + a = a->next_sibling; + } else { + if ((a->first_child == NULL) == (b->first_child == NULL)) { + if (a->first_child == NULL) { + /* found it! (return the actual node, not the search's) */ + return a; + } + } else { + *conflict = TRUE; + return NULL; /* the chain status' don't match (conflict!) */ + } + b = b->first_child; + a = a->first_child; + } + } + return NULL; // it just isn't in here +} + +static void grab_keys(gboolean grab) +{ + if (!grab) { + XUngrabKey(ob_display, AnyKey, AnyModifier, ob_root); + } else { + KeyBindingTree *p = firstnode; + while (p) { + XGrabKey(ob_display, p->key, p->state, ob_root, FALSE, + GrabModeAsync, GrabModeSync); + p = p->next_sibling; + } + } +} + +void reset_chains() +{ + /* XXX kill timer */ + curpos = NULL; + if (grabbed) { + grabbed = FALSE; + g_message("reset chains. user: %d", user_grabbed); + if (!user_grabbed) + XUngrabKeyboard(ob_display, CurrentTime); + } +} + +void kbind_fire(guint state, guint key, gboolean press) +{ + EventData *data; + struct Client *c = focus_client; + GQuark context = c != NULL ? g_quark_try_string("client") + : g_quark_try_string("root"); + + if (user_grabbed) { + data = eventdata_new_key(press ? Key_Press : Key_Release, + context, c, state, key, NULL); + g_assert(data != NULL); + hooks_fire_keyboard(data); + eventdata_free(data); + } + + if (key == reset_key && state == reset_state) { + reset_chains(); + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + } else { + KeyBindingTree *p; + if (curpos == NULL) + p = firstnode; + else + p = curpos->first_child; + while (p) { + if (p->key == key && p->state == state) { + if (p->first_child != NULL) { /* part of a chain */ + /* XXX TIMER */ + if (!grabbed && !user_grabbed) { + /*grab should never fail because we should have a sync + grab at this point */ + XGrabKeyboard(ob_display, ob_root, 0, GrabModeAsync, + GrabModeSync, CurrentTime); + } + grabbed = TRUE; + curpos = p; + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + } else { + data = eventdata_new_key(press ? Key_Press : Key_Release, + context, c, state, key, + p->keylist); + g_assert(data != NULL); + hooks_fire(data); + eventdata_free(data); + + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + reset_chains(); + } + break; + } + p = p->next_sibling; + } + } +} + +gboolean kbind_add(GList *keylist) +{ + KeyBindingTree *tree, *t; + gboolean conflict; + + if (!(tree = buildtree(keylist))) + return FALSE; /* invalid binding requested */ + + t = find(tree, &conflict); + if (conflict) { + /* conflicts with another binding */ + destroytree(tree); + return FALSE; + } + + if (t != NULL) { + /* already bound to something */ + destroytree(tree); + } else { + /* grab the server here to make sure no key pressed go missed */ + XGrabServer(ob_display); + XSync(ob_display, FALSE); + + grab_keys(FALSE); + + /* assimilate this built tree into the main tree */ + assimilate(tree); // assimilation destroys/uses the tree + + grab_keys(TRUE); + + XUngrabServer(ob_display); + XFlush(ob_display); + } + + return TRUE; +} + +void kbind_clearall() +{ + grab_keys(FALSE); + destroytree(firstnode); + firstnode = NULL; + grab_keys(TRUE); +} + +void kbind_startup() +{ + gboolean b; + + curpos = firstnode = NULL; + grabbed = user_grabbed = FALSE; + + b = translate("C-G", &reset_state, &reset_key); + g_assert(b); +} + +void kbind_shutdown() +{ + if (grabbed || user_grabbed) { + grabbed = FALSE; + kbind_grab_keyboard(FALSE); + } + grab_keys(FALSE); + destroytree(firstnode); + firstnode = NULL; +} + +gboolean kbind_grab_keyboard(gboolean grab) +{ + gboolean ret = TRUE; + + if (!grab) + g_message("grab_keyboard(false). grabbed: %d", grabbed); + + user_grabbed = grab; + if (!grabbed) { + if (grab) + ret = XGrabKeyboard(ob_display, ob_root, 0, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess; + else + XUngrabKeyboard(ob_display, CurrentTime); + } + return ret; +} diff --git a/c/kbind.h b/c/kbind.h new file mode 100644 index 0000000..3a584b7 --- /dev/null +++ b/c/kbind.h @@ -0,0 +1,23 @@ +#ifndef __kbind_h +#define __kbind_h + +#include + +void kbind_startup(); +void kbind_shutdown(); + +/*! Adds a new key binding + A binding will fail to be added if the binding already exists (as part of + a chain or not), or if any of the strings in the keylist are invalid. + @return TRUE if the binding could be added; FALSE if it could not. +*/ +gboolean kbind_add(GList *keylist); +void kbind_clearall(); + +guint kbind_translate_modifier(char *str); + +void kbind_fire(guint state, guint key, gboolean press); + +gboolean kbind_grab_keyboard(gboolean grab); + +#endif diff --git a/c/mbind.c b/c/mbind.c new file mode 100644 index 0000000..f363be6 --- /dev/null +++ b/c/mbind.c @@ -0,0 +1,220 @@ +#include "mbind.h" +#include "kbind.h" +#include "frame.h" +#include "openbox.h" +#include "eventdata.h" +#include "hooks.h" + +#include +#ifdef HAVE_STDLIB_H +# include +#endif + +/* GData of GSList*'s of PointerBinding*'s. */ +static GData *bound_contexts; + +static gboolean grabbed; + +struct mbind_foreach_grab_temp { + Client *client; + gboolean grab; +}; + +typedef struct { + guint state; + guint button; + char *name; +} PointerBinding; + +static gboolean translate(char *str, guint *state, guint *button) +{ + char **parsed; + char *l; + int i; + gboolean ret = FALSE; + + parsed = g_strsplit(str, "-", -1); + + /* first, find the button (last token) */ + l = NULL; + for (i = 0; parsed[i] != NULL; ++i) + l = parsed[i]; + if (l == NULL) + goto translation_fail; + + /* figure out the mod mask */ + *state = 0; + for (i = 0; parsed[i] != l; ++i) { + guint m = kbind_translate_modifier(parsed[i]); + if (!m) goto translation_fail; + *state |= m; + } + + /* figure out the button */ + *button = atoi(l); + if (!*button) { + g_warning("Invalid button '%s' in pointer binding.", l); + goto translation_fail; + } + + ret = TRUE; + +translation_fail: + g_strfreev(parsed); + return ret; +} + +void grab_button(Client *client, guint state, guint button, GQuark context, + gboolean grab) +{ + Window win; + int mode = GrabModeAsync; + unsigned int mask; + + if (context == g_quark_try_string("frame")) { + win = client->frame->window; + mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask; + } else if (context == g_quark_try_string("client")) { + win = client->frame->plate; + mode = GrabModeSync; /* this is handled in mbind_fire */ + mask = ButtonPressMask; /* can't catch more than this with Sync mode + the release event is manufactured in + mbind_fire */ + } else return; + + if (grab) + XGrabButton(ob_display, button, state, win, FALSE, mask, mode, + GrabModeAsync, None, None); + else + XUngrabButton(ob_display, button, state, win); +} + +static void mbind_foreach_grab(GQuark key, gpointer data, gpointer user_data) +{ + struct mbind_foreach_grab_temp *d = user_data; + PointerBinding *b = ((GSList *)data)->data; + if (b != NULL) + grab_button(d->client, b->state, b->button, key, d->grab); +} + +void mbind_grab_all(Client *client, gboolean grab) +{ + struct mbind_foreach_grab_temp bt; + bt.client = client; + bt.grab = grab; + g_datalist_foreach(&bound_contexts, mbind_foreach_grab, &bt); +} + +void grab_all_clients(gboolean grab) +{ + GSList *it; + + for (it = client_list; it != NULL; it = it->next) + mbind_grab_all(it->data, grab); +} + +void mbind_startup() +{ + grabbed = FALSE; + g_datalist_init(&bound_contexts); +} + +void mbind_shutdown() +{ + if (grabbed) + mbind_grab_pointer(FALSE); + mbind_clearall(); + g_datalist_clear(&bound_contexts); +} + +gboolean mbind_add(char *name, GQuark context) +{ + guint state, button; + PointerBinding *b; + GSList *it; + + if (!translate(name, &state, &button)) + return FALSE; + + for (it = g_datalist_id_get_data(&bound_contexts, context); + it != NULL; it = it->next){ + b = it->data; + if (b->state == state && b->button == button) + return TRUE; /* already bound */ + } + + grab_all_clients(FALSE); + + /* add the binding */ + b = g_new(PointerBinding, 1); + b->state = state; + b->button = button; + b->name = g_strdup(name); + g_datalist_id_set_data(&bound_contexts, context, + g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b)); + grab_all_clients(TRUE); + + return TRUE; +} + +static void mbind_foreach_clear(GQuark key, gpointer data, gpointer user_data) +{ + GSList *it; + user_data = user_data; + for (it = data; it != NULL; it = it->next) { + PointerBinding *b = it->data; + g_free(b->name); + g_free(b); + } + g_slist_free(data); +} +void mbind_clearall() +{ + grab_all_clients(FALSE); + g_datalist_foreach(&bound_contexts, mbind_foreach_clear, NULL); +} + +void mbind_fire(guint state, guint button, GQuark context, EventType type, + Client *client, int xroot, int yroot) +{ + GSList *it; + + if (grabbed) { + EventData *data; + data = eventdata_new_pointer(type, context, client, state, button, + NULL, xroot, yroot); + g_assert(data != NULL); + hooks_fire_pointer(data); + eventdata_free(data); + return; + } + + for (it = g_datalist_id_get_data(&bound_contexts, context); + it != NULL; it = it->next){ + PointerBinding *b = it->data; + if (b->state == state && b->button == button) { + EventData *data; + data = eventdata_new_pointer(type, context, client, state, button, + b->name, xroot, yroot); + g_assert(data != NULL); + hooks_fire(data); + eventdata_free(data); + break; + } + } +} + +gboolean mbind_grab_pointer(gboolean grab) +{ + gboolean ret = TRUE; + if (grab) + ret = XGrabPointer(ob_display, ob_root, FALSE, (ButtonPressMask | + ButtonReleaseMask | + ButtonMotionMask | + PointerMotionMask), + GrabModeAsync, GrabModeAsync, None, None, + CurrentTime) == GrabSuccess; + else + XUngrabPointer(ob_display, CurrentTime); + return ret; +} diff --git a/c/mbind.h b/c/mbind.h new file mode 100644 index 0000000..dfd8c91 --- /dev/null +++ b/c/mbind.h @@ -0,0 +1,21 @@ +#ifndef __mbind_h +#define __mbind_h + +#include "obexport.h" +#include "client.h" +#include + +void mbind_startup(); +void mbind_shutdown(); + +/*! Adds a new pointer binding */ +gboolean mbind_add(char *name, GQuark context); +void mbind_clearall(); + +void mbind_fire(guint state, guint button, GQuark context, EventType type, + Client *client, int xroot, int yroot); + +void mbind_grab_all(Client *client, gboolean grab); +gboolean mbind_grab_pointer(gboolean grab); + +#endif diff --git a/c/obexport.c b/c/obexport.c new file mode 100644 index 0000000..d92e319 --- /dev/null +++ b/c/obexport.c @@ -0,0 +1,116 @@ +#include "obexport.h" +#include +#include + +static PyMethodDef obMethods[] = { + { NULL, NULL, 0, NULL } +}; + +#define ADD_INT_CONST(n) (PyModule_AddIntConstant(ob, #n, n)) + +void obexport_startup() +{ + PyObject *ob, *obdict; + + Py_InitModule("ob", obMethods); + + /* get the ob module/dict */ + ob = PyImport_ImportModule("ob"); /* new */ + g_assert(ob != NULL); + obdict = PyModule_GetDict(ob); /* borrowed */ + g_assert(obdict != NULL); + + /* define all the constants! */ + + /* State */ + ADD_INT_CONST(State_Starting); + ADD_INT_CONST(State_Exiting); + ADD_INT_CONST(State_Running); + + /* Corner */ + ADD_INT_CONST(Corner_TopLeft); + ADD_INT_CONST(Corner_TopRight); + ADD_INT_CONST(Corner_BottomLeft); + ADD_INT_CONST(Corner_BottomRight); + + /* Orientation */ + ADD_INT_CONST(Orientation_Horz); + ADD_INT_CONST(Orientation_Vert); + + /* Gravity */ + ADD_INT_CONST(Gravity_Forget); + ADD_INT_CONST(Gravity_NE); + ADD_INT_CONST(Gravity_N); + ADD_INT_CONST(Gravity_NW); + ADD_INT_CONST(Gravity_W); + ADD_INT_CONST(Gravity_SW); + ADD_INT_CONST(Gravity_S); + ADD_INT_CONST(Gravity_SE); + ADD_INT_CONST(Gravity_E); + ADD_INT_CONST(Gravity_Center); + ADD_INT_CONST(Gravity_Static); + + /* WindowType */ + ADD_INT_CONST(Type_Desktop); + ADD_INT_CONST(Type_Dock); + ADD_INT_CONST(Type_Toolbar); + ADD_INT_CONST(Type_Menu); + ADD_INT_CONST(Type_Utility); + ADD_INT_CONST(Type_Splash); + ADD_INT_CONST(Type_Dialog); + ADD_INT_CONST(Type_Normal); + + /* Function */ + ADD_INT_CONST(Func_Resize); + ADD_INT_CONST(Func_Move); + ADD_INT_CONST(Func_Iconify); + ADD_INT_CONST(Func_Maximize); + ADD_INT_CONST(Func_Shade); + ADD_INT_CONST(Func_Fullscreen); + ADD_INT_CONST(Func_Close); + + /* Decoration */ + ADD_INT_CONST(Decor_Titlebar); + ADD_INT_CONST(Decor_Handle); + ADD_INT_CONST(Decor_Border); + ADD_INT_CONST(Decor_Icon); + ADD_INT_CONST(Decor_Iconify); + ADD_INT_CONST(Decor_Maximize); + ADD_INT_CONST(Decor_AllDesktops); + ADD_INT_CONST(Decor_Close); + + /* StackLayer */ + ADD_INT_CONST(Layer_Icon); + ADD_INT_CONST(Layer_Desktop); + ADD_INT_CONST(Layer_Below); + ADD_INT_CONST(Layer_Normal); + ADD_INT_CONST(Layer_Above); + ADD_INT_CONST(Layer_Top); + ADD_INT_CONST(Layer_Fullscreen); + ADD_INT_CONST(Layer_Internal); + + /* EventType */ + ADD_INT_CONST(Logical_EnterWindow); + ADD_INT_CONST(Logical_LeaveWindow); + ADD_INT_CONST(Logical_NewWindow); + ADD_INT_CONST(Logical_CloseWindow); + ADD_INT_CONST(Logical_Startup); + ADD_INT_CONST(Logical_Shutdown); + ADD_INT_CONST(Logical_RequestActivate); + ADD_INT_CONST(Logical_Focus); + ADD_INT_CONST(Logical_Bell); + ADD_INT_CONST(Logical_UrgentWindow); + ADD_INT_CONST(Logical_WindowShow); + ADD_INT_CONST(Logical_WindowHide); + ADD_INT_CONST(Pointer_Press); + ADD_INT_CONST(Pointer_Release); + ADD_INT_CONST(Pointer_Motion); + ADD_INT_CONST(Key_Press); + ADD_INT_CONST(Key_Release); + + Py_DECREF(ob); +} + +void obexport_shutdown() +{ +} diff --git a/c/obexport.h b/c/obexport.h new file mode 100644 index 0000000..17f68f4 --- /dev/null +++ b/c/obexport.h @@ -0,0 +1,74 @@ +#ifndef __obexport_h +#define __obexport_h + +#include + +/* Define values which will be exported in the 'ob' module. */ + +typedef enum { + /*! Occurs when the mouse enters a window */ + Logical_EnterWindow, + /*! Occurs when the mouse enters a window */ + Logical_LeaveWindow, + /*! Occurs when a window is finished being managed, just before it is + (possibly) displayed. + The python scripts are reponsible for showing the window when this is + called if they want it to be shown. + */ + Logical_NewWindow, + /*! Occurs when a window is being unmanaged */ + Logical_CloseWindow, + /*! Occurs when the window manager starts up */ + Logical_Startup, + /*! Occurs when the window manager is shutting down */ + Logical_Shutdown, + /*! Occurs when a client is requesting/requested to be activated (i.e. + focused, raised, unshaded) */ + Logical_RequestActivate, + /*! Occurs when the input focus target changes + The data.client will be NULL of no client is focused. */ + Logical_Focus, + /*! Occurs when the system is fired through X. + The data.client will hold the client associated with the bell if + one has been specified, or NULL. */ + Logical_Bell, + /*! Occurs when a client toggles its urgent status. + The client.urgent member can be used to get the status. */ + Logical_UrgentWindow, + /*! Occurs when a client becomes visible */ + Logical_WindowShow, + /*! Occurs when a client becomes non-visible */ + Logical_WindowHide, + /*! Occurs when a pointer button is pressed on a client or its + decorations. + Note: to get the event for the client's window or for the entire + window+decorations, you need to do an mgrab for the window. */ + Pointer_Press, + /*! Occurs when a pointer button is released on a client or its + decorations. + Note: to get the event for the client's window or for the entire + window+decorations, you need to do an mgrab for the window. */ + Pointer_Release, + /*! Occurs when a pointer button is held and the pointer is dragged on a + client or its decorations. + Note: to get the event for the client's window or for the entire + window+decorations, you need to do an mgrab for the window, or an + mgrab_pointer (in which case it may not be a drag). */ + Pointer_Motion, + /*! Occurs when a key is pressed. + Note: in order to recieve a key event, a kgrab must be done for the + key combination, or a kgrab_keyboard. + */ + Key_Press, + /*! Occurs when a key is released. + Note: in order to recieve a key event, a kgrab must be done for the + key combination, or a kgrab_keyboard. + */ + Key_Release +} EventType; + +/* create the 'ob' module */ +void obexport_startup(); +void obexport_shutdown(); + +#endif diff --git a/c/openbox.c b/c/openbox.c new file mode 100644 index 0000000..77e4123 --- /dev/null +++ b/c/openbox.c @@ -0,0 +1,203 @@ +#include "openbox.h" +#include "event.h" +#include "client.h" +#include "xerror.h" +#include "prop.h" +#include "screen.h" +#include "focus.h" +#include "extensions.h" +#include "python.h" +#include "hooks.h" +#include "eventdata.h" +#include "gettext.h" +#include "clientwrap.h" +#include "screenwrap.h" +#include "kbind.h" +#include "mbind.h" +#include "frame.h" + +#ifdef HAVE_FCNTL_H +# include +#endif +#ifdef HAVE_SYS_SELECT_H +# include +#endif +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_SYS_WAIT_H +# include +# include +#endif +#ifdef HAVE_LOCALE_H +# include +#endif + +#include + +Display *ob_display = NULL; +int ob_screen; +Window ob_root; +State ob_state; +gboolean ob_shutdown = FALSE; +gboolean ob_restart = FALSE; +gboolean ob_remote = FALSE; +gboolean ob_sync = TRUE; +Cursors ob_cursors; + +void signal_handler(int signal); + +int main(int argc, char **argv) +{ + struct sigaction action; + sigset_t sigset; + + ob_state = State_Starting; + + /* initialize the locale */ + if (!setlocale(LC_ALL, "")) + g_warning("Couldn't set locale from environment.\n"); + bindtextdomain(PACKAGE, LOCALEDIR); + bind_textdomain_codeset(PACKAGE, "UTF-8"); + textdomain(PACKAGE); + + /* set up signal handler */ + sigemptyset(&sigset); + action.sa_handler = signal_handler; + action.sa_mask = sigset; + action.sa_flags = SA_NOCLDSTOP | SA_NODEFER; + sigaction(SIGUSR1, &action, (struct sigaction *) NULL); + sigaction(SIGPIPE, &action, (struct sigaction *) NULL); + sigaction(SIGSEGV, &action, (struct sigaction *) NULL); + sigaction(SIGFPE, &action, (struct sigaction *) NULL); + sigaction(SIGTERM, &action, (struct sigaction *) NULL); + sigaction(SIGINT, &action, (struct sigaction *) NULL); + sigaction(SIGHUP, &action, (struct sigaction *) NULL); + sigaction(SIGCHLD, &action, (struct sigaction *) NULL); + + /* anything that died while we were restarting won't give us a SIGCHLD */ + while (waitpid(-1, NULL, WNOHANG) > 0); + + /* XXX parse out command line args */ + (void)argc;(void)argv; + + /* critical warnings will exit the program */ + g_log_set_always_fatal(G_LOG_LEVEL_CRITICAL); + + ob_display = XOpenDisplay(NULL); + if (ob_display == NULL) + /* print a message and exit */ + g_critical("Failed to open the display."); + if (fcntl(ConnectionNumber(ob_display), F_SETFD, 1) == -1) + /* print a message and exit */ + g_critical("Failed to set display as close-on-exec."); + + ob_screen = DefaultScreen(ob_display); + ob_root = RootWindow(ob_display, ob_screen); + + /* XXX fork self onto other screens */ + + XSynchronize(ob_display, ob_sync); + + /* check for locale support */ + if (!XSupportsLocale()) + g_warning("X server does not support locale."); + if (!XSetLocaleModifiers("")) + g_warning("Cannot set locale modifiers for the X server."); + + /* set our error handler */ + XSetErrorHandler(xerror_handler); + + /* set the DISPLAY environment variable for any lauched children, to the + display we're using, so they open in the right place. */ + putenv(g_strdup_printf("DISPLAY=%s", DisplayString(ob_display))); + + ob_cursors.left_ptr = XCreateFontCursor(ob_display, XC_left_ptr); + ob_cursors.ll_angle = XCreateFontCursor(ob_display, XC_ll_angle); + ob_cursors.lr_angle = XCreateFontCursor(ob_display, XC_lr_angle); + + prop_init(); /* get atoms values for the display */ + extensions_query_all(); /* find which extensions are present */ + + if (screen_annex()) { /* it will be ours! */ + frame_startup(); + python_startup(); + obexport_startup(); + hooks_startup(); + screenwrap_startup(); + clientwrap_startup(); + eventdata_startup(); + event_startup(); + screen_startup(); + focus_startup(); + client_startup(); + kbind_startup(); + mbind_startup(); + + /* load the user's settings */ + if (!python_import("rc")) + g_warning("ERROR LOADING RC FILE"); + + LOGICALHOOK(Startup, g_quark_try_string("none"), NULL); + + /* get all the existing windows */ + client_manage_all(); + + ob_state = State_Running; + while (!ob_shutdown) { + event_loop(); + } + ob_state = State_Exiting; + + client_unmanage_all(); + + LOGICALHOOK(Shutdown, g_quark_try_string("none"), NULL); + + mbind_shutdown(); + kbind_shutdown(); + client_shutdown(); + screen_shutdown(); + event_shutdown(); + eventdata_shutdown(); + clientwrap_shutdown(); + screenwrap_shutdown(); + hooks_shutdown(); + obexport_shutdown(); + python_shutdown(); + } + + XCloseDisplay(ob_display); + + /* XXX if (ob_restart) */ + + return 0; +} + +void signal_handler(int signal) +{ + switch (signal) { + case SIGUSR1: + g_message("Caught SIGUSR1 signal. Restarting."); + ob_shutdown = ob_restart = TRUE; + break; + + case SIGCHLD: + wait(NULL); + break; + + case SIGHUP: + case SIGINT: + case SIGTERM: + case SIGPIPE: + g_message("Caught signal %d. Exiting.", signal); + ob_shutdown = TRUE; + break; + + case SIGFPE: + case SIGSEGV: + g_error("Caught signal %d. Aborting and dumping core.", signal); + } +} diff --git a/c/openbox.h b/c/openbox.h new file mode 100644 index 0000000..34160c8 --- /dev/null +++ b/c/openbox.h @@ -0,0 +1,35 @@ +#ifndef __openbox_h +#define __openbox_h + +#include "obexport.h" +#include +#include + +/*! The X display */ +extern Display *ob_display; +/*! The number of the screen on which we're running */ +extern int ob_screen; +/*! The root window */ +extern Window ob_root; + +/* The state of execution of the window manager */ +State ob_state; + +/*! When set to true, Openbox will exit */ +extern gboolean ob_shutdown; +/*! When set to true, Openbox will restart instead of shutting down */ +extern gboolean ob_restart; + +/*! Runtime option to specify running on a remote display */ +extern gboolean ob_remote; +/*! Runtime option to run in synchronous mode */ +extern gboolean ob_sync; + +typedef struct Cursors { + Cursor left_ptr; + Cursor ll_angle; + Cursor lr_angle; +} Cursors; +Cursors ob_cursors; + +#endif diff --git a/c/prop.c b/c/prop.c new file mode 100644 index 0000000..d239276 --- /dev/null +++ b/c/prop.c @@ -0,0 +1,271 @@ +#include "prop.h" +#include "openbox.h" +#include + +Atoms prop_atoms; + +#define CREATE(var, name) (prop_atoms.var = \ + XInternAtom(ob_display, name, FALSE)) + +void prop_init() +{ + g_assert(ob_display != NULL); + + CREATE(cardinal, "CARDINAL"); + CREATE(window, "WINDOW"); + CREATE(pixmap, "PIXMAP"); + CREATE(atom, "ATOM"); + CREATE(string, "STRING"); + CREATE(utf8, "UTF8_STRING"); + + CREATE(wm_colormap_windows, "WM_COLORMAP_WINDOWS"); + CREATE(wm_protocols, "WM_PROTOCOLS"); + CREATE(wm_state, "WM_STATE"); + CREATE(wm_change_state, "WM_CHANGE_STATE"); + CREATE(wm_delete_window, "WM_DELETE_WINDOW"); + CREATE(wm_take_focus, "WM_TAKE_FOCUS"); + CREATE(wm_name, "WM_NAME"); + CREATE(wm_icon_name, "WM_ICON_NAME"); + CREATE(wm_class, "WM_CLASS"); + CREATE(wm_window_role, "WM_WINDOW_ROLE"); + CREATE(motif_wm_hints, "_MOTIF_WM_HINTS"); + + CREATE(net_supported, "_NET_SUPPORTED"); + CREATE(net_client_list, "_NET_CLIENT_LIST"); + CREATE(net_client_list_stacking, "_NET_CLIENT_LIST_STACKING"); + CREATE(net_number_of_desktops, "_NET_NUMBER_OF_DESKTOPS"); + CREATE(net_desktop_geometry, "_NET_DESKTOP_GEOMETRY"); + CREATE(net_desktop_viewport, "_NET_DESKTOP_VIEWPORT"); + CREATE(net_current_desktop, "_NET_CURRENT_DESKTOP"); + CREATE(net_desktop_names, "_NET_DESKTOP_NAMES"); + CREATE(net_active_window, "_NET_ACTIVE_WINDOW"); + CREATE(net_workarea, "_NET_WORKAREA"); + CREATE(net_supporting_wm_check, "_NET_SUPPORTING_WM_CHECK"); +/* CREATE(net_virtual_roots, "_NET_VIRTUAL_ROOTS"); */ + CREATE(net_desktop_layout, "_NET_DESKTOP_LAYOUT"); + CREATE(net_showing_desktop, "_NET_SHOWING_DESKTOP"); + + CREATE(net_close_window, "_NET_CLOSE_WINDOW"); + CREATE(net_wm_moveresize, "_NET_WM_MOVERESIZE"); + +/* CREATE(net_properties, "_NET_PROPERTIES"); */ + CREATE(net_wm_name, "_NET_WM_NAME"); + CREATE(net_wm_visible_name, "_NET_WM_VISIBLE_NAME"); + CREATE(net_wm_icon_name, "_NET_WM_ICON_NAME"); + CREATE(net_wm_visible_icon_name, "_NET_WM_VISIBLE_ICON_NAME"); + CREATE(net_wm_desktop, "_NET_WM_DESKTOP"); + CREATE(net_wm_window_type, "_NET_WM_WINDOW_TYPE"); + CREATE(net_wm_state, "_NET_WM_STATE"); + CREATE(net_wm_strut, "_NET_WM_STRUT"); +/* CREATE(net_wm_icon_geometry, "_NET_WM_ICON_GEOMETRY"); */ + CREATE(net_wm_icon, "_NET_WM_ICON"); +/* CREATE(net_wm_pid, "_NET_WM_PID"); */ +/* CREATE(net_wm_handled_icons, "_NET_WM_HANDLED_ICONS"); */ + CREATE(net_wm_allowed_actions, "_NET_WM_ALLOWED_ACTIONS"); + +/* CREATE(net_wm_ping, "_NET_WM_PING"); */ + + CREATE(net_wm_window_type_desktop, "_NET_WM_WINDOW_TYPE_DESKTOP"); + CREATE(net_wm_window_type_dock, "_NET_WM_WINDOW_TYPE_DOCK"); + CREATE(net_wm_window_type_toolbar, "_NET_WM_WINDOW_TYPE_TOOLBAR"); + CREATE(net_wm_window_type_menu, "_NET_WM_WINDOW_TYPE_MENU"); + CREATE(net_wm_window_type_utility, "_NET_WM_WINDOW_TYPE_UTILITY"); + CREATE(net_wm_window_type_splash, "_NET_WM_WINDOW_TYPE_SPLASH"); + CREATE(net_wm_window_type_dialog, "_NET_WM_WINDOW_TYPE_DIALOG"); + CREATE(net_wm_window_type_normal, "_NET_WM_WINDOW_TYPE_NORMAL"); + + CREATE(net_wm_moveresize_size_topleft, "_NET_WM_MOVERESIZE_SIZE_TOPLEFT"); + CREATE(net_wm_moveresize_size_topright, + "_NET_WM_MOVERESIZE_SIZE_TOPRIGHT"); + CREATE(net_wm_moveresize_size_bottomleft, + "_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT"); + CREATE(net_wm_moveresize_size_bottomright, + "_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT"); + CREATE(net_wm_moveresize_move, "_NET_WM_MOVERESIZE_MOVE"); + + CREATE(net_wm_action_move, "_NET_WM_ACTION_MOVE"); + CREATE(net_wm_action_resize, "_NET_WM_ACTION_RESIZE"); + CREATE(net_wm_action_minimize, "_NET_WM_ACTION_MINIMIZE"); + CREATE(net_wm_action_shade, "_NET_WM_ACTION_SHADE"); + CREATE(net_wm_action_stick, "_NET_WM_ACTION_STICK"); + CREATE(net_wm_action_maximize_horz, "_NET_WM_ACTION_MAXIMIZE_HORZ"); + CREATE(net_wm_action_maximize_vert, "_NET_WM_ACTION_MAXIMIZE_VERT"); + CREATE(net_wm_action_fullscreen, "_NET_WM_ACTION_FULLSCREEN"); + CREATE(net_wm_action_change_desktop, "_NET_WM_ACTION_CHANGE_DESKTOP"); + CREATE(net_wm_action_close, "_NET_WM_ACTION_CLOSE"); + CREATE(net_wm_state_modal, "_NET_WM_STATE_MODAL"); + CREATE(net_wm_state_sticky, "_NET_WM_STATE_STICKY"); + CREATE(net_wm_state_maximized_vert, "_NET_WM_STATE_MAXIMIZED_VERT"); + CREATE(net_wm_state_maximized_horz, "_NET_WM_STATE_MAXIMIZED_HORZ"); + CREATE(net_wm_state_shaded, "_NET_WM_STATE_SHADED"); + CREATE(net_wm_state_skip_taskbar, "_NET_WM_STATE_SKIP_TASKBAR"); + CREATE(net_wm_state_skip_pager, "_NET_WM_STATE_SKIP_PAGER"); + CREATE(net_wm_state_hidden, "_NET_WM_STATE_HIDDEN"); + CREATE(net_wm_state_fullscreen, "_NET_WM_STATE_FULLSCREEN"); + CREATE(net_wm_state_above, "_NET_WM_STATE_ABOVE"); + CREATE(net_wm_state_below, "_NET_WM_STATE_BELOW"); + + prop_atoms.net_wm_state_add = 1; + prop_atoms.net_wm_state_remove = 0; + prop_atoms.net_wm_state_toggle = 2; + + prop_atoms.net_wm_orientation_horz = 0; + prop_atoms.net_wm_orientation_vert = 1; + prop_atoms.net_wm_topleft = 0; + prop_atoms.net_wm_topright = 1; + prop_atoms.net_wm_bottomright = 2; + prop_atoms.net_wm_bottomleft = 3; + + CREATE(kde_net_system_tray_windows, "_KDE_NET_SYSTEM_TRAY_WINDOWS"); + CREATE(kde_net_wm_system_tray_window_for, + "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR"); + CREATE(kde_net_wm_window_type_override, + "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"); + + CREATE(kwm_win_icon, "KWM_WIN_ICON"); + + CREATE(rootpmapid, "_XROOTPMAP_ID"); + CREATE(esetrootid, "ESETROOT_PMAP_ID"); + + CREATE(openbox_pid, "_OPENBOX_PID"); + CREATE(openbox_premax, "_OPENBOX_PREMAX"); +} + +gboolean prop_get(Window win, Atom prop, Atom type, int size, + guchar **data, gulong num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + long num32 = 32 / size * num; /* num in 32-bit elements */ + + res = XGetWindowProperty(ob_display, win, prop, 0l, num32, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success && ret_items && xdata) { + if (ret_size == size && ret_items >= num) { + *data = g_memdup(xdata, num * (size / 8)); + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_prealloc(Window win, Atom prop, Atom type, int size, + guchar *data, gulong num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + long num32 = 32 / size * num; /* num in 32-bit elements */ + + res = XGetWindowProperty(ob_display, win, prop, 0l, num32, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success && ret_items && xdata) { + if (ret_size == size && ret_items >= num) { + gulong i; + for (i = 0; i < num; ++i) + switch (size) { + case 8: + data[i] = xdata[i]; + break; + case 16: + ((guint16*)data)[i] = ((guint16*)xdata)[i]; + break; + case 32: + ((guint32*)data)[i] = ((guint32*)xdata)[i]; + break; + default: + g_assert_not_reached(); /* unhandled size */ + } + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_all(Window win, Atom prop, Atom type, int size, + guchar **data, gulong *num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + + res = XGetWindowProperty(ob_display, win, prop, 0l, G_MAXLONG, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success) { + if (ret_size == size && ret_items > 0) { + *data = g_memdup(xdata, ret_items * (size / 8)); + *num = ret_items; + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_string(Window win, Atom prop, Atom type, guchar **data) +{ + guchar *raw; + gulong num; + GString *str; + + if (prop_get_all(win, prop, type, 8, &raw, &num)) { + str = g_string_new_len((char*)raw, num); + g_assert(str->str[num] == '\0'); + + g_free(raw); + + *data = (guchar*)g_string_free(str, FALSE); + return TRUE; + } + return FALSE; +} + +gboolean prop_get_strings(Window win, Atom prop, Atom type, + GPtrArray *data) +{ + guchar *raw; + gulong num; + GString *str, *str2; + guint i, start; + + if (prop_get_all(win, prop, type, 8, &raw, &num)) { + str = g_string_new_len((gchar*)raw, num); + g_assert(str->str[num] == '\0'); /* assuming this is always true.. */ + + g_free(raw); + + /* split it into the list */ + for (start = 0, i = 0; i < str->len; ++i) { + if (str->str[i] == '\0') { + str2 = g_string_new_len(&str->str[start], i - start); + g_ptr_array_add(data, g_string_free(str2, FALSE)); + start = i + 1; + } + } + g_string_free(str, TRUE); + + if (data->len > 0) + return TRUE; + } + return FALSE; +} + +void prop_erase(Window win, Atom prop) +{ + XDeleteProperty(ob_display, win, prop); +} diff --git a/c/prop.h b/c/prop.h new file mode 100644 index 0000000..2c08130 --- /dev/null +++ b/c/prop.h @@ -0,0 +1,202 @@ +#ifndef __atoms_h +#define __atoms_h + +#include +#include +#ifdef HAVE_STRING_H +# include +#endif + +#include "openbox.h" + +/*! The atoms on the X server which this class will cache */ +typedef struct Atoms { + /* types */ + Atom cardinal; /*!< The atom which represents the Cardinal data type */ + Atom window; /*!< The atom which represents window ids */ + Atom pixmap; /*!< The atom which represents pixmap ids */ + Atom atom; /*!< The atom which represents atom values */ + Atom string; /*!< The atom which represents ascii strings */ + Atom utf8; /*!< The atom which represents utf8-encoded strings */ + + /* window hints */ + Atom wm_colormap_windows; + Atom wm_protocols; + Atom wm_state; + Atom wm_delete_window; + Atom wm_take_focus; + Atom wm_change_state; + Atom wm_name; + Atom wm_icon_name; + Atom wm_class; + Atom wm_window_role; + Atom motif_wm_hints; + + /* NETWM atoms */ + + /* root window properties */ + Atom net_supported; + Atom net_client_list; + Atom net_client_list_stacking; + Atom net_number_of_desktops; + Atom net_desktop_geometry; + Atom net_desktop_viewport; + Atom net_current_desktop; + Atom net_desktop_names; + Atom net_active_window; + Atom net_workarea; + Atom net_supporting_wm_check; +/* Atom net_virtual_roots; */ + Atom net_desktop_layout; + Atom net_showing_desktop; + /* root window messages */ + Atom net_close_window; + Atom net_wm_moveresize; + /* application window properties */ +/* Atom net_properties; */ + Atom net_wm_name; + Atom net_wm_visible_name; + Atom net_wm_icon_name; + Atom net_wm_visible_icon_name; + Atom net_wm_desktop; + Atom net_wm_window_type; + Atom net_wm_state; + Atom net_wm_strut; +/* Atom net_wm_icon_geometry; */ + Atom net_wm_icon; +/* Atom net_wm_pid; */ +/* Atom net_wm_handled_icons; */ + Atom net_wm_allowed_actions; + /* application protocols */ +/* Atom Atom net_wm_ping; */ + + Atom net_wm_window_type_desktop; + Atom net_wm_window_type_dock; + Atom net_wm_window_type_toolbar; + Atom net_wm_window_type_menu; + Atom net_wm_window_type_utility; + Atom net_wm_window_type_splash; + Atom net_wm_window_type_dialog; + Atom net_wm_window_type_normal; + + Atom net_wm_moveresize_size_topleft; + Atom net_wm_moveresize_size_topright; + Atom net_wm_moveresize_size_bottomleft; + Atom net_wm_moveresize_size_bottomright; + Atom net_wm_moveresize_move; + + Atom net_wm_action_move; + Atom net_wm_action_resize; + Atom net_wm_action_minimize; + Atom net_wm_action_shade; + Atom net_wm_action_stick; + Atom net_wm_action_maximize_horz; + Atom net_wm_action_maximize_vert; + Atom net_wm_action_fullscreen; + Atom net_wm_action_change_desktop; + Atom net_wm_action_close; + + Atom net_wm_state_modal; + Atom net_wm_state_sticky; + Atom net_wm_state_maximized_vert; + Atom net_wm_state_maximized_horz; + Atom net_wm_state_shaded; + Atom net_wm_state_skip_taskbar; + Atom net_wm_state_skip_pager; + Atom net_wm_state_hidden; + Atom net_wm_state_fullscreen; + Atom net_wm_state_above; + Atom net_wm_state_below; + + Atom net_wm_state_add; + Atom net_wm_state_remove; + Atom net_wm_state_toggle; + + Atom net_wm_orientation_horz; + Atom net_wm_orientation_vert; + Atom net_wm_topleft; + Atom net_wm_topright; + Atom net_wm_bottomright; + Atom net_wm_bottomleft; + + /* Extra atoms */ + + Atom kde_net_system_tray_windows; + Atom kde_net_wm_system_tray_window_for; + Atom kde_net_wm_window_type_override; + + Atom kwm_win_icon; + + Atom rootpmapid; + Atom esetrootid; + + /* Openbox specific atoms */ + + Atom openbox_pid; + Atom openbox_premax; +} Atoms; +Atoms prop_atoms; + +void prop_init(); + +gboolean prop_get(Window win, Atom prop, Atom type, int size, + guchar **data, gulong num); + +gboolean prop_get_prealloc(Window win, Atom prop, Atom type, int size, + guchar *data, gulong num); + +gboolean prop_get_all(Window win, Atom prop, Atom type, int size, + guchar **data, gulong *num); + +gboolean prop_get_string(Window win, Atom prop, Atom type, guchar **data); +gboolean prop_get_strings(Window win, Atom prop, Atom type, + GPtrArray *data); + +void prop_erase(Window win, Atom prop); + +/* Set an 8-bit property from a string */ +#define PROP_SETS(win, prop, type, value) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 8, \ + PropModeReplace, (guchar*)value, strlen(value))) +/* Set a 32-bit property from a single value */ +#define PROP_SET32(win, prop, type, value) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 32, \ + PropModeReplace, (guchar*)&value, 1)) +/* Set a 32-bit property from an array */ +#define PROP_SET32A(win, prop, type, value, num) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 32, \ + PropModeReplace, (guchar*)value, num)) + +/* Get an 8-bit property into a string */ +#define PROP_GETS(win, prop, type, value) \ + (prop_get_string(win, prop_atoms.prop, prop_atoms.type, \ + (guchar**)&value)) +/* Get an 8-bit property into a GPtrArray of strings + (The strings must be freed, the GPtrArray must already be created.) */ +#define PROP_GETSA(win, prop, type, value) \ + (prop_get_strings(win, prop_atoms.prop, prop_atoms.type, \ + value)) + +/* Get an entire 8-bit property into an array (which must be freed) */ +#define PROP_GET8U(win, prop, type, value, num) \ + (prop_get_all(win, prop_atoms.prop, prop_atoms.type, 8, \ + (guchar**)&value, &num)) + +/* Get 1 element of a 32-bit property into a given variable */ +#define PROP_GET32(win, prop, type, value) \ + (prop_get_prealloc(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar*)&value, 1)) + +/* Get an amount of a 32-bit property into an array (which must be freed) */ +#define PROP_GET32A(win, prop, type, value, num) \ + (prop_get(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar**)&value, num)) + +/* Get an entire 32-bit property into an array (which must be freed) */ +#define PROP_GET32U(win, prop, type, value, num) \ + (prop_get_all(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar**)&value, &num)) + +#define PROP_ERASE(win, prop) (prop_erase(win, prop_atoms.prop)) + +#endif diff --git a/c/python.c b/c/python.c new file mode 100644 index 0000000..6622ed8 --- /dev/null +++ b/c/python.c @@ -0,0 +1,55 @@ +#include +#include + +#ifdef HAVE_STDLIB_H +# include +#endif + +void python_startup() +{ + PyObject *sys, *sysdict, *syspath, *path1, *path2; + char *home, *homescriptdir; + + Py_Initialize(); + + /* fix up the system path */ + + sys = PyImport_ImportModule((char*)"sys"); /* new */ + sysdict = PyModule_GetDict(sys); /* borrowed */ + syspath = PyDict_GetItemString(sysdict, (char*)"path"); /* borrowed */ + + path1 = PyString_FromString(SCRIPTDIR); /* new */ + PyList_Insert(syspath, 0, path1); + Py_DECREF(path1); + + home = getenv("HOME"); + if (home != NULL) { + homescriptdir = g_strdup_printf("%s/.openbox", home); + path2 = PyString_FromString(homescriptdir); /* new */ + g_free(homescriptdir); + + PyList_Insert(syspath, 0, path2); + Py_DECREF(path2); + } else + g_warning("Failed to read the $HOME environment variable"); + + Py_DECREF(sys); +} + +void python_shutdown() +{ + Py_Finalize(); +} + +gboolean python_import(char *module) +{ + PyObject *mod; + + mod = PyImport_ImportModule(module); /* new */ + if (mod == NULL) { + PyErr_Print(); + return FALSE; + } + Py_DECREF(mod); + return TRUE; +} diff --git a/c/python.h b/c/python.h new file mode 100644 index 0000000..9036631 --- /dev/null +++ b/c/python.h @@ -0,0 +1,10 @@ +#ifndef __python_h +#define __python_h + +void python_startup(); +void python_shutdown(); + +/*! Import a python module */ +gboolean python_import(char *module); + +#endif diff --git a/c/screen.c b/c/screen.c new file mode 100644 index 0000000..ab6765f --- /dev/null +++ b/c/screen.c @@ -0,0 +1,516 @@ +#include "openbox.h" +#include "prop.h" +#include "screen.h" +#include "client.h" +#include "focus.h" + +#include +#ifdef HAVE_UNISTD_H +# include +# include +#endif + +/*! The event mask to grab on the root window */ +#define ROOT_EVENTMASK (/*ColormapChangeMask |*/ PropertyChangeMask | \ + EnterWindowMask | LeaveWindowMask | \ + SubstructureNotifyMask | SubstructureRedirectMask | \ + ButtonPressMask | ButtonReleaseMask | ButtonMotionMask) + +guint screen_num_desktops = 1; +guint screen_desktop = 0; +Size screen_physical_size; +gboolean screen_showing_desktop; +DesktopLayout screen_desktop_layout; +GPtrArray *screen_desktop_names; + +static Rect *area = NULL; +static Strut *strut = NULL; + +static void screen_update_area(); + +static gboolean running; +static int another_running(Display *d, XErrorEvent *e) +{ + (void)d;(void)e; + g_message("A window manager is already running on screen %d", + ob_screen); + running = TRUE; + return -1; +} + +gboolean screen_annex() +{ + XErrorHandler old; + Window support; + pid_t pid; + int i, num_support; + Atom *supported; + + running = FALSE; + old = XSetErrorHandler(another_running); + XSelectInput(ob_display, ob_root, ROOT_EVENTMASK); + XSync(ob_display, FALSE); + XSetErrorHandler(old); + if (running) + return FALSE; + + g_message("Managing screen %d", ob_screen); + + /* set the mouse cursor for the root window (the default cursor) */ + XDefineCursor(ob_display, ob_root, ob_cursors.left_ptr); + + /* set the OPENBOX_PID hint */ + pid = getpid(); + PROP_SET32(ob_root, openbox_pid, cardinal, pid); + + /* create the netwm support window */ + support = XCreateSimpleWindow(ob_display, ob_root, 0, 0, 1, 1, 0, 0, 0); + + /* set supporting window */ + PROP_SET32(ob_root, net_supporting_wm_check, window, support); + + /* set properties on the supporting window */ + PROP_SETS(support, net_wm_name, utf8, "Openbox"); + PROP_SET32(support, net_supporting_wm_check, window, support); + + /* set the _NET_SUPPORTED_ATOMS hint */ + num_support = 48; + i = 0; + supported = g_new(Atom, num_support); + supported[i++] = prop_atoms.net_current_desktop; + supported[i++] = prop_atoms.net_number_of_desktops; + supported[i++] = prop_atoms.net_desktop_geometry; + supported[i++] = prop_atoms.net_desktop_viewport; + supported[i++] = prop_atoms.net_active_window; + supported[i++] = prop_atoms.net_workarea; + supported[i++] = prop_atoms.net_client_list; + supported[i++] = prop_atoms.net_client_list_stacking; + supported[i++] = prop_atoms.net_desktop_names; + supported[i++] = prop_atoms.net_close_window; + supported[i++] = prop_atoms.net_desktop_layout; + supported[i++] = prop_atoms.net_showing_desktop; + supported[i++] = prop_atoms.net_wm_name; + supported[i++] = prop_atoms.net_wm_visible_name; + supported[i++] = prop_atoms.net_wm_icon_name; + supported[i++] = prop_atoms.net_wm_visible_icon_name; + supported[i++] = prop_atoms.net_wm_desktop; + supported[i++] = prop_atoms.net_wm_strut; + supported[i++] = prop_atoms.net_wm_window_type; + supported[i++] = prop_atoms.net_wm_window_type_desktop; + supported[i++] = prop_atoms.net_wm_window_type_dock; + supported[i++] = prop_atoms.net_wm_window_type_toolbar; + supported[i++] = prop_atoms.net_wm_window_type_menu; + supported[i++] = prop_atoms.net_wm_window_type_utility; + supported[i++] = prop_atoms.net_wm_window_type_splash; + supported[i++] = prop_atoms.net_wm_window_type_dialog; + supported[i++] = prop_atoms.net_wm_window_type_normal; + supported[i++] = prop_atoms.net_wm_allowed_actions; + supported[i++] = prop_atoms.net_wm_action_move; + supported[i++] = prop_atoms.net_wm_action_resize; + supported[i++] = prop_atoms.net_wm_action_minimize; + supported[i++] = prop_atoms.net_wm_action_shade; + supported[i++] = prop_atoms.net_wm_action_maximize_horz; + supported[i++] = prop_atoms.net_wm_action_maximize_vert; + supported[i++] = prop_atoms.net_wm_action_fullscreen; + supported[i++] = prop_atoms.net_wm_action_change_desktop; + supported[i++] = prop_atoms.net_wm_action_close; + supported[i++] = prop_atoms.net_wm_state; + supported[i++] = prop_atoms.net_wm_state_modal; + supported[i++] = prop_atoms.net_wm_state_maximized_vert; + supported[i++] = prop_atoms.net_wm_state_maximized_horz; + supported[i++] = prop_atoms.net_wm_state_shaded; + supported[i++] = prop_atoms.net_wm_state_skip_taskbar; + supported[i++] = prop_atoms.net_wm_state_skip_pager; + supported[i++] = prop_atoms.net_wm_state_hidden; + supported[i++] = prop_atoms.net_wm_state_fullscreen; + supported[i++] = prop_atoms.net_wm_state_above; + supported[i++] = prop_atoms.net_wm_state_below; + g_assert(i == num_support); +/* + supported[] = prop_atoms.net_wm_moveresize; + supported[] = prop_atoms.net_wm_moveresize_size_topleft; + supported[] = prop_atoms.net_wm_moveresize_size_topright; + supported[] = prop_atoms.net_wm_moveresize_size_bottomleft; + supported[] = prop_atoms.net_wm_moveresize_size_bottomright; + supported[] = prop_atoms.net_wm_moveresize_move; + supported[] = prop_atoms.net_wm_action_stick; +*/ + + PROP_SET32A(ob_root, net_supported, atom, supported, num_support); + g_free(supported); + + return TRUE; +} + +void screen_startup() +{ + screen_desktop_names = g_ptr_array_new(); + + /* get the initial size */ + screen_resize(); + + screen_set_num_desktops(4); + screen_set_desktop(0); + + /* don't start in showing-desktop mode */ + screen_showing_desktop = FALSE; + PROP_SET32(ob_root, net_showing_desktop, cardinal, screen_showing_desktop); + + screen_update_layout(); +} + +void screen_shutdown() +{ + guint i; + for (i = 0; i < screen_desktop_names->len; ++i) + g_free(g_ptr_array_index(screen_desktop_names, i)); + g_ptr_array_free(screen_desktop_names, TRUE); + g_free(strut); + g_free(area); +} + +void screen_resize() +{ + /* Set the _NET_DESKTOP_GEOMETRY hint */ + /* XXX RandR support here? */ + int geometry[2]; + + geometry[0] = WidthOfScreen(ScreenOfDisplay(ob_display, ob_screen)); + geometry[1] = HeightOfScreen(ScreenOfDisplay(ob_display, ob_screen)); + PROP_SET32A(ob_root, net_desktop_geometry, cardinal, geometry, 2); + screen_physical_size.width = geometry[0]; + screen_physical_size.height = geometry[1]; + + if (ob_state == State_Starting) + return; + + screen_update_struts(); + + /* XXX adjust more stuff ? */ +} + +void screen_set_num_desktops(guint num) +{ + unsigned long *viewport; + + g_assert(num > 0); + + /* move windows on desktops that will no longer exist! */ + /* XXX + std::list::iterator it, end = clients.end(); + for (it = clients.begin(); it != end; ++it) { + unsigned int d = (*it)->desktop(); + if (d >= num && d != 0xffffffff) { + XEvent ce; + ce.xclient.type = ClientMessage; + ce.xclient.message_type = otk::Property::atoms.net_wm_desktop; + ce.xclient.display = **otk::display; + ce.xclient.window = (*it)->window(); + ce.xclient.format = 32; + ce.xclient.data.l[0] = num - 1; + XSendEvent(**otk::display, _info->rootWindow(), false, + SubstructureNotifyMask | SubstructureRedirectMask, &ce); + } + } + */ + + screen_num_desktops = num; + PROP_SET32(ob_root, net_number_of_desktops, cardinal, num); + + /* set the viewport hint */ + viewport = g_new0(unsigned long, num * 2); + PROP_SET32A(ob_root, net_desktop_viewport, cardinal, viewport, num * 2); + g_free(viewport); + + /* change our struts/area to match */ + screen_update_struts(); + + /* the number of rows/columns will differ */ + screen_update_layout(); + + /* may be some unnamed desktops that we need to fill in with names */ + screen_update_desktop_names(); + + /* change our desktop if we're on one that no longer exists! */ + if (screen_desktop >= screen_num_desktops) + screen_set_desktop(num - 1); +} + +void screen_set_desktop(guint num) +{ + guint old = screen_desktop; + + g_assert(num < screen_num_desktops); + + g_message("Moving to desktop %u", num); + + screen_desktop = num; + PROP_SET32(ob_root, net_current_desktop, cardinal, num); + + if (old == num) return; + + /* XXX show/hide all the clients + std::list::iterator it, end = clients.end(); + for (it = clients.begin(); it != end; ++it) + (*it)->showhide(); */ + + /* XXX force the callbacks to fire + if (!openbox->focusedClient()) + openbox->setFocusedClient(0); */ +} + +void screen_update_layout() +{ + unsigned long *data = NULL; + + /* defaults */ + screen_desktop_layout.orientation = prop_atoms.net_wm_orientation_horz; + screen_desktop_layout.start_corner = prop_atoms.net_wm_topleft; + screen_desktop_layout.rows = 1; + screen_desktop_layout.columns = screen_num_desktops; + + if (PROP_GET32A(ob_root, net_desktop_layout, cardinal, data, 4)) { + if (data[0] == prop_atoms.net_wm_orientation_vert) + screen_desktop_layout.orientation = data[0]; + if (data[3] == prop_atoms.net_wm_topright) + screen_desktop_layout.start_corner = data[3]; + else if (data[3] == prop_atoms.net_wm_bottomright) + screen_desktop_layout.start_corner = data[3]; + else if (data[3] == prop_atoms.net_wm_bottomleft) + screen_desktop_layout.start_corner = data[3]; + + /* fill in a zero rows/columns */ + if (!(data[1] == 0 && data[2] == 0)) { /* both 0's is bad data.. */ + if (data[1] == 0) { + data[1] = (screen_num_desktops + + screen_num_desktops % data[2]) / data[2]; + } else if (data[2] == 0) { + data[2] = (screen_num_desktops + + screen_num_desktops % data[1]) / data[1]; + } + screen_desktop_layout.columns = data[1]; + screen_desktop_layout.rows = data[2]; + } + + /* bounds checking */ + if (screen_desktop_layout.orientation == + prop_atoms.net_wm_orientation_horz) { + if (screen_desktop_layout.rows > screen_num_desktops) + screen_desktop_layout.rows = screen_num_desktops; + if (screen_desktop_layout.columns > ((screen_num_desktops + + screen_num_desktops % + screen_desktop_layout.rows) / + screen_desktop_layout.rows)) + screen_desktop_layout.columns = + (screen_num_desktops + screen_num_desktops % + screen_desktop_layout.rows) / + screen_desktop_layout.rows; + } else { + if (screen_desktop_layout.columns > screen_num_desktops) + screen_desktop_layout.columns = screen_num_desktops; + if (screen_desktop_layout.rows > ((screen_num_desktops + + screen_num_desktops % + screen_desktop_layout.columns) / + screen_desktop_layout.columns)) + screen_desktop_layout.rows = + (screen_num_desktops + screen_num_desktops % + screen_desktop_layout.columns) / + screen_desktop_layout.columns; + } + g_free(data); + } +} + +void screen_update_desktop_names() +{ + guint i; + + /* empty the array */ + for (i = 0; i < screen_desktop_names->len; ++i) + g_free(g_ptr_array_index(screen_desktop_names, i)); + g_ptr_array_set_size(screen_desktop_names, 0); + + PROP_GETSA(ob_root, net_desktop_names, utf8, screen_desktop_names); + + while (screen_desktop_names->len < screen_num_desktops) + g_ptr_array_add(screen_desktop_names, g_strdup("Unnamed Desktop")); +} + +void screen_show_desktop(gboolean show) +{ + GSList *it; + static Window saved_focus = 0; + + if (show == screen_showing_desktop) return; /* no change */ + + /* save the window focus, and restore it when leaving the show-desktop + mode */ + if (show && focus_client) + saved_focus = focus_client->window; + + screen_showing_desktop = show; + + for (it = client_list; it; it = g_slist_next(it)) { + Client *client = it->data; + if (client->type == Type_Desktop) { + if (show) client_focus(client); + } else + client_showhide(client); + } + + if (!show) { + Client *f = focus_client; + if (!f || f->type == Type_Desktop) { + Client *c = g_hash_table_lookup(client_map, + (gpointer)saved_focus); + if (c) client_focus(c); + } + } + + show = show ? 1 : 0; /* make it boolean */ + PROP_SET32(ob_root, net_showing_desktop, cardinal, show); +} + +void screen_install_colormap(Client *client, gboolean install) +{ + if (client == NULL) { + /* XXX DONT USE THE DEFAULT SHIT HERE */ + if (install) + XInstallColormap(ob_display, + DefaultColormap(ob_display, ob_screen)); + else + XUninstallColormap(ob_display, + DefaultColormap(ob_display, ob_screen)); + } else { + XWindowAttributes wa; + if (XGetWindowAttributes(ob_display, client->window, &wa)) { + if (install) + XInstallColormap(ob_display, wa.colormap); + else + XUninstallColormap(ob_display, wa.colormap); + } + } +} + +void screen_update_struts() +{ + GSList *it; + guint i; + + if (strut != NULL) + g_free(strut); + strut = g_new0(Strut, screen_num_desktops + 1); + + for (it = client_list; it; it = it->next) { + Client *c = it->data; + if (c->iconic) continue; /* these dont count in the strut */ + + if (c->desktop == 0xffffffff) { + for (i = 0; i < screen_num_desktops; ++i) + STRUT_ADD(strut[i], c->strut); + } else { + g_assert(c->desktop < screen_num_desktops); + STRUT_ADD(strut[c->desktop], c->strut); + } + /* apply to the 'all desktops' strut */ + STRUT_ADD(strut[screen_num_desktops], c->strut); + } + screen_update_area(); +} + +static void screen_update_area() +{ + guint i; + gulong *dims; + + if (area != NULL) + g_free(area); + area = g_new0(Rect, screen_num_desktops + 1); + + dims = g_new(unsigned long, 4 * screen_num_desktops); + for (i = 0; i < screen_num_desktops + 1; ++i) { + Rect old_area = area[i]; +/* + #ifdef XINERAMA + // reset to the full areas + if (isXineramaActive()) + xineramaUsableArea = getXineramaAreas(); + #endif // XINERAMA +*/ + + RECT_SET(area[i], strut[i].left, strut[i].top, + screen_physical_size.width - (strut[i].left + + strut[i].right), + screen_physical_size.height - (strut[i].top + + strut[i].bottom)); + +/* + #ifdef XINERAMA + if (isXineramaActive()) { + // keep each of the ximerama-defined areas inside the strut + RectList::iterator xit, xend = xineramaUsableArea.end(); + for (xit = xineramaUsableArea.begin(); xit != xend; ++xit) { + if (xit->x() < usableArea.x()) { + xit->setX(usableArea.x()); + xit->setWidth(xit->width() - usableArea.x()); + } + if (xit->y() < usableArea.y()) { + xit->setY(usableArea.y()); + xit->setHeight(xit->height() - usableArea.y()); + } + if (xit->x() + xit->width() > usableArea.width()) + xit->setWidth(usableArea.width() - xit->x()); + if (xit->y() + xit->height() > usableArea.height()) + xit->setHeight(usableArea.height() - xit->y()); + } + } + #endif // XINERAMA +*/ + if (!RECT_EQUAL(old_area, area[i])) { + /* the area has changed, adjust all the maximized windows */ + GSList *it; + for (it = client_list; it; it = it->next) { + Client *c = it->data; + if (i < screen_num_desktops) { + if (c->desktop == i) + client_remaximize(c); + } else { + /* the 'all desktops' size */ + if (c->desktop == DESKTOP_ALL) + client_remaximize(c); + } + } + } + + /* don't set these for the 'all desktops' area */ + if (i < screen_num_desktops) { + dims[(i * 4) + 0] = area[i].x; + dims[(i * 4) + 1] = area[i].y; + dims[(i * 4) + 2] = area[i].width; + dims[(i * 4) + 3] = area[i].height; + } + } + PROP_SET32A(ob_root, net_workarea, cardinal, + dims, 4 * screen_num_desktops); + g_free(dims); +} + +Rect *screen_area(guint desktop) +{ + if (desktop >= screen_num_desktops) { + if (desktop == DESKTOP_ALL) + return &area[screen_num_desktops]; + return NULL; + } + return &area[desktop]; +} + +Strut *screen_strut(guint desktop) +{ + if (desktop >= screen_num_desktops) { + if (desktop == DESKTOP_ALL) + return &strut[screen_num_desktops]; + return NULL; + } + return &strut[desktop]; +} diff --git a/c/screen.h b/c/screen.h new file mode 100644 index 0000000..a591a20 --- /dev/null +++ b/c/screen.h @@ -0,0 +1,66 @@ +#ifndef __screen_h +#define __screen_h + +#include "geom.h" + +struct Client; + +#define DESKTOP_ALL (0xffffffff) + +/*! The number of available desktops */ +extern guint screen_num_desktops; +/*! The current desktop */ +extern guint screen_desktop; +/*! The size of the screen */ +extern Size screen_physical_size; +/*! Are we in showing-desktop mode? */ +extern gboolean screen_showing_desktop; + +typedef struct DesktopLayout { + guint orientation; + guint start_corner; + guint rows; + guint columns; +} DesktopLayout; +extern DesktopLayout screen_desktop_layout; + +/*! An array of gchar*'s which are desktop names in UTF-8 format */ +extern GPtrArray *screen_desktop_names; + +/*! Take over the screen, set the basic hints on it claming it as ours */ +gboolean screen_annex(); + +/*! Once the screen is ours, set up its initial state */ +void screen_startup(); +/*! Free resources */ +void screen_shutdown(); + +/*! Figure out the new size of the screen and adjust stuff for it */ +void screen_resize(); + +/*! Change the number of available desktops */ +void screen_set_num_desktops(guint num); +/*! Change the current desktop */ +void screen_set_desktop(guint num); + +/*! Shows and focuses the desktop and hides all the client windows, or + returns to the normal state, showing client windows. */ +void screen_show_desktop(gboolean show); + +/*! Updates the desktop layout from the root property if available */ +void screen_update_layout(); + +/*! Get desktop names from the root window property */ +void screen_update_desktop_names(); + +/*! Installs or uninstalls a colormap for a client. If client is NULL, then + it handles the root colormap. */ +void screen_install_colormap(struct Client *client, gboolean install); + +void screen_update_struts(); + +Rect *screen_area(guint desktop); + +Strut *screen_strut(guint desktop); + +#endif diff --git a/c/screenwrap.c b/c/screenwrap.c new file mode 100644 index 0000000..9ef14d8 --- /dev/null +++ b/c/screenwrap.c @@ -0,0 +1,433 @@ +#include "screenwrap.h" +#include "openbox.h" +#include "screen.h" +#include "kbind.h" +#include "mbind.h" + +ScreenWrap *screenwrap_instance; + +/*************************************************************************** + + Define the type 'ScreenWrap' + + ***************************************************************************/ + +#define IS_SWRAP(v) ((v)->ob_type == &ScreenWrapType) +#define CHECK_SWRAP(self, funcname) { \ + if (!IS_SWRAP(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' a 'Screen' object"); \ + return NULL; \ + } \ +} + +staticforward PyTypeObject ScreenWrapType; + +/*************************************************************************** + + Attribute methods + + ***************************************************************************/ + +static PyObject *swrap_number(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "number"); + if (!PyArg_ParseTuple(args, ":number")) + return NULL; + return PyInt_FromLong(ob_screen); +} + +static PyObject *swrap_rootWindow(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "rootWindow"); + if (!PyArg_ParseTuple(args, ":rootWindow")) + return NULL; + return PyInt_FromLong(ob_root); +} + +static PyObject *swrap_state(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "state"); + if (!PyArg_ParseTuple(args, ":state")) + return NULL; + return PyInt_FromLong(ob_state); +} + +static PyObject *swrap_numDesktops(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "numDesktops"); + if (!PyArg_ParseTuple(args, ":numDesktops")) + return NULL; + return PyInt_FromLong(screen_num_desktops); +} + +static PyObject *swrap_desktop(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "desktop"); + if (!PyArg_ParseTuple(args, ":desktop")) + return NULL; + return PyInt_FromLong(screen_desktop); +} + +static PyObject *swrap_physicalSize(ScreenWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_SWRAP(self, "physicalSize"); + if (!PyArg_ParseTuple(args, ":physicalSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(screen_physical_size.width)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(screen_physical_size.height)); + return tuple; +} + +static PyObject *swrap_showingDesktop(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "showingDesktop"); + if (!PyArg_ParseTuple(args, ":showingDesktop")) + return NULL; + return PyInt_FromLong(screen_showing_desktop ? 1 : 0); +} + +static PyObject *swrap_desktopLayout(ScreenWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_SWRAP(self, "desktopLayout"); + if (!PyArg_ParseTuple(args, ":desktopLayout")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, + PyInt_FromLong(screen_desktop_layout.orientation)); + PyTuple_SET_ITEM(tuple, 1, + PyInt_FromLong(screen_desktop_layout.start_corner)); + PyTuple_SET_ITEM(tuple, 2, + PyInt_FromLong(screen_desktop_layout.rows)); + PyTuple_SET_ITEM(tuple, 3, + PyInt_FromLong(screen_desktop_layout.columns)); + return tuple; +} + +static PyObject *swrap_desktopNames(ScreenWrap *self, PyObject *args) +{ + PyObject *list; + guint s, i; + + CHECK_SWRAP(self, "desktopNames"); + if (!PyArg_ParseTuple(args, ":desktopNames")) + return NULL; + s = screen_desktop_names->len; + list = PyList_New(s); + for (i = 0; i < s; ++i) + PyList_SET_ITEM(list, i, PyString_FromString + (g_ptr_array_index(screen_desktop_names, i))); + return list; +} + +static PyObject *swrap_area(ScreenWrap *self, PyObject *args) +{ + PyObject * tuple; + Rect *r; + guint i; + + CHECK_SWRAP(self, "area"); + if (!PyArg_ParseTuple(args, "i:area", &i)) + return NULL; + r = screen_area(i); + if (r == NULL) { + PyErr_SetString(PyExc_IndexError, + "the requested desktop was not valid"); + return NULL; + } + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(r->x)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(r->y)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(r->width)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(r->height)); + return tuple; +} + +static PyObject *swrap_strut(ScreenWrap *self, PyObject *args) +{ + PyObject *tuple; + Strut *s; + guint i; + + CHECK_SWRAP(self, "strut"); + if (!PyArg_ParseTuple(args, "i:strut", &i)) + return NULL; + s = screen_strut(i); + if (s == NULL) { + PyErr_SetString(PyExc_IndexError, + "the requested desktop was not valid"); + return NULL; + } + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(s->left)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(s->top)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(s->right)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(s->bottom)); + return tuple; +} + +static PyObject *swrap_grabKey(ScreenWrap *self, PyObject *args) +{ + PyObject *item, *tuple; + GList *keylist = NULL, *it; + int i, s; + gboolean grab = FALSE; + + CHECK_SWRAP(self, "grabKey"); + if (!PyArg_ParseTuple(args, "O:grabKey", &tuple)) + return NULL; + + if (PyTuple_Check(tuple)) { + s = PyTuple_GET_SIZE(tuple); + if (s > 0) { + for (i = 0; i < s; ++i) { + item = PyTuple_GET_ITEM(tuple, i); + if (!PyString_Check(item)) + break; + keylist = g_list_append(keylist, + g_strdup(PyString_AsString(item))); + } + if (i == s) + grab = kbind_add(keylist); + + for (it = keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(it); + + if (grab) { + Py_INCREF(Py_None); + return Py_None; + } else { + PyErr_SetString(PyExc_ValueError, + "the key could not be grabbed"); + return NULL; + } + } + } + + PyErr_SetString(PyExc_TypeError, "expected a tuple of strings"); + return NULL; +} + +static PyObject *swrap_clearKeyGrabs(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "clearKeyGrabs"); + if (!PyArg_ParseTuple(args, ":clearKeyGrabs")) + return NULL; + kbind_clearall(); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *swrap_grabKeyboard(ScreenWrap *self, PyObject *args) +{ + int grab; + + CHECK_SWRAP(self, "grabKeyboard"); + if (!PyArg_ParseTuple(args, "i:grabKeyboard", &grab)) + return NULL; + if (!kbind_grab_keyboard(grab)) { + PyErr_SetString(PyExc_RuntimeError, "failed to grab keyboard"); + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *swrap_grabButton(ScreenWrap *self, PyObject *args) +{ + char *name; + char *context_str; + GQuark context; + + CHECK_SWRAP(self, "grabButton"); + if (!PyArg_ParseTuple(args, "ss:grabKey", &name, &context_str)) + return NULL; + + context = g_quark_try_string(context_str); + + if (!context) { + PyErr_SetString(PyExc_ValueError, "invalid context"); + return NULL; + } + + if (mbind_add(name, context)) { + Py_INCREF(Py_None); + return Py_None; + } else { + PyErr_SetString(PyExc_ValueError, + "the button could not be grabbed"); + return NULL; + } +} + +static PyObject *swrap_clearButtonGrabs(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "clearButtonGrabs"); + if (!PyArg_ParseTuple(args, ":clearButtonGrabs")) + return NULL; + mbind_clearall(); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *swrap_grabPointer(ScreenWrap *self, PyObject *args) +{ + int grab; + + CHECK_SWRAP(self, "grabPointer"); + if (!PyArg_ParseTuple(args, "i:grabPointer", &grab)) + return NULL; + if (!mbind_grab_pointer(grab)) { + PyErr_SetString(PyExc_RuntimeError, "failed to grab pointer"); + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef ScreenWrapAttributeMethods[] = { + {"number", (PyCFunction)swrap_number, METH_VARARGS, + "Screen.number() -- Returns the number of the screen on the X server on " + "which this Openbox instance is running."}, + {"rootWindow", (PyCFunction)swrap_rootWindow, METH_VARARGS, + "Screen.rootWindow() -- Returns the window id of the root window."}, + {"state", (PyCFunction)swrap_state, METH_VARARGS, + "Screen.state() -- Returns the running state of Openbox. One of the " + "ob.State_ constants."}, + {"numDesktops", (PyCFunction)swrap_numDesktops, METH_VARARGS, + "Screen.numDesktops() -- Returns the number of desktops available."}, + {"desktop", (PyCFunction)swrap_desktop, METH_VARARGS, + "Screen.desktop() -- Returns the currently visible desktop."}, + {"physicalSize", (PyCFunction)swrap_physicalSize, METH_VARARGS, + "Screen.physicalSize() -- Returns the physical size (in pixels) of the " + "display in a tuple. The tuple is formatted as (width, height)."}, + {"showingDesktop", (PyCFunction)swrap_showingDesktop, METH_VARARGS, + "Screen.showingDesktop() -- Returns if in showing-the-desktop mode or " + "not."}, + {"desktopNames", (PyCFunction)swrap_desktopNames, METH_VARARGS, + "Screen.desktopNames() -- Returns a list of the names of all the " + "desktops, and possibly for desktops beyond those. The names are encoded " + "as UTF-8."}, + {"desktopLayout", (PyCFunction)swrap_desktopLayout, METH_VARARGS, + "Screen.desktopLayout() -- Returns the layout of the desktops, as " + "specified by a compliant pager, in a tuple. The format of the tuple is " + "(orientation, corner, rows, columns). Where, orientation is one of the " + "ob.Orientation_ constants, corner is one of the ob.Corner_ constants, " + "and rows and columns specify the size of the layout. The rows and " + "columns will always include all the desktops."}, + {"area", (PyCFunction)swrap_area, METH_VARARGS, + "Screen.area(d) -- Returns the usuable area on the Screen for a desktop, " + "in the form of a tuple. The tuples format is (x, y, width, height). The " + "desktop must be in the range of desktops on the screen, or 0xffffffff " + "to get a combined area for 'all desktops'."}, + {"strut", (PyCFunction)swrap_area, METH_VARARGS, + "Screen.strut(d) -- Returns the combined strut of all clients on a " + "desktop, in the form of a tuple. The tuples format is " + "(left, top, right, bottom). The desktop must be in the range of " + "desktops on the screen, or 0xffffffff to get a combined strut for " + "'all desktops'."}, + {"grabKey", (PyCFunction)swrap_grabKey, METH_VARARGS, + "Screen.grabKey(('Mod1-C-a', 'd')) -- Grabs a key chain so that key " + "events for it will occur. The argument must be a tuple of one or " + "more elements. Each key element is made up of " + "Modifier-Modifier-...-Key, where Modifier is one of Mod1, Mod2, " + "Mod3, Mod4, Mod5, S (for Shift), or C (for Control)."}, + {"clearKeyGrabs", (PyCFunction)swrap_clearKeyGrabs, METH_VARARGS, + "Screen.clearKeyGrabs() -- Removes all key grabs that have been done " + "with grabKey()."}, + {"grabKeyboard", (PyCFunction)swrap_grabKeyboard, METH_VARARGS, + "Screen.grabKeyboard(grab) -- Grabs or ungrabs the entire keyboard. When " + "the keyboard is grabbed, all key presses will be sent to the " + "hooks.keyboard hook. (grabbed keys will go to the hooks.events hook " + "too. "}, + {"grabButton", (PyCFunction)swrap_grabButton, METH_VARARGS, + "Screen.grabButton('C-1', \"frame\") -- Grabs a pointer button " + "for the given context. The context must be one of the ob.Context_* " + "constants. The button definition is made up of " + "Modifier-Modifier-...-Button, where Modifier is one of Mod1, Mod2, " + "Mod3, Mod4, Mod5, S (for Shift), or C (for Control)."}, + {"clearButtonGrabs", (PyCFunction)swrap_clearButtonGrabs, METH_VARARGS, + "Screen.clearButtonGrabs() -- Removes all button grabs that have been " + "done with grabButton()."}, + {"grabPointer", (PyCFunction)swrap_grabPointer, METH_VARARGS, + "grabPointer(grab) -- Grabs or ungrabs the pointer device. When the " + "pointer is grabbed, all pointer events will be sent to the " + "hooks.pointer hook. (grabbed buttons will NOT go to the hooks.events " + "hook while the pointer is grabbed)."}, + {NULL, NULL, 0, NULL} +}; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +static PyObject *swrap_getattr(ScreenWrap *self, char *name) +{ + CHECK_SWRAP(self, "getattr"); + return Py_FindMethod(ScreenWrapAttributeMethods, (PyObject*)self, name); +} + +static void swrap_dealloc(ScreenWrap *self) +{ + PyObject_Del((PyObject*) self); +} + +static PyTypeObject ScreenWrapType = { + PyObject_HEAD_INIT(NULL) + 0, + "Screen", + sizeof(ScreenWrap), + 0, + (destructor) swrap_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc) swrap_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/*************************************************************************** + + External methods + + ***************************************************************************/ + +void screenwrap_startup() +{ + PyObject *ob, *obdict; + ScreenWrap *swrap; + + ScreenWrapType.ob_type = &PyType_Type; + ScreenWrapType.tp_doc = "Wraps information and functionality global to an " + "instance of Openbox."; + PyType_Ready(&ScreenWrapType); + swrap = PyObject_New(ScreenWrap, &ScreenWrapType); + + /* get the ob module/dict */ + ob = PyImport_ImportModule("ob"); /* new */ + g_assert(ob != NULL); + obdict = PyModule_GetDict(ob); /* borrowed */ + g_assert(obdict != NULL); + + PyDict_SetItemString(obdict, "Screen", (PyObject*)swrap); + Py_DECREF(swrap); + Py_DECREF(ob); +} + +void screenwrap_shutdown() +{ +} + + diff --git a/c/screenwrap.h b/c/screenwrap.h new file mode 100644 index 0000000..2ca278c --- /dev/null +++ b/c/screenwrap.h @@ -0,0 +1,14 @@ +#ifndef __screenwrap_h +#define __screenwrap_h + +#include + +/* ScreenWrap is a PyObject */ +typedef struct ScreenWrap { + PyObject_HEAD +} ScreenWrap; + +void screenwrap_startup(); +void screenwrap_shutdown(); + +#endif diff --git a/c/stacking.c b/c/stacking.c new file mode 100644 index 0000000..081bde9 --- /dev/null +++ b/c/stacking.c @@ -0,0 +1,116 @@ +#include "openbox.h" +#include "prop.h" +#include "focus.h" +#include "client.h" +#include "frame.h" +#include + +GList *stacking_list = NULL; + +void stacking_set_list() +{ + Window *windows, *win_it; + GList *it; + guint size = g_list_length(stacking_list); + + /* create an array of the window ids (from bottom to top, + reverse order!) */ + if (size > 0) { + windows = g_new(Window, size); + win_it = windows; + for (it = g_list_last(stacking_list); it; it = it->prev, ++win_it) + *win_it = ((Client*)it->data)->window; + } else + windows = NULL; + + PROP_SET32A(ob_root, net_client_list_stacking, window, windows, size); + + if (windows) + g_free(windows); +} + +void stacking_raise(Client *client) +{ + Window wins[2]; /* only ever restack 2 windows. */ + GList *it; + Client *m; + + g_assert(stacking_list != NULL); /* this would be bad */ + + m = client_find_modal_child(client); + /* if we have a modal child, raise it instead, we'll go along tho later */ + if (m) stacking_raise(m); + + /* remove the client before looking so we can't run into ourselves */ + stacking_list = g_list_remove(stacking_list, client); + + /* the stacking list is from highest to lowest */ + it = stacking_list; + while (it) { + Client *c = it->data; + if (client->layer >= c->layer && m != c) + break; + it = it->next; + } + + /* + if our new position is the top, we want to stack under the focus_backup. + otherwise, we want to stack under the previous window in the stack. + */ + if (it == stacking_list) + wins[0] = focus_backup; + else if (it != NULL) + wins[0] = ((Client*)it->prev->data)->frame->window; + else + wins[0] = ((Client*)g_list_last(stacking_list)->data)->frame->window; + wins[1] = client->frame->window; + + stacking_list = g_list_insert_before(stacking_list, it, client); + + XRestackWindows(ob_display, wins, 2); + + stacking_set_list(); +} + +void stacking_lower(Client *client) +{ + Window wins[2]; /* only ever restack 2 windows. */ + GList *it; + + g_assert(stacking_list != NULL); /* this would be bad */ + + it = g_list_last(stacking_list); + + if (client->modal && client->transient_for) { + /* don't let a modal window lower below its transient_for */ + it = g_list_find(stacking_list, client->transient_for); + g_assert(it != NULL); + + wins[0] = (it == stacking_list ? focus_backup : + ((Client*)it->prev->data)->frame->window); + wins[1] = client->frame->window; + if (wins[0] == wins[1]) return; /* already right above the window */ + + stacking_list = g_list_remove(stacking_list, client); + stacking_list = g_list_insert_before(stacking_list, it, client); + } else { + while (it != stacking_list) { + Client *c = it->data; + if (client->layer >= c->layer) + break; + it = it->prev; + } + if (it->data == client) return; /* already the bottom, return */ + + wins[0] = ((Client*)it->data)->frame->window; + wins[1] = client->frame->window; + + stacking_list = g_list_remove(stacking_list, client); + stacking_list = g_list_insert_before(stacking_list, + it->next, client); + } + + XRestackWindows(ob_display, wins, 2); + stacking_set_list(); +} + diff --git a/c/stacking.h b/c/stacking.h new file mode 100644 index 0000000..fa6bcf8 --- /dev/null +++ b/c/stacking.h @@ -0,0 +1,26 @@ +#ifndef __stacking_h +#define __stacking_h + +#include + +struct Client; + +extern GList *stacking_list; + +/*! Sets the client stacking list on the root window from the + stacking_clientlist */ +void stacking_set_list(); + +/*! Raises a client window above all others in its stacking layer + raiseWindow has a couple of constraints that lowerWindow does not.
+ 1) raiseWindow can be called after changing a Client's stack layer, and + the list will be reorganized properly.
+ 2) raiseWindow guarantees that XRestackWindows() will always be + called for the specified client. +*/ +void stacking_raise(struct Client *client); + +/*! Lowers a client window below all others in its stacking layer */ +void stacking_lower(struct Client *client); + +#endif diff --git a/c/xerror.c b/c/xerror.c new file mode 100644 index 0000000..49a795f --- /dev/null +++ b/c/xerror.c @@ -0,0 +1,32 @@ +#include "openbox.h" +#include +#include + +static gboolean xerror_ignore = FALSE; + +int xerror_handler(Display *d, XErrorEvent *e) +{ +#ifdef DEBUG + if (!xerror_ignore) { + char errtxt[128]; + + /*if (e->error_code != BadWindow) */ + { + XGetErrorText(d, e->error_code, errtxt, 127); + if (e->error_code == BadWindow) + g_warning("X Error: %s", errtxt); + else + g_error("X Error: %s", errtxt); + } + } +#else + (void)d; (void)e; +#endif + return 0; +} + +void xerror_set_ignore(gboolean ignore) +{ + XSync(ob_display, FALSE); + xerror_ignore = ignore; +} diff --git a/c/xerror.h b/c/xerror.h new file mode 100644 index 0000000..74b236f --- /dev/null +++ b/c/xerror.h @@ -0,0 +1,11 @@ +#ifndef __xerror_h +#define __xerror_h + +#include +#include + +int xerror_handler(Display *, XErrorEvent *); + +void xerror_set_ignore(gboolean ignore); + +#endif diff --git a/configure.ac b/configure.ac index b83776f..dc1404c 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ AC_PREREQ([2.50]) AC_INIT([src/main.cc]) -AC_CONFIG_HEADERS(config.h) -AC_LANG([C++]) +#AC_CONFIG_HEADERS(config.h) +AC_LANG([C]) AC_ENABLE_STATIC([no]) AC_ENABLE_SHARED([yes]) @@ -13,23 +13,27 @@ fi AM_INIT_AUTOMAKE([openbox], [2.90.0cvs]) -AC_PATH_PROG([awk_cmd], [awk]) # used by swig.m4 -test "$awk_cmd" || AC_MSG_ERROR([awk not found]) +##AC_PATH_PROG([awk_cmd], [awk]) # used by swig.m4 +##test "$awk_cmd" || AC_MSG_ERROR([awk not found]) AC_PATH_PROG([regex_cmd], [sed]) test "$regex_cmd" || AC_MSG_ERROR([sed not found]) AM_MAINTAINER_MODE # Determine build target OB_DEBUG -# Pick compiler specific/build target flags +# Pick compiler specific/build target flags, and set $CVS OB_COMPILER_FLAGS -AC_C_BIGENDIAN -SWIG_PROG(1.3.14) -SWIG_ENABLE_CXX -SWIG_MULTI_MODULE_SUPPORT -SWIG_PYTHON +PKG_CHECK_MODULES(GLIB, glib-2.0) +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) +PKG_CHECK_MODULES(GMODULE, gmodule-2.0) +AC_SUBST(GMODULE_CFLAGS) +AC_SUBST(GMODULE_LIBS) + +PYTHON_DEVEL + AC_PROG_LN_S AC_PROG_LIBTOOL LIBTOOL="$LIBTOOL --silent" @@ -39,12 +43,14 @@ ALL_LINGUAS="" AM_GNU_GETTEXT_VERSION(0.11.5) AM_GNU_GETTEXT([external]) -AC_CHECK_HEADERS(fcntl.h signal.h stdarg.h stdint.h unistd.h sys/select.h sys/signal.h sys/stat.h sys/time.h sys/types.h sys/wait.h) +AC_CHECK_HEADERS(ctype.h fcntl.h locale.h signal.h string.h stdlib.h unistd.h) +AC_CHECK_HEADERS(sys/select.h sys/time.h sys/wait.h) # AC_HEADER_TIME # AC_TYPE_SIGNAL -# Check for Xft2 -XFT_DEVEL(2.0.0) +PKG_CHECK_MODULES(XFT, xft) +AC_SUBST(XFT_CFLAGS) +AC_SUBST(XFT_LIBS) # Check for X11 extensions X11_EXT_XKB @@ -52,16 +58,18 @@ X11_EXT_SHAPE X11_EXT_XINERAMA AC_CONFIG_FILES([Makefile po/Makefile.in - otk/Makefile - src/Makefile - tools/Makefile - wrap/Makefile - scripts/Makefile + c/Makefile + kernel/Makefile + render/Makefile + engines/Makefile + engines/openbox/Makefile + python/Makefile doc/Makefile doc/doxygen/Makefile data/Makefile data/buttons/Makefile - data/styles/Makefile + themes/Makefile + themes/openbox/Makefile ]) AC_OUTPUT @@ -74,10 +82,5 @@ if test "$DEBUG" = "yes"; then else AC_MSG_RESULT([Creating a RELEASE build.]) fi -AC_MSG_RESULT([Using '$prefix' for installation.]) -AC_MSG_RESULT([Using '$CXX' for C++ compiler.]) -AC_MSG_RESULT([Building with '$CPPFLAGS' for C++ preprocessor flags.]) -AC_MSG_RESULT([Building with '$CXXFLAGS' for C++ compiler flags.]) -AC_MSG_RESULT([Building with '$LIBS' for linker flags.]) AC_MSG_RESULT AC_MSG_RESULT([configure complete, now type \"make\"]) diff --git a/data/styles/Makefile.am b/data/styles/Makefile.am deleted file mode 100644 index eef4b14..0000000 --- a/data/styles/Makefile.am +++ /dev/null @@ -1,13 +0,0 @@ -#styledir = $(pkgdatadir)/styles -MAINTAINERCLEANFILES = Makefile.in -#style_DATA = -#artwiz bbs bluebox cthulhain deep fieron fieron2 flux frobozz \ -#frobust mbdtex miklos nyz nyzclone ob20 operation outcomes paper \ -#purplehaaze shade steelblue steelblue2 the_orange trisb twice \ -#warp-xp -#EXTRA_DIST = $(style_DATA) - -distclean-local: - $(RM) *\~ .\#* -#uninstall-am: -# -rmdir -p $(DESTDIR)$(styledir) diff --git a/doc/Makefile.am b/doc/Makefile.am index a79bc85..3409d8c 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,9 +1,9 @@ -CLEANFILES = openbox.1 +#CLEANFILES = openbox.1 MAINTAINERCLEANFILES = Makefile.in -man_MANS = openbox.1 +#man_MANS = openbox.1 SUBDIRS = doxygen -EXTRA_DIST = openbox.1.in +#EXTRA_DIST = openbox.1.in DEFAULT_MENU=$(pkgdatadir)/menu diff --git a/doc/bsetbg.1 b/doc/bsetbg.1 deleted file mode 100644 index a233325..0000000 --- a/doc/bsetbg.1 +++ /dev/null @@ -1,142 +0,0 @@ -.TH bsetbg 1 "February 2002" "bsetbg" "v2.0" -.SH NAME -bsetbg \- utility to manipulate the appearance of the X11 desktop's root window. -.SH SYNOPSIS -\fBbsetbg\fR [options] \fIimage\fR -.SH DESCRIPTION -\fBbsetbg\fR is intended to provide a standard method for the \fIBlackbox\fR -window manager to alter the background of the root window -(although it will work with any other window manager as well). \fBbsetbg\fR -acts as a wrapper both to \fIbsetroot\fR -and to a flexible variety of third-party applications that it uses when handling images files. - -.SH OPTIONS -.TP -\fB\-f\fR, \fB\-full\fR \fIimage\fR -\fIimage\fR is stretched to fill the entire desktop. -.TP -\fB\-t\fR, \fB\-tile\fR \fIimage\fR -\fIimage\fR is tiled on the desktop. -.TP -\fB\-c\fR, \fB\-center\fR \fIimage\fR -\fIimage\fR is centered on the desktop. -.TP -\fB\-e\fR, \fB\-exec\fR \fIprogram\fR \fIoptions\fR \fIfallback\-\fIarg\fR \fIimage\fR -This option tells \fBbsetbg\fR to run a separate command by executing \fIprogram\fR with -\fIoptions\fR, where \fIoptions\fR are arguments to \fIprogram\fR. - -If a \fIfallback\-arg\fR is supplied (\fB\-full\fR, \fB\-tile\fR, or \fB\-center\fR -as described above), -\fBbsetbg\fR will assume that the last argument is a filename. In the case that -\fIprogram\fR exits non-zero or isn't available on the target system, \fBbsetbg\fR -will try to handle the file with the fallback argument. - -See the \fBEXAMPLES\fR section for more information on \fB\-exec\fR. -.TP -\fB\-p\fR, \fB\-post\fR \fIlist\fR -Specifies a list of arguments to pass to the $POST_COMMAND. -.TP -\fB\-d\fR, \fB\-debug\fR -Debugging mode. \fBbsetbg\fR will print commands without executing them. -.TP -\fB\-g\fR, \fB\-generate\fR \fIlist\fR -Output a list of default configuration values, suitable for redirecting into -\fI~/.bsetbgrc\fR. Any arguments that are supplied will be considered applications -to search for in the system path, overriding \fBbsetbg\fR's internal defaults. -.TP -\fB\-app\fR \fIimageApp\fR -Use this flag to specify which image application to use. This -application may be one of the pre-defined list or any application -capable of displaying an image on the root window. This flag may be -used in conjunction with passing application specific parameters to -the application, in which -case they should be enclosed in double quotes. -.TP -\fB\-v\fR, \fB\-version\fR -Output version number. -.TP -\fB\-h\fR, \fB\-help\fR -Output a brief usage message. - -.SH OTHER OPTIONS -\fBbsetbg\fR will also accept all of the arguments for \fIbsetroot\fR. -Consult the \fIbsetroot\fR(1) man page for further information. - -.SH CONFIGURATION -\fBbsetbg\fR will read its configuration values from the file \fI~/.bsetbgrc\fR -if it exists. Otherwise, it will scan the -system path for a pre-defined list of image applications to use -(currently this list consists of qiv, xli, xv, wmsetbg, Esetroot, -display, and xsetbg). -\fP -\fI~/.bsetbgrc\fR should contain the following variables: -.TP -\fB CENTER=\fR\fI"string"\fR -Application and arguments to be used to center an image on the root window -when the \fB-center\fR argument is specified. - -.TP -\fB FULL=\fR\fI"string"\fR -Application and arguments to be used to stretch an image to fill the root window -when the \fB-full\fR argument is specified. - -.TP -\fB TILE=\fR\fI"string"\fR -Application and arguments to be used to tile an image on the root window -when the \fB-tile\fR argument is specified. - -.TP -\fB DEFAULT=\fR\fI"string"\fR -Action to take place by default if none of the above have been specified. - -.TP -The following variables are optional: - -.TP -\fB NO_EXEC=\fR\fI"boolean"\fR -If this variable is set, bsetbg will never modify the root window. - -.TP -\fB POST_COMMAND=\fR\fI"string"\fR -This variable specifies a command that \fBbsetbg\fR will run after every -successful modification of the root window. - -.TP -\fB LOG_LAST_CMD=\fR\fI"boolean"\fR -If this variable is set, \fBbsetbg\fR will keep a logfile of the last two -successful commands. - -.TP -\fB LOGFILE=\fR\fI"string"\fR -This variable can specify the logfile to be used when $LOG_LAST_CMD is defined. -The default is ~/.bsetbg_last_cmd . - -.TP -As mentioned above, \fBbsetbg\fR will function perfectly for the majority of users without having a configuration file. Power users who want more control over \fBbsetbg\fR's behavior should run \fBbsetbg -g\fR and use the output to create a \fI~/.bsetbgrc\fR which may then be tweaked by hand. - -.SH EXAMPLES -In this example, bsetbg will set the image in centered mode: - - bsetbg -center foo.png - -An example of the \fB-exec\fR argument: - - bsetbg -exec xv -root -quit -rmode 5 -rbg rgb:2/2/2 \\ - -center foo.png - -An example in which bsetbg creates a configuration file using xv and qiv: - - bsetbg -g xv qiv > ~/.bsetbgrc - -An example of the use of the \fB-app\fR argument: - - bsetbg -app qiv "-o rgb:d6/c5/a2 -x" -c foo.png - -.SH AUTHOR -The author of -.B bsetbg -may be reached at \fItmk@lordzork.com\fR. - -.SH SEE ALSO -\fIblackbox\fR(1), \fIbsetroot\fR(1), \fIqiv\fR(1), \fIxli\fR(1), \fIxv\fR(1), \fIdisplay\fR(1), -\fIwmsetbg\fR(1) diff --git a/doc/bsetroot.1 b/doc/bsetroot.1 deleted file mode 100644 index 9445c0d..0000000 --- a/doc/bsetroot.1 +++ /dev/null @@ -1,89 +0,0 @@ -.\" -.\" Man page for Bsetroot -.\" -.\" Copyright (c) 2000 by Wilbert Berendsen -.\" -.TH bsetroot 1 "June 16th, 2000" "0.60.3" -.SH NAME -bsetroot \- Openbox utility to change root window appearance -.SH SYNOPSIS -.BR bsetroot " \-help" -.br -.B bsetroot -.RI "[ \-display " display " ] \-solid " color -.br -.B bsetroot -.RI "[ \-display " display " ] \-mod " "x y" " \-fg " color " \-bg " color -.br -.B bsetroot -.RI "[ \-display " display " ] \-gradient " texture " \-from " color " \-to " color -.SH DESCRIPTION -Bsetroot is a utility that can control the appearance of the root window in -three ways: Either give it a solid color, or write a two color modula pattern -to it, or render a gradient texture, based on two different colors. -.PP -Bsetroot resembles -.IR xsetroot (1) -in this functionality but it supports multiple screen displays, and gradient -textures the same way as Openbox does. -It doesn't handle cursors etc. -Bsetroot is part of the Openbox package. -.SH OPTIONS -Bsetroot operates in three ways, you must choose one of the first 3 options: -.TP -.BI \-solid " color" -Sets the root window to specified color. -.TP -.BI \-mod " x y" -Creates a modula pattern. You must specify -.BR \-bg " and " \-fg -colors. -.TP -.BI \-gradient " texturestring" -Renders the specified texture string to the root window. -For possible texture strings, please refer to -.IR openbox (1). -You must also specify both a -.BR \-from " and a " \-to -color. -.TP -.BI \-display " display" -Tells Bsetroot to connect to the specified display. -.TP -.BI "\-bg, \-background " color -Background color. -Needed for -.B \-mod -patterns. -.TP -.BI "\-fg, \-foreground " color -Foreground color. -Needed for -.B \-mod -patterns. -.TP -.BI \-from " color" -Start color for rendering textures. -Needed for -.B \-gradient -operation mode. -.TP -.BI \-to " color" -Ending color for rendering textures. -Needed for -.B \-gradient -operation mode. -.TP -.B \-help -Prints version info and short help text. -.SH AUTHOR -Bsetroot was written and maintained by Brad Hughes -.nh \" hyphenation off -(blackbox@alug.org) -.hy \" on again -and Jeff Raven -.nh -(jraven@psu.edu). -.hy -.SH SEE ALSO -.IR openbox (1) diff --git a/doc/openbox.1.in b/doc/openbox.1.in deleted file mode 100644 index 674d836..0000000 --- a/doc/openbox.1.in +++ /dev/null @@ -1,859 +0,0 @@ -.\" -.\" Man page for Openbox -.\" -.\" Copyright (c) 2000 by Wilbert Berendsen -.\" -.\" This manual page may be freely distributed and modified. -.\" Parts of the text are taken from website and several README's -.\" by His Great Hughesness himself. Why reinvent wheels? -.\" -.\" Created with NEdit, tested with ``man'' and ``tkman.'' -.\" This manpage uses only standard groff and tmac.an macros. -.\" To all translators who didn't do manpages earlier (like me ;-): -.\" Read the Man-Page-Mini-HOWTO and the LDP manpage ``man 7 man'' -.\" There's all I needed to know about these macros. -.\" -.\" Updated for bb 0.61 at Sat Sep 9 06:56:04 CEST 2000 -.\" -.\" ..define sort of
 macro
-.de EX
-.ne 5
-.if n .sp 1
-.if t .sp .5
-.nf
-.in +.5i
-..
-.de EE
-.fi
-.in -.5i
-.if n .sp 1
-.if t .sp .5
-..
-.TH openbox 1 "April 8th, 2002" "@VERSION@"
-.SH NAME
-openbox \- a window manager for X11
-.SH SYNOPSIS
-.BR openbox " \-help | \-version"
-.br
-.B openbox 
-.RI "[ \-rc" " rcfile " "] [ \-display" " display " ]
-.SH DESCRIPTION
-.\"
-.\" First few Paragraphs taken from FILLMEINNOW
-.\"
-.\"Openbox is yet another addition to the list of window managers for the Open
-.\"Group's X Window System, Version 11 Release 6 and above.
-.\"Openbox is built with C++, sharing no common code with any other window
-.\"manager (even though the graphics implementation is similar to that of Window
-.\"Maker).
-.\".PP
-.\"From the time the first line of code was written, Openbox has evolved around
-.\"one premise, minimalism.
-.\"It's not meant to be Eye Candy, nor the most Featureful, nor the most Adorned
-.\"for modelling the Widely acclaimed NeXT interface.
-.\"It is just meant to be
-.\".BR fast .
-.\".PP
-.\"Openbox provides configurable window decorations, a root menu to launch
-.\"applications, and a toolbar that shows the current workspace name, the focused
-.\"application name, and the current time.
-.\"There is also a workspace menu to add or remove workspaces. The `slit' can be
-.\"used to dock small applications, e.g. most of the bbtools can use the slit.
-.\".PP
-.\"Openbox features a special kind of icon handling: When you minimize a window,
-.\"no icon appears; instead, you can view all minimized applications in the `Icons'
-.\"submenu of the workspace menu.
-.\"Your desktop will never get cluttered with icons. As an alternative to icons,
-.\"shaded windows are provided: A double click on the titlebar of a window will
-.\"shade it (i.e. the window will disappear; only the titlebar stays visible).
-.\".PP
-.\"Openbox uses its own graphics class to render its images on the fly.
-.\"By using style files, you can determine at a great level how your desktop looks.
-.\"Openbox currently uses the Blackbox protocol to communicate with other clients
-.\"such as the pager.  Work is underway to support the new window manager
-.\"specification that both GNOME and KDE use.
-.SH OPTIONS
-Openbox supports the following command line options:
-.TP
-.B \-help
-Display command line options and compiled-in features, then exit.
-.TP
-.B \-version
-Display version info and exit.
-.TP
-.BI \-rc \ rcfile
-Use another rcfile than the default
-.IR  "~/.openbox/rc" .
-.TP
-.BI \-display \ display
-Start Openbox on the specified display.
-Programs started by Openbox will have the
-.B DISPLAY
-environment variable set to this value, too.
-.SH RUNNING OPENBOX
-This program is usually started by the user's startup script, most times called
-.IR ~/.xinitrc .
-To run openbox, modify the script by adding
-.EX 0
-exec openbox
-.EE
-as the last executed command of the script.
-When Openbox terminates, the X session will terminate too.
-.PP
-When started, Openbox will try to find a default menu file in
-.IR @defaultmenu@ .
-You can provide a system-wide menu for your users here.
-.PP
-On exit or restart, Openbox will save user defaults in the file
-.I ~/.openbox/rc
-in the user's home directory.
-Some resources in this file can be edited by hand.
-.SH USING OPENBOX
-Openbox does no keyboard handling by itself; instead, it relies on an external
-program
-.IR epist (1)
-or
-.IR bbkeys (1)
-for this.
-So, in this section, we will discuss all mouse commands.
-.SS Root window (background):
-A right click (button 3) will pop up the root menu.
-With this, you can launch your applications.
-You can also customize this menu for your needs.  See above for its location.
-A middle click (button 2) will pop up the workspace menu.
-You can add or remove a workspace, view
-applications running on all workspace, inspect your iconified applications,
-and jump directly to any workspace or application.
-.PP
-Left clicking (button 1) on an application in the Workspaces menu will bring
-you to that workspace and raise/focus that application;
-middle clicking (button 2) will warp the application to the current workspace.
-.SS Toolbar:
-The toolbar consists of three fields: a workspace name, the name of the
-window that currently has focus, and a clock.
-A left click on the toolbar will bring it to the foreground, a
-middle click will hide it behind other windows (if AlwaysOnTop is not set), and
-the right button will bring up a little menu.
-.PP
-Using this menu, you can enter a name for the current workspace (when finished,
-press Enter).
-Also, you can choose the toolbar's position, whether or not it
-should be always on top (i.e. it cannot be obscured by other windows),
-and whether it should hide itself when the mouse moves away.
-.PP
-Note: In Openbox versions below 0.60.0, a right click on the toolbar
-immediately entered workspace name edit mode.
-.SS Window Titlebar and Borders:
-A left click on any part of the window's border will raise it.
-Dragging then moves the window.
-Dragging the resize grips at the bottom left and bottom right
-corners resizes the window.
-Middle clicking will immediately lower the window.
-Right clicking on the border or titlebar pops up the window menu,
-containing these commands:
-.TP
-.B Send To...
-Send window to another workspace.
-When you select the workspace with the middle button, Openbox will
-send you, along with the application, to the selected workspace.
-.TP
-.B Shade
-Shade window (display titlebar only).
-.TP
-.B Iconify
-Iconify window.
-The `icon' can be found in the `Icons' submenu of the workspace menu.  It will
-*NOT* appear on screen otherwise.
-.TP
-.B Maximize
-(Un)Maximize window.
-When you click the middle button on this item, the
-window will maximize only vertically.
-.TP
-.B Raise
-Raise window.
-.TP
-.B Lower
-Lower window.
-.TP
-.B Stick
-(Un)Stick window.
-A stuck window will always be displayed in the current workspace.
-.TP
-.B Kill Client
-Kill (-SIGKILL) owner of window.  Only use this if the client refuses to close.
-.TP
-.B Close
-Close the application cleanly.
-.PP
-When you double click on the titlebar of a window, it will `shade', so
-that only the titlebar stays visible.
-Another double click will redisplay the window contents.
-.SS Window Buttons:
-The button at the left upper corner of a window is the Minimize button.
-Clicking with any button causes the window to be iconified.
-The rightmost button (with the X) closes the application.
-The other button on the right (if present) maximizes the window in three ways:
-Button 1 causes full screen maximization, button 2 maximizes the window only
-vertically, and button 3 only horizontally.
-.SS Any menu:
-Clicking button 3 in a menu will popdown the menu.
-Clicking button 1 on the titlebar of any (sub)menu and then dragging it somewhere
-else will cause the menu to stay visible, and not disappear when you click on
-a menu item.
-.SS Miscellaneous:
-When you want to drag a window, but cannot see either the bottom handle or its
-titlebar, you can press Alt + button 1 anywhere in the window and then
-drag it around.
-You can also use Alt + button 1 to raise a partially visible window.
-Finally, Alt + button 2 lowers a window, and Alt + button 3 resizes the window.
-.SH MENU FILE
-A default menu file is installed in
-.IR @defaultmenu@ .
-Of course, this system-wide menu can be customized for all users at once.
-But it is also possible to create a personal menu.
-It is a convention to use the directory
-.IR "~/.openbox/"
-in your home directory, and to create a menu file, e.g.
-.I menu
-in this directory, or copy the system-wide menu file to this location.
-Next, we have to tell Openbox to load our menu file instead of the default.
-This is accomplished by adding (or changing) a resource value in the
-.I ~/.openbox/rc
-file, e.g.:
-.EX
-session.menuFile:       ~/.openbox/menu
-.EE
-For this change to take effect, Openbox has to be restarted.
-Be sure that your menu is usable, then choose `Restart' from the default
-Openbox root menu.
-.SS Menu syntax
-The menu syntax is very simple and very effective.
-There are up to three fields in a menu line.
-They are of the form:
-.EX
-[tag] (label or filename) {command or filename}
-.EE
-The supported tags are as follows:
-.TP
-.B [begin] (label for root menu)
-This tells Openbox to start parsing the menu file.
-This tag is required for Openbox to parse your menu file.
-If it cannot find it, the system default menu is used instead.
-.TP
-.B [end]
-This tells Openbox that it is at the end of a menu.
-This can either be a submenu or the main root menu.
-There must be at least one of these tags in your menu to correspond to the
-required [begin] tag.
-.TP
-.B [exec] (label for command) {shell command}
-Inserts a command item into the menu.
-When you select the menu item from the menu, Openbox runs `shell command.'
-.TP
-.B [exit] (label for exit)
-Inserts an item that shuts down and exits Openbox.
-Any running programs are not closed.
-.TP
-.B [include] (filename)
-Parses the file specified by
-.I filename
-and includes it with the current menu. The filename can be the full path to a
- file, or it can begin with
-.IR ~/ ,
-which will be expanded into your home directory (e.g.
-.EX
-[include] (~/.openbox/stylesmenu)
-.EE
-will include
-.I /home/bhughes/.openbox/stylesmenu
-in my menu).
-.TP
-.B [nop] (label - optional)
-Insert a non-operational item into the current menu.
-This can be used to help format the menu into blocks or sections
-if so desired.
-.B [nop]
-does accept a label, but it is not required, and a blank item will be used
-if none is supplied.
-.TP 
-.B [style] (label) {filename}
-This tells Openbox to insert an item that, when selected, reads the style file
-named
-.I filename
-and applies the new textures, colors, and fonts to the current
-running session.
-.TP
-.B [stylesdir] (directory name)
-Reads all filenames (directories are ignored) from the specified directory and
- creates menu items in the current menu for every filename.  Openbox assumes
- that each file is a valid style file.  When selected by the user, Openbox 
-applies the selected style file to the current session.
-The labels that are created in the menu are the filenames of the style files.
-.TP
-.B [stylesmenu] (label) {directory name}
-Creates a submenu entry with
-.I label 
-(that is also the title of the new submenu), and inserts in that submenu all
-filenames in the specified directory, in the same way as the
-.B [stylesdir]
-command does.
-.IP
-.RB Both\  [stylesdir] \ and\  [stylesmenu]
-commands make it possible to install style files without editing your menu file.
-.TP
-.B [submenu] (label) {title for menu - optional}
-This tells Openbox to create and parse a new menu.
-This menu is inserted as a submenu into the parent menu.
-These menus are parsed recursively, so there is no limit to the number of levels
-or nested submenus you can have.
-The title for the new menu is optional; if none is supplied,
-the new menu's title is the same as the item label.
-An
-.B [end]
-tag is required to end the submenu.
-.TP
-.B [reconfig] (label)
-When selected, this item rereads the current style, menu files and rc file,
-and applies any changes.
-This is useful for creating a new style or theme, as you don't have to
-constantly restart Openbox every time you save your style.
-However, Openbox automagically rereads the menu whenever it changes.
-.TP
-.B [restart] (label) {shell command - optional}
-This tells Openbox to restart.
-If `shell command' is supplied, it shuts down and runs the command (which is
-commonly the name of another window manager).
-If the command is omitted, Openbox restarts itself.
-.TP
-.B [config] (label)
-Inserts a Openbox generated submenu item containing numerous configuration
-options concerning window placement, focus style, window moving style, etc.
-.TP
-.B [workspaces] (label)
-This tells Openbox to insert a link to the workspaces menu directly
-into your menu.
-This is handy for those users who can't access the
-workspace menu directly (e.g. if you don't have a 3 button mouse).
-.PP
-Any line that starts with a `#' is considered a comment and ignored by Openbox.
-Also, in the labels/commands/filenames fields,
-you can escape any character like so:
-.EX
-[exec] (\\(my cool\\) \\{XTERM\\}) {xterm -T \\"cool XTERM\\"}
-.EE
-Using `\\\\' inserts a literal back-slash into the label/command/filename field.
-.SS Menu example
-Now let's put together some things.
-Here is a short example of a menu file:
-.PP
-.nf
-# Openbox menu file
-[begin] (Openbox @version@)
-  [exec] (rxvt) {rxvt -ls}
-  [exec] (Mozilla) {mozilla}
-  [exec] (The GIMP) {gimp}
-  [submenu] (Window Manager)
-    [exec] (Edit Menus) {nedit .openbox/Menu}
-    [submenu] (Style) {Which Style?}
-      [stylesdir] (~/.openbox/styles)
-      [stylesmenu] (Openbox Styles) {@pkgdatadir@/styles}
-    [end]
-    [config] (Config Options)
-    [reconfig] (Reconfigure)
-    [restart] (Restart)
-  [end]
-  [exit] (Log Out)
-[end]
-# end of menu file
-.fi
-.SH STYLES
-Openbox enables you to use specialized files that contain
-.IR X (1)
-resources to specify colors, textures, and fonts and therefore
-the overall look of your window borders, menus, and the toolbar.
-.PP
-The default installation of Openbox provides some of these style files.
-Usually they are put in
-.IR @pkgdatadir@/styles .
-You can study or edit these files to grasp how the Openbox style mechanism
-works.
-You can use the
-.BR [style] ", " [stylesdir] " and " [stylesmenu]
-menu commands in your menu file to be able to select and change between styles
-on the fly.
-.PP
-You can also create a directory in your home directory (usually named
-.I ~/.openbox/styles)
-and put your own style files there.
-Of course, you may choose any name for this directory, but many downloadable
-themes will rely on the name
-.I styles
-(following the bb.themes.org naming scheme).
-.PP
-To understand how the style mechanism works, you should have a little knowledge
-of how X resources work.
-.PP
-X resources consist of a key and a value.
-The key is constructed of several smaller keys (sometimes referred to as
-children), delimited by a period (`.').
-Keys may also contain a star (`*') to serve as a wildcard, which means that one
-line of typed text will match several keys.
-This is useful for styles that are based on one or two colors.
-.PP
-Openbox allows you to configure its four main components: the toolbar, the
-slit, the menus, and the window decorations.
-.PP
-The little window that shows the x-y position while dragging windows borrows
-its style from the window's titlebar.
-.PP
-Here are some quick examples:
-.EX
-toolbar.clock.color:	green
-.EE
-This sets the color resource of the toolbar clock to `green'. Another example:
-.EX
-menu*color:	rgb:3/4/5
-.EE
-This sets the color resource of the menu
-.I and all of its `children'
-to `rgb:3/4/5'.
-(For a description of color names, see
-.IR X (1).)
-So this one also applies to
-.IR menu.title.color " and " menu.frame.color .
-And with
-.EX
-*font:	-b&h-lucida-medium-r-normal-*-*-140-*
-.EE
-you set the font resource for all keys to this font name all at once.
-(For information about the fonts installed on your system, you can use a program like
-.IR xfontsel "(1), " gtkfontsel ", or " xlsfonts "(1).)"
-.PP
-Now what makes Openbox just so spectacular is its ability to render textures
-on the fly.
-Texture descriptions are specified in a similar way to the colors shown above
-e.g.:
-.ta \w'toolbar.clock.colorTo:\ 'u
-.EX
-toolbar.clock:	Raised Gradient Diagonal Bevel1
-toolbar.clock.color:	rgb:8/6/4
-toolbar.clock.colorTo:	rgb:4/3/2
-.EE
-Don't worry; we will explain right now!
-A texture description consists of up to five fields, which are as follows:
-.TP
-.B Flat / Raised / Sunken
-gives the component either a flat, raised, or sunken appearance.
-.TP
-.B Gradient / Solid
-tells Openbox to draw either a solid color or a texture with gradient.
-.TP
-.B Horizontal / Vertical / Diagonal / Crossdiagonal / Pipecross / Elliptic / Rectangle / Pyramid
-Select one of these texture types. They only work when
-.B Gradient
-is also specified!
-.TP
-.B Interlaced
-tells Openbox to interlace the texture (darken every other line).
-This option is most commonly used with gradiented textures, but, from Openbox
-version 0.60.3 on, it also works in solid textures.
-.TP
-.B Bevel1 / Bevel2
-tells Openbox which type of bevel to use.
-Bevel1 is the default bevel.
-The shading is placed on the edge of the image.
-Bevel2 is an alternative.
-The shading is placed one pixel in from the edge of the image.
-.PP
-Instead of a texture description, the option
-.B ParentRelative
-is also available, which makes the component appear as a part of its parent, i.e.
-totally transparent.
-.PP
-All gradient textures are composed of two color values: the
-.IR color " and " colorTo " resources."
-color represents the initial color, colorTo represents the final color of the
-gradient.
-When
-.B Interlaced 
-is used in
-.B Solid
-mode, the
-.I colorTo
-resource is used to specify the interlacing color.
-.PP
-Well, here is the complete component list; also, all components together with
-which kind of value they can contain.
-Comments are preceded with an exclamation sign (!), which is also used for
-comments in Openbox style c.q. X resource files.
-.PP
-.ta \w'window.button.unfocus.picColor: \ 'u 
-.nf
-.\"
-.\" The comments also to be translated!
-.\"
-! The toolbar itself.
-toolbar:	Texture
-toolbar.color:	Color
-toolbar.colorTo:	Color
-
-! The buttons on the toolbar.
-toolbar.button:	Texture or \fIParentRelative\fR
-toolbar.button.color:	Color
-toolbar.button.colorTo:	Color
-
-! Color of the button arrows.
-toolbar.button.picColor:	Color
-
-! Buttons in pressed state.
-toolbar.button.pressed:	Texture \fI(e.g. Sunken)\fR or \fIParentRelative\fR
-toolbar.button.pressed.color:	Color
-toolbar.button.pressed.colorTo:	Color
-
-! Color of pressed button arrows.
-toolbar.button.pressed.picColor:	Color
-
-! The toolbar workspace label.
-toolbar.label:	Texture or \fIParentRelative\fR
-toolbar.label.color:	Color
-toolbar.label.colorTo:	Color
-toolbar.label.textColor:	Color
-
-! The toolbar window label.
-toolbar.windowLabel:	Texture or \fIParentRelative\fR
-toolbar.windowLabel.color:	Color
-toolbar.windowLabel.colorTo:	Color
-toolbar.windowLabel.textColor:	Color
-
-! The toolbar clock.
-toolbar.clock:	Texture or \fIParentRelative\fR
-toolbar.clock.color:	Color
-toolbar.clock.colorTo:	Color
-toolbar.clock.textColor:	Color
-
-! How the toolbar's text should be justified.
-toolbar.justify:	\fIcenter\fR, \fIleft\fR, or \fIright\fR
-
-.ta \w'toolbar.font:\ 'u 
-! Font to be used for all toolbar components.
-toolbar.font:	Font \fI(e.g. -*-helvetica-medium-r-normal-*-*-100-*)\fR
-
-.ta \w'window.button.unfocus.picColor: \ 'u 
-! The menu titlebar.
-menu.title:	Texture
-menu.title.color:	Color
-menu.title.colorTo:	Color
-menu.title.textColor:	Color
-menu.title.font:	Font
-menu.title.justify:	\fIcenter\fR, \fIleft\fR, or \fIright\fR
-
-! The menu frame.
-menu.frame:	Texture
-menu.frame.color:	Color
-menu.frame.colorTo:	Color
-menu.frame.textColor:	Color
-menu.frame.disableColor:	Color
-menu.frame.font:	Font
-menu.frame.justify:	\fIcenter\fR, \fIleft\fR, or \fIright\fR
-
-! Bullets for submenu items.
-menu.bullet:	\fIempty\fR, \fItriangle\fR, \fIsquare\fR, or \fIdiamond\fR
-menu.bullet.position:	\fIright\fR or \fIleft\fR
-
-! The highlighted menu item.
-menu.hilite:	Texture (e.g. \fIRaised\fR)
-menu.hilite.color:	Color
-menu.hilite.colorTo:	Color
-menu.hilite.textColor:	Color
-
-! A focused window.
-window.title.focus:	Texture
-window.title.focus.color:	Color
-window.title.focus.colorTo:	Color
-
-! An unfocused window.
-window.title.unfocus:	Texture
-window.title.unfocus.color:	Color
-window.title.unfocus.colorTo:	Color
-
-! Window label.
-window.label.focus:	Texture or \fIParentRelative\fR
-window.label.focus.color:	Color
-window.label.focus.colorTo:	Color
-window.label.focus.textColor:	Color
-
-window.label.unfocus:	Texture or \fIParentRelative\fR
-window.label.unfocus.color:	Color
-window.label.unfocus.colorTo:	Color
-window.label.unfocus.textColor:	Color
-
-! Handlebar.
-window.handle.focus:	Texture
-window.handle.focus.color:	Color
-window.handle.focus.colorTo:	Color
-
-window.handle.unfocus:	Texture
-window.handle.unfocus.color:	Color
-window.handle.unfocus.colorTo:	Color
-
-! Resize grips.
-window.grip.focus:	Texture
-window.grip.focus.color:	Color
-window.grip.focus.colorTo:	Color
-
-window.grip.unfocus:	Texture
-window.grip.unfocus.color:	Color
-window.grip.unfocus.colorTo:	Color
-
-! Window buttons.
-window.button.focus:	Texture or \fIParentRelative\fR
-window.button.focus.color:	Color
-window.button.focus.colorTo:	Color
-window.button.focus.picColor:	Color
-
-window.button.unfocus:	Texture or \fIParentRelative\fR
-window.button.unfocus.color:	Color
-window.button.unfocus.colorTo:	Color
-window.button.unfocus.picColor:	Color
-
-window.button.pressed:	Texture (e.g. \fISunken\fR)
-window.button.pressed.color:	Color
-window.button.pressed.colorTo:	Color
-
-.ta \w'window.button.pressed.unfocus.colorTo:\ 'u 
-! If either the focused, or unfocused pressed button isn't
-! defined, it will use the normal pressed: resource
-window.button.pressed.focus:	Texture (eg. \fISunken\fR)
-window.button.pressed.focus.color:	Color
-window.button.pressed.focus.colorTo:	Color
-window.button.pressed.unfocus:	Texture (eg. \fISunken\fR)
-window.button.pressed.unfocus.color:	Color
-window.button.pressed.unfocus.colorTo:	Color
-
-.ta \w'window.button.unfocus.picColor:\ 'u 
-! Frame around window.
-window.frame.focusColor:	Color
-window.frame.unfocusColor:	Color
-
-! Font and justification for window labels.
-window.font:	Font
-window.justify:	\fIcenter\fR, \fIleft\fR, or \fIright\fR
-
-! Miscellaneous resources.
-
-! A border can be drawn around all components.
-borderWidth:	a number of pixels, e.g. \fI1\fR
-borderColor:	Color
-
-bevelWidth:	a number of pixels > 0
-handleWidth:	a number of pixels > 0
-
-! Width of the window frame (from version 0.61 on).
-! When not specified, frameWidth defaults to the value of bevelWidth.
-frameWidth:	a number of pixels >= 0
-
-! This command is executed whenever this style is selected.
-! Typically it sets the root window to a nice picture.
-rootCommand: Shell command, e.g. \fIbsetroot -mod 4 4 -fg rgb:	5/6/6 -bg grey20\fR
-
-! Some of the bbtools read these old 0.51 resources.
-menuFont:	Font
-titleFont:	Font
-.fi
-.PP
-Now, this seems a long list, but, remember, when you create your own style, you
-can easily set lots of keys with a single command, e.g.
-.EX
-.ta \w'*unfocus.textColor:\ 'u
-*color:	slategrey
-*colorTo:	darkslategrey
-*unfocus.color:	darkslategrey
-*unfocus.colorTo:	black
-*textColor:	white
-*unfocus.textColor:	lightgrey
-*font:	lucidasans-10
-.EE
-This sets already nice defaults for many components.
-.PP
-Openbox also lets you pick what the small graphics in the buttons and in the manu look like. It uses the X Bitmap file format (.xbm extension). The style resources are as follows:
-.EX
-! Close, maximize, sticky and iconify
-! buttons on a window.
-window.button.close.mask:       xbm file
-window.button.max.mask:         xbm file
-window.button.stick.mask:       xbm file
-window.button.icon.mask:        xbm file
-
-! The icon for submenus in a menu.
-menu.arrow.mask:                xbm file
-
-! The icon for a selected option or workspace
-! in the menus.
-menu.selected.mask:             xbm file
-
-! The left and right buttons in the toolbar
-toolbar.button.left.mask:       xbm file
-toolbar.button.right.mask:      xbm file
-.EE
-If the xbm file value is the path to a .xbm image. If the value is absolute, it uses that file. If the file is just a file name, e.g. `close.xbm', it uses ~/.openbox/buttons/close.xbm.
-.SH THE SLIT
-The slit is a special Openbox window frame that can contain dockable
-applications, e.g. the `bbtools' or WindowMaker's dockapps.
-When applications are run in the slit, they have no window borders of their own;
-instead, they are framed in the slit, and they are always visible in the current
-workspace.
-You can click button 3 on the edge of the slit window to get a menu to
-determine its position, whether its contained applications should be grouped
-horizontally or vertically and whether it should hide itself when the
-mouse moves away.
-.PP
-Most dockable applications use the
-.B -w
-option to run in the slit.
-For example, you could put in your
-.IR ~/.xinitrc :
-.EX
-bbmail -w &
-bbpager -w &
-exec openbox
-.EE
-Of course, to use the slit, you must have slit support compiled in (this is
-the default).
-.SH RESOURCE FILE
-Usually the
-.I ~/.openbox/rc
-resource file is created and maintained by Openbox itself.
-All options from the
-.B [config]
-menu (from 0.60.x on), the last selected style file, your workspace names and
-so on are saved automatically in this file.
-However, there are some resources in it you might want to edit yourself:
-.TP
-.B session.menuFile:
-This tells Openbox where to look for its menu file.
-.TP
-.B session.screen0.toolbar.widthPercent:
-This determines the amount (in %) of space the toolbar will take.
-Default value is:
-.IR 66 .
-.TP
-.B session.screen0.strftimeFormat:
-This adjusts the way the current time is displayed in the toolbar.
-The
-.IR strftime (3)
-format is used.
-Default value is:
-.IR "%I:%M %p" .
-.TP
-.B session.autoRaiseDelay:
-This adjusts the delay (in ms) before focused windows will raise when using the
-Auto Raise option.
-Default value is:
-.IR 250 .
-.TP
-.B session.doubleClickInterval:
-This adjusts the delay (in ms) between mouse clicks for Openbox to consider a double click.
-Default value is:
-.IR 250 .
-.TP
-.B session.screen0.edgeSnapThreshold:
-When moving a window across your screen, Openbox is able to have it `snap' to
-the edges of the screen for easy placement.
-This variable tells Openbox the distance (in pixels) at which the window will
-jump to the edge.
-Default value is:
-.IR 0 .
-.TP
-.B session.cacheMax:
-This tells Openbox how much memory (in Kb) it may use to store cached pixmaps on
-the X server.
-If your machine runs short of memory, you may lower this value.
-Default value is:
-.IR 200 .
-.TP
-.B session.cacheLife:
-This tells Openbox how long (in minutes) unused pixmaps may stay in the X
-server's memory.
-Default value is:
-.IR 5 .
-.TP
-.B session.titleBarLayout:
-The order of the elements of window titlebars. This is a string built
-of the following characters:
-.IP
-.nf I: Iconify button
-L: Label (window title)
-M: Maximize button
-S: Sticky button
-C: Close button 
-.fi
-.IP
-The order in which these character appear in the string is the order they will appear in window titlebars. You can also omit an item to not have it appear in the titlebar at all, except for the label which will always appear.
-Default value is:
-.IR ILMC
-.TP
-.B session.colorsPerChannel:
-This tells Openbox how many colors to take from the X server on pseudocolor
-displays.  A channel would be red, green, or blue.
-Openbox will allocate this variable ^ 3 colors and make them always available.
-This value must be between 2 and 6.
-When you run Openbox on an 8-bit display, you must set this resource to 4.
-Default value is:
-.IR 4.
-.PP
-When running Openbox in a multiple desktop environment, the
-.B screen0
-key can also be
-.B screen1, 2
-etc. for any appropriate desktop.
-.SH ENVIRONMENT
-.TP
-.B HOME
-Openbox uses
-.RB $ HOME
-to find its
-.I .openbox/rc
-file, and to resolve style file and directory names.
-.TP
-.B DISPLAY
-When no other display was given on the command line, Openbox will start on the
-display specified by this variable.
-.SH AUTHOR and CREDITS
-All of the code was initially written and maintained as Blackbox by Brad Hughes
-.nh \" hyphenation off
-(blackbox@alug.org)
-.hy \" on again
-and then Jeff Raven
-.nh
-(jraven@psu.edu),
-.hy
-and then Sean 'Shaleh' Perry
-.nh
-(shaleh@debian.org)
-.hy
-.hy
-and is now maintained as Openbox by Ben Jansens
-.nh
-(ben@orodu.net)
-.hy
-with contributions and patches merged from
-many individuals around the world.
-.PP
-The official Openbox website:
-.nh
-.B http://openbox.sunsite.dk/
-.hy
-.br
-Many themes and other contributions:
-.nh
-.B http://themes.freshmeat.net/
-.hy
-.PP
-This manpage was put together by Wilbert Berendsen
-.nh
-(wbsoft@xs4all.nl).
-.hy
-Numerous other languages will be available.
-.SH SEE ALSO
-.IR bsetroot (1), \ bbkeys (1), \ xftlsfonts (1), \ epist (1)
diff --git a/doc/python/client.txt b/doc/python/client.txt
new file mode 100644
index 0000000..fbb5700
--- /dev/null
+++ b/doc/python/client.txt
@@ -0,0 +1,553 @@
+ob.Client
+
+----
+
+This document describes the 'ob.Client' class, exposed by Openbox to its python
+scripts. The 'Client' class cannot be instantiated, and can only be
+retrieved by catching events from Openbox, or from the ob.Openbox.clientList()
+method.
+
+A Client instance is associated with a single client window which Openbox is
+managing. When the client window is closed/destroyed/released, the Client
+instance will be marked as invalid (see valid()). Any methods of Client, with
+the exception of valid(), will raise a ReferenceError exception if they are
+called on a non-valid Client instance. For this reason, it is not encouraged to
+keep references to Client instances between events, unless you are tracking the
+hooks.closed hook or check valid() before attempting to reuse a Client
+instance.
+
+----
+
+Methods
+
+----
+
+valid()
+
+Returns if the Client instance is still valid. Client instances are marked as
+invalid when the Client they are associated is closed/destroyed/released.
+
+	Returns: True or False for if the Client instance is valid.
+
+----
+
+title()
+
+Returns the client's title.
+
+	Returns: A string containing the client's title.
+
+----
+
+setTitle(title)
+
+Change the client's title to the given string. This change will be overwritten
+if/when the client changes its title.
+
+	title: A string containing the new title for the client.
+
+----
+
+iconTitle()
+
+Returns's the client's icon title. The icon title is the title to be displayed
+when the client is iconified.
+
+	Returns: A string containing the client's icon title.
+
+----
+
+setIconTitle(title)
+
+Change the client's icon title to the given string. This change will be
+overwritten if/when the client changes its icon title.
+
+	title: A string containing the new icon title for the client.
+
+----
+
+desktop()
+
+Returns the desktop on which the client is visible. This value will always be
+in the range [0, ob.Openbox.numDesktops()), unless it is 0xffffffff. A value of
+0xffffffff indicates the client is visible on all desktops.
+
+	Returns: An integer containing the client's desktop,
+
+----
+
+setDesktop(desktop)
+
+Moves the client to the specified desktop. The desktop must be in the range
+[0, ob.Openbox.numDesktops()), unless it is 0xffffffff. A value of 0xffffffff
+indicates the client is visible on all desktops.
+
+	desktop: The desktop on which to place the client.
+
+----
+
+resName()
+
+Returns the resouce name of the client. The resource name is meant to provide
+an instance name for the client.
+
+	Returns: A string containing the client's resource name.
+
+----
+resClass()
+
+Returns the resouce class of the client. The resource class is meant to provide
+the genereal class of the application. e.g. 'Emacs', 'Xterm', 'XClock',
+'XLoad', and so on.
+
+	Returns: A string containing the client's resource class.
+
+----
+
+role()
+
+Returns the client's role. The role is meant to distinguish between different
+windows of an application. Each window should have a unique role.
+
+	Returns: A string containing the client window's role.
+
+----
+
+transient()
+
+Returns True or False describing if the client is a transient window. Transient
+windows are 'temporary' windows, such as preference dialogs, and usually have
+a parent window, which can be found from transientFor().
+
+	Returns: True or False for if the client is a transient window.
+
+----
+
+transientFor()
+
+Returns the client for which this client is a transient. See transient() for
+a description of transience.
+
+	Returns: A Client containing the client which this client is transient
+		 for. None if such a client does not exist.
+
+----
+
+transients()
+
+Returns a tuple containing all the Clients which are transients of this window.
+See transient() for a description of transience.
+
+	Returns: A tuple containing Clients which are transients for this
+		 client. The tuple may be empty.
+
+----
+
+type()
+
+Returns the logical type of the window. This is one of the ClientType
+constants. See also normal().
+
+	Returns: The type of the window.
+
+----
+
+normal()
+
+Returns True or False for if the client is a 'normal' window. Normal windows
+make up most applications. Non-normal windows have special rules applied to
+them at times such as for focus handling. An example of a non-normal window
+is 'gnome-panel'. This value is determined from the client's type(), but does
+not imply that the window is ClientType.Normal. Rather this is a more generic
+definition of 'normal' windows, and includes dialogs and others.
+
+	Returns: True or False declaring the client as 'normal' or not.
+
+----
+
+area()
+
+Returns the area of the screen which the client occupies. It may be important
+to note that this is the position and size of the client *with* its
+decorations. If you want the underlying position and size of the client
+itself, you should use clientArea(). See also logicalSize().
+
+	Returns: A tuple containing the area of the client and decorations on
+		 the screen. The tuple is in the format (x, y, width, height).
+
+----
+
+setArea(area, [final])
+
+Sets the client's area, moving and resizing it as specified (or as close as can
+be accomidated).
+
+	area: The new area for the client, in a tuple. The tuple should be of
+	      the format (x, y, width, height).
+
+	final: Optional True or False for if this is a final change. This
+	       should be set to False if the change is only part of a
+	       move/resize. Otherwise, it should be set to True. If it is not
+	       specified, it will default to True.
+
+----
+
+clientArea()
+
+Returns the area of the screen which the client considers itself to be
+occupying. This value is not what you see and should not be used for most
+things (it should, for example, be used for persisting a client's dimentions
+across sessions). See also area().
+
+	Returns: A tuple containing the area the client considers itself to be
+		 occupying. The tuple is in the format (x, y, width, height).
+
+----
+
+setClientArea(area)
+
+Sets the area of the screen which the client considers itself to be occupying.
+This is not the on-screen visible position and size, and should be used with
+care. You probably want to use setArea() to adjust the client. This should be
+used if you want the client window (inside the decorations) to be a specific
+size. Adjusting the client's position with this function is probably always a
+bad idea, because of window gravity.
+
+	area: The new area for the client. in a tuple. The tuple should be of
+	      the format (x, y, width, height).
+
+----
+
+frameSize()
+
+Returns the size of the decorations around the client window.
+
+	Returns: A tuple containing the size of the decorations on each side
+		 of the client window. The tuple has the format
+		 (left, top, right, bottom).
+
+----
+
+strut()
+
+Returns the application's specified strut. The strut is the amount of space
+that should be reserved for the application on each side of the screen.
+
+
+	Returns: A tuple containing the application's strut. The tuple has the
+		 format (left, top, right, bottom).
+
+----
+
+logicalSize()
+
+Returns the client's logical size. The logical size is the client's size in
+more user friendly terms. For many apps this is simply the size of the client
+in pixels, however for some apps this will differ (e.g. terminal emulators).
+This value should be used when displaying an applications size to the user.
+
+	Returns: A tuple containing the client's logical size. The tuple has
+		 the format (width, height).
+
+----
+
+canFocus()
+
+Returns True or False for if the client can be focused.
+
+	Returns: True or False for if the client can recieve input focus.
+
+----
+
+focus([focus])
+
+Focuses (or unfocuses) the client window. Windows which return False for
+canFocus() or visible() cannot be focused. When this function returns, the
+client's focused() state will not be changed yet. This only sends the request
+through the X server. You should wait for the hooks.focused hook to fire, and
+not assume the client has been focused.
+
+	focus: Optional. If True, the window will be focused. If False, and
+	       focused() is True, it will lose its focus. If the argument is
+	       not passed, it will default to True.
+
+	Returns: True if the client could be focused, and focus has been sent
+		 to the window. False if the client could not be focused.
+
+----
+
+focused()
+
+Returns True or False for if the client has the input focus.
+
+	Returns: True or False for if the client has the input focus.
+
+----
+
+visible()
+
+Returns True or False for if the client is visible. A client is not visible if
+it is iconic() or if its desktop() is not visible.
+
+	Returns: True or False for if the client is visible.
+
+----
+
+setVisible(show)
+
+Shows or hides the client. This has no effect if its current visible() state
+is requested.
+
+	show: True or False specifying if the client should be hidden or shown.
+
+----
+
+modal()
+
+Returns True or False for if the client is a modal window. Modal windows
+indicate that they must be dealt with before the program can continue. When
+a modal window is a transient(), its transientFor() client cannot be focused or
+raised above it.
+
+	Returns: True or False for if the client is a modal window.
+
+----
+
+setModal(modal)
+
+Make the client window modal or non-modal.
+
+	mdal: True or False to make the client modal or not respectively.
+
+----
+
+shaded()
+
+Returns True or False for if the client is shaded. Shaded windows have only
+their titlebar decorations showing.
+
+----
+
+setShaded(shade)
+
+Shade or unshade the client. Shaded windows have only their titlebar
+decorations showing. Windows which do not have a titlebar cannot be shaded.
+
+	shade: True or False to make the client shaded or not respectively.
+
+----
+
+iconic()
+
+Returns True or False for if the window is iconified. Iconified windows are not
+visible on any desktops.
+
+	Returns: True or False for if the client is iconified.
+
+----
+
+setIconic(iconify, [current])
+
+Iconifies or restores the client window. Iconified windows are not visible on
+any desktops. Iconified windows can be restored to the currently visible
+desktop or to their original (native) desktop.
+
+	iconify: True or False to iconify or deiconify the client repectively.
+
+	current: Optional True or False to specify if the client should be
+		 restored to the currently visible desktop or to the desktop
+		 from which it was iconified. This does not apply to windows
+		 who's desktop() is 0xffffffff. If this is not specified, it
+		 defaults to True (the current desktop).
+
+----
+
+maximizedHorz()
+
+Returns whether the client is maximized in the horizontal direction.
+
+	Returns: True if the client is maximized horizontally; False if it is
+		 not.
+
+----
+
+setMaximizedHorz(max)
+
+Maximizes or restores a client horizontally.
+
+	max: True or False for if the client should be maximized or
+	     unmaximized in the horizontal direction.
+
+----
+
+maximizedVert()
+
+Returns whether the client is maximized in the vertical direction.
+
+	Returns: True if the client is maximized vertically; False if it is
+		 not.
+
+----
+
+setMaximizedVert(max)
+
+Maximizes or restores a client vertically.
+
+	max: True or False for if the client should be maximized or
+	     unmaximized in the vertical direction.
+
+----
+
+maximized()
+
+Returns whether the client is maximized in the horizontal or vertical
+direction.
+
+	Returns: True if the client is maximized horizontally or vertically;
+		 False if it is not.
+
+----
+
+setMaximized(max)
+
+Maximizes or restores a client vertically and horzontally.
+
+	max: True or False for if the client should be maximized or
+	     unmaximized in the vertical and horizontal direction.
+-
+---
+
+fullscreen()
+
+Returns if the client is in fullscreen mode. Fullscreen windows are kept above
+all other windows and are stretched to fill the entire physical display.
+
+	Returns: True or False for if the client is fullscreen.
+
+----
+
+setFullscreen(full)
+
+Set a client into or out of fullscreen mode. Fullscreen windows are kept above
+all other windows and are stretched to fill the entire physical display.
+
+	full: True or False to set the client into or out of fullscreen mode
+	      respectively.
+
+----
+
+stacking()
+
+Returns if the client will be stacked above/below other clients in the same
+layer.
+
+	Returns: An integer > 0 if the client will be stacked above other
+		 clients in its layer. An integer < 0 if it will be stacked
+		 below other clients. 0 will be returned if the client is
+		 stacked as normal amongst other clients in its layer.
+
+----
+
+setStacking(stack)
+
+Set how the client will be stacked according to other clients in its layer.
+
+	stack: An integer > 0 if the client should be stacked above other
+	       clients in its layer. An integer < 0 if it should be stacked
+	       below other clients. Exactly 0 if the client should be stacked
+	       as normal amongst other clients in its layer.
+
+----
+
+raiseWindow()
+
+Raises the window to the top of its stacking layer.
+
+----
+
+lowerWindow()
+
+Lowers the window to the bottom of its stacking layer.
+
+----
+
+skipPager()
+
+Returns if the client has requested to be skipped (not displayed) by pagers.
+
+	Returns: True or False for if the client has requested to be skiped by
+		 pagers.
+
+----
+
+setSkipPager(skip)
+
+Set whether the client should be skipped (not displayed) by pagers.
+
+	skip: True or False to make the client be skipped or not skipped by
+	      pagers.
+
+----
+
+skipTaskbar()
+
+Returns if the client has requested to be skipped (not displayed) by taskbars.
+
+	Returns: True or False for if the client has requested to be skiped by
+		 taskbars.
+
+----
+
+setSkipTaskbar(skip)
+
+Set whether the client should be skipped (not displayed) by taskbars.
+
+	skip: True or False to make the client be skipped or not skipped by
+	      taskbars.
+
+----
+
+disableDecorations(titlebar, handle, border)
+
+Choose which decorations to disable on the client. Note that decorations can
+only be disabled, and decorations that would normally not be shown cannot be
+added. These values may have slightly different meanings in different theme
+engines.
+
+	titlebar: True to disable, or False to enable (if possible) the
+		  client's titlebar.
+
+	handle: True to disable, or False to enable (if possible) the
+		client's handle.
+
+	border: True to disable, or False to enable (if possible) the
+		client's border.
+
+----
+
+close()
+
+Requests the client to close its window.
+
+----
+
+window()
+
+Returns the client's window id. This is the id by which the X server knows the
+client.
+
+	Returns: An integer containing the client's window id.
+
+----
+
+ob.ClientType
+
+ClientType.Normal: a normal application window.
+ClientType.Dialog: a dialog window (usually a transient()).
+ClientType.Desktop: a desktop (bottom-most) window.
+ClientType.Dock: a dock or panel window.
+ClientType.Toolbar: a toolbar "torn off" from an application.
+ClientType.Menu: a pinnable menu "torn off" from an application.
+ClientType.Utility: a small persistent utility window such as a
+		    palette or toolbox.
+ClientType.Splash: a splash screen window.
diff --git a/doc/python/config.txt b/doc/python/config.txt
new file mode 100644
index 0000000..0362fde
--- /dev/null
+++ b/doc/python/config.txt
@@ -0,0 +1,13 @@
+config.Config
+
+----
+
+This document describes the 'Config' class, exposed by Openbox's 'config'
+module to its python scripts.
+
+----
+
+Methods
+
+----
+
diff --git a/doc/python/helpers.txt b/doc/python/helpers.txt
new file mode 100644
index 0000000..6a11e40
--- /dev/null
+++ b/doc/python/helpers.txt
@@ -0,0 +1,25 @@
+helpers
+
+----
+
+This document describes the 'helpers' module. The 'helpers' module provides
+methods to make writing python scripts easier.
+
+----
+
+Methods
+
+----
+
+execute(path)
+
+Forks and executes a process.
+
+	path: The executable to execute. The $PATH is searched so the full
+	      path to the executable is not generally needed.
+
+Example:
+	execute("xterm")
+
+----
+
diff --git a/doc/python/hooks.txt b/doc/python/hooks.txt
new file mode 100644
index 0000000..3bfc270
--- /dev/null
+++ b/doc/python/hooks.txt
@@ -0,0 +1,255 @@
+*******************************************************************************
+*******************************************************************************
+**    CAUTION: changing any value in the hook for that value changing is     **
+**    dangerous, as this can easily lead to an infinate loop of updating!    **
+*******************************************************************************
+*******************************************************************************
+
+hooks
+
+----
+
+This document describes the 'Hook' class, exposed by Openbox's 'hooks' module
+to its python scripts, and the standard hooks exposed in the 'hooks' module.
+
+----
+
+hooks.Hook
+
+----
+
+Methods
+
+----
+
+__call__(args)
+
+Fires the hook, passing the given arguments on to all functions registered with
+the hook. Functions are called in the order in which they were added. If any
+function defined as a hook returns any value other than None, the hook will
+not fire any more functions.
+
+	args: Any number of function arguments, which are passed on to the
+	      hook's registered functions.
+
+----
+
+append(func)
+
+Appends a function to the hook.
+
+	func: The function to add to the hook.
+
+----
+
+remove(func)
+
+Removes the function from the hook.
+
+	func: The function to remove from the hook.
+
+----
+
+hooks
+
+----
+
+Hooks
+
+The following standard hooks are defined by Openbox. After each name comes the
+expected format of a function added to the hook. Adding a function of another
+format will lead to an exception when the hook is fired.
+
+----
+
+startup - function()
+
+When Openbox is starting, just before it begins managing clients.
+
+----
+
+shutdown - function()
+
+When Openbox is shutting down, after releasing all clients.
+
+----
+
+visibledesktop - function(new, old)
+
+When the current desktop changes.
+
+	new: An integer containing the new desktop.
+
+	old: An integer containing the old desktop.
+
+----
+
+numdesktops - function(desktops)
+
+When the number of desktops changes.
+
+	desktops: An integer containing the number of available desktops.
+
+----
+
+desktopnames - function()
+
+When the desktops' names have been changed.
+
+----
+
+showdesktop - function(showing)
+
+When Openbox enters or leaves 'showing the desktop' mode. Called after the
+desktop is shown/hidden.
+
+	showing: True if entering 'showing the desktop' mode, False if leaving.
+
+----
+
+screenconfiguration - function(size)
+
+When the screen's size (ob.Openbox.physicalSize()) has changed.
+
+	size: The new size of the screen, as returned by
+	      ob.Openbox.physicalSize().
+
+----
+
+screenarea - function()
+
+When the screen's area (ob.Openbox.screenArea()) has changed.
+
+----
+
+managed - function(client)
+
+When a client is managed.
+
+	client: The Client being managed.
+
+----
+
+closed - function(client)
+
+When a client is being closed/destroyed/released.
+
+	client: The Client which has been closed.
+
+----
+
+bell - function(client)
+
+When the system bell is fired.
+
+	client: The Client the bell is associated with, or None. Clients can
+		only be associated with bells through the XKB extension.
+
+----
+
+urgent - function(client)
+
+When a window enters/leaves urgent status.
+
+	client: The client which has become/stopped being urgent.
+
+----
+
+pointerenter - function(client)
+
+When the pointer moves above a client.
+
+	client: The Client that the pointer has move above.
+
+----
+
+pointerleave - function(client)
+
+When the pointer moves off of a client.
+
+	client: The Client that the pointer has moved off of.
+
+----
+
+focused - function(client)
+
+When focus changes.
+
+	client: The Client which has recieved input focus, or None if no client
+		is focused.
+
+----
+
+requestactivate - function(client)
+
+When a request is made to activate a client.
+
+	client: The Client who has been requested to be made active.
+
+----
+
+title - function(client)
+
+When a client's title or icon title changes.
+
+	client: The Client whose title changed.
+
+----
+
+desktop - function(client, new, old)
+
+When a client's desktop changes.
+
+	client: The Client that changed desktops.
+
+	new: An integer containing the client's new desktop.
+
+	old: An integer containing the client's old desktop.
+
+----
+
+iconic - function(client)
+
+When a client's iconic status changes.
+
+	client: The Client that has been (un)iconified.
+
+----
+
+shaded - function(client)
+
+When a client's shaded status changes.
+
+	client: The Client that has been (un)shaded.
+
+----
+
+maximized - function(client)
+
+When a client's maximized status changes.
+
+	client: The Client that has been (un)maximized.
+
+----
+
+fullscreen - function(client)
+
+When a client's fullscreen status changes.
+
+	client: The Client which as been (un)fullscreened.
+
+----
+
+visible - function(client)
+
+When a client becomes visible or hidden, but not for desktop changes.
+
+	client: The Client which has been shown/hidden.
+
+----
+
+configuration - function(client)
+
+When a client's configuration (area/position/size) changes.
+
+	client: The Client which has moved or resized.
diff --git a/doc/python/keyboard.txt b/doc/python/keyboard.txt
new file mode 100644
index 0000000..0cb2caa
--- /dev/null
+++ b/doc/python/keyboard.txt
@@ -0,0 +1,59 @@
+input.Keyboard
+
+----
+
+This document describes the 'Keyboard' class, exposed by Openbox's 'input'
+module to its python scripts.
+
+All keyboard events which will be generated because of the Keyboard class can
+be caught from the hooks.keyboard hook.
+
+----
+
+Methods
+
+----
+
+bind(keychain, func)
+
+Binds a key-chain to a function. The keychain is a tuple of strings which
+define a chain of key presses. Each member of the tuple has the format
+[Modifier-]...[Key]. Modifiers can be 'mod1', 'mod2', 'mod3', 'mod4', 'mod5',
+'control', and 'shift'. The keys on your keyboard that are bound to each of
+these modifiers can be found by running 'xmodmap'. The Key can be any valid
+key definition. Key definitions can be found by running 'xev', pressing the
+key while its window is focused, and watching its output. Here are some
+examples of valid keychains: ('a'), ('F7'), ('control-a', 'd'),
+('control-mod1-x', 'control-mod4-g'), ('F1', 'space').
+The func must have a definition similar to 'def func(keydata, client)'. The
+arguments passed to the function are a KeyboardData object and a Client object.
+A keychain cannot be bound to more than one function.
+
+	keychain: A tuple containing strings defining a chain of key presses.
+
+	func: A function to bind to the keychain.
+
+----
+
+clearBinds()
+
+Removes all bindings that were previously made by bind().
+
+----
+
+grab(func)
+
+Grabs the entire keyboard, causing all possible keyboard events to be passed
+to the given function. CAUTION: Be sure when you grab() that you also have an
+ungrab() that will execute, or you will not be able to type until you restart
+Openbox. The func must have a definition similar to 'def func(keydata)'. The
+argument passed to the function is a KeyboardData object. The keyboard cannot
+be grabbed if it is already grabbed.
+
+	func: A function to receive all the grabbed keyboard events.
+
+----
+
+ungrab()
+
+Ungrabs the keyboard. The keyboard cannot be ungrabbed if it is not grabbed.
diff --git a/doc/python/keyboarddata.txt b/doc/python/keyboarddata.txt
new file mode 100644
index 0000000..4be5588
--- /dev/null
+++ b/doc/python/keyboarddata.txt
@@ -0,0 +1,35 @@
+KeyboardData
+
+----
+
+This document describes the 'KeyboardData' class, passed to functions bound to
+keyboard events.
+
+----
+
+Attributes
+
+----
+
+keychain
+
+The keychain (in the format that input.Keyboard.bind() expects them) that
+was pressed.
+
+----
+
+state
+
+An integer bitmask of the modifiers pressed down when the key was pressed.
+
+----
+
+keycode
+
+An integer containing the keycode for the key that was pressed.
+
+----
+
+press
+
+True or False for if the event was a key press or a key release
diff --git a/doc/python/openbox.txt b/doc/python/openbox.txt
new file mode 100644
index 0000000..b18c8d3
--- /dev/null
+++ b/doc/python/openbox.txt
@@ -0,0 +1,208 @@
+ob.Openbox
+
+----
+
+This document describes the 'Openbox' class, exposed by Openbox's 'ob' module
+to its python scripts.
+
+----
+
+Methods
+
+----
+
+shutdown()
+
+Causes Openbox to shutdown and exit.
+
+
+----
+
+restart([path])
+
+Causes Openbox to shutdown and restart. If path is specified, Openbox will
+shutdown and attempt to run the specified executable instead of restarting
+itself. If that fails, however, it will restart itself.
+
+	path: Optional path to replacement executable.
+
+----
+
+state()
+
+Returns Openbox's current state, this will be one of the State constants.
+
+	Returns: One of the State values representing the internal state
+		 of Openbox.
+
+----
+
+desktop()
+
+Returns the number of the currently visible desktop. This will be in the range
+of [0, numDesktops()).
+
+	Returns: The currently visible desktop.
+
+----
+
+setDesktop(desktop)
+
+Sets the specified desktop as the visible desktop.
+
+	desktop: The desktop to make visible. Must be in the range of
+		 [0, numDesktops()) or a ValueError exception will be raised.
+
+----
+
+setNextDesktop([wrap])
+
+Sets the visible desktop to the next desktop, optionally wrapping around when
+reaching the last.
+
+	wrap: An optional argument which, when True will cause the function to
+	      wrap to the first desktop when going past the last. If it is not
+	      specified, it defaults to true.
+
+----
+
+setPreviousDesktop([wrap])
+
+Sets the visible desktop to the previous desktop, optionally wrapping around
+when reaching the first.
+
+	wrap: An optional argument which, when True will cause the function to
+	      wrap to the last desktop when going past the first. If it is not
+	      specified, it defaults to true.
+
+----
+
+numDesktops()
+
+Returns the number of desktops available.
+
+	Returns: The number of available desktops.
+
+----
+
+setNumDesktops(num)
+
+Sets the number of desktops available.
+
+	num: The new number of available desktops. This must be greater than 0.
+
+----
+
+desktopNames()
+
+Returns a tuple of names, containing a name for each desktop. The tuple may
+have a length greater than numDesktops() if more names have been specified.
+
+	Returns: A tuple of names.
+
+----
+
+setDesktopNames(names)
+
+Sets the names for the desktops.
+
+	names: An iterable list of names to apply to the desktops. Can contain
+	       more or less names than numDesktops(). If it contains fewer
+	       names, then Openbox will give the remaining desktops a
+	       placeholder name.
+
+----
+
+showingDesktop()
+
+Returns True or False, depicting if Openbox is in 'showing the desktop' mode.
+In 'showing the desktop' mode, all normal clients are hidden and the desktop
+is given focus if possible.
+
+	Returns: True or False describing if Openbox is in 'showing the
+		 desktop' mode.
+
+----
+
+setShowingDesktop(show)
+
+Enters or leaves 'showing the desktop' mode. See showingDesktop() for a
+description of this mode.
+
+	show: True or False to enter or leave 'showing the desktop' mode
+	      respectively.
+
+----
+
+screenArea(desktop)
+
+Returns the on-screen available area. This is the area not reserved by
+applications' struts. Windows should be placed within this area, not within
+the physicalSize().
+
+	desktop: The desktop for which to get the available area. 0xffffffff to
+		 get the combined area for all desktops (combined
+		 subtractively).
+
+	Returns: A tuple of form (x, y, width, height) containing the
+		 dimensions of the available on-screen area for the desktop.
+
+
+----
+
+screenStrut(desktop)
+
+Returns the combined strut which has been reserved by all applications on the
+desktops.
+
+	desktop: The desktop for which to get the strut. 0xffffffff to get the
+		 combined strut for all desktops (combined additively).
+
+	Returns: A tuple of form (left, top, right, bottom) containing the
+		 size of the reserved strut space for the desktop.
+
+----
+
+physicalSize()
+
+Returns the physical size of the display device (in pixels).
+
+	Returns: A tuple of the form (width, height) containing the size of
+		 the display device's area.
+
+----
+
+screenNumber()
+
+Returns the number of the screen on which Openbox is running.
+
+	Returns: An integer specifying the screen on which this instance of
+		 Openbox is running.
+
+----
+
+rootWindow()
+
+Return the window id of the root window.
+
+	Returns: An integer containing the window id of the root window.
+
+----
+
+clientList()
+
+Returns a all clients currently being managed by Openbox. This list is updated
+as clients are managed and closed/destroyed/released.
+
+	Returns: A list of Clients containing all the clients managed by
+		 Openbox.
+
+----
+
+ob.State
+
+State.Starting: Openbox is starting up and has not yet reached its
+		main event loop.
+State.Running:  Openbox is running normally.
+State.Exiting:  Openbox has left its main event loop and is shutting
+		down.
diff --git a/doc/python/pointer.txt b/doc/python/pointer.txt
new file mode 100644
index 0000000..b204924
--- /dev/null
+++ b/doc/python/pointer.txt
@@ -0,0 +1,130 @@
+input.Pointer
+
+----
+
+This document describes the 'Pointer' class, exposed by Openbox's 'input'
+module to its python scripts.
+
+All pointer events which will be generated because of the Pointer class can
+be caught from the hooks.pointer hook.
+
+----
+
+Terminology
+
+----
+
+Context -- A context is a string describing a part of a window or screen in
+	   which an event can occur. Contexts are used for grabbing and
+	   tracking pointer events. A list of possible contexts are exposed by
+	   the engine through the contexts() method. The standard list of
+	   contexts are:
+	   * "none" - no context associated.
+	   * "root" - the root window.
+	   * "frame" - the client's entire frame. Note: when a button is
+		       grabbed in the "frame" context, it will not get passed
+		       through to the client.
+	   * "client" - client's window.
+	   * "titlebar" - a client's titlebar.
+	   * "handle" - a client's handle.
+	   * "tlcorner" - the top-left corner of a client's decorations.
+	   * "trcorner" - the top-right corner of a client's decorations.
+	   * "blcorner" - the bottom-left corner of a client's decorations.
+	   * "brcorner" - the bottom-right corner of a client's decorations.
+	   * "maximize" - the maximize button in the titlebar.
+	   * "close" - the close button in the titlebar.
+	   * "iconify" - the iconify button in the titlebar.
+	   * "alldesktops" - the all-desktops button in the titlebar.
+	   * "icon" - the window icon in the titlebar.
+
+	   An engine may add to this list as it sees fit (most engines will
+	   include "close", "maximize", etc. contexts for titlebar buttons).
+
+----
+
+Methods
+
+----
+
+bind(button, context, action, func)
+
+Binds a pointer button for a context and action to a function. See the
+Terminology section for a decription and list of common contexts. The button is
+a string which defines a modifier and button combination with the format
+[Modifier-]...[Button]. Modifiers can be 'mod1', 'mod2', 'mod3', 'mod4',
+'mod5', 'control', and 'shift'. The keys on your keyboard that are bound to
+each of these modifiers can be found by running 'xmodmap'. The button is the
+number of the button. Button numbers can be found by running 'xev', pressing
+the button with the pointer over its window, and watching its output. Here
+are some examples of valid buttons: 'control-1', '2', 'mod1-shift-5'. The 
+action is one of the Action_* constants. The func must have a definition
+similar to 'def func(ptrdata, client)'. The arguments passed to the function
+are a PointerData object and a Client object (or None). A button and context
+may be bound to more than one function.
+
+	button: A string defining the modifiers and button to for which events
+		should be generated.
+
+	context: The context in which the button events should be generated.
+
+	action: One of the Action_* constants specifying the action to bind
+		the function to.
+
+	func: A function to bind to the button/context.
+
+----
+
+clearBinds()
+
+Removes all bindings that were previously made by bind().
+
+----
+
+grab(func)
+
+Grabs the pointer device, causing all possible pointer events to be sent to
+the given function. CAUTION: Be sure when you grab() that you also have an
+ungrab() that will execute, or you will not be able to use the pointer device
+until you restart Openbox. The func must have a definition similar to
+'def func(data)'. The argument passed to the function is a PointerData object.
+The pointer cannot be grabbed if it is already grabbed. When a grab is active,
+no pointer bindings will fire, everything is sent only to the specified func.
+
+	func: A function to receive all the grabbed pointer events.
+
+----
+
+ungrab()
+
+Ungrabs the pointer. The pointer cannot be ungrabbed if it is not grabbed.
+
+----
+
+Constants
+
+----
+
+Action_Press		a pointer button press
+Action_Release		a pointer button release
+Action_Click		a pointer button click (press-release)
+Action_DoubleClick	a pointer button double-click
+Action_Motion		a pointer drag
+
+----
+
+Configuration options (through the config module)
+
+----
+
+double_click_rate
+
+An integer containing the number of milliseconds in which 2 clicks must be
+received to cause a double-click event.
+
+----
+
+drag_threshold
+
+An integer containing the number of pixels a drag must go before motion events
+start getting generated. Once a drag has begun, the button release will not
+count as a click event.
diff --git a/doc/python/pointerdata.txt b/doc/python/pointerdata.txt
new file mode 100644
index 0000000..8db7f0a
--- /dev/null
+++ b/doc/python/pointerdata.txt
@@ -0,0 +1,67 @@
+PointerData
+
+----
+
+This document describes the 'PointerData' class, passed to functions bound to
+pointer events.
+
+----
+
+Attributes
+
+----
+
+action
+
+The type of event. One of the input.Pointer.Action_* constants.
+
+----
+
+button
+
+The button (in the format that input.Pointer.bind() expects them) that caused
+the event. This will be 'None' if there was no button associated with the
+event. (This can only happen during a pointer grab.)
+
+----
+
+context
+
+A string containing the context of the event. See the Terminology section in
+the Pointer documentation for a decription and list of common contexts.
+
+----
+
+state
+
+An integer bitmask of the modifiers pressed down when the event occured.
+
+----
+
+buttonnum
+
+An integer containing the number of the pointer button associated with the
+event.
+
+----
+
+pos
+
+A tuple containing the current position of the cursor. The tuple has the format
+(x, y). This is only valid when 'action' is 'Action_Motion'.
+
+-----
+
+presspos
+
+A tuple containing the position of the cursor when the drag started. The tuple
+has the format (x, y). This is only valid when 'action' is 'Action_Motion'.
+
+----
+
+pressclientarea
+
+A tuple containing the area of the client when the drag started. The tuple has
+the format (x, y, width, height). This will be None if there is no client
+associated with the event (during a pointer grab, or a drag on the root
+window). This is only valid when 'action' is 'Action_Motion'.
diff --git a/doc/themerc.txt b/doc/themerc.txt
new file mode 100644
index 0000000..95f5290
--- /dev/null
+++ b/doc/themerc.txt
@@ -0,0 +1,54 @@
+The themerc file dictates which engine and theme openbox will use, as well as
+parameters for the engine.
+
+----
+
+Options for the themerc file are specified as:
+option = value
+
+Lines that begin with # are considered comments.
+
+----
+
+These are the valid options:
+
+engine
+	String value, containing the name of the engine to load.
+	example: 'openbox'
+	default: 'openbox'
+
+theme
+	String value containing	the theme to load in the engine. This can be
+	an absolute path to the theme, or simply the theme's name in which case
+	the appropriate paths will be searched for the theme.
+	example: 'mytheme'
+	default: engine-dependant
+
+font
+	String value containing the font to use in the window titlebars. The
+	format of this string is described in:
+	http://keithp.com/~keithp/render/Xft.tutorial
+	example: 'times-12:bold:slant=italic,oblique'
+	default: 'sans-8'
+
+titlebarlayout
+	The layout of the titlebar of windows. This is a string that can
+	consist of only valid characters. The characters are:
+		I - iconify button
+		M - maximize button
+		C - close button
+		D - all-desktops button
+		N - window icon
+		L - text label (window title)
+	example: 'ILMC'
+	default: 'NDLIMC'
+
+----
+
+example themerc:
+
+# My themerc file!
+engine='openbox'
+theme='nyz'
+titlebarlayout='ILMC'
+font='arial,sans-9:bold'
diff --git a/data/styles/.cvsignore b/engines/.cvsignore
similarity index 100%
rename from data/styles/.cvsignore
rename to engines/.cvsignore
diff --git a/engines/Makefile.am b/engines/Makefile.am
new file mode 100644
index 0000000..db3bc8d
--- /dev/null
+++ b/engines/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = openbox
diff --git a/engines/engineinterface.h b/engines/engineinterface.h
new file mode 100644
index 0000000..96796ed
--- /dev/null
+++ b/engines/engineinterface.h
@@ -0,0 +1,53 @@
+#ifndef __engineinterface_h
+#define __engineinterface_h
+
+#include "../kernel/frame.h"
+#include 
+
+/* startup */
+typedef gboolean EngineStartup();
+
+/* shutdown */
+typedef void EngineShutdown();
+
+/* frame_new */
+typedef Frame *EngineFrameNew();
+
+/* frame_grab_client */
+typedef void EngineFrameGrabClient(Frame *self, Client *client);
+/* frame_release_client */
+typedef void EngineFrameReleaseClient(Frame *self, Client *client);
+
+/* frame_adjust_size */
+/*! Update the frame's size to match the client */
+typedef void EngineFrameAdjustSize(Frame *self);
+/* frame_adjust_position */
+/*! Update the frame's position to match the client */
+typedef void EngineFrameAdjustPosition(Frame *self);
+/* frame_adjust_shape */
+/*! Shape the frame window to the client window */
+typedef void EngineFrameAdjustShape(Frame *self);
+/* frame_adjust_state */
+/*! Update the frame to match the client's new state (for things like toggle
+  buttons, focus, and the title) XXX break this up */
+typedef void EngineFrameAdjustState(Frame *self);
+/* frame_adjust_focus */
+/*! Update the frame to match the client's focused state */
+typedef void EngineFrameAdjustFocus(Frame *self);
+/* frame_adjust_title */
+/*! Update the frame to display the client's current title */
+typedef void EngineFrameAdjustTitle(Frame *self);
+/* frame_adjust_icon */
+/*! Update the frame to display the client's current icon */
+typedef void EngineFrameAdjustIcon(Frame *self);
+
+/* frame_show */
+/*! Shows the frame */
+typedef void EngineFrameShow(Frame *self);
+/*! Hides the frame */
+typedef void EngineFrameHide(Frame *self);
+
+/* get_context */
+typedef GQuark EngineGetContext(Client *client, Window win);
+
+#endif
diff --git a/engines/openbox/.cvsignore b/engines/openbox/.cvsignore
new file mode 100644
index 0000000..b15c7f6
--- /dev/null
+++ b/engines/openbox/.cvsignore
@@ -0,0 +1,7 @@
+Makefile
+Makefile.in
+.deps
+.libs
+openbox.la
+openbox.lo
+theme.lo
diff --git a/engines/openbox/Makefile.am b/engines/openbox/Makefile.am
new file mode 100644
index 0000000..64f9315
--- /dev/null
+++ b/engines/openbox/Makefile.am
@@ -0,0 +1,20 @@
+enginedir=$(libdir)/openbox/engines
+themedir=$(datadir)/openbox/themes/openbox
+
+CPPFLAGS=$(XFT_CFLAGS) $(GLIB_CFLAGS) @CPPFLAGS@ \
+-DENGINEDIR=\"$(enginedir)\" \
+-DTHEMEDIR=\"$(themedir)\" \
+-DDEFAULT_THEME=\"nyz\" \
+-DG_LOG_DOMAIN=\"Openbox-Engine\"
+
+engine_LTLIBRARIES=openbox.la
+
+openbox_la_LDFLAGS=-module -avoid-version
+openbox_la_SOURCES=openbox.c theme.c
+
+noinst_HEADERS=
+
+MAINTAINERCLEANFILES= Makefile.in
+
+distclean-local:
+	$(RM) *\~ *.orig *.rej .\#*
diff --git a/engines/openbox/openbox.c b/engines/openbox/openbox.c
new file mode 100644
index 0000000..fdab22a
--- /dev/null
+++ b/engines/openbox/openbox.c
@@ -0,0 +1,829 @@
+#include "theme.h"
+#include "../../kernel/openbox.h"
+#include "../../kernel/screen.h"
+#include "../../kernel/extensions.h"
+#include "../../kernel/themerc.h"
+#include "../../kernel/frame.h"
+#include "../../render/render.h"
+#include "../../render/color.h"
+
+#include 
+#include 
+
+#define TITLE_HEIGHT    (s_font_height + s_bevel * 2)
+#define LABEL_HEIGHT    (s_font_height)
+#define HANDLE_Y(f)     (f->innersize.top + f->frame.client->area.height + \
+		         f->cbwidth)
+#define BUTTON_SIZE     (LABEL_HEIGHT - 2)
+#define GRIP_WIDTH      (BUTTON_SIZE * 2)
+#define HANDLE_WIDTH(f) (f->width - (GRIP_WIDTH + f->bwidth) * 2)
+
+#define PLATE_EVENTMASK (SubstructureRedirectMask | ButtonPressMask)
+#define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask)
+
+/* style settings - geometry */
+int s_font_height;
+int s_bevel;
+int s_handle_height;
+int s_bwidth;
+int s_cbwidth;
+/* style settings - colors */
+color_rgb *s_b_color;
+color_rgb *s_cb_focused_color;
+color_rgb *s_cb_unfocused_color;
+
+/* global appearances */
+Appearance *a_focused_unpressed_max;
+Appearance *a_focused_pressed_max;
+Appearance *a_unfocused_unpressed_max;
+Appearance *a_unfocused_pressed_max;
+Appearance *a_focused_unpressed_close;
+Appearance *a_focused_pressed_close;
+Appearance *a_unfocused_unpressed_close;
+Appearance *a_unfocused_pressed_close;
+Appearance *a_focused_unpressed_desk;
+Appearance *a_focused_pressed_desk;
+Appearance *a_unfocused_unpressed_desk;
+Appearance *a_unfocused_pressed_desk;
+Appearance *a_focused_unpressed_iconify;
+Appearance *a_focused_pressed_iconify;
+Appearance *a_unfocused_unpressed_iconify;
+Appearance *a_unfocused_pressed_iconify;
+Appearance *a_focused_grip;
+Appearance *a_unfocused_grip;
+Appearance *a_focused_title;
+Appearance *a_unfocused_title;
+Appearance *a_focused_label;
+Appearance *a_unfocused_label;
+Appearance *a_icon; /* always parentrelative, so no focused/unfocused */
+Appearance *a_focused_handle;
+Appearance *a_unfocused_handle;
+
+typedef struct ObFrame {
+    Frame frame;
+
+    Window title;
+    Window label;
+    Window max;
+    Window close;
+    Window desk;
+    Window icon;
+    Window iconify;
+    Window handle;
+    Window lgrip;
+    Window rgrip;
+
+    Appearance *a_unfocused_title;
+    Appearance *a_focused_title;
+    Appearance *a_unfocused_label;
+    Appearance *a_focused_label;
+    Appearance *a_icon;
+    Appearance *a_unfocused_handle;
+    Appearance *a_focused_handle;
+
+    Strut  innersize;
+
+    GSList *clients;
+
+    int width; /* title and handle */
+    int label_width;
+    int icon_x;        /* x-position of the window icon button */
+    int label_x;       /* x-position of the window title */
+    int iconify_x;     /* x-position of the window iconify button */
+    int desk_x;         /* x-position of the window all-desktops button */
+    int max_x;         /* x-position of the window maximize button */
+    int close_x;       /* x-position of the window close button */
+    int bwidth;        /* border width */
+    int cbwidth;       /* client border width */
+
+    gboolean max_press;
+    gboolean close_press;
+    gboolean desk_press;
+    gboolean iconify_press;
+} ObFrame;
+
+static void layout_title(ObFrame *self);
+static void render(ObFrame *self);
+static void render_label(ObFrame *self);
+static void render_max(ObFrame *self);
+static void render_icon(ObFrame *self);
+static void render_iconify(ObFrame *self);
+static void render_desk(ObFrame *self);
+static void render_close(ObFrame *self);
+
+gboolean startup()
+{
+    g_quark_from_string("none");
+    g_quark_from_string("root");
+    g_quark_from_string("client");
+    g_quark_from_string("titlebar");
+    g_quark_from_string("handle");
+    g_quark_from_string("frame");
+    g_quark_from_string("blcorner");
+    g_quark_from_string("brcorner");
+    g_quark_from_string("maximize");
+    g_quark_from_string("alldesktops");
+    g_quark_from_string("iconify");
+    g_quark_from_string("icon");
+    g_quark_from_string("close");
+
+    s_b_color = s_cb_unfocused_color = s_cb_focused_color = NULL;
+
+    a_focused_unpressed_max = appearance_new(Surface_Planar, 0);//1);
+    a_focused_pressed_max = appearance_new(Surface_Planar, 0);//1);
+    a_unfocused_unpressed_max = appearance_new(Surface_Planar, 0);//1);
+    a_unfocused_pressed_max = appearance_new(Surface_Planar, 0);//1);
+    a_focused_unpressed_close = NULL;
+    a_focused_pressed_close = NULL;
+    a_unfocused_unpressed_close = NULL;
+    a_unfocused_pressed_close = NULL;
+    a_focused_unpressed_desk = NULL;
+    a_focused_pressed_desk = NULL;
+    a_unfocused_unpressed_desk = NULL;
+    a_unfocused_pressed_desk = NULL;
+    a_focused_unpressed_iconify = NULL;
+    a_focused_pressed_iconify = NULL;
+    a_unfocused_unpressed_iconify = NULL;
+    a_unfocused_pressed_iconify = NULL;
+    a_focused_grip = appearance_new(Surface_Planar, 0);
+    a_unfocused_grip = appearance_new(Surface_Planar, 0);
+    a_focused_title = appearance_new(Surface_Planar, 0);
+    a_unfocused_title = appearance_new(Surface_Planar, 0);
+    a_focused_label = appearance_new(Surface_Planar, 0);//1);
+    a_unfocused_label = appearance_new(Surface_Planar, 0);//1);
+    a_icon = appearance_new(Surface_Planar, 0);//1);
+    a_focused_handle = appearance_new(Surface_Planar, 0);
+    a_unfocused_handle = appearance_new(Surface_Planar, 0);
+
+    return load();
+}
+
+void shutdown()
+{
+    if (s_b_color != NULL) color_free(s_b_color);
+    if (s_cb_unfocused_color != NULL) color_free(s_cb_unfocused_color);
+    if (s_cb_focused_color != NULL) color_free(s_cb_focused_color);
+
+    appearance_free(a_focused_unpressed_max);
+    appearance_free(a_focused_pressed_max);
+    appearance_free(a_unfocused_unpressed_max);
+    appearance_free(a_unfocused_pressed_max);
+    if (a_focused_unpressed_close != NULL)
+	appearance_free(a_focused_unpressed_close);
+    if (a_focused_pressed_close != NULL)
+	appearance_free(a_focused_pressed_close);
+    if (a_unfocused_unpressed_close != NULL)
+	appearance_free(a_unfocused_unpressed_close);
+    if (a_unfocused_pressed_close != NULL)
+	appearance_free(a_unfocused_pressed_close);
+    if (a_focused_unpressed_desk != NULL)
+	appearance_free(a_focused_unpressed_desk);
+    if (a_focused_pressed_desk != NULL)
+	appearance_free(a_focused_pressed_desk);
+    if (a_unfocused_unpressed_desk != NULL)
+	appearance_free(a_unfocused_unpressed_desk);
+    if (a_unfocused_pressed_desk != NULL)
+	appearance_free(a_unfocused_pressed_desk);
+    if (a_focused_unpressed_iconify != NULL)
+	appearance_free(a_focused_unpressed_iconify);
+    if (a_focused_pressed_iconify != NULL)
+	appearance_free(a_focused_pressed_iconify);
+    if (a_unfocused_unpressed_iconify != NULL)
+	appearance_free(a_unfocused_unpressed_iconify);
+    if (a_unfocused_pressed_iconify != NULL)
+	appearance_free(a_unfocused_pressed_iconify);
+    appearance_free(a_focused_grip);
+    appearance_free(a_unfocused_grip);
+    appearance_free(a_focused_title);
+    appearance_free(a_unfocused_title);
+    appearance_free(a_focused_label);
+    appearance_free(a_unfocused_label);
+    appearance_free(a_icon);
+    appearance_free(a_focused_handle);
+    appearance_free(a_unfocused_handle);
+}
+
+static Window createWindow(Window parent, unsigned long mask,
+			   XSetWindowAttributes *attrib)
+{
+    /* XXX DONT USE THE DEFAULT SHIT */
+    return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
+			 DefaultDepth(ob_display, ob_screen), InputOutput,
+			 DefaultVisual(ob_display, ob_screen),
+			 mask, attrib);
+                       
+}
+
+Frame *frame_new()
+{
+    XSetWindowAttributes attrib;
+    unsigned long mask;
+    ObFrame *self;
+
+    self = g_new(ObFrame, 1);
+
+    self->frame.visible = FALSE;
+
+    /* create all of the decor windows */
+    mask = CWOverrideRedirect | CWEventMask;
+    attrib.event_mask = FRAME_EVENTMASK;
+    attrib.override_redirect = TRUE;
+    self->frame.window = createWindow(ob_root, mask, &attrib);
+
+    mask = 0;
+    self->frame.plate = createWindow(self->frame.window, mask, &attrib);
+
+    mask = CWEventMask;
+    attrib.event_mask = (ButtonPressMask | ButtonReleaseMask |
+			 ButtonMotionMask | ExposureMask);
+    self->title = createWindow(self->frame.window, mask, &attrib);
+    self->label = createWindow(self->title, mask, &attrib);
+    self->max = createWindow(self->title, mask, &attrib);
+    self->close = createWindow(self->title, mask, &attrib);
+    self->desk = createWindow(self->title, mask, &attrib);
+    self->icon = createWindow(self->title, mask, &attrib);
+    self->iconify = createWindow(self->title, mask, &attrib);
+    self->handle = createWindow(self->frame.window, mask, &attrib);
+    mask |= CWCursor;
+    attrib.cursor = ob_cursors.ll_angle;
+    self->lgrip = createWindow(self->handle, mask, &attrib);
+    attrib.cursor = ob_cursors.lr_angle;
+    self->rgrip = createWindow(self->handle, mask, &attrib);
+
+    /* the other stuff is shown based on decor settings */
+    XMapWindow(ob_display, self->frame.plate);
+    XMapWindow(ob_display, self->lgrip);
+    XMapWindow(ob_display, self->rgrip);
+    XMapWindow(ob_display, self->label);
+
+    /* set colors/appearance/sizes for stuff that doesn't change */
+    XSetWindowBorder(ob_display, self->frame.window, s_b_color->pixel);
+    XSetWindowBorder(ob_display, self->label, s_b_color->pixel);
+    XSetWindowBorder(ob_display, self->rgrip, s_b_color->pixel);
+    XSetWindowBorder(ob_display, self->lgrip, s_b_color->pixel);
+
+    XResizeWindow(ob_display, self->max, BUTTON_SIZE, BUTTON_SIZE);
+    XResizeWindow(ob_display, self->iconify, BUTTON_SIZE, BUTTON_SIZE);
+    XResizeWindow(ob_display, self->icon, BUTTON_SIZE, BUTTON_SIZE);
+    XResizeWindow(ob_display, self->close, BUTTON_SIZE, BUTTON_SIZE);
+    XResizeWindow(ob_display, self->desk, BUTTON_SIZE, BUTTON_SIZE);
+    XResizeWindow(ob_display, self->lgrip, GRIP_WIDTH, s_handle_height);
+    XResizeWindow(ob_display, self->rgrip, GRIP_WIDTH, s_handle_height);
+
+    /* set up the dynamic appearances */
+    self->a_unfocused_title = appearance_copy(a_unfocused_title);
+    self->a_focused_title = appearance_copy(a_focused_title);
+    self->a_unfocused_label = appearance_copy(a_unfocused_label);
+    self->a_focused_label = appearance_copy(a_focused_label);
+    self->a_unfocused_handle = appearance_copy(a_unfocused_handle);
+    self->a_focused_handle = appearance_copy(a_focused_handle);
+    self->a_icon = appearance_copy(a_icon);
+
+    self->max_press = self->close_press = self->desk_press = 
+	self->iconify_press = FALSE;
+
+    return (Frame*)self;
+}
+
+static void frame_free(ObFrame *self)
+{
+    appearance_free(self->a_unfocused_title); 
+    appearance_free(self->a_focused_title);
+    appearance_free(self->a_unfocused_label);
+    appearance_free(self->a_focused_label);
+    appearance_free(self->a_unfocused_handle);
+    appearance_free(self->a_focused_handle);
+    appearance_free(self->a_icon);
+
+    XDestroyWindow(ob_display, self->frame.window);
+
+    g_free(self);
+}
+
+void frame_show(ObFrame *self)
+{
+    if (!self->frame.visible) {
+	self->frame.visible = TRUE;
+	XMapWindow(ob_display, self->frame.window);
+    }
+}
+
+void frame_hide(ObFrame *self)
+{
+    if (self->frame.visible) {
+	self->frame.visible = FALSE;
+	self->frame.client->ignore_unmaps++;
+	XUnmapWindow(ob_display, self->frame.window);
+    }
+}
+
+void frame_adjust_shape(ObFrame *self)
+{
+#ifdef SHAPE
+    int num;
+    XRectangle xrect[2];
+
+    if (!self->frame.client->shaped) {
+	/* clear the shape on the frame window */
+	XShapeCombineMask(ob_display, self->frame.window, ShapeBounding,
+			  self->innersize.left,
+			  self->innersize.top,
+			  None, ShapeSet);
+    } else {
+	/* make the frame's shape match the clients */
+	XShapeCombineShape(ob_display, self->frame.window, ShapeBounding,
+			   self->innersize.left,
+			   self->innersize.top,
+			   self->frame.client->window,
+			   ShapeBounding, ShapeSet);
+
+	num = 0;
+	if (self->frame.client->decorations & Decor_Titlebar) {
+	    xrect[0].x = -s_bevel;
+	    xrect[0].y = -s_bevel;
+	    xrect[0].width = self->width + self->bwidth * 2;
+	    xrect[0].height = TITLE_HEIGHT +
+		self->bwidth * 2;
+	    ++num;
+	}
+
+	if (self->frame.client->decorations & Decor_Handle) {
+	    xrect[1].x = -s_bevel;
+	    xrect[1].y = HANDLE_Y(self);
+	    xrect[1].width = self->width + self->bwidth * 2;
+	    xrect[1].height = s_handle_height +
+		self->bwidth * 2;
+	    ++num;
+	}
+
+	XShapeCombineRectangles(ob_display, self->frame.window,
+				ShapeBounding, 0, 0, xrect, num,
+				ShapeUnion, Unsorted);
+    }
+#endif
+}
+
+void frame_adjust_size(ObFrame *self)
+{
+    if (self->frame.client->decorations & Decor_Border) {
+	self->bwidth = s_bwidth;
+	self->cbwidth = s_cbwidth;
+    } else {
+	self->bwidth = self->cbwidth = 0;
+    }
+    STRUT_SET(self->innersize, self->cbwidth, self->cbwidth,
+	      self->cbwidth, self->cbwidth);
+    self->width = self->frame.client->area.width + self->cbwidth * 2;
+    g_assert(self->width > 0);
+
+    /* set border widths */
+    XSetWindowBorderWidth(ob_display, self->frame.plate,  self->cbwidth);
+    XSetWindowBorderWidth(ob_display, self->frame.window, self->bwidth);
+    XSetWindowBorderWidth(ob_display, self->title,  self->bwidth);
+    XSetWindowBorderWidth(ob_display, self->handle, self->bwidth);
+    XSetWindowBorderWidth(ob_display, self->lgrip,  self->bwidth);
+    XSetWindowBorderWidth(ob_display, self->rgrip,  self->bwidth);
+  
+    /* position/size and map/unmap all the windows */
+
+    if (self->frame.client->decorations & Decor_Titlebar) {
+	XMoveResizeWindow(ob_display, self->title,
+			  -self->bwidth, -self->bwidth,
+			  self->width, TITLE_HEIGHT);
+	self->innersize.top += TITLE_HEIGHT + self->bwidth;
+	XMapWindow(ob_display, self->title);
+
+	/* layout the title bar elements */
+	layout_title(self);
+    } else {
+	XUnmapWindow(ob_display, self->title);
+	/* make all the titlebar stuff not render */
+	self->frame.client->decorations &= ~(Decor_Icon | Decor_Iconify |
+			       Decor_Maximize | Decor_Close |
+			       Decor_AllDesktops);
+    }
+
+    if (self->frame.client->decorations & Decor_Handle) {
+	XMoveResizeWindow(ob_display, self->handle,
+			  -self->bwidth, HANDLE_Y(self),
+			  self->width, s_handle_height);
+	XMoveWindow(ob_display, self->lgrip,
+		    -self->bwidth, -self->bwidth);
+	XMoveWindow(ob_display, self->rgrip,
+		    -self->bwidth + self->width -
+		    GRIP_WIDTH, -self->bwidth);
+	self->innersize.bottom += s_handle_height +
+	    self->bwidth;
+	XMapWindow(ob_display, self->handle);
+    } else
+	XUnmapWindow(ob_display, self->handle);
+  
+    XResizeWindow(ob_display, self->frame.window, self->width,
+		  (self->frame.client->shaded ? TITLE_HEIGHT :
+		   self->innersize.top + self->innersize.bottom +
+		   self->frame.client->area.height));
+
+    /* do this in two steps because clients whose gravity is set to
+       'Static' don't end up getting moved at all with an XMoveResizeWindow */
+    XMoveWindow(ob_display, self->frame.plate,
+		self->innersize.left - self->cbwidth,
+		self->innersize.top - self->cbwidth);
+    XResizeWindow(ob_display, self->frame.plate,
+		  self->frame.client->area.width,
+		  self->frame.client->area.height);
+
+    STRUT_SET(self->frame.size,
+	      self->innersize.left + self->bwidth,
+	      self->innersize.top + self->bwidth,
+	      self->innersize.right + self->bwidth,
+	      self->innersize.bottom + self->bwidth);
+
+    RECT_SET_SIZE(self->frame.area,
+		  self->frame.client->area.width +
+		  self->frame.size.left + self->frame.size.right,
+		  self->frame.client->area.height +
+		  self->frame.size.top + self->frame.size.bottom);
+
+    render(self);
+     
+    frame_adjust_shape(self);
+}
+
+void frame_adjust_position(ObFrame *self)
+{
+    self->frame.area.x = self->frame.client->area.x;
+    self->frame.area.y = self->frame.client->area.y;
+    frame_client_gravity((Frame*)self,
+			 &self->frame.area.x, &self->frame.area.y);
+    XMoveWindow(ob_display, self->frame.window,
+		self->frame.area.x, self->frame.area.y);
+}
+
+void frame_adjust_state(ObFrame *self)
+{
+    render_max(self);
+    render_desk(self);
+}
+
+void frame_adjust_focus(ObFrame *self)
+{
+    render(self);
+}
+
+void frame_adjust_title(ObFrame *self)
+{
+    render_label(self);
+}
+
+void frame_adjust_icon(ObFrame *self)
+{
+    render_icon(self);
+}
+
+void frame_grab_client(ObFrame *self, Client *client)
+{
+    self->frame.client = client;
+
+    /* reparent the client to the frame */
+    XReparentWindow(ob_display, client->window, self->frame.plate, 0, 0);
+    /*
+      When reparenting the client window, it is usually not mapped yet, since
+      this occurs from a MapRequest. However, in the case where Openbox is
+      starting up, the window is already mapped, so we'll see unmap events for
+      it. There are 2 unmap events generated that we see, one with the 'event'
+      member set the root window, and one set to the client, but both get
+      handled and need to be ignored.
+    */
+    if (ob_state == State_Starting)
+	client->ignore_unmaps += 2;
+
+    /* select the event mask on the client's parent (to receive config/map
+       req's) the ButtonPress is to catch clicks on the client border */
+    XSelectInput(ob_display, self->frame.plate, PLATE_EVENTMASK);
+
+    /* map the client so it maps when the frame does */
+    XMapWindow(ob_display, client->window);
+
+    frame_adjust_size(self);
+    frame_adjust_position(self);
+
+    /* set all the windows for the frame in the client_map */
+    g_hash_table_insert(client_map, (gpointer)self->frame.window, client);
+    g_hash_table_insert(client_map, (gpointer)self->frame.plate, client);
+    g_hash_table_insert(client_map, (gpointer)self->title, client);
+    g_hash_table_insert(client_map, (gpointer)self->label, client);
+    g_hash_table_insert(client_map, (gpointer)self->max, client);
+    g_hash_table_insert(client_map, (gpointer)self->close, client);
+    g_hash_table_insert(client_map, (gpointer)self->desk, client);
+    g_hash_table_insert(client_map, (gpointer)self->icon, client);
+    g_hash_table_insert(client_map, (gpointer)self->iconify, client);
+    g_hash_table_insert(client_map, (gpointer)self->handle, client);
+    g_hash_table_insert(client_map, (gpointer)self->lgrip, client);
+    g_hash_table_insert(client_map, (gpointer)self->rgrip, client);
+}
+
+void frame_release_client(ObFrame *self, Client *client)
+{
+    XEvent ev;
+
+    g_assert(self->frame.client == client);
+
+    /* check if the app has already reparented its window away */
+    if (XCheckTypedWindowEvent(ob_display, client->window,
+			       ReparentNotify, &ev)) {
+	XPutBackEvent(ob_display, &ev);
+	/* re-map the window since the unmanaging process unmaps it */
+	XMapWindow(ob_display, client->window);
+    } else {
+	/* according to the ICCCM - if the client doesn't reparent itself,
+	   then we will reparent the window to root for them */
+	XReparentWindow(ob_display, client->window, ob_root,
+			client->area.x,
+			client->area.y);
+    }
+
+    /* remove all the windows for the frame from the client_map */
+    g_hash_table_remove(client_map, (gpointer)self->frame.window);
+    g_hash_table_remove(client_map, (gpointer)self->frame.plate);
+    g_hash_table_remove(client_map, (gpointer)self->title);
+    g_hash_table_remove(client_map, (gpointer)self->label);
+    g_hash_table_remove(client_map, (gpointer)self->max);
+    g_hash_table_remove(client_map, (gpointer)self->close);
+    g_hash_table_remove(client_map, (gpointer)self->desk);
+    g_hash_table_remove(client_map, (gpointer)self->icon);
+    g_hash_table_remove(client_map, (gpointer)self->iconify);
+    g_hash_table_remove(client_map, (gpointer)self->handle);
+    g_hash_table_remove(client_map, (gpointer)self->lgrip);
+    g_hash_table_remove(client_map, (gpointer)self->rgrip);
+
+    frame_free(self);
+}
+
+static void layout_title(ObFrame *self)
+{
+    const char *lc;
+    int x;
+    gboolean n, d, i, l, m ,c;
+    n = d = i = l = m = c = FALSE;
+
+    /* figure out whats being shown, and the width of the label */
+    self->label_width = self->width - s_bevel * 2;
+    for (lc = themerc_titlebar_layout; *lc != '\0'; ++lc) {
+	switch (*lc) {
+	case 'N':
+	    if (!(self->frame.client->decorations & Decor_Icon)) break;
+	    n = TRUE;
+	    self->label_width -= BUTTON_SIZE + s_bevel;
+	    break;
+	case 'D':
+	    if (!(self->frame.client->decorations & Decor_AllDesktops)) break;
+	    d = TRUE;
+	    self->label_width -= BUTTON_SIZE + s_bevel;
+	    break;
+	case 'I':
+	    if (!(self->frame.client->decorations & Decor_Iconify)) break;
+	    i = TRUE;
+	    self->label_width -= BUTTON_SIZE + s_bevel;
+	    break;
+	case 'L':
+	    l = TRUE;
+	    break;
+	case 'M':
+	    if (!(self->frame.client->decorations & Decor_Maximize)) break;
+	    m = TRUE;
+	    self->label_width -= BUTTON_SIZE + s_bevel;
+	    break;
+	case 'C':
+	    if (!(self->frame.client->decorations & Decor_Close)) break;
+	    c = TRUE;
+	    self->label_width -= BUTTON_SIZE + s_bevel;
+	    break;
+	}
+    }
+    if (self->label_width < 1) self->label_width = 1;
+
+    XResizeWindow(ob_display, self->label, self->label_width, s_font_height);
+  
+    if (!n) {
+	self->frame.client->decorations &= ~Decor_Icon;
+	XUnmapWindow(ob_display, self->icon);
+	self->icon_x = -1;
+    }
+    if (!d) {
+	self->frame.client->decorations &= ~Decor_AllDesktops;
+	XUnmapWindow(ob_display, self->desk);
+	self->desk_x = -1;
+    }
+    if (!i) {
+	self->frame.client->decorations &= ~Decor_Iconify;
+	XUnmapWindow(ob_display, self->iconify);
+	self->icon_x = -1;
+    }
+    if (!l) {
+	XUnmapWindow(ob_display, self->label);
+	self->label_x = -1;
+    }
+    if (!m) {
+	self->frame.client->decorations &= ~Decor_Maximize;
+	XUnmapWindow(ob_display, self->max);
+	self->max_x = -1;
+    }
+    if (!c) {
+	self->frame.client->decorations &= ~Decor_Close;
+	XUnmapWindow(ob_display, self->close);
+	self->close_x = -1;
+    }
+
+    x = s_bevel;
+    for (lc = themerc_titlebar_layout; *lc != '\0'; ++lc) {
+	switch (*lc) {
+	case 'N':
+	    if (!n) break;
+	    self->icon_x = x;
+	    XMapWindow(ob_display, self->icon);
+	    XMoveWindow(ob_display, self->icon, x, s_bevel + 1);
+	    x += BUTTON_SIZE + s_bevel;
+	    break;
+	case 'D':
+	    if (!d) break;
+	    self->desk_x = x;
+	    XMapWindow(ob_display, self->desk);
+	    XMoveWindow(ob_display, self->desk, x, s_bevel + 1);
+	    x += BUTTON_SIZE + s_bevel;
+	    break;
+	case 'I':
+	    if (!i) break;
+	    self->iconify_x = x;
+	    XMapWindow(ob_display, self->iconify);
+	    XMoveWindow(ob_display, self->iconify, x, s_bevel + 1);
+	    x += BUTTON_SIZE + s_bevel;
+	    break;
+	case 'L':
+	    if (!l) break;
+	    self->label_x = x;
+	    XMapWindow(ob_display, self->label);
+	    XMoveWindow(ob_display, self->label, x, s_bevel);
+	    x += self->label_width + s_bevel;
+	    break;
+	case 'M':
+	    if (!m) break;
+	    self->max_x = x;
+	    XMapWindow(ob_display, self->max);
+	    XMoveWindow(ob_display, self->max, x, s_bevel + 1);
+	    x += BUTTON_SIZE + s_bevel;
+	    break;
+	case 'C':
+	    if (!c) break;
+	    self->close_x = x;
+	    XMapWindow(ob_display, self->close);
+	    XMoveWindow(ob_display, self->close, x, s_bevel + 1);
+	    x += BUTTON_SIZE + s_bevel;
+	    break;
+	}
+    }
+}
+
+static void render(ObFrame *self)
+{
+    if (self->frame.client->focused) {
+	XSetWindowBorder(ob_display, self->frame.plate,
+			 s_cb_focused_color->pixel);
+    } else {
+	XSetWindowBorder(ob_display, self->frame.plate,
+			 s_cb_unfocused_color->pixel);
+    }
+
+    if (self->frame.client->decorations & Decor_Titlebar) {
+	paint(self->title, (self->frame.client->focused ?
+			    self->a_focused_title :
+			    self->a_unfocused_title),
+	      self->width, TITLE_HEIGHT);
+	render_label(self);
+	render_max(self);
+	render_icon(self);
+	render_iconify(self);
+	render_desk(self);
+	render_close(self);
+    }
+
+    if (self->frame.client->decorations & Decor_Handle) {
+	paint(self->handle, (self->frame.client->focused ?
+			     self->a_focused_handle :
+			     self->a_unfocused_handle),
+	      HANDLE_WIDTH(self), s_handle_height);
+	paint(self->lgrip, (self->frame.client->focused ?
+			    a_focused_grip :
+			    a_unfocused_grip),
+	      GRIP_WIDTH, s_handle_height);
+	paint(self->rgrip, (self->frame.client->focused ?
+			    a_focused_grip :
+			    a_unfocused_grip),
+	      GRIP_WIDTH, s_handle_height);
+    }
+}
+
+static void render_label(ObFrame *self)
+{
+    if (self->label_x < 0) return;
+
+    /* XXX set the texture's text! */
+    paint(self->label, (self->frame.client->focused ?
+			self->a_focused_label :
+			self->a_unfocused_label),
+	  self->label_width, LABEL_HEIGHT);
+}
+
+static void render_icon(ObFrame *self)
+{
+    if (self->icon_x < 0) return;
+
+    /* XXX set the texture's icon picture! */
+    paint(self->icon, self->a_icon, BUTTON_SIZE, BUTTON_SIZE);
+}
+
+static void render_max(ObFrame *self)
+{
+    gboolean press = self->max_press ||
+	self->frame.client->max_vert || self->frame.client->max_horz;
+    
+    if (self->max_x < 0) return;
+
+    paint(self->max, (self->frame.client->focused ?
+		      (press ?
+		       a_focused_pressed_max :
+		       a_focused_unpressed_max) :
+		      (press ?
+		       a_unfocused_pressed_max :
+		       a_unfocused_unpressed_max)),
+	  BUTTON_SIZE, BUTTON_SIZE);
+}
+
+static void render_iconify(ObFrame *self)
+{
+    if (self->iconify_x < 0) return;
+
+    paint(self->iconify, (self->frame.client->focused ?
+			  (self->iconify_press ?
+			   a_focused_pressed_iconify :
+			   a_focused_unpressed_iconify) :
+			  (self->iconify_press ?
+			   a_unfocused_pressed_iconify :
+			   a_unfocused_unpressed_iconify)),
+	  BUTTON_SIZE, BUTTON_SIZE);
+}
+
+static void render_desk(ObFrame *self)
+{
+    gboolean press = self->desk_press ||
+	self->frame.client->desktop == DESKTOP_ALL;
+    
+    if (self->desk_x < 0) return;
+
+    paint(self->desk, (self->frame.client->focused ?
+		       (press ?
+			a_focused_pressed_desk :
+			a_focused_unpressed_desk) :
+		       (press ?
+			a_unfocused_pressed_desk :
+			a_unfocused_unpressed_desk)),
+	  BUTTON_SIZE, BUTTON_SIZE);
+}
+
+static void render_close(ObFrame *self)
+{
+    if (self->close_x < 0) return;
+
+    paint(self->close, (self->frame.client->focused ?
+			  (self->close_press ?
+			   a_focused_pressed_close :
+			   a_focused_unpressed_close) :
+			  (self->close_press ?
+			   a_unfocused_pressed_close :
+			   a_unfocused_unpressed_close)),
+	  BUTTON_SIZE, BUTTON_SIZE);
+}
+
+GQuark get_context(Client *client, Window win)
+{
+    ObFrame *self;
+
+    if (win == ob_root) return g_quark_try_string("root");
+    if (client == NULL) return g_quark_try_string("none");
+    if (win == client->window) return g_quark_try_string("client");
+
+    self = (ObFrame*) client->frame;
+    if (win == self->frame.window) return g_quark_try_string("frame");
+    if (win == self->frame.plate)  return g_quark_try_string("client");
+    if (win == self->title)  return g_quark_try_string("titlebar");
+    if (win == self->label)  return g_quark_try_string("titlebar");
+    if (win == self->handle) return g_quark_try_string("handle");
+    if (win == self->lgrip)  return g_quark_try_string("blcorner");
+    if (win == self->rgrip)  return g_quark_try_string("brcorner");
+    if (win == self->max)  return g_quark_try_string("maximize");
+    if (win == self->iconify)  return g_quark_try_string("iconify");
+    if (win == self->close)  return g_quark_try_string("close");
+    if (win == self->icon)  return g_quark_try_string("icon");
+    if (win == self->desk)  return g_quark_try_string("alldesktops");
+
+    return g_quark_try_string("none");
+}
diff --git a/engines/openbox/openbox.h b/engines/openbox/openbox.h
new file mode 100644
index 0000000..f072a6e
--- /dev/null
+++ b/engines/openbox/openbox.h
@@ -0,0 +1,43 @@
+#ifndef __engine_openbox_h
+#define __engine_openbox_h
+
+#include "../../render/render.h"
+#include "../../render/color.h"
+
+extern int s_font_height;
+extern int s_bevel;
+extern int s_handle_height;
+extern int s_bwidth;
+extern int s_cbwidth;
+
+extern color_rgb *s_b_color;
+extern color_rgb *s_cb_focused_color;
+extern color_rgb *s_cb_unfocused_color;
+
+extern Appearance *a_focused_unpressed_max;
+extern Appearance *a_focused_pressed_max;
+extern Appearance *a_unfocused_unpressed_max;
+extern Appearance *a_unfocused_pressed_max;
+extern Appearance *a_focused_unpressed_close;
+extern Appearance *a_focused_pressed_close;
+extern Appearance *a_unfocused_unpressed_close;
+extern Appearance *a_unfocused_pressed_close;
+extern Appearance *a_focused_unpressed_desk;
+extern Appearance *a_focused_pressed_desk;
+extern Appearance *a_unfocused_unpressed_desk;
+extern Appearance *a_unfocused_pressed_desk;
+extern Appearance *a_focused_unpressed_iconify;
+extern Appearance *a_focused_pressed_iconify;
+extern Appearance *a_unfocused_unpressed_iconify;
+extern Appearance *a_unfocused_pressed_iconify;
+extern Appearance *a_focused_grip;
+extern Appearance *a_unfocused_grip;
+extern Appearance *a_focused_title;
+extern Appearance *a_unfocused_title;
+extern Appearance *a_focused_label;
+extern Appearance *a_unfocused_label;
+extern Appearance *a_icon;
+extern Appearance *a_focused_handle;
+extern Appearance *a_unfocused_handle;
+
+#endif
diff --git a/engines/openbox/theme.c b/engines/openbox/theme.c
new file mode 100644
index 0000000..2c96224
--- /dev/null
+++ b/engines/openbox/theme.c
@@ -0,0 +1,327 @@
+#include "openbox.h"
+#include "../../kernel/themerc.h"
+
+#include 
+#include 
+#include 
+#ifdef HAVE_STDLIB_H
+#  include 
+#endif
+#ifdef HAVE_CTYPE_H
+#  include 
+#endif
+#ifdef HAVE_STRING_H
+#  include 
+#endif
+
+static XrmDatabase loaddb(char *theme)
+{
+    XrmDatabase db;
+
+    db = XrmGetFileDatabase(theme);
+    if (db == NULL) {
+	char *s = g_build_filename(g_get_home_dir(), ".openbox", "themes",
+				   "openbox", theme, NULL);
+	db = XrmGetFileDatabase(s);
+	g_free(s);
+    }
+    if (db == NULL) {
+	char *s = g_build_filename(THEMEDIR, theme, NULL);
+	db = XrmGetFileDatabase(s);
+	g_free(s);
+    }
+    return db;
+}
+
+static char *create_class_name(char *rname)
+{
+    char *rclass = g_strdup(rname);
+    char *p = rclass;
+
+    while (TRUE) {
+	*p = toupper(*p);
+	p = strchr(p+1, '.');
+	if (p == NULL) break;
+	++p;
+	if (*p == '\0') break;
+    }
+    return rclass;
+}
+
+gboolean read_bool(XrmDatabase db, char *rname, gboolean *value)
+{
+    gboolean ret = FALSE;
+    char *rclass = create_class_name(rname);
+    char *rettype;
+    XrmValue retvalue;
+  
+    if (XrmGetResource(db, rname, rclass, &rettype, &retvalue) &&
+	retvalue.addr != NULL) {
+	if (!g_ascii_strcasecmp(retvalue.addr, "true"))
+	    *value = TRUE;
+	else
+	    *value = FALSE;
+	ret = TRUE;
+    }
+
+    g_free(rclass);
+    return ret;
+}
+
+gboolean read_int(XrmDatabase db, char *rname, int *value)
+{
+    gboolean ret = FALSE;
+    char *rclass = create_class_name(rname);
+    char *rettype, *end;
+    XrmValue retvalue;
+  
+    if (XrmGetResource(db, rname, rclass, &rettype, &retvalue) &&
+	retvalue.addr != NULL) {
+	*value = (int)strtol(retvalue.addr, &end, 10);
+	if (end != retvalue.addr)
+	    ret = TRUE;
+    }
+
+    g_free(rclass);
+    return ret;
+}
+
+gboolean read_string(XrmDatabase db, char *rname, char **value)
+{
+    gboolean ret = FALSE;
+    char *rclass = create_class_name(rname);
+    char *rettype;
+    XrmValue retvalue;
+  
+    if (XrmGetResource(db, rname, rclass, &rettype, &retvalue) &&
+	retvalue.addr != NULL) {
+	*value = retvalue.addr;
+	ret = TRUE;
+    }
+
+    g_free(rclass);
+    return ret;
+}
+
+gboolean read_color(XrmDatabase db, char *rname, color_rgb **value)
+{
+    gboolean ret = FALSE;
+    char *rclass = create_class_name(rname);
+    char *rettype;
+    XrmValue retvalue;
+  
+    if (XrmGetResource(db, rname, rclass, &rettype, &retvalue) &&
+	retvalue.addr != NULL) {
+	color_rgb *c = color_parse(retvalue.addr);
+	if (c != NULL) {
+	    *value = c;
+	    ret = TRUE;
+	}
+    }
+
+    g_free(rclass);
+    return ret;
+}
+
+static void parse_appearance(char *tex, SurfaceColorType *grad,
+			  ReliefType *relief, BevelType *bevel,
+			  gboolean *interlaced, gboolean *border)
+{
+    char *t;
+
+    /* convert to all lowercase */
+    for (t = tex; *t != '\0'; ++t)
+	*t = g_ascii_tolower(*t);
+
+    if (strstr(tex, "parentrelative") != NULL) {
+	*grad = Background_ParentRelative;
+    } else {
+	if (strstr(tex, "gradient") != NULL) {
+	    if (strstr(tex, "crossdiagonal") != NULL)
+		*grad = Background_CrossDiagonal;
+	    else if (strstr(tex, "rectangle") != NULL)
+		*grad = Background_Rectangle;
+	    else if (strstr(tex, "pyramid") != NULL)
+		*grad = Background_Pyramid;
+	    else if (strstr(tex, "pipecross") != NULL)
+		*grad = Background_PipeCross;
+	    else if (strstr(tex, "elliptic") != NULL)
+		*grad = Background_Elliptic;
+	    else if (strstr(tex, "horizontal") != NULL)
+		*grad = Background_Horizontal;
+	    else if (strstr(tex, "vertical") != NULL)
+		*grad = Background_Vertical;
+	    else
+		*grad = Background_Diagonal;
+	} else {
+	    *grad = Background_Solid;
+	}
+
+	if (strstr(tex, "sunken") != NULL)
+	    *relief = Sunken;
+	else if (strstr(tex, "flat") != NULL)
+	    *relief = Flat;
+	else
+	    *relief = Raised;
+	
+	*border = FALSE;
+	if (*relief == Flat) {
+	    if (strstr(tex, "border") != NULL)
+		*border = TRUE;
+	} else {
+	    if (strstr(tex, "bevel2") != NULL)
+		*bevel = Bevel2;
+	    else
+		*bevel = Bevel1;
+	}
+
+	if (strstr(tex, "interlaced") != NULL)
+	    *interlaced = TRUE;
+	else
+	    *interlaced = FALSE;
+    }
+}
+
+
+gboolean read_appearance(XrmDatabase db, char *rname, Appearance *value)
+{
+    gboolean ret = FALSE;
+    char *rclass = create_class_name(rname), *cname, *ctoname, *bcname;
+    char *rettype;
+    XrmValue retvalue;
+
+    cname = g_strconcat(rname, ".color", NULL);
+    ctoname = g_strconcat(rname, ".colorTo", NULL);
+    bcname = g_strconcat(rname, ".borderColor", NULL);
+
+    if (XrmGetResource(db, rname, rclass, &rettype, &retvalue) &&
+	retvalue.addr != NULL) {
+	parse_appearance(retvalue.addr,
+			 &value->surface.data.planar.grad,
+			 &value->surface.data.planar.relief,
+			 &value->surface.data.planar.bevel,
+			 &value->surface.data.planar.interlaced,
+			 &value->surface.data.planar.border);
+	if (!read_color(db, cname, &value->surface.data.planar.primary))
+	    value->surface.data.planar.primary = color_new(0, 0, 0);
+	if (!read_color(db, ctoname, &value->surface.data.planar.secondary))
+	    value->surface.data.planar.secondary = color_new(0, 0, 0);
+	if (value->surface.data.planar.border)
+	    if (!read_color(db, bcname,
+			    &value->surface.data.planar.border_color))
+		value->surface.data.planar.border_color = color_new(0, 0, 0);
+	ret = TRUE;
+    }
+
+    g_free(bcname);
+    g_free(ctoname);
+    g_free(cname);
+    g_free(rclass);
+    return ret;
+}
+
+void set_default_appearance(Appearance *a)
+{
+    a->surface.data.planar.grad = Background_Solid;
+    a->surface.data.planar.relief = Flat;
+    a->surface.data.planar.bevel = Bevel1;
+    a->surface.data.planar.interlaced = FALSE;
+    a->surface.data.planar.border = FALSE;
+    a->surface.data.planar.primary = color_new(0, 0, 0);
+    a->surface.data.planar.secondary = color_new(0, 0, 0);
+}
+
+gboolean load()
+{
+    XrmDatabase db = NULL;
+
+    if (themerc_theme != NULL) {
+	db = loaddb(themerc_theme);
+        if (db == NULL) {
+	    g_warning("Failed to load the theme '%s'", themerc_theme);
+	    g_message("Falling back to the default: '%s'", DEFAULT_THEME);
+	}
+    }
+    if (db == NULL) {
+	db = loaddb(DEFAULT_THEME);
+	if (db == NULL) {
+	    g_warning("Failed to load the theme '%s'.", DEFAULT_THEME);
+	    return FALSE;
+	}
+    }
+
+    /* XXX load the font, not from the theme file tho, its in themerc_font */
+    s_font_height = 10;
+
+    if (!read_int(db, "handleWidth", &s_handle_height) ||
+	s_handle_height < 0 || s_handle_height > 100) s_handle_height = 6;
+    if (!read_int(db, "bevelWidth", &s_bevel) ||
+	s_bevel <= 0 || s_bevel > 100) s_bevel = 3;
+    if (!read_int(db, "borderWidth", &s_bwidth) ||
+	s_bwidth < 0 || s_bwidth > 100) s_bwidth = 1;
+    if (!read_int(db, "frameWidth", &s_cbwidth) ||
+	s_cbwidth < 0 || s_cbwidth > 100) s_cbwidth = s_bevel;
+
+    if (!read_color(db, "borderColor", &s_b_color))
+	s_b_color = color_new(0, 0, 0);
+    if (!read_color(db, "window.frame.focusColor", &s_cb_focused_color))
+	s_cb_focused_color = color_new(0xff, 0xff, 0xff);
+    if (!read_color(db, "window.frame.unfocusColor", &s_cb_unfocused_color))
+	s_cb_unfocused_color = color_new(0xff, 0xff, 0xff);
+
+    if (!read_appearance(db, "window.title.focus", a_focused_title))
+	set_default_appearance(a_focused_title);
+    if (!read_appearance(db, "window.title.unfocus", a_unfocused_title))
+	set_default_appearance(a_unfocused_title);
+    if (!read_appearance(db, "window.label.focus", a_focused_label))
+	set_default_appearance(a_focused_label);
+    if (!read_appearance(db, "window.label.unfocus", a_unfocused_label))
+	set_default_appearance(a_unfocused_label);
+    if (!read_appearance(db, "window.handle.focus", a_focused_handle))
+	set_default_appearance(a_focused_handle);
+    if (!read_appearance(db, "window.handle.unfocus", a_unfocused_handle))
+	set_default_appearance(a_unfocused_handle);
+    if (!read_appearance(db, "window.grip.focus", a_focused_grip))
+	set_default_appearance(a_focused_grip);
+    if (!read_appearance(db, "window.grip.unfocus", a_unfocused_grip))
+	set_default_appearance(a_unfocused_grip);
+
+    if (!read_appearance(db, "window.button.pressed.focus",
+			 a_focused_pressed_max))
+	if (!read_appearance(db, "window.button.pressed",
+			     a_focused_pressed_max))
+	    set_default_appearance(a_focused_pressed_max);
+    if (!read_appearance(db, "window.button.pressed.unfocus",
+			 a_unfocused_pressed_max))
+	if (!read_appearance(db, "window.button.pressed",
+			     a_unfocused_pressed_max))
+	    set_default_appearance(a_unfocused_pressed_max);
+    if (!read_appearance(db, "window.button.focus",
+			 a_focused_unpressed_max))
+	    set_default_appearance(a_focused_unpressed_max);
+    if (!read_appearance(db, "window.button.unfocus",
+			 a_unfocused_unpressed_max))
+	    set_default_appearance(a_unfocused_unpressed_max);
+
+    a_unfocused_unpressed_close = appearance_copy(a_unfocused_unpressed_max);
+    a_unfocused_pressed_close = appearance_copy(a_unfocused_pressed_max);
+    a_focused_unpressed_close = appearance_copy(a_focused_unpressed_max);
+    a_focused_pressed_close = appearance_copy(a_focused_pressed_max);
+    a_unfocused_unpressed_desk = appearance_copy(a_unfocused_unpressed_max);
+    a_unfocused_pressed_desk = appearance_copy(a_unfocused_pressed_max);
+    a_focused_unpressed_desk = appearance_copy(a_focused_unpressed_max);
+    a_focused_pressed_desk = appearance_copy(a_focused_pressed_max);
+    a_unfocused_unpressed_iconify = appearance_copy(a_unfocused_unpressed_max);
+    a_unfocused_pressed_iconify = appearance_copy(a_unfocused_pressed_max);
+    a_focused_unpressed_iconify = appearance_copy(a_focused_unpressed_max);
+    a_focused_pressed_iconify = appearance_copy(a_focused_pressed_max);
+
+    a_icon->surface.data.planar.grad = Background_ParentRelative;
+
+    /* XXX load the button masks */
+
+    XrmDestroyDatabase(db);
+    return TRUE;
+}
+
+
diff --git a/engines/openbox/theme.h b/engines/openbox/theme.h
new file mode 100644
index 0000000..1b6011f
--- /dev/null
+++ b/engines/openbox/theme.h
@@ -0,0 +1,8 @@
+#ifndef __engine_theme_h
+#define __engine_theme_h
+
+#include 
+
+gboolean load();
+
+#endif
diff --git a/m4/openbox.m4 b/m4/openbox.m4
index 645301f..aef6828 100644
--- a/m4/openbox.m4
+++ b/m4/openbox.m4
@@ -2,6 +2,7 @@
 #
 # Check if the user has requested a debug build.
 # Sets the DEBUG or NDEBUG variables as appropriate
+# Sets the CVS environment variable when building CVS sources.
 AC_DEFUN([OB_DEBUG],
 [
   DEBUG="no"
@@ -28,47 +29,55 @@ AC_DEFUN([OB_DEBUG],
 # keep the asserts in
 #    AC_DEFINE([NDEBUG], [1], [Creating a release build])
   fi
+
+  AM_CONDITIONAL(CVS, test "$CVS" = "yes")
 ])
 
 
 # OB_COMPILER_FLAGS()
 #
 # Check what compiler is being used for compilation.
-# It sets the CXXFLAGS variable appropriately for the compiler, including flags
+# It sets the CFLAGS variable appropriately for the compiler, including flags
 # for debug builds.
 AC_DEFUN([OB_COMPILER_FLAGS],
 [
-  AC_REQUIRE([AC_PROG_CXXCPP])
-  AC_REQUIRE([AC_PROG_CXX])
+  AC_REQUIRE([AC_PROG_CPP])
+  AC_REQUIRE([AC_PROG_CC])
 
   FLAGS=""
 
   # Check what compiler we are using
-  AC_MSG_CHECKING([for GNU C++])
-  if test "$GXX" = "yes"; then
+  AC_MSG_CHECKING([for GNU CC])
+  if test "$GCC" = "yes"; then
     AC_MSG_RESULT([yes])
-    FLAGS="-Wall -W -fno-check-new -fno-exceptions"
-    # -pedantic
-    test "$DEBUG" = "yes" && FLAGS="$FLAGS -g -fno-inline"
-  else
-    AC_MSG_RESULT([no, trying other compilers])
-    AC_MSG_CHECKING(for MIPSpro)
-    mips_pro_ver=`$CXX -version 2>&1 | grep -i mipspro | cut -f4 -d ' '`
-    if test -z "$mips_pro_ver"; then
-      AC_MSG_RESULT([no])
+    if test "$DEBUG" = "yes"; then
+      FLAGS="-g -fno-inline"
+      FLAGS="$FLAGS -Wall -Wsign-compare -Waggregate-return"
+      FLAGS="$FLAGS -Wcast-qual -Wbad-function-cast -Wpointer-arith"
+      # for Python.h
+      FLAGS="$FLAGS -Wno-long-long"
     else
-      AC_MSG_RESULT([yes, version $mips_pro_ver.])
-      AC_MSG_CHECKING(for -LANG:std in CXXFLAGS)
-      lang_std_not_set=`echo $CXXFLAGS | grep "\-LANG:std"`
-      if test "x$lang_std_not_set" = "x"; then
-        AC_MSG_RESULT([not set, setting.])
-        FLAGS="-LANG:std"
-      else
-        AC_MSG_RESULT([already set.])
-      fi
+      FLAGS=""
     fi
+#  else
+#    AC_MSG_RESULT([no, trying other compilers])
+#    AC_MSG_CHECKING(for MIPSpro)
+#    mips_pro_ver=`$CC -version 2>&1 | grep -i mipspro | cut -f4 -d ' '`
+#    if test -z "$mips_pro_ver"; then
+#      AC_MSG_RESULT([no])
+#    else
+#      AC_MSG_RESULT([yes, version $mips_pro_ver.])
+#      AC_MSG_CHECKING(for -LANG:std in CFLAGS)
+#      lang_std_not_set=`echo $CFLAGS | grep "\-LANG:std"`
+#      if test "x$lang_std_not_set" = "x"; then
+#        AC_MSG_RESULT([not set, setting.])
+#        FLAGS="-LANG:std"
+#      else
+#        AC_MSG_RESULT([already set.])
+#      fi
+#    fi
   fi
   AC_MSG_CHECKING([for compiler specific flags])
   AC_MSG_RESULT([$FLAGS])
-  CXXFLAGS="$CXXFLAGS $FLAGS"
+  CFLAGS="$CFLAGS $FLAGS"
 ])
diff --git a/m4/swig.m4 b/m4/swig.m4
deleted file mode 100644
index ad6d139..0000000
--- a/m4/swig.m4
+++ /dev/null
@@ -1,90 +0,0 @@
-# SWIG_PROG([required-version])
-#
-# Checks for the SWIG program.  If found you can (and should) call SWIG via $(SWIG).
-# You can use the optional first argument to check if the version of the available SWIG
-# is greater or equal to the value of the argument.  It should have the format:
-# N[.N[.N]] (N is a number between 0 and 999.  Only the first N is mandatory.)
-AC_DEFUN([SWIG_PROG],[
-	AC_REQUIRE([AC_PROG_MAKE_SET])
-	AC_CHECK_PROG(SWIG,swig,[`which swig`])
-	if test -z "$SWIG" ; then
-		AC_MSG_WARN([cannot find 'swig' program])
-		SWIG=false
-	elif test -n "$1" ; then
-		AC_MSG_CHECKING([for SWIG version])
-		swig_version=`$SWIG -version 2>&1 | \
-			awk '/^SWIG Version [[0-9]+\.[0-9]+\.[0-9]]+.*$/ { split($[3],a,"[[^.0-9]]"); print a[[1]] }'`
-		AC_MSG_RESULT([$swig_version])
-		if test -n "$swig_version" ; then
-			swig_version=`echo $swig_version | \
-				awk '{ split($[1],a,"\."); print [a[1]*1000000+a[2]*1000+a[3]] }' 2>/dev/null`
-			swig_required_version=`echo $1 | \
-				awk '{ split($[1],a,"\."); print [a[1]*1000000+a[2]*1000+a[3]] }' 2>/dev/null`
-			if test $swig_required_version -gt $swig_version ; then
-				AC_MSG_WARN([SWIG version $1 required])
-			fi
-		else
-			AC_MSG_WARN([cannot determine SWIG version])
-		fi
-	fi
-])
-
-# SWIG_ENABLE_CXX()
-#
-# Enable swig C++ support.  This effects all invocations of $(SWIG).
-AC_DEFUN([SWIG_ENABLE_CXX],[
-	AC_REQUIRE([SWIG_PROG])
-	AC_REQUIRE([AC_PROG_CXX])
-	if test "$SWIG" != "false" ; then
-		SWIG="$SWIG -c++"
-	fi
-])
-
-# SWIG_MULTI_MODULE_SUPPORT()
-#
-# Enable support for multiple modules.  This effects all invocations of $(SWIG).
-# You have to link all generated modules against the appropriate SWIG library.
-# If you want to build Python modules for example, use the SWIG_PYTHON() macro
-# and link the modules against $(SWIG_PYTHON_LIB).  The $(SWIG_LDFLAGS) variable
-# can be used to help the linker to find this library.
-AC_DEFUN([SWIG_MULTI_MODULE_SUPPORT],[
-	AC_REQUIRE([SWIG_PROG])
-	if test "$SWIG" != "false" ; then
-		SWIG="$SWIG -c"
-        
-		# Check for SWIG library path
-		AC_MSG_CHECKING([for SWIG library path])
-		swig_path=${SWIG%/bin*}/lib
-		swig_path=`find $swig_path -type f -name libswig*.a -o -name libswig*.so -print`
-		for i in $swig_path ; do
-			swig_path=${i%/libswig*}
-			break
-		done
-		AC_MSG_RESULT([$swig_path])
-		if test -n "$swig_path" ; then
-			AC_SUBST(SWIG_LDFLAGS,[-L$swig_path])
-		else
-			AC_MSG_WARN([cannot find SWIG library path])
-		fi
-	fi
-])
-
-# SWIG_PYTHON([use-shadow-classes])
-#
-# Checks for Python and provides the $(SWIG_PYTHON_CFLAGS), $(SWIG_PYTHON_LIB) and
-# $(SWIG_PYTHON_OPT) output variables.  $(SWIG_PYTHON_OPT) contains all necessary swig
-# options to generate code for Python.  Shadow classes are enabled unless the
-# value of the optional first argument is exactly 'no'.  If you need multi module
-# support use $(SWIG_PYTHON_LIB) (provided by the SWIG_MULTI_MODULE_SUPPORT() macro)
-# to link against the appropriate library.  It contains the SWIG Python runtime library
-# that is needed by the type check system for example.
-AC_DEFUN([SWIG_PYTHON],[
-	AC_REQUIRE([SWIG_PROG])
-	AC_REQUIRE([PYTHON_DEVEL])
-	if test "$SWIG" != "false" ; then
-		AC_SUBST(SWIG_PYTHON_LIB,[`$SWIG -python -ldflags`])
-		test ! "x$1" = "xno" && swig_shadow=" -shadow" || swig_shadow=""
-		AC_SUBST(SWIG_PYTHON_OPT,[-python$swig_shadow])
-	fi
-	AC_SUBST(SWIG_PYTHON_CFLAGS,[$PYTHON_CFLAGS])
-])
diff --git a/openbox/.cvsignore b/openbox/.cvsignore
new file mode 100644
index 0000000..4d46e8c
--- /dev/null
+++ b/openbox/.cvsignore
@@ -0,0 +1,5 @@
+ob3
+Makefile.in
+Makefile
+.libs
+.deps
diff --git a/openbox/Makefile.am b/openbox/Makefile.am
new file mode 100644
index 0000000..5eff8fd
--- /dev/null
+++ b/openbox/Makefile.am
@@ -0,0 +1,33 @@
+localedir=$(datadir)/locale
+themercdir=$(datadir)/openbox
+scriptdir=$(libdir)/openbox/python
+enginedir=$(libdir)/openbox/engines
+
+CPPFLAGS=$(XFT_CFLAGS) $(PYTHON_CFLAGS) $(GLIB_CFLAGS) $(GMODULE_CFLAGS) \
+@CPPFLAGS@ \
+-DLOCALEDIR=\"$(localedir)\" \
+-DTHEMERCDIR=\"$(themercdir)\" \
+-DSCRIPTDIR=\"$(scriptdir)\" \
+-DENGINEDIR=\"$(enginedir)\" \
+-DDEFAULT_ENGINE=\"openbox\" \
+-DG_LOG_DOMAIN=\"Openbox\"
+
+LIBS=$(XFT_LIBS) $(PYTHON_LIBS) $(GLIB_LIBS) $(GMODULE_LIBS) @LIBS@
+
+bin_PROGRAMS= ob3
+
+ob3_LDADD=@LIBINTL@ ../render/librender.a
+ob3_LDFLAGS=-export-dynamic
+ob3_SOURCES=client.c event.c extensions.c focus.c frame.c openbox.c prop.c \
+	python.c screen.c stacking.c xerror.c hooks.c themerc.c timer.c \
+	clientwrap.c openboxwrap.c pointer.c keyboard.c engine.c
+
+noinst_HEADERS=client.h event.h extensions.h focus.h frame.h geom.h gettext.h \
+	openbox.h prop.h python.h screen.h stacking.h xerror.h themerc.h \
+	timer.h hooks.h clientwrap.h openboxwrap.h pointer.h keyboard.h \
+	engine.h
+
+MAINTAINERCLEANFILES= Makefile.in
+
+distclean-local:
+	$(RM) *\~ *.orig *.rej .\#*
diff --git a/openbox/client.c b/openbox/client.c
new file mode 100644
index 0000000..40c6120
--- /dev/null
+++ b/openbox/client.c
@@ -0,0 +1,1896 @@
+#include "client.h"
+#include "screen.h"
+#include "prop.h"
+#include "extensions.h"
+#include "frame.h"
+#include "engine.h"
+#include "event.h"
+#include "focus.h"
+#include "stacking.h"
+#include "pointer.h"
+#include "hooks.h"
+#include "openboxwrap.h"
+#include "clientwrap.h"
+
+#include 
+
+/*! The event mask to grab on client windows */
+#define CLIENT_EVENTMASK (PropertyChangeMask | FocusChangeMask | \
+			  StructureNotifyMask)
+
+#define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
+				ButtonMotionMask)
+
+GSList     *client_list      = NULL;
+GHashTable *client_map       = NULL;
+
+static void client_get_all(Client *self);
+static void client_toggle_border(Client *self, gboolean show);
+static void client_get_area(Client *self);
+static void client_get_desktop(Client *self);
+static void client_get_state(Client *self);
+static void client_get_shaped(Client *self);
+static void client_get_mwm_hints(Client *self);
+static void client_get_gravity(Client *self);
+static void client_change_allowed_actions(Client *self);
+static void client_change_state(Client *self);
+static Client *search_focus_tree(Client *node, Client *skip);
+static void client_apply_startup_state(Client *self);
+static Client *search_modal_tree(Client *node, Client *skip);
+
+static guint map_hash(Window w) { return w; }
+static gboolean map_key_comp(Window w1, Window w2) { return w1 == w2; }
+
+void client_startup()
+{
+    client_map = g_hash_table_new((GHashFunc)map_hash,
+				  (GEqualFunc)map_key_comp);
+    client_set_list();
+}
+
+void client_shutdown()
+{
+    g_hash_table_destroy(client_map);
+}
+
+void client_set_list()
+{
+    Window *windows, *win_it;
+    GSList *it;
+    guint size = g_slist_length(client_list);
+
+    /* create an array of the window ids */
+    if (size > 0) {
+	windows = g_new(Window, size);
+	win_it = windows;
+	for (it = client_list; it != NULL; it = it->next, ++win_it)
+	    *win_it = ((Client*)it->data)->window;
+    } else
+	windows = NULL;
+
+    PROP_SET32A(ob_root, net_client_list, window, windows, size);
+
+    if (windows)
+	g_free(windows);
+
+    stacking_set_list();
+}
+
+void client_manage_all()
+{
+    unsigned int i, j, nchild;
+    Window w, *children;
+    XWMHints *wmhints;
+    XWindowAttributes attrib;
+
+    XQueryTree(ob_display, ob_root, &w, &w, &children, &nchild);
+
+    /* remove all icon windows from the list */
+    for (i = 0; i < nchild; i++) {
+	if (children[i] == None) continue;
+	wmhints = XGetWMHints(ob_display, children[i]);
+	if (wmhints) {
+	    if ((wmhints->flags & IconWindowHint) &&
+		(wmhints->icon_window != children[i]))
+		for (j = 0; j < nchild; j++)
+		    if (children[j] == wmhints->icon_window) {
+			children[j] = None;
+			break;
+		    }
+	    XFree(wmhints);
+	}
+    }
+
+    for (i = 0; i < nchild; ++i) {
+	if (children[i] == None)
+	    continue;
+	if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
+	    if (attrib.override_redirect) continue;
+
+	    if (attrib.map_state != IsUnmapped)
+		client_manage(children[i]);
+	}
+    }
+    XFree(children);
+}
+
+void client_manage(Window window)
+{
+    Client *client;
+    XEvent e;
+    XWindowAttributes attrib;
+    XSetWindowAttributes attrib_set;
+/*    XWMHints *wmhint; */
+    PyObject *cw;
+     
+    XGrabServer(ob_display);
+    XSync(ob_display, FALSE);
+
+    /* check if it has already been unmapped by the time we started mapping
+       the grab does a sync so we don't have to here */
+    if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
+	XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e)) {
+	XPutBackEvent(ob_display, &e);
+    
+	XUngrabServer(ob_display);
+	XFlush(ob_display);
+	return; /* don't manage it */
+    }
+
+    /* make sure it isn't an override-redirect window */
+    if (!XGetWindowAttributes(ob_display, window, &attrib) ||
+	attrib.override_redirect) {
+	XUngrabServer(ob_display);
+	XFlush(ob_display);
+	return; /* don't manage it */
+    }
+  
+/*    /\* is the window a docking app *\/
+    if ((wmhint = XGetWMHints(ob_display, window))) {
+	if ((wmhint->flags & StateHint) &&
+	    wmhint->initial_state == WithdrawnState) {
+	    /\* XXX: make dock apps work! *\/
+	    XUngrabServer(ob_display);
+	    XFlush(ob_display);
+	    XFree(wmhint);
+	    return;
+	}
+	XFree(wmhint);
+    }
+*/
+
+    /* choose the events we want to receive on the CLIENT window */
+    attrib_set.event_mask = CLIENT_EVENTMASK;
+    attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
+    XChangeWindowAttributes(ob_display, window,
+			    CWEventMask|CWDontPropagate, &attrib_set);
+
+
+    /* create the Client struct, and populate it from the hints on the
+       window */
+    client = g_new(Client, 1);
+    client->window = window;
+    client_get_all(client);
+
+    /* remove the client's border (and adjust re gravity) */
+    client_toggle_border(client, FALSE);
+     
+    /* specify that if we exit, the window should not be destroyed and should
+       be reparented back to root automatically */
+    XChangeSaveSet(ob_display, window, SetModeInsert);
+
+    /* create the decoration frame for the client window */
+    client->frame = engine_frame_new();
+
+    engine_frame_grab_client(client->frame, client);
+
+    client_apply_startup_state(client);
+
+    XUngrabServer(ob_display);
+    XFlush(ob_display);
+     
+    client_list = g_slist_append(client_list, client);
+    stacking_list = g_list_append(stacking_list, client);
+    g_hash_table_insert(client_map, (gpointer)window, client);
+
+    stacking_raise(client);
+
+    screen_update_struts();
+
+    /* add to the python list */
+    cw = clientwrap_new(client);
+    PyList_Append(openboxwrap_obj->client_list, cw);
+    Py_DECREF(cw);
+
+    HOOKFIRECLIENT(managed, client);
+
+    client_showhide(client, TRUE);
+
+    /* grab all mouse bindings */
+    pointer_grab_all(client, TRUE);
+
+    /* update the list hints */
+    client_set_list();
+
+    g_message("Managed window 0x%lx", window);
+}
+
+void client_unmanage_all()
+{
+    while (client_list != NULL)
+	client_unmanage(client_list->data);
+}
+
+void client_unmanage(Client *client)
+{
+    int j, seq;
+    PyObject *cw;
+    GSList *it;
+
+    g_message("Unmanaging window: %lx", client->window);
+
+    HOOKFIRECLIENT(closed, client);
+
+    /* remove the window from our save set */
+    XChangeSaveSet(ob_display, client->window, SetModeDelete);
+
+    /* we dont want events no more */
+    XSelectInput(ob_display, client->window, NoEventMask);
+
+    /* ungrab any mouse bindings */
+    pointer_grab_all(client, FALSE);
+     
+    engine_frame_hide(client->frame);
+
+    /* give the client its border back */
+    client_toggle_border(client, TRUE);
+
+    /* reparent the window out of the frame, and free the frame */
+    engine_frame_release_client(client->frame, client);
+     
+    client_list = g_slist_remove(client_list, client);
+    stacking_list = g_list_remove(stacking_list, client);
+    g_hash_table_remove(client_map, (gpointer)client->window);
+
+    /* once the client is out of the list, update the struts to remove it's
+       influence */
+    screen_update_struts();
+
+    /* remove from the python list */
+    cw = clientwrap_new(client);
+    seq = PySequence_Index(openboxwrap_obj->client_list, cw);
+    if (seq == -1)
+	PyErr_Clear();
+    else
+	PySequence_DelItem(openboxwrap_obj->client_list, seq);
+    Py_DECREF(cw);
+
+    /* notify the wrapper that its useless now */
+    if (client->wrap != NULL)
+	client->wrap->client = NULL;
+
+    /* tell our parent that we're gone */
+    if (client->transient_for != NULL)
+	client->transient_for->transients =
+	    g_slist_remove(client->transient_for->transients, client);
+
+    /* tell our transients that we're gone */
+    for (it = client->transients; it != NULL; it = it->next) {
+	((Client*)it->data)->transient_for = NULL;
+	client_calc_layer(it->data);
+    }
+
+    /* unfocus the client (calls the focus callbacks) (we're out of the
+     transient lists already, so being modal doesn't matter) */
+    if (client->focused)
+	client_unfocus(client);
+    
+    if (ob_state != State_Exiting) {
+	/* these values should not be persisted across a window
+	   unmapping/mapping */
+	prop_erase(client->window, prop_atoms.net_wm_desktop);
+	prop_erase(client->window, prop_atoms.net_wm_state);
+    } else {
+	/* if we're left in an iconic state, the client wont be mapped. this is
+	   bad, since we will no longer be managing the window on restart */
+	if (client->iconic)
+	    XMapWindow(ob_display, client->window);
+    }
+
+    /* free all data allocated in the client struct */
+    g_slist_free(client->transients);
+    for (j = 0; j < client->nicons; ++j)
+	g_free(client->icons[j].data);
+    if (client->nicons > 0)
+	g_free(client->icons);
+    g_free(client->title);
+    g_free(client->icon_title);
+    g_free(client->res_name);
+    g_free(client->res_class);
+    g_free(client->role);
+    g_free(client);
+     
+    /* update the list hints */
+    client_set_list();
+}
+
+static void client_toggle_border(Client *self, gboolean show)
+{
+    /* adjust our idea of where the client is, based on its border. When the
+       border is removed, the client should now be considered to be in a
+       different position.
+       when re-adding the border to the client, the same operation needs to be
+       reversed. */
+    int oldx = self->area.x, oldy = self->area.y;
+    int x = oldx, y = oldy;
+    switch(self->gravity) {
+    default:
+    case NorthWestGravity:
+    case WestGravity:
+    case SouthWestGravity:
+	break;
+    case NorthEastGravity:
+    case EastGravity:
+    case SouthEastGravity:
+	if (show) x -= self->border_width * 2;
+	else      x += self->border_width * 2;
+	break;
+    case NorthGravity:
+    case SouthGravity:
+    case CenterGravity:
+    case ForgetGravity:
+    case StaticGravity:
+	if (show) x -= self->border_width;
+	else      x += self->border_width;
+	break;
+    }
+    switch(self->gravity) {
+    default:
+    case NorthWestGravity:
+    case NorthGravity:
+    case NorthEastGravity:
+	break;
+    case SouthWestGravity:
+    case SouthGravity:
+    case SouthEastGravity:
+	if (show) y -= self->border_width * 2;
+	else      y += self->border_width * 2;
+	break;
+    case WestGravity:
+    case EastGravity:
+    case CenterGravity:
+    case ForgetGravity:
+    case StaticGravity:
+	if (show) y -= self->border_width;
+	else      y += self->border_width;
+	break;
+    }
+    self->area.x = x;
+    self->area.y = y;
+
+    if (show) {
+	XSetWindowBorderWidth(ob_display, self->window, self->border_width);
+
+	/* move the client so it is back it the right spot _with_ its
+	   border! */
+	if (x != oldx || y != oldy)
+	    XMoveWindow(ob_display, self->window, x, y);
+    } else
+	XSetWindowBorderWidth(ob_display, self->window, 0);
+}
+
+
+static void client_get_all(Client *self)
+{
+    /* update EVERYTHING!! */
+
+    self->ignore_unmaps = 0;
+  
+    /* defaults */
+    self->frame = NULL;
+    self->title = self->icon_title = NULL;
+    self->res_name = self->res_class = self->role = NULL;
+    self->wmstate = NormalState;
+    self->focused = FALSE;
+    self->transient = FALSE;
+    self->transients = NULL;
+    self->transient_for = NULL;
+    self->layer = -1;
+    self->urgent = FALSE;
+    self->positioned = FALSE;
+    self->disabled_decorations = 0;
+    self->group = None;
+    self->nicons = 0;
+    self->wrap = NULL;
+
+    client_get_area(self);
+    client_get_desktop(self);
+    client_get_state(self);
+    client_get_shaped(self);
+
+    client_update_transient_for(self);
+    client_get_mwm_hints(self);
+    client_get_type(self);/* this can change the mwmhints for special cases */
+
+    client_update_protocols(self);
+
+    client_get_gravity(self); /* get the attribute gravity */
+    client_update_normal_hints(self); /* this may override the attribute
+					 gravity */
+
+    /* got the type, the mwmhints, the protocols, and the normal hints
+       (min/max sizes), so we're ready to set up the decorations/functions */
+    client_setup_decor_and_functions(self);
+  
+    client_update_wmhints(self);
+    client_update_title(self);
+    client_update_icon_title(self);
+    client_update_class(self);
+    client_update_strut(self);
+    client_update_icons(self);
+    client_update_kwm_icon(self);
+
+    /* this makes sure that these windows appear on all desktops */
+    if (self->type == Type_Desktop)
+	self->desktop = DESKTOP_ALL;
+
+    /* set the desktop hint, to make sure that it always exists, and to
+       reflect any changes we've made here */
+    PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
+
+    client_change_state(self);
+}
+
+static void client_get_area(Client *self)
+{
+    XWindowAttributes wattrib;
+    Status ret;
+  
+    ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
+    g_assert(ret != BadWindow);
+
+    RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
+    self->border_width = wattrib.border_width;
+}
+
+static void client_get_desktop(Client *self)
+{
+    unsigned int d;
+
+    if (PROP_GET32(self->window, net_wm_desktop, cardinal, d)) {
+	if (d >= screen_num_desktops && d != DESKTOP_ALL)
+	    d = screen_num_desktops - 1;
+	self->desktop = d;
+    } else {
+	/* defaults to the current desktop */
+	self->desktop = screen_desktop;
+    }
+}
+
+static void client_get_state(Client *self)
+{
+    gulong *state;
+    gulong num;
+  
+    self->modal = self->shaded = self->max_horz = self->max_vert =
+	self->fullscreen = self->above = self->below = self->iconic =
+	self->skip_taskbar = self->skip_pager = FALSE;
+
+    if (PROP_GET32U(self->window, net_wm_state, atom, state, num)) {
+	gulong i;
+	for (i = 0; i < num; ++i) {
+	    if (state[i] == prop_atoms.net_wm_state_modal)
+		self->modal = TRUE;
+	    else if (state[i] == prop_atoms.net_wm_state_shaded)
+		self->shaded = TRUE;
+	    else if (state[i] == prop_atoms.net_wm_state_hidden)
+		self->iconic = TRUE;
+	    else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
+		self->skip_taskbar = TRUE;
+	    else if (state[i] == prop_atoms.net_wm_state_skip_pager)
+		self->skip_pager = TRUE;
+	    else if (state[i] == prop_atoms.net_wm_state_fullscreen)
+		self->fullscreen = TRUE;
+	    else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
+		self->max_vert = TRUE;
+	    else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
+		self->max_horz = TRUE;
+	    else if (state[i] == prop_atoms.net_wm_state_above)
+		self->above = TRUE;
+	    else if (state[i] == prop_atoms.net_wm_state_below)
+		self->below = TRUE;
+	}
+
+	g_free(state);
+    }
+}
+
+static void client_get_shaped(Client *self)
+{
+    self->shaped = FALSE;
+#ifdef   SHAPE
+    if (extensions_shape) {
+	int foo;
+	guint ufoo;
+	int s;
+
+	XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
+
+	XShapeQueryExtents(ob_display, self->window, &s, &foo,
+			   &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
+			   &ufoo);
+	self->shaped = (s != 0);
+    }
+#endif
+}
+
+void client_update_transient_for(Client *self)
+{
+    Window t = None;
+    Client *c = NULL;
+
+    if (XGetTransientForHint(ob_display, self->window, &t) &&
+	t != self->window) { /* cant be transient to itself! */
+	self->transient = TRUE;
+	c = g_hash_table_lookup(client_map, (gpointer)t);
+	g_assert(c != self);/* if this happens then we need to check for it*/
+
+	if (!c /*XXX: && _group*/) {
+	    /* not transient to a client, see if it is transient for a
+	       group */
+	    if (/*t == _group->leader() || */
+		t == None ||
+		t == ob_root) {
+		/* window is a transient for its group! */
+		/* XXX: for now this is treated as non-transient.
+		   this needs to be fixed! */
+	    }
+	}
+    } else
+	self->transient = FALSE;
+
+    /* if anything has changed... */
+    if (c != self->transient_for) {
+	if (self->transient_for)
+	    /* remove from old parent */
+	    g_slist_remove(self->transient_for->transients, self);
+	self->transient_for = c;
+	if (self->transient_for)
+	    /* add to new parent */
+	    g_slist_append(self->transient_for->transients, self);
+    }
+}
+
+static void client_get_mwm_hints(Client *self)
+{
+    unsigned long num;
+    unsigned long *hints;
+
+    self->mwmhints.flags = 0; /* default to none */
+
+    if (PROP_GET32U(self->window, motif_wm_hints, motif_wm_hints, hints, num)) {
+	if (num >= MWM_ELEMENTS) {
+	    self->mwmhints.flags = hints[0];
+	    self->mwmhints.functions = hints[1];
+	    self->mwmhints.decorations = hints[2];
+	}
+	g_free(hints);
+    }
+}
+
+void client_get_type(Client *self)
+{
+    gulong *val, num, i;
+
+    self->type = -1;
+  
+    if (PROP_GET32U(self->window, net_wm_window_type, atom, val, num)) {
+	/* use the first value that we know about in the array */
+	for (i = 0; i < num; ++i) {
+	    if (val[i] == prop_atoms.net_wm_window_type_desktop)
+		self->type = Type_Desktop;
+	    else if (val[i] == prop_atoms.net_wm_window_type_dock)
+		self->type = Type_Dock;
+	    else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
+		self->type = Type_Toolbar;
+	    else if (val[i] == prop_atoms.net_wm_window_type_menu)
+		self->type = Type_Menu;
+	    else if (val[i] == prop_atoms.net_wm_window_type_utility)
+		self->type = Type_Utility;
+	    else if (val[i] == prop_atoms.net_wm_window_type_splash)
+		self->type = Type_Splash;
+	    else if (val[i] == prop_atoms.net_wm_window_type_dialog)
+		self->type = Type_Dialog;
+	    else if (val[i] == prop_atoms.net_wm_window_type_normal)
+		self->type = Type_Normal;
+	    else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
+		/* prevent this window from getting any decor or
+		   functionality */
+		self->mwmhints.flags &= (MwmFlag_Functions |
+					 MwmFlag_Decorations);
+		self->mwmhints.decorations = 0;
+		self->mwmhints.functions = 0;
+	    }
+	    if (self->type != (WindowType) -1)
+		break; /* grab the first legit type */
+	}
+	g_free(val);
+    }
+    
+    if (self->type == (WindowType) -1) {
+	/*the window type hint was not set, which means we either classify
+	  ourself as a normal window or a dialog, depending on if we are a
+	  transient. */
+	if (self->transient)
+	    self->type = Type_Dialog;
+	else
+	    self->type = Type_Normal;
+    }
+}
+
+void client_update_protocols(Client *self)
+{
+    Atom *proto;
+    gulong num_return, i;
+
+    self->focus_notify = FALSE;
+    self->delete_window = FALSE;
+
+    if (PROP_GET32U(self->window, wm_protocols, atom, proto, num_return)) {
+	for (i = 0; i < num_return; ++i) {
+	    if (proto[i] == prop_atoms.wm_delete_window) {
+		/* this means we can request the window to close */
+		self->delete_window = TRUE;
+	    } else if (proto[i] == prop_atoms.wm_take_focus)
+		/* if this protocol is requested, then the window will be
+		   notified whenever we want it to receive focus */
+		self->focus_notify = TRUE;
+	}
+	g_free(proto);
+    }
+}
+
+static void client_get_gravity(Client *self)
+{
+    XWindowAttributes wattrib;
+    Status ret;
+
+    ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
+    g_assert(ret != BadWindow);
+    self->gravity = wattrib.win_gravity;
+}
+
+void client_update_normal_hints(Client *self)
+{
+    XSizeHints size;
+    long ret;
+    int oldgravity = self->gravity;
+
+    /* defaults */
+    self->min_ratio = 0.0f;
+    self->max_ratio = 0.0f;
+    SIZE_SET(self->size_inc, 1, 1);
+    SIZE_SET(self->base_size, 0, 0);
+    SIZE_SET(self->min_size, 0, 0);
+    SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
+
+    /* get the hints from the window */
+    if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
+	self->positioned = (size.flags & (PPosition|USPosition));
+
+	if (size.flags & PWinGravity) {
+	    self->gravity = size.win_gravity;
+      
+	    /* if the client has a frame, i.e. has already been mapped and
+	       is changing its gravity */
+	    if (self->frame && self->gravity != oldgravity) {
+		/* move our idea of the client's position based on its new
+		   gravity */
+		self->area.x = self->frame->area.x;
+		self->area.y = self->frame->area.y;
+		frame_frame_gravity(self->frame, &self->area.x, &self->area.y);
+	    }
+	}
+
+	if (size.flags & PAspect) {
+	    if (size.min_aspect.y)
+		self->min_ratio = (float)size.min_aspect.x / size.min_aspect.y;
+	    if (size.max_aspect.y)
+		self->max_ratio = (float)size.max_aspect.x / size.max_aspect.y;
+	}
+
+	if (size.flags & PMinSize)
+	    SIZE_SET(self->min_size, size.min_width, size.min_height);
+    
+	if (size.flags & PMaxSize)
+	    SIZE_SET(self->max_size, size.max_width, size.max_height);
+    
+	if (size.flags & PBaseSize)
+	    SIZE_SET(self->base_size, size.base_width, size.base_height);
+    
+	if (size.flags & PResizeInc)
+	    SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
+    }
+}
+
+void client_setup_decor_and_functions(Client *self)
+{
+    /* start with everything (cept fullscreen) */
+    self->decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
+	Decor_Icon | Decor_AllDesktops | Decor_Iconify | Decor_Maximize;
+    self->functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
+	Func_Shade;
+    if (self->delete_window) {
+	self->decorations |= Decor_Close;
+	self->functions |= Func_Close;
+    }
+
+    if (!(self->min_size.width < self->max_size.width ||
+	  self->min_size.height < self->max_size.height)) {
+	self->decorations &= ~(Decor_Maximize | Decor_Handle);
+	self->functions &= ~(Func_Resize | Func_Maximize);
+    }
+
+    switch (self->type) {
+    case Type_Normal:
+	/* normal windows retain all of the possible decorations and
+	   functionality, and are the only windows that you can fullscreen */
+	self->functions |= Func_Fullscreen;
+	break;
+
+    case Type_Dialog:
+	/* dialogs cannot be maximized */
+	self->decorations &= ~Decor_Maximize;
+	self->functions &= ~Func_Maximize;
+	break;
+
+    case Type_Menu:
+    case Type_Toolbar:
+    case Type_Utility:
+	/* these windows get less functionality */
+	self->decorations &= ~(Decor_Iconify | Decor_Handle);
+	self->functions &= ~(Func_Iconify | Func_Resize);
+	break;
+
+    case Type_Desktop:
+    case Type_Dock:
+    case Type_Splash:
+	/* none of these windows are manipulated by the window manager */
+	self->decorations = 0;
+	self->functions = 0;
+	break;
+    }
+
+    /* Mwm Hints are applied subtractively to what has already been chosen for
+       decor and functionality */
+    if (self->mwmhints.flags & MwmFlag_Decorations) {
+	if (! (self->mwmhints.decorations & MwmDecor_All)) {
+	    if (! (self->mwmhints.decorations & MwmDecor_Border))
+		self->decorations &= ~Decor_Border;
+	    if (! (self->mwmhints.decorations & MwmDecor_Handle))
+		self->decorations &= ~Decor_Handle;
+	    if (! (self->mwmhints.decorations & MwmDecor_Title))
+		self->decorations &= ~Decor_Titlebar;
+	    if (! (self->mwmhints.decorations & MwmDecor_Iconify))
+		self->decorations &= ~Decor_Iconify;
+	    if (! (self->mwmhints.decorations & MwmDecor_Maximize))
+		self->decorations &= ~Decor_Maximize;
+	}
+    }
+
+    if (self->mwmhints.flags & MwmFlag_Functions) {
+	if (! (self->mwmhints.functions & MwmFunc_All)) {
+	    if (! (self->mwmhints.functions & MwmFunc_Resize))
+		self->functions &= ~Func_Resize;
+	    if (! (self->mwmhints.functions & MwmFunc_Move))
+		self->functions &= ~Func_Move;
+	    if (! (self->mwmhints.functions & MwmFunc_Iconify))
+		self->functions &= ~Func_Iconify;
+	    if (! (self->mwmhints.functions & MwmFunc_Maximize))
+		self->functions &= ~Func_Maximize;
+	    /* dont let mwm hints kill the close button
+	       if (! (self->mwmhints.functions & MwmFunc_Close))
+	       self->functions &= ~Func_Close; */
+	}
+    }
+
+    /* can't maximize without moving/resizing */
+    if (!((self->functions & Func_Move) && (self->functions & Func_Resize)))
+	self->functions &= ~Func_Maximize;
+
+    /* finally, user specified disabled decorations are applied to subtract
+       decorations */
+    if (self->disabled_decorations & Decor_Titlebar)
+	self->decorations &= ~Decor_Titlebar;
+    if (self->disabled_decorations & Decor_Handle)
+	self->decorations &= ~Decor_Handle;
+    if (self->disabled_decorations & Decor_Border)
+	self->decorations &= ~Decor_Border;
+    if (self->disabled_decorations & Decor_Iconify)
+	self->decorations &= ~Decor_Iconify;
+    if (self->disabled_decorations & Decor_Maximize)
+	self->decorations &= ~Decor_Maximize;
+    if (self->disabled_decorations & Decor_AllDesktops)
+	self->decorations &= ~Decor_AllDesktops;
+    if (self->disabled_decorations & Decor_Close)
+	self->decorations &= ~Decor_Close;
+
+    /* if we don't have a titlebar, then we cannot shade! */
+    if (!(self->decorations & Decor_Titlebar))
+	self->functions &= ~Func_Shade;
+
+    client_change_allowed_actions(self);
+
+    if (self->frame) {
+	/* change the decors on the frame */
+	engine_frame_adjust_size(self->frame);
+	/* with more/less decorations, we may need to be repositioned */
+	engine_frame_adjust_position(self->frame);
+	/* with new decor, the window's maximized size may change */
+	client_remaximize(self);
+    }
+}
+
+static void client_change_allowed_actions(Client *self)
+{
+    Atom actions[9];
+    int num = 0;
+
+    actions[num++] = prop_atoms.net_wm_action_change_desktop;
+
+    if (self->functions & Func_Shade)
+	actions[num++] = prop_atoms.net_wm_action_shade;
+    if (self->functions & Func_Close)
+	actions[num++] = prop_atoms.net_wm_action_close;
+    if (self->functions & Func_Move)
+	actions[num++] = prop_atoms.net_wm_action_move;
+    if (self->functions & Func_Iconify)
+	actions[num++] = prop_atoms.net_wm_action_minimize;
+    if (self->functions & Func_Resize)
+	actions[num++] = prop_atoms.net_wm_action_resize;
+    if (self->functions & Func_Fullscreen)
+	actions[num++] = prop_atoms.net_wm_action_fullscreen;
+    if (self->functions & Func_Maximize) {
+	actions[num++] = prop_atoms.net_wm_action_maximize_horz;
+	actions[num++] = prop_atoms.net_wm_action_maximize_vert;
+    }
+
+    PROP_SET32A(self->window, net_wm_allowed_actions, atom, actions, num);
+
+    /* make sure the window isn't breaking any rules now */
+
+    if (!(self->functions & Func_Shade) && self->shaded) {
+	if (self->frame) client_shade(self, FALSE);
+	else self->shaded = FALSE;
+    }
+    if (!(self->functions & Func_Iconify) && self->iconic) {
+	if (self->frame) client_iconify(self, FALSE, TRUE);
+	else self->iconic = FALSE;
+    }
+    if (!(self->functions & Func_Fullscreen) && self->fullscreen) {
+	if (self->frame) client_fullscreen(self, FALSE, TRUE);
+	else self->fullscreen = FALSE;
+    }
+    if (!(self->functions & Func_Maximize) && (self->max_horz ||
+					       self->max_vert)) {
+	if (self->frame) client_maximize(self, FALSE, 0, TRUE);
+	else self->max_vert = self->max_horz = FALSE;
+    }
+}
+
+void client_remaximize(Client *self)
+{
+    int dir;
+    if (self->max_horz && self->max_vert)
+	dir = 0;
+    else if (self->max_horz)
+	dir = 1;
+    else if (self->max_vert)
+	dir = 2;
+    else
+	return; /* not maximized */
+    self->max_horz = self->max_vert = FALSE;
+    client_maximize(self, TRUE, dir, FALSE);
+}
+
+void client_update_wmhints(Client *self)
+{
+    XWMHints *hints;
+    gboolean ur = FALSE;
+
+    /* assume a window takes input if it doesnt specify */
+    self->can_focus = TRUE;
+  
+    if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
+	if (hints->flags & InputHint)
+	    self->can_focus = hints->input;
+
+	/* only do this when starting! */
+	if (ob_state == State_Starting && (hints->flags & StateHint))
+	    self->iconic = hints->initial_state == IconicState;
+
+	if (hints->flags & XUrgencyHint)
+	    ur = TRUE;
+
+	if (hints->flags & WindowGroupHint) {
+	    if (hints->window_group != self->group) {
+		/* XXX: remove from the old group if there was one */
+		self->group = hints->window_group;
+		/* XXX: do stuff with the group */
+	    }
+	} else /* no group! */
+	    self->group = None;
+
+	if (hints->flags & IconPixmapHint) {
+	    client_update_kwm_icon(self);
+	    /* try get the kwm icon first, this is a fallback only */
+	    if (self->pixmap_icon == None) {
+		self->pixmap_icon = hints->icon_pixmap;
+		if (hints->flags & IconMaskHint)
+		    self->pixmap_icon_mask = hints->icon_mask;
+		else
+		    self->pixmap_icon_mask = None;
+
+		if (self->frame)
+		    engine_frame_adjust_icon(self->frame);
+	    }
+	}
+
+	XFree(hints);
+    }
+
+    if (ur != self->urgent) {
+	self->urgent = ur;
+	g_message("Urgent Hint for 0x%lx: %s\n", self->window,
+		  ur ? "ON" : "OFF");
+	/* fire the urgent callback if we're mapped, otherwise, wait until
+	   after we're mapped */
+	if (self->frame)
+	    HOOKFIRECLIENT(urgent, self);
+    }
+}
+
+void client_update_title(Client *self)
+{
+    gchar *data = NULL;
+
+    if (self->title != NULL)
+	g_free(self->title);
+     
+    /* try netwm */
+    if (!PROP_GETS(self->window, net_wm_name, utf8, data)) {
+	/* try old x stuff */
+	if (PROP_GETS(self->window, wm_name, string, data)) {
+	    /* convert it to UTF-8 */
+	    gsize r, w;
+	    gchar *u;
+
+	    u = g_locale_to_utf8(data, -1, &r, &w, NULL);
+	    if (u == NULL) {
+		g_warning("Unable to convert string to UTF-8");
+	    } else {
+		g_free(data);
+		data = u;
+	    }
+	}
+	if (data == NULL)
+	    data = g_strdup("Unnamed Window");
+
+	PROP_SETS(self->window, net_wm_visible_name, utf8, data);
+    }
+
+    self->title = data;
+
+    if (self->frame)
+	engine_frame_adjust_title(self->frame);
+}
+
+void client_update_icon_title(Client *self)
+{
+    gchar *data = NULL;
+
+    if (self->icon_title != NULL)
+	g_free(self->icon_title);
+     
+    /* try netwm */
+    if (!PROP_GETS(self->window, net_wm_icon_name, utf8, data)) {
+	/* try old x stuff */
+	if (PROP_GETS(self->window, wm_icon_name, string, data)) {
+	    /* convert it to UTF-8 */
+	    gsize r, w;
+	    gchar *u;
+
+	    u = g_locale_to_utf8(data, -1, &r, &w, NULL);
+	    if (u == NULL) {
+		g_warning("Unable to convert string to UTF-8");
+	    } else {
+		g_free(data);
+		data = u;
+	    }
+	}
+	if (data == NULL)
+	    data = g_strdup("Unnamed Window");
+
+	PROP_SETS(self->window, net_wm_visible_icon_name, utf8, data);
+    }
+
+    self->icon_title = data;
+}
+
+void client_update_class(Client *self)
+{
+    GPtrArray *data;
+    gchar *s;
+    guint i;
+
+    if (self->res_name) g_free(self->res_name);
+    if (self->res_class) g_free(self->res_class);
+    if (self->role) g_free(self->role);
+
+    self->res_name = self->res_class = self->role = NULL;
+
+    data = g_ptr_array_new();
+     
+    if (PROP_GETSA(self->window, wm_class, string, data)) {
+	if (data->len > 0)
+	    self->res_name = g_strdup(g_ptr_array_index(data, 0));
+	if (data->len > 1)
+	    self->res_class = g_strdup(g_ptr_array_index(data, 1));
+    }
+     
+    for (i = 0; i < data->len; ++i)
+	g_free(g_ptr_array_index(data, i));
+    g_ptr_array_free(data, TRUE);
+
+    if (PROP_GETS(self->window, wm_window_role, string, s))
+	self->role = g_strdup(s);
+
+    if (self->res_name == NULL) self->res_name = g_strdup("");
+    if (self->res_class == NULL) self->res_class = g_strdup("");
+    if (self->role == NULL) self->role = g_strdup("");
+}
+
+void client_update_strut(Client *self)
+{
+    gulong *data;
+
+    if (PROP_GET32A(self->window, net_wm_strut, cardinal, data, 4)) {
+	STRUT_SET(self->strut, data[0], data[1], data[2], data[3]);
+	g_free(data);
+    } else
+	STRUT_SET(self->strut, 0, 0, 0, 0);
+
+    /* updating here is pointless while we're being mapped cuz we're not in
+       the client list yet */
+    if (self->frame)
+	screen_update_struts();
+}
+
+void client_update_icons(Client *self)
+{
+    unsigned long num;
+    unsigned long *data;
+    unsigned long w, h, i;
+    int j;
+
+    for (j = 0; j < self->nicons; ++j)
+	g_free(self->icons[j].data);
+    if (self->nicons > 0)
+	g_free(self->icons);
+    self->nicons = 0;
+
+    if (PROP_GET32U(self->window, net_wm_icon, cardinal, data, num)) {
+	/* figure out how many valid icons are in here */
+	i = 0;
+	while (num - i > 2) {
+	    w = data[i++];
+	    h = data[i++];
+	    i += w * h;
+	    if (i > num) break;
+	    ++self->nicons;
+	}
+
+	self->icons = g_new(Icon, self->nicons);
+    
+	/* store the icons */
+	i = 0;
+	for (j = 0; j < self->nicons; ++j) {
+	    w = self->icons[j].w = data[i++];
+	    h = self->icons[j].h = data[i++];
+	    self->icons[j].data =
+		g_memdup(&data[i], w * h * sizeof(gulong));
+	    i += w * h;
+	    g_assert(i <= num);
+	}
+
+	g_free(data);
+    }
+
+    if (self->nicons <= 0) {
+	self->nicons = 1;
+	self->icons = g_new0(Icon, 1);
+    }
+
+    if (self->frame)
+	engine_frame_adjust_icon(self->frame);
+}
+
+void client_update_kwm_icon(Client *self)
+{
+    Pixmap *data;
+
+    if (PROP_GET32A(self->window, kwm_win_icon, kwm_win_icon, data, 2)) {
+	self->pixmap_icon = data[0];
+	self->pixmap_icon_mask = data[1];
+	g_free(data);
+    } else {
+	self->pixmap_icon = self->pixmap_icon_mask = None;
+    }
+    if (self->frame)
+	engine_frame_adjust_icon(self->frame);
+}
+
+static void client_change_state(Client *self)
+{
+    unsigned long state[2];
+    Atom netstate[10];
+    int num;
+
+    state[0] = self->wmstate;
+    state[1] = None;
+    PROP_SET32A(self->window, wm_state, wm_state, state, 2);
+
+    num = 0;
+    if (self->modal)
+	netstate[num++] = prop_atoms.net_wm_state_modal;
+    if (self->shaded)
+	netstate[num++] = prop_atoms.net_wm_state_shaded;
+    if (self->iconic)
+	netstate[num++] = prop_atoms.net_wm_state_hidden;
+    if (self->skip_taskbar)
+	netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
+    if (self->skip_pager)
+	netstate[num++] = prop_atoms.net_wm_state_skip_pager;
+    if (self->fullscreen)
+	netstate[num++] = prop_atoms.net_wm_state_fullscreen;
+    if (self->max_vert)
+	netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
+    if (self->max_horz)
+	netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
+    if (self->above)
+	netstate[num++] = prop_atoms.net_wm_state_above;
+    if (self->below)
+	netstate[num++] = prop_atoms.net_wm_state_below;
+    PROP_SET32A(self->window, net_wm_state, atom, netstate, num);
+
+    client_calc_layer(self);
+
+    if (self->frame)
+	engine_frame_adjust_state(self->frame);
+}
+
+static Client *search_focus_tree(Client *node, Client *skip)
+{
+    GSList *it;
+    Client *ret;
+
+    for (it = node->transients; it != NULL; it = g_slist_next(it)) {
+	Client *c = it->data;
+	if (c == skip) continue; /* circular? */
+	if ((ret = search_focus_tree(c, skip))) return ret;
+	if (c->focused) return c;
+    }
+    return NULL;
+}
+
+void client_calc_layer(Client *self)
+{
+    StackLayer l;
+    gboolean fs;
+    Client *c;
+
+    /* are we fullscreen, or do we have a fullscreen transient parent? */
+    c = self;
+    fs = FALSE;
+    while (c) {
+	if (c->fullscreen) {
+	    fs = TRUE;
+	    break;
+	}
+	c = c->transient_for;
+    }
+    if (!fs && self->fullscreen) {
+	/* is one of our transients focused? */
+	c = search_focus_tree(self, self);
+	if (c != NULL) fs = TRUE;
+    }
+  
+    if (self->iconic) l = Layer_Icon;
+    else if (fs) l = Layer_Fullscreen;
+    else if (self->type == Type_Desktop) l = Layer_Desktop;
+    else if (self->type == Type_Dock) {
+	if (!self->below) l = Layer_Top;
+	else l = Layer_Normal;
+    }
+    else if (self->above) l = Layer_Above;
+    else if (self->below) l = Layer_Below;
+    else l = Layer_Normal;
+     
+    if (l != self->layer) {
+	self->layer = l;
+	if (self->frame)
+	    stacking_raise(self);
+    }
+}
+
+void client_showhide(Client *self, gboolean firehook)
+{
+    gboolean show;
+
+    if (self->iconic) show = FALSE;
+    else if (!(self->desktop == screen_desktop ||
+	       self->desktop == DESKTOP_ALL)) show = FALSE;
+    else if (client_normal(self) && screen_showing_desktop) show = FALSE;
+    else show = TRUE;
+
+    if (show) engine_frame_show(self->frame);
+    else      engine_frame_hide(self->frame);
+
+    if (firehook)
+	HOOKFIRECLIENT(visible, self);
+}
+
+gboolean client_normal(Client *self) {
+    return ! (self->type == Type_Desktop || self->type == Type_Dock ||
+	      self->type == Type_Splash);
+}
+
+static void client_apply_startup_state(Client *self)
+{
+    /* these are in a carefully crafted order.. */
+
+    if (self->iconic) {
+	self->iconic = FALSE;
+	client_iconify(self, TRUE, FALSE);
+    }
+    if (self->fullscreen) {
+	self->fullscreen = FALSE;
+	client_fullscreen(self, TRUE, FALSE);
+    }
+    if (self->shaded) {
+	self->shaded = FALSE;
+	client_shade(self, TRUE);
+    }
+    if (self->urgent)
+	HOOKFIRECLIENT(urgent, self);
+  
+    if (self->max_vert && self->max_horz) {
+	self->max_vert = self->max_horz = FALSE;
+	client_maximize(self, TRUE, 0, FALSE);
+    } else if (self->max_vert) {
+	self->max_vert = FALSE;
+	client_maximize(self, TRUE, 2, FALSE);
+    } else if (self->max_horz) {
+	self->max_horz = FALSE;
+	client_maximize(self, TRUE, 1, FALSE);
+    }
+
+    /* nothing to do for the other states:
+       skip_taskbar
+       skip_pager
+       modal
+       above
+       below
+    */
+}
+
+void client_configure(Client *self, Corner anchor, int x, int y, int w, int h,
+		      gboolean user, gboolean final)
+{
+    gboolean moved = FALSE, resized = FALSE;
+
+    w -= self->base_size.width;
+    h -= self->base_size.height;
+
+    if (user) {
+	/* for interactive resizing. have to move half an increment in each
+	   direction. */
+
+	/* how far we are towards the next size inc */
+	int mw = w % self->size_inc.width; 
+	int mh = h % self->size_inc.height;
+	/* amount to add */
+	int aw = self->size_inc.width / 2;
+	int ah = self->size_inc.height / 2;
+	/* don't let us move into a new size increment */
+	if (mw + aw >= self->size_inc.width)
+	    aw = self->size_inc.width - mw - 1;
+	if (mh + ah >= self->size_inc.height)
+	    ah = self->size_inc.height - mh - 1;
+	w += aw;
+	h += ah;
+    
+	/* if this is a user-requested resize, then check against min/max
+	   sizes and aspect ratios */
+
+	/* smaller than min size or bigger than max size? */
+	if (w > self->max_size.width) w = self->max_size.width;
+	if (w < self->min_size.width) w = self->min_size.width;
+	if (h > self->max_size.height) h = self->max_size.height;
+	if (h < self->min_size.height) h = self->min_size.height;
+
+	/* adjust the height ot match the width for the aspect ratios */
+	if (self->min_ratio)
+	    if (h * self->min_ratio > w) h = (int)(w / self->min_ratio);
+	if (self->max_ratio)
+	    if (h * self->max_ratio < w) h = (int)(w / self->max_ratio);
+    }
+
+    /* keep to the increments */
+    w /= self->size_inc.width;
+    h /= self->size_inc.height;
+
+    /* you cannot resize to nothing */
+    if (w < 1) w = 1;
+    if (h < 1) h = 1;
+  
+    /* store the logical size */
+    SIZE_SET(self->logical_size, w, h);
+
+    w *= self->size_inc.width;
+    h *= self->size_inc.height;
+
+    w += self->base_size.width;
+    h += self->base_size.height;
+
+    switch (anchor) {
+    case Corner_TopLeft:
+	break;
+    case Corner_TopRight:
+	x -= w - self->area.width;
+	break;
+    case Corner_BottomLeft:
+	y -= h - self->area.height;
+	break;
+    case Corner_BottomRight:
+	x -= w - self->area.width;
+	y -= h - self->area.height;
+	break;
+    }
+
+    moved = x != self->area.x || y != self->area.y;
+    resized = w != self->area.width || h != self->area.height;
+
+    RECT_SET(self->area, x, y, w, h);
+
+    if (resized)
+	XResizeWindow(ob_display, self->window, w, h);
+
+    /* move/resize the frame to match the request */
+    if (self->frame) {
+	/* Adjust the size and then the position, as required by the EWMH */
+	if (resized)
+	    engine_frame_adjust_size(self->frame);
+	if (moved) {
+	    engine_frame_adjust_position(self->frame);
+
+	    if (!user || final) {
+		XEvent event;
+		event.type = ConfigureNotify;
+		event.xconfigure.display = ob_display;
+		event.xconfigure.event = self->window;
+		event.xconfigure.window = self->window;
+    
+		/* root window coords with border in mind */
+		event.xconfigure.x = x - self->border_width +
+		    self->frame->size.left;
+		event.xconfigure.y = y - self->border_width +
+		    self->frame->size.top;
+    
+		event.xconfigure.width = self->area.width;
+		event.xconfigure.height = self->area.height;
+		event.xconfigure.border_width = self->border_width;
+		event.xconfigure.above = self->frame->plate;
+		event.xconfigure.override_redirect = FALSE;
+		XSendEvent(event.xconfigure.display, event.xconfigure.window,
+			   FALSE, StructureNotifyMask, &event);
+	    }
+	}
+    }
+}
+
+void client_fullscreen(Client *self, gboolean fs, gboolean savearea)
+{
+    static int saved_func, saved_decor;
+    int x, y, w, h;
+
+    if (!(self->functions & Func_Fullscreen) || /* can't */
+	self->fullscreen == fs) return;         /* already done */
+
+    self->fullscreen = fs;
+    client_change_state(self); /* change the state hints on the client */
+
+    if (fs) {
+	/* save the functions and remove them */
+	saved_func = self->functions;
+	self->functions &= (Func_Close | Func_Fullscreen |
+			    Func_Iconify);
+	/* save the decorations and remove them */
+	saved_decor = self->decorations;
+	self->decorations = 0;
+	if (savearea) {
+	    long dimensions[4];
+	    dimensions[0] = self->area.x;
+	    dimensions[1] = self->area.y;
+	    dimensions[2] = self->area.width;
+	    dimensions[3] = self->area.height;
+  
+	    PROP_SET32A(self->window, openbox_premax, cardinal,
+			dimensions, 4);
+	}
+	x = 0;
+	y = 0;
+	w = screen_physical_size.width;
+	h = screen_physical_size.height;
+    } else {
+	long *dimensions;
+
+	self->functions = saved_func;
+	self->decorations = saved_decor;
+	  
+	if (PROP_GET32A(self->window, openbox_premax, cardinal,
+			dimensions, 4)) {
+	    x = dimensions[0];
+	    y = dimensions[1];
+	    w = dimensions[2];
+	    h = dimensions[3];
+	    g_free(dimensions);
+	} else {
+	    /* pick some fallbacks... */
+	    x = screen_area(self->desktop)->x +
+		screen_area(self->desktop)->width / 4;
+	    y = screen_area(self->desktop)->y +
+		screen_area(self->desktop)->height / 4;
+	    w = screen_area(self->desktop)->width / 2;
+	    h = screen_area(self->desktop)->height / 2;
+	}
+    }
+
+    client_change_allowed_actions(self); /* based on the new _functions */
+
+    /* when fullscreening, don't obey things like increments, fill the
+       screen */
+    client_configure(self, Corner_TopLeft, x, y, w, h, !fs, TRUE);
+
+    /* raise (back) into our stacking layer */
+    stacking_raise(self);
+
+    /* try focus us when we go into fullscreen mode */
+    client_focus(self);
+}
+
+void client_iconify(Client *self, gboolean iconic, gboolean curdesk)
+{
+    if (self->iconic == iconic) return; /* nothing to do */
+
+    g_message("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
+	      self->window);
+
+    self->iconic = iconic;
+
+    if (iconic) {
+	self->wmstate = IconicState;
+	self->ignore_unmaps++;
+	/* we unmap the client itself so that we can get MapRequest events,
+	   and because the ICCCM tells us to! */
+	XUnmapWindow(ob_display, self->window);
+    } else {
+	if (curdesk)
+	    client_set_desktop(self, screen_desktop);
+	self->wmstate = self->shaded ? IconicState : NormalState;
+	XMapWindow(ob_display, self->window);
+    }
+    client_change_state(self);
+    client_showhide(self, TRUE);
+    screen_update_struts();
+}
+
+void client_maximize(Client *self, gboolean max, int dir, gboolean savearea)
+{
+    int x, y, w, h;
+     
+    g_assert(dir == 0 || dir == 1 || dir == 2);
+    if (!(self->functions & Func_Maximize)) return; /* can't */
+
+    /* check if already done */
+    if (max) {
+	if (dir == 0 && self->max_horz && self->max_vert) return;
+	if (dir == 1 && self->max_horz) return;
+	if (dir == 2 && self->max_vert) return;
+    } else {
+	if (dir == 0 && !self->max_horz && !self->max_vert) return;
+	if (dir == 1 && !self->max_horz) return;
+	if (dir == 2 && !self->max_vert) return;
+    }
+
+    /* work with the frame's coords */
+    x = self->frame->area.x;
+    y = self->frame->area.y;
+    w = self->area.width;
+    h = self->area.height;
+
+    if (max) {
+	if (savearea) {
+	    long dimensions[4];
+	    long *readdim;
+
+	    dimensions[0] = x;
+	    dimensions[1] = y;
+	    dimensions[2] = w;
+	    dimensions[3] = h;
+
+	    /* get the property off the window and use it for the dimensions
+	       we are already maxed on */
+	    if (PROP_GET32A(self->window, openbox_premax, cardinal,
+			    readdim, 4)) {
+		if (self->max_horz) {
+		    dimensions[0] = readdim[0];
+		    dimensions[2] = readdim[2];
+		}
+		if (self->max_vert) {
+		    dimensions[1] = readdim[1];
+		    dimensions[3] = readdim[3];
+		}
+		g_free(readdim);
+	    }
+
+	    PROP_SET32A(self->window, openbox_premax, cardinal,
+			dimensions, 4);
+	}
+	if (dir == 0 || dir == 1) { /* horz */
+	    x = screen_area(self->desktop)->x - self->frame->size.left;
+	    w = screen_area(self->desktop)->x +
+		screen_area(self->desktop)->width;
+	}
+	if (dir == 0 || dir == 2) { /* vert */
+	    y = screen_area(self->desktop)->y;
+	    h = screen_area(self->desktop)->y +
+		screen_area(self->desktop)->height -
+		self->frame->size.top - self->frame->size.bottom;
+	}
+    } else {
+	long *dimensions;
+
+	if (PROP_GET32A(self->window, openbox_premax, cardinal,
+			dimensions, 4)) {
+	    if (dir == 0 || dir == 1) { /* horz */
+		x = dimensions[0];
+		w = dimensions[2];
+	    }
+	    if (dir == 0 || dir == 2) { /* vert */
+		y = dimensions[1];
+		h = dimensions[3];
+	    }
+	    g_free(dimensions);
+	} else {
+	    /* pick some fallbacks... */
+	    if (dir == 0 || dir == 1) { /* horz */
+		x = screen_area(self->desktop)->x +
+		    screen_area(self->desktop)->width / 4;
+		w = screen_area(self->desktop)->width / 2;
+	    }
+	    if (dir == 0 || dir == 2) { /* vert */
+		y = screen_area(self->desktop)->y +
+		    screen_area(self->desktop)->height / 4;
+		h = screen_area(self->desktop)->height / 2;
+	    }
+	}
+    }
+
+    if (dir == 0 || dir == 1) /* horz */
+	self->max_horz = max;
+    if (dir == 0 || dir == 2) /* vert */
+	self->max_vert = max;
+
+    if (!self->max_horz && !self->max_vert)
+	PROP_ERASE(self->window, openbox_premax);
+
+    client_change_state(self); /* change the state hints on the client */
+
+    /* figure out where the client should be going */
+    frame_frame_gravity(self->frame, &x, &y);
+    client_configure(self, Corner_TopLeft, x, y, w, h, TRUE, TRUE);
+}
+
+void client_shade(Client *self, gboolean shade)
+{
+    if (!(self->functions & Func_Shade) || /* can't */
+	self->shaded == shade) return;     /* already done */
+
+    /* when we're iconic, don't change the wmstate */
+    if (!self->iconic)
+	self->wmstate = shade ? IconicState : NormalState;
+    self->shaded = shade;
+    client_change_state(self);
+    engine_frame_adjust_size(self->frame);
+}
+
+void client_close(Client *self)
+{
+    XEvent ce;
+
+    if (!(self->functions & Func_Close)) return;
+
+    /*
+      XXX: itd be cool to do timeouts and shit here for killing the client's
+      process off
+      like... if the window is around after 5 seconds, then the close button
+      turns a nice red, and if this function is called again, the client is
+      explicitly killed.
+    */
+
+    ce.xclient.type = ClientMessage;
+    ce.xclient.message_type =  prop_atoms.wm_protocols;
+    ce.xclient.display = ob_display;
+    ce.xclient.window = self->window;
+    ce.xclient.format = 32;
+    ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
+    ce.xclient.data.l[1] = CurrentTime;
+    ce.xclient.data.l[2] = 0l;
+    ce.xclient.data.l[3] = 0l;
+    ce.xclient.data.l[4] = 0l;
+    XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
+}
+
+void client_set_desktop(Client *self, unsigned int target)
+{
+    if (target == self->desktop) return;
+  
+    g_message("Setting desktop %u\n", target);
+
+    if (!(target < screen_num_desktops ||
+	  target == DESKTOP_ALL))
+	return;
+
+    self->desktop = target;
+    PROP_SET32(self->window, net_wm_desktop, cardinal, target);
+    /* the frame can display the current desktop state */
+    engine_frame_adjust_state(self->frame);
+    /* 'move' the window to the new desktop */
+    client_showhide(self, TRUE);
+    screen_update_struts();
+}
+
+static Client *search_modal_tree(Client *node, Client *skip)
+{
+    GSList *it;
+    Client *ret;
+  
+    for (it = node->transients; it != NULL; it = it->next) {
+	Client *c = it->data;
+	if (c == skip) continue; /* circular? */
+	if ((ret = search_modal_tree(c, skip))) return ret;
+	if (c->modal) return c;
+    }
+    return NULL;
+}
+
+Client *client_find_modal_child(Client *self)
+{
+    return search_modal_tree(self, self);
+}
+
+gboolean client_validate(Client *self)
+{
+    XEvent e; 
+
+    XSync(ob_display, FALSE); /* get all events on the server */
+
+    if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
+	XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
+	XPutBackEvent(ob_display, &e);
+	return FALSE;
+    }
+
+    return TRUE;
+}
+
+void client_set_wm_state(Client *self, long state)
+{
+    if (state == self->wmstate) return; /* no change */
+  
+    switch (state) {
+    case IconicState:
+	client_iconify(self, TRUE, TRUE);
+	break;
+    case NormalState:
+	client_iconify(self, FALSE, TRUE);
+	break;
+    }
+}
+
+void client_set_state(Client *self, Atom action, long data1, long data2)
+{
+    gboolean shaded = self->shaded;
+    gboolean fullscreen = self->fullscreen;
+    gboolean max_horz = self->max_horz;
+    gboolean max_vert = self->max_vert;
+    int i;
+
+    if (!(action == prop_atoms.net_wm_state_add ||
+	  action == prop_atoms.net_wm_state_remove ||
+	  action == prop_atoms.net_wm_state_toggle))
+	/* an invalid action was passed to the client message, ignore it */
+	return; 
+
+    for (i = 0; i < 2; ++i) {
+	Atom state = i == 0 ? data1 : data2;
+    
+	if (!state) continue;
+
+	/* if toggling, then pick whether we're adding or removing */
+	if (action == prop_atoms.net_wm_state_toggle) {
+	    if (state == prop_atoms.net_wm_state_modal)
+		action = self->modal ? prop_atoms.net_wm_state_remove :
+		    prop_atoms.net_wm_state_add;
+	    else if (state == prop_atoms.net_wm_state_maximized_vert)
+		action = self->max_vert ? prop_atoms.net_wm_state_remove :
+		    prop_atoms.net_wm_state_add;
+	    else if (state == prop_atoms.net_wm_state_maximized_horz)
+		action = self->max_horz ? prop_atoms.net_wm_state_remove :
+		    prop_atoms.net_wm_state_add;
+	    else if (state == prop_atoms.net_wm_state_shaded)
+		action = self->shaded ? prop_atoms.net_wm_state_remove :
+		    prop_atoms.net_wm_state_add;
+	    else if (state == prop_atoms.net_wm_state_skip_taskbar)
+		action = self->skip_taskbar ?
+		    prop_atoms.net_wm_state_remove :
+		    prop_atoms.net_wm_state_add;
+	    else if (state == prop_atoms.net_wm_state_skip_pager)
+		action = self->skip_pager ?
+		    prop_atoms.net_wm_state_remove :
+		    prop_atoms.net_wm_state_add;
+	    else if (state == prop_atoms.net_wm_state_fullscreen)
+		action = self->fullscreen ?
+		    prop_atoms.net_wm_state_remove :
+		    prop_atoms.net_wm_state_add;
+	    else if (state == prop_atoms.net_wm_state_above)
+		action = self->above ? prop_atoms.net_wm_state_remove :
+		    prop_atoms.net_wm_state_add;
+	    else if (state == prop_atoms.net_wm_state_below)
+		action = self->below ? prop_atoms.net_wm_state_remove :
+		    prop_atoms.net_wm_state_add;
+	}
+    
+	if (action == prop_atoms.net_wm_state_add) {
+	    if (state == prop_atoms.net_wm_state_modal) {
+		/* XXX raise here or something? */
+		self->modal = TRUE;
+	    } else if (state == prop_atoms.net_wm_state_maximized_vert) {
+		max_vert = TRUE;
+	    } else if (state == prop_atoms.net_wm_state_maximized_horz) {
+		max_horz = TRUE;
+	    } else if (state == prop_atoms.net_wm_state_shaded) {
+		shaded = TRUE;
+	    } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
+		self->skip_taskbar = TRUE;
+	    } else if (state == prop_atoms.net_wm_state_skip_pager) {
+		self->skip_pager = TRUE;
+	    } else if (state == prop_atoms.net_wm_state_fullscreen) {
+		fullscreen = TRUE;
+	    } else if (state == prop_atoms.net_wm_state_above) {
+		self->above = TRUE;
+	    } else if (state == prop_atoms.net_wm_state_below) {
+		self->below = TRUE;
+	    }
+
+	} else { /* action == prop_atoms.net_wm_state_remove */
+	    if (state == prop_atoms.net_wm_state_modal) {
+		self->modal = FALSE;
+	    } else if (state == prop_atoms.net_wm_state_maximized_vert) {
+		max_vert = FALSE;
+	    } else if (state == prop_atoms.net_wm_state_maximized_horz) {
+		max_horz = FALSE;
+	    } else if (state == prop_atoms.net_wm_state_shaded) {
+		shaded = FALSE;
+	    } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
+		self->skip_taskbar = FALSE;
+	    } else if (state == prop_atoms.net_wm_state_skip_pager) {
+		self->skip_pager = FALSE;
+	    } else if (state == prop_atoms.net_wm_state_fullscreen) {
+		fullscreen = FALSE;
+	    } else if (state == prop_atoms.net_wm_state_above) {
+		self->above = FALSE;
+	    } else if (state == prop_atoms.net_wm_state_below) {
+		self->below = FALSE;
+	    }
+	}
+    }
+    if (max_horz != self->max_horz || max_vert != self->max_vert) {
+	if (max_horz != self->max_horz && max_vert != self->max_vert) {
+	    /* toggling both */
+	    if (max_horz == max_vert) { /* both going the same way */
+		client_maximize(self, max_horz, 0, TRUE);
+	    } else {
+		client_maximize(self, max_horz, 1, TRUE);
+		client_maximize(self, max_vert, 2, TRUE);
+	    }
+	} else {
+	    /* toggling one */
+	    if (max_horz != self->max_horz)
+		client_maximize(self, max_horz, 1, TRUE);
+	    else
+		client_maximize(self, max_vert, 2, TRUE);
+	}
+    }
+    /* change fullscreen state before shading, as it will affect if the window
+       can shade or not */
+    if (fullscreen != self->fullscreen)
+	client_fullscreen(self, fullscreen, TRUE);
+    if (shaded != self->shaded)
+	client_shade(self, shaded);
+    client_calc_layer(self);
+    client_change_state(self); /* change the hint to relect these changes */
+}
+
+gboolean client_focus(Client *self)
+{
+    XEvent ev;
+    Client *child;
+     
+    /* if we have a modal child, then focus it, not us */
+    child = client_find_modal_child(self);
+    if (child)
+	return client_focus(child);
+
+    /* won't try focus if the client doesn't want it, or if the window isn't
+       visible on the screen */
+    if (!(self->frame->visible &&
+	  (self->can_focus || self->focus_notify)))
+	return FALSE;
+
+    /* do a check to see if the window has already been unmapped or destroyed
+       do this intelligently while watching out for unmaps we've generated
+       (ignore_unmaps > 0) */
+    if (XCheckTypedWindowEvent(ob_display, self->window,
+			       DestroyNotify, &ev)) {
+	XPutBackEvent(ob_display, &ev);
+	return FALSE;
+    }
+    while (XCheckTypedWindowEvent(ob_display, self->window,
+				  UnmapNotify, &ev)) {
+	if (self->ignore_unmaps) {
+	    self->ignore_unmaps--;
+	} else {
+	    XPutBackEvent(ob_display, &ev);
+	    return FALSE;
+	}
+    }
+     
+    if (self->can_focus)
+	XSetInputFocus(ob_display, self->window, RevertToNone, CurrentTime);
+
+    if (self->focus_notify) {
+	XEvent ce;
+	ce.xclient.type = ClientMessage;
+	ce.xclient.message_type = prop_atoms.wm_protocols;
+	ce.xclient.display = ob_display;
+	ce.xclient.window = self->window;
+	ce.xclient.format = 32;
+	ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
+	ce.xclient.data.l[1] = event_lasttime;
+	ce.xclient.data.l[2] = 0l;
+	ce.xclient.data.l[3] = 0l;
+	ce.xclient.data.l[4] = 0l;
+	XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
+    }
+
+    /*XSync(ob_display, FALSE); XXX Why sync? */
+    return TRUE;
+}
+
+void client_unfocus(Client *self)
+{
+    g_assert(focus_client == self);
+    focus_set_client(NULL);
+}
diff --git a/openbox/client.h b/openbox/client.h
new file mode 100644
index 0000000..0f9eaa4
--- /dev/null
+++ b/openbox/client.h
@@ -0,0 +1,448 @@
+#ifndef __client_h
+#define __client_h
+
+#include "geom.h"
+#include "stacking.h"
+#include 
+#include 
+
+struct ClientWrap;
+struct Frame;
+
+
+/*! Holds an icon in ARGB format */
+typedef struct Icon {
+    unsigned long w, h;
+    unsigned long *data;
+} Icon;
+     
+/*! The MWM Hints as retrieved from the window property
+  This structure only contains 3 elements, even though the Motif 2.0
+  structure contains 5. We only use the first 3, so that is all gets
+  defined.
+*/
+typedef struct MwmHints {
+    /*! A bitmask of Client::MwmFlags values */
+    unsigned long flags;
+    /*! A bitmask of Client::MwmFunctions values */
+    unsigned long functions;
+    /*! A bitmask of Client::MwmDecorations values */
+    unsigned long decorations;
+} MwmHints;
+/*! The number of elements in the Client::MwmHints struct */
+#define MWM_ELEMENTS 3
+     
+/*! Possible flags for MWM Hints (defined by Motif 2.0) */
+typedef enum {
+    MwmFlag_Functions   = 1 << 0, /*!< The MMW Hints define funcs */
+    MwmFlag_Decorations = 1 << 1  /*!< The MWM Hints define decor */
+} MwmFlags;
+
+/*! Possible functions for MWM Hints (defined by Motif 2.0) */
+typedef enum {
+    MwmFunc_All      = 1 << 0, /*!< All functions */
+    MwmFunc_Resize   = 1 << 1, /*!< Allow resizing */
+    MwmFunc_Move     = 1 << 2, /*!< Allow moving */
+    MwmFunc_Iconify  = 1 << 3, /*!< Allow to be iconfied */
+    MwmFunc_Maximize = 1 << 4  /*!< Allow to be maximized */
+    /*MwmFunc_Close    = 1 << 5 /!< Allow to be closed */
+} MwmFunctions;
+
+/*! Possible decorations for MWM Hints (defined by Motif 2.0) */
+typedef enum {
+    MwmDecor_All      = 1 << 0, /*!< All decorations */
+    MwmDecor_Border   = 1 << 1, /*!< Show a border */
+    MwmDecor_Handle   = 1 << 2, /*!< Show a handle (bottom) */
+    MwmDecor_Title    = 1 << 3, /*!< Show a titlebar */
+    /*MwmDecor_Menu     = 1 << 4, /!< Show a menu */
+    MwmDecor_Iconify  = 1 << 5, /*!< Show an iconify button */
+    MwmDecor_Maximize = 1 << 6  /*!< Show a maximize button */
+} MemDecorations;
+
+/*! Corners of the client window, used for anchor positions */
+typedef enum {
+    Corner_TopLeft,
+    Corner_TopRight,
+    Corner_BottomLeft,
+    Corner_BottomRight
+} Corner;
+
+/*! Possible window types */
+typedef enum {
+    Type_Desktop, /*!< A desktop (bottom-most window) */
+    Type_Dock,    /*!< A dock bar/panel window */
+    Type_Toolbar, /*!< A toolbar window, pulled off an app */
+    Type_Menu,    /*!< An unpinned menu from an app */
+    Type_Utility, /*!< A small utility window such as a palette */
+    Type_Splash,  /*!< A splash screen window */
+    Type_Dialog,  /*!< A dialog window */
+    Type_Normal   /*!< A normal application window */
+} WindowType;
+
+/*! The things the user can do to the client window */
+typedef enum {
+    Func_Resize     = 1 << 0, /*!< Allow resizing */
+    Func_Move       = 1 << 1, /*!< Allow moving */
+    Func_Iconify    = 1 << 2, /*!< Allow to be iconified */
+    Func_Maximize   = 1 << 3, /*!< Allow to be maximized */
+    Func_Shade      = 1 << 4, /*!< Allow to be shaded */
+    Func_Fullscreen = 1 << 5, /*!< Allow to be made fullscreen */
+    Func_Close      = 1 << 6  /*!< Allow to be closed */
+} Function;
+
+/*! The decorations the client window wants to be displayed on it */
+typedef enum {
+    Decor_Titlebar    = 1 << 0, /*!< Display a titlebar */
+    Decor_Handle      = 1 << 1, /*!< Display a handle (bottom) */
+    Decor_Border      = 1 << 2, /*!< Display a border */
+    Decor_Icon        = 1 << 3, /*!< Display the window's icon */
+    Decor_Iconify     = 1 << 4, /*!< Display an iconify button */
+    Decor_Maximize    = 1 << 5, /*!< Display a maximize button */
+    /*! Display a button to toggle the window's placement on
+      all desktops */
+    Decor_AllDesktops = 1 << 6,
+    Decor_Close       = 1 << 7  /*!< Display a close button */
+} Decoration;
+
+
+typedef struct Client {
+    Window  window;
+
+    struct Frame *frame;
+
+    /*! The number of unmap events to ignore on the window */
+    int ignore_unmaps;
+
+    /*! The id of the group the window belongs to */
+    Window  group;
+    /*! Whether or not the client is a transient window. This is guaranteed to 
+      be TRUE if transient_for != NULL, but not guaranteed to be FALSE if
+      transient_for == NULL. */
+    gboolean transient;
+    /*! The client which this client is a transient (child) for */
+    struct Client *transient_for;
+    /*! The clients which are transients (children) of this client */
+    GSList *transients;
+    /*! The desktop on which the window resides (0xffffffff for all
+      desktops) */
+    unsigned int desktop;
+
+    /*! Normal window title */
+    gchar *title;
+    /*! Window title when iconified */
+    gchar *icon_title;
+
+    /*! The application that created the window */
+    gchar *res_name;
+    /*! The class of the window, can used for grouping */
+    gchar *res_class;
+    /*! The specified role of the window, used for identification */
+    gchar *role;
+
+    /*! The type of window (what its function is) */
+    WindowType type;
+
+    /*! Position and size of the window
+      This will not always be the actual position of the window on screen, it
+      is, rather, the position requested by the client, to which the window's
+      gravity is applied.
+    */
+    Rect    area;
+
+    /*! The window's strut
+      The strut defines areas of the screen that are marked off-bounds for
+      window placement. In theory, where this window exists.
+    */
+    Strut   strut;
+     
+    /*! The logical size of the window
+      The "logical" size of the window is refers to the user's perception of
+      the size of the window, and is the value that should be displayed to the
+      user. For example, with xterms, this value it the number of characters
+      being displayed in the terminal, instead of the number of pixels.
+    */
+    Size   logical_size;
+
+    /*! Width of the border on the window.
+      The window manager will set this to 0 while the window is being managed,
+      but needs to restore it afterwards, so it is saved here.
+    */
+    guint border_width;
+
+    /*! The minimum aspect ratio the client window can be sized to.
+      A value of 0 means this is ignored.
+    */
+    float min_ratio;
+    /*! The maximum aspect ratio the client window can be sized to.
+      A value of 0 means this is ignored.
+    */
+    float max_ratio;
+  
+    /*! The minimum size of the client window
+      If the min is > the max, then the window is not resizable
+    */
+    Size min_size;
+    /*! The maximum size of the client window
+      If the min is > the max, then the window is not resizable
+    */
+    Size max_size;
+    /*! The size of increments to resize the client window by */
+    Size size_inc;
+    /*! The base size of the client window
+      This value should be subtracted from the window's actual size when
+      displaying its size to the user, or working with its min/max size
+    */
+    Size base_size;
+
+    /*! Window decoration and functionality hints */
+    MwmHints mwmhints;
+  
+    /*! Where to place the decorated window in relation to the undecorated
+      window */
+    int gravity;
+
+    /*! The state of the window, one of WithdrawnState, IconicState, or
+      NormalState */
+    long wmstate;
+
+    /*! True if the client supports the delete_window protocol */
+    gboolean delete_window;
+  
+    /*! Was the window's position requested by the application? if not, we
+      should place the window ourselves when it first appears */
+    gboolean positioned;
+  
+    /*! Can the window receive input focus? */
+    gboolean can_focus;
+    /*! Urgency flag */
+    gboolean urgent;
+    /*! Notify the window when it receives focus? */
+    gboolean focus_notify;
+    /*! Does the client window have the input focus? */
+    gboolean focused;
+
+    /*! The window uses shape extension to be non-rectangular? */
+    gboolean shaped;
+
+    /*! The window is modal, so it must be processed before any windows it is
+      related to can be focused */
+    gboolean modal;
+    /*! Only the window's titlebar is displayed */
+    gboolean shaded;
+    /*! The window is iconified */
+    gboolean iconic;
+    /*! The window is maximized to fill the screen vertically */
+    gboolean max_vert;
+    /*! The window is maximized to fill the screen horizontally */
+    gboolean max_horz;
+    /*! The window should not be displayed by pagers */
+    gboolean skip_pager;
+    /*! The window should not be displayed by taskbars */
+    gboolean skip_taskbar;
+    /*! The window is a 'fullscreen' window, and should be on top of all
+      others */
+    gboolean fullscreen;
+    /*! The window should be on top of other windows of the same type.
+      above takes priority over below. */
+    gboolean above;
+    /*! The window should be underneath other windows of the same type.
+      above takes priority over below. */
+    gboolean below;
+
+    /*! The layer in which the window will be stacked, windows in lower layers
+      are always below windows in higher layers. */
+    StackLayer layer;
+
+    /*! A bitmask of values in the Decoration enum
+      The values in the variable are the decorations that the client wants to
+      be displayed around it.
+    */
+    int decorations;
+
+    /*! A bitmask of values in the Decoration enum.
+      Specifies the decorations that should NOT be displayed on the client.
+    */
+    int disabled_decorations;
+
+    /*! A bitmask of values in the Function enum
+      The values in the variable specify the ways in which the user is allowed
+      to modify this window.
+    */
+    int functions;
+
+    /*! Icons for the client as specified on the client window */
+    Icon *icons;
+    /*! The number of icons in icons */
+    int nicons;
+
+    /*! The icon for the client specified in the WMHints or the KWM hints */
+    Pixmap pixmap_icon;
+    /*! The mask for the pixmap_icon, or None if its not masked */
+    Pixmap pixmap_icon_mask;
+
+    /* The instance of the wrapper class if one exists */
+    struct ClientWrap *wrap;
+} Client;
+
+extern GSList *client_list;
+extern GHashTable *client_map;
+
+void client_startup();
+void client_shutdown();
+
+/*! Manages all existing windows */
+void client_manage_all();
+/*! Manages a given window */
+void client_manage(Window win);
+/*! Unmanages all managed windows */
+void client_unmanage_all();
+/*! Unmanages a given client */
+void client_unmanage(Client *client);
+
+/*! Sets the client list on the root window from the client_list */
+void client_set_list();
+
+/*! Reapplies the maximized state to the window
+  Use this to make the window readjust its maximized size to new
+  surroundings (struts, etc). */
+void client_remaximize(Client *self);
+
+/*! Shows the window if it should be shown, or hides it
+  Used when changing desktops, the window's state, etc. */
+void client_showhide(Client *self, gboolean firehook);
+
+/*! Returns if the window should be treated as a normal window.
+  Some windows (desktops, docks, splash screens) have special rules applied
+  to them in a number of places regarding focus or user interaction. */
+gboolean client_normal(Client *self);
+
+/*! Move and/or resize the window.
+  This also maintains things like the client's minsize, and size increments.
+  @param anchor The corner to keep in the same position when resizing.
+  @param x The x coordiante of the new position for the client.
+  @param y The y coordiante of the new position for the client.
+  @param w The width component of the new size for the client.
+  @param h The height component of the new size for the client.
+  @param user Specifies whether this is a user-requested change or a
+              program requested change. For program requested changes, the
+	      constraints are not checked.
+  @param final If user is true, then this should specify if this is a final
+               configuration. e.g. Final should be FALSE if doing an
+	       interactive move/resize, and then be TRUE for the last call
+	       only.
+*/
+void client_configure(Client *self, Corner anchor, int x, int y, int w, int h,
+		      gboolean user, gboolean final);
+
+/*! Fullscreen's or unfullscreen's the client window
+  @param fs true if the window should be made fullscreen; false if it should
+            be returned to normal state.
+  @param savearea true to have the client's current size and position saved;
+                  otherwise, they are not. You should not save when mapping a
+		  new window that is set to fullscreen. This has no effect
+		  when restoring a window from fullscreen.
+*/
+void client_fullscreen(Client *self, gboolean fs, gboolean savearea);
+
+/*! Iconifies or uniconifies the client window
+  @param iconic true if the window should be iconified; false if it should be
+                restored.
+  @param curdesk If iconic is FALSE, then this determines if the window will
+                 be uniconified to the current viewable desktop (true) or to
+		 its previous desktop (false)
+*/
+void client_iconify(Client *self, gboolean iconic, gboolean curdesk);
+
+/*! Maximize or unmaximize the client window
+  @param max true if the window should be maximized; false if it should be
+             returned to normal size.
+  @param dir 0 to set both horz and vert, 1 to set horz, 2 to set vert.
+  @param savearea true to have the client's current size and position saved;
+                  otherwise, they are not. You should not save when mapping a
+		  new window that is set to fullscreen. This has no effect
+		  when unmaximizing a window.
+*/
+void client_maximize(Client *self, gboolean max, int dir,
+		     gboolean savearea);
+
+/*! Shades or unshades the client window
+  @param shade true if the window should be shaded; false if it should be
+               unshaded.
+*/
+void client_shade(Client *self, gboolean shade);
+
+/*! Request the client to close its window. */
+void client_close(Client *self);
+
+/*! Sends the window to the specified desktop */
+void client_set_desktop(Client *self, unsigned int target);
+
+/*! Return a modal child of the client window
+    @return A modal child of the client window, or 0 if none was found.
+*/
+Client *client_find_modal_child(Client *self);
+
+/*! Validate client, by making sure no Destroy or Unmap events exist in
+  the event queue for the window.
+  @return true if the client is valid; false if the client has already
+          been unmapped/destroyed, and so is invalid.
+*/
+gboolean client_validate(Client *self);
+
+/*! Sets the wm_state to the specified value */
+void client_set_wm_state(Client *self, long state);
+
+/*! Adjusts the window's net_state
+  This should not be called as part of the window mapping process! It is for
+  use when updating the state post-mapping.
+ client_apply_startup_state is used to do the same things during the mapping + process. +*/ +void client_set_state(Client *self, Atom action, long data1, long data2); + +/*! Attempt to focus the client window */ +gboolean client_focus(Client *self); + +/*! Remove focus from the client window */ +void client_unfocus(Client *self); + +/*! Calculates the stacking layer for the client window */ +void client_calc_layer(Client *self); + +/*! Updates the window's transient status, and any parents of it */ +void client_update_transient_for(Client *self); +/*! Update the protocols that the window supports and adjusts things if they + change */ +void client_update_protocols(Client *self); +/*! Updates the WMNormalHints and adjusts things if they change */ +void client_update_normal_hints(Client *self); + +/*! Updates the WMHints and adjusts things if they change + @param initstate Whether to read the initial_state property from the + WMHints. This should only be used during the mapping + process. +*/ +void client_update_wmhints(Client *self); +/*! Updates the window's title */ +void client_update_title(Client *self); +/*! Updates the window's icon title */ +void client_update_icon_title(Client *self); +/*! Updates the window's application name and class */ +void client_update_class(Client *self); +/*! Updates the strut for the client */ +void client_update_strut(Client *self); +/*! Updates the window's icons */ +void client_update_icons(Client *self); +/*! Updates the window's kwm icon */ +void client_update_kwm_icon(Client *self); + +/*! Set up what decor should be shown on the window and what functions should + be allowed (Client::decorations and Client::functions). + This also updates the NET_WM_ALLOWED_ACTIONS hint. +*/ +void client_setup_decor_and_functions(Client *self); + +/*! Retrieves the window's type and sets Client->type */ +void client_get_type(Client *self); + +#endif diff --git a/openbox/clientwrap.c b/openbox/clientwrap.c new file mode 100644 index 0000000..fe3783e --- /dev/null +++ b/openbox/clientwrap.c @@ -0,0 +1,1029 @@ +#include "clientwrap.h" +#include "client.h" +#include "frame.h" +#include "engine.h" +#include "stacking.h" +#include "focus.h" +#include "prop.h" +#include + +/*************************************************************************** + + Define the type 'ClientWrap' + + ***************************************************************************/ + +#define IS_CWRAP(v) ((v)->ob_type == &ClientWrapType) +#define IS_VALID_CWRAP(v) ((v)->client != NULL) +#define CHECK_CWRAP(self, funcname) { \ + if (!IS_CWRAP(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'Client' " \ + "object"); \ + return NULL; \ + } \ + if (!IS_VALID_CWRAP(self)) { \ + PyErr_SetString(PyExc_ValueError, \ + "This 'Client' is wrapping a client which no longer "\ + "exists."); \ + return NULL; \ + } \ +} + + +staticforward PyTypeObject ClientWrapType; + +/*************************************************************************** + + Attribute methods + + ***************************************************************************/ + +static PyObject *cwrap_valid(ClientWrap *self, PyObject *args) +{ + if (!IS_CWRAP(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'valid' requires a 'Client' object"); + return NULL; + } + return PyInt_FromLong(self->client != NULL); +} + +static PyObject *cwrap_title(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "title"); + if (!PyArg_ParseTuple(args, ":title")) + return NULL; + return PyString_FromString(self->client->title); +} + +static PyObject *cwrap_setTitle(ClientWrap *self, PyObject *args) +{ + char *title; + + CHECK_CWRAP(self, "setTitle"); + if (!PyArg_ParseTuple(args, "s:setTitle", &title)) + return NULL; + + PROP_SETS(self->client->window, net_wm_name, utf8, title); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_iconTitle(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "iconTitle"); + if (!PyArg_ParseTuple(args, ":iconTitle")) + return NULL; + return PyString_FromString(self->client->icon_title); +} + +static PyObject *cwrap_setIconTitle(ClientWrap *self, PyObject *args) +{ + char *title; + + CHECK_CWRAP(self, "setIconTitle"); + if (!PyArg_ParseTuple(args, "s:setIconTitle", &title)) + return NULL; + + PROP_SETS(self->client->window, net_wm_icon_name, utf8, title); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_desktop(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "desktop"); + if (!PyArg_ParseTuple(args, ":desktop")) + return NULL; + return PyInt_FromLong(self->client->desktop); +} + +static PyObject *cwrap_setDesktop(ClientWrap *self, PyObject *args) +{ + int desktop; + CHECK_CWRAP(self, "setDesktop"); + if (!PyArg_ParseTuple(args, "i:setDesktop", &desktop)) + return NULL; + client_set_desktop(self->client, desktop); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_resName(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "resName"); + if (!PyArg_ParseTuple(args, ":resName")) + return NULL; + return PyString_FromString(self->client->res_name); +} + +static PyObject *cwrap_resClass(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "resClass"); + if (!PyArg_ParseTuple(args, ":resClass")) + return NULL; + return PyString_FromString(self->client->res_class); +} + +static PyObject *cwrap_role(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "role"); + if (!PyArg_ParseTuple(args, ":role")) + return NULL; + return PyString_FromString(self->client->role); +} + +static PyObject *cwrap_transient(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "transient"); + if (!PyArg_ParseTuple(args, ":transient")) + return NULL; + return PyInt_FromLong(!!self->client->transient); +} + +static PyObject *cwrap_transientFor(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "transientFor"); + if (!PyArg_ParseTuple(args, ":transientFor")) + return NULL; + if (self->client->transient_for != NULL) + return clientwrap_new(self->client->transient_for); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_transients(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + GSList *it; + guint i, s; + + CHECK_CWRAP(self, "transients"); + if (!PyArg_ParseTuple(args, ":transients")) + return NULL; + s = g_slist_length(self->client->transients); + tuple = PyTuple_New(s); + for (i = 0, it = self->client->transients; i < s; ++i, it = it->next) + PyTuple_SET_ITEM(tuple, i, clientwrap_new(it->data)); + return tuple; +} + +static PyObject *cwrap_type(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "type"); + if (!PyArg_ParseTuple(args, ":type")) + return NULL; + return PyInt_FromLong(self->client->type); +} + +static PyObject *cwrap_normal(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "normal"); + if (!PyArg_ParseTuple(args, ":normal")) + return NULL; + return PyInt_FromLong(!!client_normal(self->client)); +} + +static PyObject *cwrap_area(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "area"); + if (!PyArg_ParseTuple(args, ":area")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->frame->area.x)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->frame->area.y)); + PyTuple_SET_ITEM(tuple, 2, + PyInt_FromLong(self->client->frame->area.width)); + PyTuple_SET_ITEM(tuple, 3, + PyInt_FromLong(self->client->frame->area.height)); + return tuple; +} + +static PyObject *cwrap_setArea(ClientWrap *self, PyObject *args) +{ + int x, y, w, h, final = TRUE; + + CHECK_CWRAP(self, "setArea"); + if (!PyArg_ParseTuple(args, "(iiii)|i:setArea", &x, &y, &w, &h, &final)) + return NULL; + + frame_frame_gravity(self->client->frame, &x, &y); + w -= self->client->frame->size.left + self->client->frame->size.right; + h -= self->client->frame->size.top + self->client->frame->size.bottom; + client_configure(self->client, Corner_TopLeft, x, y, w, h, TRUE, final); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_clientArea(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "clientArea"); + if (!PyArg_ParseTuple(args, ":clientArea")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->area.x)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->area.y)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(self->client->area.width)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(self->client->area.height)); + return tuple; +} + +static PyObject *cwrap_setClientArea(ClientWrap *self, PyObject *args) +{ + int x, y, w, h; + + CHECK_CWRAP(self, "setClientArea"); + if (!PyArg_ParseTuple(args, "(iiii)|i:setClientArea", &x, &y, &w, &h)) + return NULL; + + client_configure(self->client, Corner_TopLeft, x, y, w, h, TRUE, TRUE); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_frameSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "frameSize"); + if (!PyArg_ParseTuple(args, ":frameSize")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->frame->size.left)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->frame->size.top)); + PyTuple_SET_ITEM(tuple, 2, + PyInt_FromLong(self->client->frame->size.right)); + PyTuple_SET_ITEM(tuple, 3, + PyInt_FromLong(self->client->frame->size.bottom)); + return tuple; +} + +static PyObject *cwrap_strut(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "strut"); + if (!PyArg_ParseTuple(args, ":strut")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->strut.left)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->strut.top)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(self->client->strut.right)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(self->client->strut.bottom)); + return tuple; +} + +static PyObject *cwrap_logicalSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "logicalSize"); + if (!PyArg_ParseTuple(args, ":logicalSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, + PyInt_FromLong(self->client->logical_size.width)); + PyTuple_SET_ITEM(tuple, 1, + PyInt_FromLong(self->client->logical_size.height)); + return tuple; +} + +static PyObject *cwrap_canFocus(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "canFocus"); + if (!PyArg_ParseTuple(args, ":canFocus")) + return NULL; + return PyInt_FromLong(!!self->client->can_focus); +} + +static PyObject *cwrap_focus(ClientWrap *self, PyObject *args) +{ + int focus = TRUE; + + CHECK_CWRAP(self, "focus"); + if (!PyArg_ParseTuple(args, "|i:focus", &focus)) + return NULL; + if (focus) + return PyInt_FromLong(!!client_focus(self->client)); + else { + if (focus_client == self->client) + client_unfocus(self->client); + Py_INCREF(Py_None); + return Py_None; + } +} + +static PyObject *cwrap_focused(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "focused"); + if (!PyArg_ParseTuple(args, ":focused")) + return NULL; + return PyInt_FromLong(!!self->client->focused); +} + +static PyObject *cwrap_visible(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "visible"); + if (!PyArg_ParseTuple(args, ":visible")) + return NULL; + return PyInt_FromLong(!!self->client->frame->visible); +} + +static PyObject *cwrap_setVisible(ClientWrap *self, PyObject *args) +{ + int show; + + CHECK_CWRAP(self, "setVisible"); + if (!PyArg_ParseTuple(args, "i:setVisible", &show)) + return NULL; + if (show) + engine_frame_show(self->client->frame); + else + engine_frame_hide(self->client->frame); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_modal(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "modal"); + if (!PyArg_ParseTuple(args, ":modal")) + return NULL; + return PyInt_FromLong(!!self->client->modal); +} + +static PyObject *cwrap_setModal(ClientWrap *self, PyObject *args) +{ + int modal; + + CHECK_CWRAP(self, "setModal"); + if (!PyArg_ParseTuple(args, "i:setModal", &modal)) + return NULL; + + client_set_state(self->client, + (modal ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_modal, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_shaded(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "shaded"); + if (!PyArg_ParseTuple(args, ":shaded")) + return NULL; + return PyInt_FromLong(!!self->client->shaded); +} + +static PyObject *cwrap_setShaded(ClientWrap *self, PyObject *args) +{ + int shaded; + + CHECK_CWRAP(self, "setShaded"); + if (!PyArg_ParseTuple(args, "i:setShaded", &shaded)) + return NULL; + + client_shade(self->client, shaded); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_iconic(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "iconic"); + if (!PyArg_ParseTuple(args, ":iconic")) + return NULL; + return PyInt_FromLong(!!self->client->iconic); +} + +static PyObject *cwrap_setIconic(ClientWrap *self, PyObject *args) +{ + int iconify, current = TRUE; + + CHECK_CWRAP(self, "setIconic"); + if (!PyArg_ParseTuple(args, "i|i:setIconic", &iconify, ¤t)) + return NULL; + + client_iconify(self->client, iconify, current); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_maximizedHorz(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maximizedHorz"); + if (!PyArg_ParseTuple(args, ":maximizedHorz")) + return NULL; + return PyInt_FromLong(!!self->client->max_horz); +} + +static PyObject *cwrap_setMaximizedHorz(ClientWrap *self, PyObject *args) +{ + int max; + + CHECK_CWRAP(self, "setMaximizedHorz"); + if (!PyArg_ParseTuple(args, "i:setMaximizedHorz", &max)) + return NULL; + + client_set_state(self->client, + (max ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_maximized_horz, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_maximizedVert(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maximizedVert"); + if (!PyArg_ParseTuple(args, ":maximizedVert")) + return NULL; + return PyInt_FromLong(!!self->client->max_vert); +} + +static PyObject *cwrap_setMaximizedVert(ClientWrap *self, PyObject *args) +{ + int max; + + CHECK_CWRAP(self, "setMaximizedVert"); + if (!PyArg_ParseTuple(args, "i:setMaximizedVert", &max)) + return NULL; + + client_set_state(self->client, + (max ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_maximized_vert, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_maximized(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maximized"); + if (!PyArg_ParseTuple(args, ":maximized")) + return NULL; + return PyInt_FromLong(self->client->max_vert || self->client->max_horz); +} + +static PyObject *cwrap_setMaximized(ClientWrap *self, PyObject *args) +{ + int max; + + CHECK_CWRAP(self, "setMaximized"); + if (!PyArg_ParseTuple(args, "i:setMaximized", &max)) + return NULL; + + client_set_state(self->client, + (max ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_maximized_vert, + prop_atoms.net_wm_state_maximized_horz); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_fullscreen(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "fullscreen"); + if (!PyArg_ParseTuple(args, ":fullscreen")) + return NULL; + return PyInt_FromLong(!!self->client->fullscreen); +} + +static PyObject *cwrap_setFullscreen(ClientWrap *self, PyObject *args) +{ + int fs; + + CHECK_CWRAP(self, "setFullscreen"); + if (!PyArg_ParseTuple(args, "i:setFullscreen", &fs)) + return NULL; + + client_set_state(self->client, + (fs ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_fullscreen, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_stacking(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "stacking"); + if (!PyArg_ParseTuple(args, ":stacking")) + return NULL; + return PyInt_FromLong(self->client->above ? 1 : + self->client->below ? -1 : 0); +} + +static PyObject *cwrap_setStacking(ClientWrap *self, PyObject *args) +{ + int stack; + + CHECK_CWRAP(self, "setStacking"); + if (!PyArg_ParseTuple(args, "i:setStacking", &stack)) + return NULL; + client_set_state(self->client, + (stack > 0 ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_above, 0); + client_set_state(self->client, + (stack < 0 ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_below, 0); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_raiseWindow(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "raiseWindow"); + if (!PyArg_ParseTuple(args, ":raiseWindow")) + return NULL; + stacking_raise(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_lowerWindow(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "lowerWindow"); + if (!PyArg_ParseTuple(args, ":lowerWindow")) + return NULL; + stacking_lower(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_skipPager(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "skipPager"); + if (!PyArg_ParseTuple(args, ":skipPager")) + return NULL; + return PyInt_FromLong(!!self->client->skip_pager); +} + +static PyObject *cwrap_setSkipPager(ClientWrap *self, PyObject *args) +{ + int skip; + + CHECK_CWRAP(self, "setSkipPager"); + if (!PyArg_ParseTuple(args, "i:setSkipPager", &skip)) + return NULL; + + client_set_state(self->client, + (skip ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_skip_pager, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_skipTaskbar(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "skipTaskbar"); + if (!PyArg_ParseTuple(args, ":skipTaskbar")) + return NULL; + return PyInt_FromLong(!!self->client->skip_taskbar); +} + +static PyObject *cwrap_setSkipTaskbar(ClientWrap *self, PyObject *args) +{ + int skip; + + CHECK_CWRAP(self, "setSkipTaskbar"); + if (!PyArg_ParseTuple(args, "i:setSkipTaskbar", &skip)) + return NULL; + + client_set_state(self->client, + (skip ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_skip_taskbar, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_disableDecorations(ClientWrap *self, PyObject *args) +{ + int title, handle, border; + + CHECK_CWRAP(self, "disableDecorations"); + if (!PyArg_ParseTuple(args, "iii:disableDecorations", &title, &handle, + &border)) + return NULL; + + self->client->disabled_decorations = 0; + if (title) self->client->disabled_decorations |= Decor_Titlebar; + if (handle) self->client->disabled_decorations |= Decor_Handle; + if (border) self->client->disabled_decorations |= Decor_Border; + client_setup_decor_and_functions(self->client); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_close(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "close"); + if (!PyArg_ParseTuple(args, ":close")) + return NULL; + client_close(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_window(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "window"); + if (!PyArg_ParseTuple(args, ":window")) + return NULL; + return PyInt_FromLong(self->client->window); +} + +#define METH(n, d) {#n, (PyCFunction)cwrap_##n, METH_VARARGS, #d} + +static PyMethodDef ClientWrapMethods[] = { + METH(valid, + "Returns if the Client instance is still valid. Client instances are " + "marked as invalid when the Client they are associated is " + "closed/destroyed/released."), + METH(title, + "Returns the client's title."), + METH(setTitle, + "Change the client's title to the given string. This change will be " + "overwritten if/when the client changes its title."), + METH(iconTitle, + "Returns's the client's icon title. The icon title is the title to " + "be displayed when the client is iconified."), + METH(setIconTitle, + "Change the client's icon title to the given string. This change " + "will be overwritten if/when the client changes its icon title."), + METH(desktop, + "Returns the desktop on which the client is visible. This value will " + "always be in the range [0, ob.Openbox.numDesktops()), unless it is " + "0xffffffff. A value of 0xffffffff indicates the client is visible " + "on all desktops."), + METH(setDesktop, + "Moves the client to the specified desktop. The desktop must be in " + "the range [0, ob.Openbox.numDesktops()), unless it is 0xffffffff. A " + "value of 0xffffffff indicates the client is visible on all " + "desktops."), + METH(resName, + "Returns the resouce name of the client. The resource name is meant " + "to provide an instance name for the client."), + METH(resClass, + "Returns the resouce class of the client. The resource class is " + "meant to provide the genereal class of the application. e.g. " + "'Emacs', 'Xterm', 'XClock', 'XLoad', and so on."), + METH(role, + "Returns the client's role. The role is meant to distinguish between " + "different windows of an application. Each window should have a " + "unique role."), + METH(transient, + "Returns True or False describing if the client is a transient " + "window. Transient windows are 'temporary' windows, such as " + "preference dialogs, and usually have a parent window, which can be " + "found from transientFor()."), + METH(transientFor, + "Returns the client for which this client is a transient. See " + "transient() for a description of transience."), + METH(transients, + "Returns a tuple containing all the Clients which are transients of " + "this window. See transient() for a description of transience."), + METH(type, + "Returns the logical type of the window. This is one of the " + "ClientType constants. See also normal()."), + METH(normal, + "Returns True or False for if the client is a 'normal' window. " + "Normal windows make up most applications. Non-normal windows have " + "special rules applied to them at times such as for focus handling. " + "An example of a non-normal window is 'gnome-panel'. This value is " + "determined from the client's type(), but does not imply that the " + "window is ClientType.Normal. Rather this is a more generic " + "definition of 'normal' windows, and includes dialogs and others."), + METH(area, + "Returns the area of the screen which the client occupies. It may be " + "important to note that this is the position and size of the client " + "*with* its decorations. If you want the underlying position and " + "size of the client itself, you should use clientArea(). See also " + "logicalSize()."), + + METH(setArea, + "Sets the client's area, moving and resizing it as specified (or as " + "close as can be accomidated)."), + METH(clientArea, + "Returns the area of the screen which the client considers itself to " + "be occupying. This value is not what you see and should not be used " + "for most things (it should, for example, be used for persisting a " + "client's dimentions across sessions). See also area()."), + METH(setClientArea, + "Sets the area of the screen which the client considers itself to be " + "occupying. This is not the on-screen visible position and size, and " + "should be used with care. You probably want to use setArea() to " + "adjust the client. This should be used if you want the client " + "window (inside the decorations) to be a specific size. Adjusting " + "the client's position with this function is probably always a bad " + "idea, because of window gravity."), + METH(strut, + "Returns the application's specified strut. The strut is the amount " + "of space that should be reserved for the application on each side " + "of the screen."), + METH(frameSize, + "Returns the size of the decorations around the client window."), + METH(logicalSize, + "Returns the client's logical size. The logical size is the client's " + "size in more user friendly terms. For many apps this is simply the " + "size of the client in pixels, however for some apps this will " + "differ (e.g. terminal emulators). This value should be used when " + "displaying an applications size to the user."), + METH(canFocus, + "Returns True or False for if the client can be focused."), + METH(focus, + "Focuses (or unfocuses) the client window. Windows which return " + "False for canFocus() or visible() cannot be focused. When this " + "function returns, the client's focused() state will not be changed " + "yet. This only sends the request through the X server. You should " + "wait for the hooks.focused hook to fire, and not assume the client " + "has been focused."), + METH(focused, + "Returns True or False for if the client has the input focus."), + METH(visible, + "Returns True or False for if the client is visible. A client is not " + "visible if it is iconic() or if its desktop() is not visible."), + METH(setVisible, + "Shows or hides the client. This has no effect if its current " + "visible() state is requested."), + METH(modal, + "Returns True or False for if the client is a modal window. Modal " + "windows indicate that they must be dealt with before the program " + "can continue. When a modal window is a transient(), its " + "transientFor() client cannot be focused or raised above it."), + METH(setModal, + "Make the client window modal or non-modal."), + METH(shaded, + "Returns True or False for if the client is shaded. Shaded windows " + "have only their titlebar decorations showing."), + METH(setShaded, + "Shade or unshade the client. Shaded windows have only their " + "titlebar decorations showing. Windows which do not have a titlebar " + "cannot be shaded."), + METH(iconic, + "Returns True or False for if the window is iconified. Iconified " + "windows are not visible on any desktops."), + METH(setIconic, + "Iconifies or restores the client window. Iconified windows are not " + "visible on any desktops. Iconified windows can be restored to the " + "currently visible desktop or to their original (native) desktop."), + METH(maximizedHorz, + "Returns whether the client is maximized in the horizontal " + "direction."), + METH(setMaximizedHorz, + "Maximizes or restores a client horizontally."), + METH(maximizedVert, + "Returns whether the client is maximized in the vertical direction."), + METH(setMaximizedVert, + "Maximizes or restores a client vertically."), + METH(maximized, + "Returns whether the client is maximized in the horizontal or " + "vertical direction."), + METH(setMaximized, + "Maximizes or restores a client vertically and horzontally."), + METH(fullscreen, + "Returns if the client is in fullscreen mode. Fullscreen windows are " + "kept above all other windows and are stretched to fill the entire " + "physical display."), + METH(setFullscreen, + "Set a client into or out of fullscreen mode. Fullscreen windows are " + "kept above all other windows and are stretched to fill the entire " + "physical display."), + METH(stacking, + "Returns if the client will be stacked above/below other clients in " + "the same layer."), + METH(setStacking, + "Set how the client will be stacked according to other clients in " + "its layer."), + METH(raiseWindow, + "Raises the window to the top of its stacking layer."), + METH(lowerWindow, + "Lowers the window to the bottom of its stacking layer."), + METH(skipPager, + "Returns if the client has requested to be skipped (not displayed) " + "by pagers."), + METH(setSkipPager, + "Set whether the client should be skipped (not displayed) by " + "pagers."), + METH(skipTaskbar, + "Returns if the client has requested to be skipped (not displayed) " + "by taskbars."), + METH(setSkipTaskbar, + "Set whether the client should be skipped (not displayed) by " + "taskbars."), + METH(disableDecorations, + "Choose which decorations to disable on the client. Note that " + "decorations can only be disabled, and decorations that would " + "normally not be shown cannot be added. These values may have " + "slightly different meanings in different theme engines."), + METH(close, + "Requests the client to close its window."), + METH(window, + "Returns the client's window id. This is the id by which the X " + "server knows the client."), + { NULL, NULL, 0, NULL } +}; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +/*static PyObject *cwrap_getattr(ClientWrap *self, char *name) +{ + CHECK_CWRAP(self, "getattr"); + return Py_FindMethod(ClientWrapAttributeMethods, (PyObject*)self, name); +}*/ + +static void cwrap_dealloc(ClientWrap *self) +{ + if (self->client != NULL) + self->client->wrap = NULL; + PyObject_Del((PyObject*) self); +} + +static PyObject *cwrap_repr(ClientWrap *self) +{ + CHECK_CWRAP(self, "repr"); + return PyString_FromFormat("Client: 0x%x", (guint)self->client->window); +} + +static int cwrap_compare(ClientWrap *self, ClientWrap *other) +{ + Window w1, w2; + if (!IS_VALID_CWRAP(self)) { + PyErr_SetString(PyExc_ValueError, + "This 'Client' is wrapping a client which no longer " + "exists."); + } + + w1 = self->client->window; + w2 = other->client->window; + return w1 > w2 ? 1 : w1 < w2 ? -1 : 0; +} + +static PyTypeObject ClientWrapType = { + PyObject_HEAD_INIT(NULL) + 0, + "Client", + sizeof(ClientWrap), + 0, + (destructor) cwrap_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + (cmpfunc) cwrap_compare, /*tp_compare*/ + (reprfunc) cwrap_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + + +/*************************************************************************** + + Define the type 'ClientType' + + ***************************************************************************/ + +#define IS_CTYPE(v) ((v)->ob_type == &ClientTypeType) +#define CHECK_CTYPE(self, funcname) { \ + if (!IS_CTYPE(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'ClientType' " \ + "object"); \ + return NULL; \ + } \ +} + +staticforward PyTypeObject ClientTypeType; + +typedef struct ClientType { + PyObject_HEAD +} ClientType; + +static void ctype_dealloc(PyObject *self) +{ + PyObject_Del(self); +} + +static PyObject *ctype_getattr(ClientType *self, char *name) +{ + struct S { char *name; int val; }; + struct S s[] = { + { "Normal", Type_Normal }, + { "Dialog", Type_Dialog }, + { "Desktop", Type_Desktop }, + { "Dock", Type_Dock }, + { "Toolbar", Type_Toolbar }, + { "Menu", Type_Menu }, + { "Utility", Type_Utility }, + { "Splash", Type_Splash }, + { NULL, 0 } }; + int i; + + CHECK_CTYPE(self, "__getattr__"); + + for (i = 0; s[i].name != NULL; ++i) + if (!strcmp(s[i].name, name)) + return PyInt_FromLong(s[i].val); + PyErr_SetString(PyExc_AttributeError, "invalid attribute"); + return NULL; +} + +static PyTypeObject ClientTypeType = { + PyObject_HEAD_INIT(NULL) + 0, + "Type", + sizeof(ClientType), + 0, + (destructor) ctype_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc) ctype_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/*************************************************************************** + + External methods + + ***************************************************************************/ + +void clientwrap_startup() +{ + PyObject *ob, *obdict, *type; + + ClientWrapType.ob_type = &PyType_Type; + ClientWrapType.tp_methods = ClientWrapMethods; + PyType_Ready(&ClientWrapType); + + ClientTypeType.ob_type = &PyType_Type; + PyType_Ready(&ClientTypeType); + + /* get the ob module/dict */ + ob = PyImport_ImportModule("ob"); /* new */ + g_assert(ob != NULL); + obdict = PyModule_GetDict(ob); /* borrowed */ + g_assert(obdict != NULL); + + /* add the Client type to the ob module */ + PyDict_SetItemString(obdict, "Client", (PyObject*) &ClientWrapType); + + /* add an instance of ClientType */ + type = (PyObject*) PyObject_New(ClientType, &ClientTypeType); + PyDict_SetItemString(obdict, "ClientType", type); + Py_DECREF(type); + + Py_DECREF(ob); +} + +void clientwrap_shutdown() +{ +} + +PyObject *clientwrap_new(Client *client) +{ + g_assert(client != NULL); + + if (client->wrap != NULL) { + /* already has a wrapper! */ + Py_INCREF((PyObject*) client->wrap); + } else { + client->wrap = PyObject_New(ClientWrap, &ClientWrapType); + client->wrap->client = client; + } + return (PyObject*) client->wrap; +} diff --git a/openbox/clientwrap.h b/openbox/clientwrap.h new file mode 100644 index 0000000..b20ca91 --- /dev/null +++ b/openbox/clientwrap.h @@ -0,0 +1,19 @@ +#ifndef __clientwrap_h +#define __clientwrap_h + +#include + +struct Client; + +/* ClientWrap is a PyObject */ +typedef struct ClientWrap { + PyObject_HEAD + struct Client *client; +} ClientWrap; + +void clientwrap_startup(); +void clientwrap_shutdown(); + +PyObject *clientwrap_new(struct Client *client); + +#endif diff --git a/openbox/engine.c b/openbox/engine.c new file mode 100644 index 0000000..3457da1 --- /dev/null +++ b/openbox/engine.c @@ -0,0 +1,84 @@ +#include "engine.h" + +#include +#include +#ifdef HAVE_STDLIB_H +# include +#endif + +static GModule *module; +static EngineStartup *estartup; +static EngineShutdown *eshutdown; + +#define LOADSYM(name, var) \ + if (!g_module_symbol(module, #name, (gpointer*)&var)) { \ + g_warning("Failed to load symbol %s from engine", #name); \ + return FALSE; \ + } + +static gboolean load(char *name) +{ + char *path; + + g_assert(module == NULL); + + path = g_build_filename(ENGINEDIR, name, NULL); + module = g_module_open(path, G_MODULE_BIND_LAZY); + g_free(path); + + if (module == NULL) { + path = g_build_filename(g_get_home_dir(), ".openbox", "engines", name, + NULL); + module = g_module_open(path, G_MODULE_BIND_LAZY); + g_free(path); + } + + if (module == NULL) + return FALSE; + + /* load the engine's symbols */ + LOADSYM(startup, estartup); + LOADSYM(shutdown, eshutdown); + LOADSYM(frame_new, engine_frame_new); + LOADSYM(frame_grab_client, engine_frame_grab_client); + LOADSYM(frame_release_client, engine_frame_release_client); + LOADSYM(frame_adjust_size, engine_frame_adjust_size); + LOADSYM(frame_adjust_position, engine_frame_adjust_position); + LOADSYM(frame_adjust_shape, engine_frame_adjust_shape); + LOADSYM(frame_adjust_state, engine_frame_adjust_state); + LOADSYM(frame_adjust_focus, engine_frame_adjust_focus); + LOADSYM(frame_adjust_title, engine_frame_adjust_title); + LOADSYM(frame_adjust_icon, engine_frame_adjust_icon); + LOADSYM(frame_show, engine_frame_show); + LOADSYM(frame_hide, engine_frame_hide); + LOADSYM(get_context, engine_get_context); + + if (!estartup()) + return FALSE; + + return TRUE; +} + +void engine_startup(char *engine) +{ + module = NULL; + + if (engine != NULL) { + if (load(engine)) + return; + g_warning("Failed to load the engine '%s'", engine); + g_message("Falling back to the default: '%s'", DEFAULT_ENGINE); + } + if (!load(DEFAULT_ENGINE)) { + g_critical("Failed to load the engine '%s'. Aborting", DEFAULT_ENGINE); + exit(1); + } +} + +void engine_shutdown() +{ + if (module != NULL) { + eshutdown(); + g_module_close(module); + } +} diff --git a/openbox/engine.h b/openbox/engine.h new file mode 100644 index 0000000..067f02f --- /dev/null +++ b/openbox/engine.h @@ -0,0 +1,27 @@ +#ifndef __engine_h +#define __engine_h + +#include "../engines/engineinterface.h" + +void engine_startup(char *engine); +void engine_shutdown(); + +EngineFrameNew *engine_frame_new; + +EngineFrameGrabClient *engine_frame_grab_client; +EngineFrameReleaseClient *engine_frame_release_client; + +EngineFrameAdjustSize *engine_frame_adjust_size; +EngineFrameAdjustPosition *engine_frame_adjust_position; +EngineFrameAdjustShape *engine_frame_adjust_shape; +EngineFrameAdjustState *engine_frame_adjust_state; +EngineFrameAdjustFocus *engine_frame_adjust_focus; +EngineFrameAdjustTitle *engine_frame_adjust_title; +EngineFrameAdjustIcon *engine_frame_adjust_icon; + +EngineFrameShow *engine_frame_show; +EngineFrameHide *engine_frame_hide; + +EngineGetContext *engine_get_context; + +#endif diff --git a/openbox/event.c b/openbox/event.c new file mode 100644 index 0000000..00b2857 --- /dev/null +++ b/openbox/event.c @@ -0,0 +1,598 @@ +#include "openbox.h" +#include "client.h" +#include "xerror.h" +#include "prop.h" +#include "screen.h" +#include "frame.h" +#include "engine.h" +#include "focus.h" +#include "stacking.h" +#include "keyboard.h" +#include "pointer.h" +#include "hooks.h" +#include "extensions.h" +#include "timer.h" + +#include +#include +#include + +static void event_process(XEvent *e); +static void event_handle_root(XEvent *e); +static void event_handle_client(Client *c, XEvent *e); + +Time event_lasttime = 0; + +/*! A list of all possible combinations of keyboard lock masks */ +static unsigned int mask_list[8]; +/*! The value of the mask for the NumLock modifier */ +static unsigned int NumLockMask; +/*! The value of the mask for the ScrollLock modifier */ +static unsigned int ScrollLockMask; +/*! The key codes for the modifier keys */ +static XModifierKeymap *modmap; +/*! Table of the constant modifier masks */ +static const int mask_table[] = { + ShiftMask, LockMask, ControlMask, Mod1Mask, + Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask +}; +static int mask_table_size; + +void event_startup() +{ + mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]); + + /* get lock masks that are defined by the display (not constant) */ + modmap = XGetModifierMapping(ob_display); + g_assert(modmap); + if (modmap && modmap->max_keypermod > 0) { + size_t cnt; + const size_t size = mask_table_size * modmap->max_keypermod; + /* get the values of the keyboard lock modifiers + Note: Caps lock is not retrieved the same way as Scroll and Num + lock since it doesn't need to be. */ + const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock); + const KeyCode scroll_lock = XKeysymToKeycode(ob_display, + XK_Scroll_Lock); + + for (cnt = 0; cnt < size; ++cnt) { + if (! modmap->modifiermap[cnt]) continue; + + if (num_lock == modmap->modifiermap[cnt]) + NumLockMask = mask_table[cnt / modmap->max_keypermod]; + if (scroll_lock == modmap->modifiermap[cnt]) + ScrollLockMask = mask_table[cnt / modmap->max_keypermod]; + } + } + + mask_list[0] = 0; + mask_list[1] = LockMask; + mask_list[2] = NumLockMask; + mask_list[3] = LockMask | NumLockMask; + mask_list[4] = ScrollLockMask; + mask_list[5] = ScrollLockMask | LockMask; + mask_list[6] = ScrollLockMask | NumLockMask; + mask_list[7] = ScrollLockMask | LockMask | NumLockMask; +} + +void event_shutdown() +{ + XFreeModifiermap(modmap); +} + +void event_loop() +{ + fd_set selset; + XEvent e; + int x_fd; + struct timeval *wait; + + while (TRUE) { + /* + There are slightly different event retrieval semantics here for + local (or high bandwidth) versus remote (or low bandwidth) + connections to the display/Xserver. + */ + if (ob_remote) { + if (!XPending(ob_display)) + break; + } else { + /* + This XSync allows for far more compression of events, which + makes things like Motion events perform far far better. Since + it also means network traffic for every event instead of every + X events (where X is the number retrieved at a time), it + probably should not be used for setups where Openbox is + running on a remote/low bandwidth display/Xserver. + */ + XSync(ob_display, FALSE); + if (!XEventsQueued(ob_display, QueuedAlready)) + break; + } + XNextEvent(ob_display, &e); + + event_process(&e); + } + + timer_dispatch((GTimeVal**)&wait); + x_fd = ConnectionNumber(ob_display); + FD_ZERO(&selset); + FD_SET(x_fd, &selset); + select(x_fd + 1, &selset, NULL, NULL, wait); +} + +void event_process(XEvent *e) +{ + XEvent ce; + KeyCode *kp; + Window window; + int i, k; + Client *client; + + /* pick a window */ + switch (e->type) { + case UnmapNotify: + window = e->xunmap.window; + break; + case DestroyNotify: + window = e->xdestroywindow.window; + break; + case ConfigureRequest: + window = e->xconfigurerequest.window; + break; + default: + /* XKB events */ + if (e->type == extensions_xkb_event_basep) { + switch (((XkbAnyEvent*)&e)->xkb_type) { + case XkbBellNotify: + window = ((XkbBellNotifyEvent*)&e)->window; + default: + window = None; + } + } else + window = e->xany.window; + } + + /* grab the lasttime and hack up the state */ + switch (e->type) { + case ButtonPress: + case ButtonRelease: + event_lasttime = e->xbutton.time; + e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + break; + case KeyPress: + event_lasttime = e->xkey.time; + e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* add to the state the mask of the modifier being pressed, if it is + a modifier key being pressed (this is a little ugly..) */ +/* I'm commenting this out cuz i don't want "C-Control_L" being returned. */ +/* kp = modmap->modifiermap;*/ +/* for (i = 0; i < mask_table_size; ++i) {*/ +/* for (k = 0; k < modmap->max_keypermod; ++k) {*/ +/* if (*kp == e->xkey.keycode) {*/ /* found the keycode */ + /* add the mask for it */ +/* e->xkey.state |= mask_table[i];*/ + /* cause the first loop to break; */ +/* i = mask_table_size;*/ +/* break;*/ /* get outta here! */ +/* }*/ +/* ++kp;*/ +/* }*/ +/* }*/ + + break; + case KeyRelease: + event_lasttime = e->xkey.time; + e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* remove from the state the mask of the modifier being released, if + it is a modifier key being released (this is a little ugly..) */ + kp = modmap->modifiermap; + for (i = 0; i < mask_table_size; ++i) { + for (k = 0; k < modmap->max_keypermod; ++k) { + if (*kp == e->xkey.keycode) { /* found the keycode */ + /* remove the mask for it */ + e->xkey.state &= ~mask_table[i]; + /* cause the first loop to break; */ + i = mask_table_size; + break; /* get outta here! */ + } + ++kp; + } + } + break; + case MotionNotify: + event_lasttime = e->xmotion.time; + e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* compress events */ + while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) { + e->xmotion.x_root = ce.xmotion.x_root; + e->xmotion.y_root = ce.xmotion.y_root; + } + break; + case PropertyNotify: + event_lasttime = e->xproperty.time; + break; + case FocusIn: + case FocusOut: + if (e->xfocus.mode == NotifyGrab) + /*|| e.xfocus.mode == NotifyUngrab ||*/ + + /* From Metacity, from WindowMaker, ignore all funky pointer + root events. Its commented out cuz I don't think we need this + at all. If problems arise we can look into it */ + /*e.xfocus.detail > NotifyNonlinearVirtual) */ + return; /* skip me! */ + if (e->type == FocusOut) { + /* FocusOut events just make us look for FocusIn events. They + are mostly ignored otherwise. */ + XEvent fi; + if (XCheckTypedEvent(ob_display, FocusIn, &fi)) { + event_process(&fi); + /* dont unfocus the window we just focused! */ + if (fi.xfocus.window == e->xfocus.window) + return; + } + } + break; + case EnterNotify: + case LeaveNotify: + event_lasttime = e->xcrossing.time; + if (e->xcrossing.mode != NotifyNormal) + return; /* skip me! */ + break; + } + + client = g_hash_table_lookup(client_map, (gpointer)window); + + if (client) { + event_handle_client(client, e); + } else if (window == ob_root) + event_handle_root(e); + else if (e->type == ConfigureRequest) { + /* unhandled configure requests must be used to configure the + window directly */ + XWindowChanges xwc; + + xwc.x = e->xconfigurerequest.x; + xwc.y = e->xconfigurerequest.y; + xwc.width = e->xconfigurerequest.width; + xwc.height = e->xconfigurerequest.height; + xwc.border_width = e->xconfigurerequest.border_width; + xwc.sibling = e->xconfigurerequest.above; + xwc.stack_mode = e->xconfigurerequest.detail; + + g_message("Proxying configure event for 0x%lx", window); + + /* we are not to be held responsible if someone sends us an + invalid request! */ + xerror_set_ignore(TRUE); + XConfigureWindow(ob_display, window, + e->xconfigurerequest.value_mask, &xwc); + xerror_set_ignore(FALSE); + } + + /* dispatch Crossing, Pointer and Key events to the hooks */ + switch(e->type) { + case EnterNotify: + HOOKFIRECLIENT(pointerenter, client); + break; + case LeaveNotify: + HOOKFIRECLIENT(pointerleave, client); + break; + case ButtonPress: + case ButtonRelease: + case MotionNotify: + pointer_event(e, client); + break; + case KeyPress: + case KeyRelease: + keyboard_event(&e->xkey); + break; + default: + /* XKB events */ + if (e->type == extensions_xkb_event_basep) { + switch (((XkbAnyEvent*)&e)->xkb_type) { + case XkbBellNotify: + HOOKFIRECLIENT(bell, client); + break; + } + } + } +} + +static void event_handle_root(XEvent *e) +{ + Atom msgtype; + + switch(e->type) { + case MapRequest: + g_message("MapRequest on root"); + client_manage(e->xmap.window); + break; + case ClientMessage: + if (e->xclient.format != 32) break; + + msgtype = e->xclient.message_type; + if (msgtype == prop_atoms.net_current_desktop) { + unsigned int d = e->xclient.data.l[0]; + if (d <= screen_num_desktops) + screen_set_desktop(d); + } else if (msgtype == prop_atoms.net_number_of_desktops) { + unsigned int d = e->xclient.data.l[0]; + if (d > 0) + screen_set_num_desktops(d); + } else if (msgtype == prop_atoms.net_showing_desktop) { + screen_show_desktop(e->xclient.data.l[0] != 0); + } + break; + case PropertyNotify: + if (e->xproperty.atom == prop_atoms.net_desktop_names) + screen_update_desktop_names(); + else if (e->xproperty.atom == prop_atoms.net_desktop_layout) + screen_update_layout(); + break; + } +} + +static void event_handle_client(Client *client, XEvent *e) +{ + XEvent ce; + Atom msgtype; + + switch (e->type) { + case FocusIn: + client->focused = TRUE; + engine_frame_adjust_focus(client->frame); + + /* focus state can affect the stacking layer */ + client_calc_layer(client); + + focus_set_client(client); + break; + case FocusOut: + client->focused = FALSE; + engine_frame_adjust_focus(client->frame); + + /* focus state can affect the stacking layer */ + client_calc_layer(client); + + if (focus_client == client) + focus_set_client(NULL); + break; + case ConfigureRequest: + g_message("ConfigureRequest for window %lx", client->window); + /* compress these */ + while (XCheckTypedWindowEvent(ob_display, client->window, + ConfigureRequest, &ce)) { + /* XXX if this causes bad things.. we can compress config req's + with the same mask. */ + e->xconfigurerequest.value_mask |= + ce.xconfigurerequest.value_mask; + if (ce.xconfigurerequest.value_mask & CWX) + e->xconfigurerequest.x = ce.xconfigurerequest.x; + if (ce.xconfigurerequest.value_mask & CWY) + e->xconfigurerequest.y = ce.xconfigurerequest.y; + if (ce.xconfigurerequest.value_mask & CWWidth) + e->xconfigurerequest.width = ce.xconfigurerequest.width; + if (ce.xconfigurerequest.value_mask & CWHeight) + e->xconfigurerequest.height = ce.xconfigurerequest.height; + if (ce.xconfigurerequest.value_mask & CWBorderWidth) + e->xconfigurerequest.border_width = + ce.xconfigurerequest.border_width; + if (ce.xconfigurerequest.value_mask & CWStackMode) + e->xconfigurerequest.detail = ce.xconfigurerequest.detail; + } + + /* if we are iconic (or shaded (fvwm does this)) ignore the event */ + if (client->iconic || client->shaded) return; + + if (e->xconfigurerequest.value_mask & CWBorderWidth) + client->border_width = e->xconfigurerequest.border_width; + + /* resize, then move, as specified in the EWMH section 7.7 */ + if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight | + CWX | CWY)) { + int x, y, w, h; + Corner corner; + + x = (e->xconfigurerequest.value_mask & CWX) ? + e->xconfigurerequest.x : client->area.x; + y = (e->xconfigurerequest.value_mask & CWY) ? + e->xconfigurerequest.y : client->area.y; + w = (e->xconfigurerequest.value_mask & CWWidth) ? + e->xconfigurerequest.width : client->area.width; + h = (e->xconfigurerequest.value_mask & CWHeight) ? + e->xconfigurerequest.height : client->area.height; + + switch (client->gravity) { + case NorthEastGravity: + case EastGravity: + corner = Corner_TopRight; + break; + case SouthWestGravity: + case SouthGravity: + corner = Corner_BottomLeft; + break; + case SouthEastGravity: + corner = Corner_BottomRight; + break; + default: /* NorthWest, Static, etc */ + corner = Corner_TopLeft; + } + + client_configure(client, corner, x, y, w, h, FALSE, FALSE); + } + + if (e->xconfigurerequest.value_mask & CWStackMode) { + switch (e->xconfigurerequest.detail) { + case Below: + case BottomIf: + stacking_lower(client); + break; + + case Above: + case TopIf: + default: + stacking_raise(client); + break; + } + } + break; + case UnmapNotify: + if (client->ignore_unmaps) { + client->ignore_unmaps--; + break; + } + g_message("UnmapNotify for %lx", client->window); + client_unmanage(client); + break; + case DestroyNotify: + g_message("DestroyNotify for %lx", client->window); + client_unmanage(client); + break; + case ReparentNotify: + /* this is when the client is first taken captive in the frame */ + if (e->xreparent.parent == client->frame->plate) break; + + /* + This event is quite rare and is usually handled in unmapHandler. + However, if the window is unmapped when the reparent event occurs, + the window manager never sees it because an unmap event is not sent + to an already unmapped window. + */ + + /* we don't want the reparent event, put it back on the stack for the + X server to deal with after we unmanage the window */ + XPutBackEvent(ob_display, e); + + client_unmanage(client); + break; + case MapRequest: + /* we shouldn't be able to get this unless we're iconic */ + g_assert(client->iconic); + + HOOKFIRECLIENT(requestactivate, client); + break; + case ClientMessage: + /* validate cuz we query stuff off the client here */ + if (!client_validate(client)) break; + + if (e->xclient.format != 32) return; + + msgtype = e->xclient.message_type; + if (msgtype == prop_atoms.wm_change_state) { + /* compress changes into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL messages of a + type, not just messages in a row without other + message types between. */ + if (ce.xclient.message_type != msgtype) { + XPutBackEvent(ob_display, &ce); + break; + } + e->xclient = ce.xclient; + } + client_set_wm_state(client, e->xclient.data.l[0]); + } else if (msgtype == prop_atoms.net_wm_desktop) { + /* compress changes into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL messages of a + type, not just messages in a row without other + message types between. */ + if (ce.xclient.message_type != msgtype) { + XPutBackEvent(ob_display, &ce); + break; + } + e->xclient = ce.xclient; + } + client_set_desktop(client, e->xclient.data.l[0]); + } else if (msgtype == prop_atoms.net_wm_state) { + /* can't compress these */ + g_message("net_wm_state %s %ld %ld for 0x%lx\n", + (e->xclient.data.l[0] == 0 ? "Remove" : + e->xclient.data.l[0] == 1 ? "Add" : + e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"), + e->xclient.data.l[1], e->xclient.data.l[2], + client->window); + client_set_state(client, e->xclient.data.l[0], + e->xclient.data.l[1], e->xclient.data.l[2]); + } else if (msgtype == prop_atoms.net_close_window) { + g_message("net_close_window for 0x%lx\n", client->window); + client_close(client); + } else if (msgtype == prop_atoms.net_active_window) { + g_message("net_active_window for 0x%lx\n", client->window); + if (screen_showing_desktop) + screen_show_desktop(FALSE); + if (client->iconic) + client_iconify(client, FALSE, TRUE); + else if (!client->frame->visible) + /* if its not visible for other reasons, then don't mess + with it */ + return; + HOOKFIRECLIENT(requestactivate, client); + } + break; + case PropertyNotify: + /* validate cuz we query stuff off the client here */ + if (!client_validate(client)) break; + + /* compress changes to a single property into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL changes to a property, + not just changes in a row without other props between. */ + if (ce.xproperty.atom != e->xproperty.atom) { + XPutBackEvent(ob_display, &ce); + break; + } + } + + msgtype = e->xproperty.atom; + if (msgtype == XA_WM_NORMAL_HINTS) { + client_update_normal_hints(client); + /* normal hints can make a window non-resizable */ + client_setup_decor_and_functions(client); + } + else if (msgtype == XA_WM_HINTS) + client_update_wmhints(client); + else if (msgtype == XA_WM_TRANSIENT_FOR) { + client_update_transient_for(client); + client_get_type(client); + /* type may have changed, so update the layer */ + client_calc_layer(client); + client_setup_decor_and_functions(client); + } + else if (msgtype == prop_atoms.net_wm_name || + msgtype == prop_atoms.wm_name) + client_update_title(client); + else if (msgtype == prop_atoms.net_wm_icon_name || + msgtype == prop_atoms.wm_icon_name) + client_update_icon_title(client); + else if (msgtype == prop_atoms.wm_class) + client_update_class(client); + else if (msgtype == prop_atoms.wm_protocols) { + client_update_protocols(client); + client_setup_decor_and_functions(client); + } + else if (msgtype == prop_atoms.net_wm_strut) + client_update_strut(client); + else if (msgtype == prop_atoms.net_wm_icon) + client_update_icons(client); + else if (msgtype == prop_atoms.kwm_win_icon) + client_update_kwm_icon(client); + } +} diff --git a/openbox/event.h b/openbox/event.h new file mode 100644 index 0000000..9153116 --- /dev/null +++ b/openbox/event.h @@ -0,0 +1,12 @@ +#ifndef __events_h +#define __events_h + +/*! Time at which the last event with a timestamp occured. */ +extern Time event_lasttime; + +void event_startup(); +void event_shutdown(); + +void event_loop(); + +#endif diff --git a/openbox/extensions.c b/openbox/extensions.c new file mode 100644 index 0000000..3fe4319 --- /dev/null +++ b/openbox/extensions.c @@ -0,0 +1,34 @@ +#include "openbox.h" +#include "extensions.h" + +gboolean extensions_xkb = FALSE; +int extensions_xkb_event_basep; +gboolean extensions_shape = FALSE; +int extensions_shape_event_basep; +gboolean extensions_xinerama = FALSE; +int extensions_xinerama_event_basep; + + +void extensions_query_all() +{ + int junk; + (void)junk; + +#ifdef XKB + extensions_xkb = + XkbQueryExtension(ob_display, &junk, &extensions_xkb_event_basep, + &junk, NULL, NULL); +#endif + +#ifdef SHAPE + extensions_shape = + XShapeQueryExtension(ob_display, &extensions_shape_event_basep, + &junk); +#endif + +#ifdef XINERAMA + extensions_xinerama = + XineramaQueryExtension(ob_display, &extensions_xinerama_event_basep, + &junk); +#endif +} diff --git a/openbox/extensions.h b/openbox/extensions.h new file mode 100644 index 0000000..3c11076 --- /dev/null +++ b/openbox/extensions.h @@ -0,0 +1,33 @@ +#ifndef __extensions_h +#define __extensions_h + +#include +#ifdef XKB +#include +#endif +#ifdef SHAPE +#include +#endif +#ifdef XINERAMA +#include +#endif +#include + +/*! Does the display have the XKB extension? */ +extern gboolean extensions_xkb; +/*! Base for events for the XKB extension */ +extern int extensions_xkb_event_basep; + +/*! Does the display have the Shape extension? */ +extern gboolean extensions_shape; +/*! Base for events for the Shape extension */ +extern int extensions_shape_event_basep; + +/*! Does the display have the Xinerama extension? */ +extern gboolean extensions_xinerama; +/*! Base for events for the Xinerama extension */ +extern int extensions_xinerama_event_basep; + +void extensions_query_all(); + +#endif diff --git a/openbox/focus.c b/openbox/focus.c new file mode 100644 index 0000000..3c7b635 --- /dev/null +++ b/openbox/focus.c @@ -0,0 +1,57 @@ +#include "openbox.h" +#include "client.h" +#include "screen.h" +#include "prop.h" +#include "hooks.h" + +#include + +Client *focus_client = NULL; + +Window focus_backup = None; + +void focus_set_client(Client *client); + +void focus_startup() +{ + /* create the window which gets focus when no clients get it. Have to + make it override-redirect so we don't try manage it, since it is + mapped. */ + XSetWindowAttributes attrib; + + attrib.override_redirect = TRUE; + focus_backup = XCreateWindow(ob_display, ob_root, + -100, -100, 1, 1, 0, 0, InputOnly, + CopyFromParent, CWOverrideRedirect, &attrib); + XMapRaised(ob_display, focus_backup); + + /* start with nothing focused */ + focus_set_client(NULL); +} + +void focus_set_client(Client *client) +{ + Window active; + + /* sometimes this is called with the already-focused window, this is + important for the python scripts to work (eg, c = 0 twice). don't just + return if _focused_client == c */ + + /* uninstall the old colormap, and install the new one */ + screen_install_colormap(focus_client, FALSE); + screen_install_colormap(client, TRUE); + + + if (client == NULL) { + /* when nothing will be focused, send focus to the backup target */ + XSetInputFocus(ob_display, focus_backup, RevertToNone, CurrentTime); + } + + focus_client = client; + + /* set the NET_ACTIVE_WINDOW hint */ + active = client ? client->window : None; + PROP_SET32(ob_root, net_active_window, window, active); + + HOOKFIRECLIENT(focused, client); +} diff --git a/openbox/focus.h b/openbox/focus.h new file mode 100644 index 0000000..9db5202 --- /dev/null +++ b/openbox/focus.h @@ -0,0 +1,20 @@ +#ifndef __focus_h +#define __focus_h + +#include + +struct Client; + +/*! The window which gets focus when nothing else will be focused */ +extern Window focus_backup; + +/*! The client which is currently focused */ +extern struct Client *focus_client; + +void focus_startup(); + +/*! Specify which client is currently focused, this doesn't actually + send focus anywhere, its called by the Focus event handlers */ +void focus_set_client(struct Client *client); + +#endif diff --git a/openbox/frame.c b/openbox/frame.c new file mode 100644 index 0000000..eee01e4 --- /dev/null +++ b/openbox/frame.c @@ -0,0 +1,105 @@ +#include "frame.h" + +void frame_client_gravity(Frame *self, int *x, int *y) +{ + /* horizontal */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case SouthWestGravity: + case WestGravity: + break; + + case NorthGravity: + case SouthGravity: + case CenterGravity: + *x -= (self->size.left + self->size.right) / 2; + break; + + case NorthEastGravity: + case SouthEastGravity: + case EastGravity: + *x -= self->size.left + self->size.right; + break; + + case ForgetGravity: + case StaticGravity: + *x -= self->size.left; + break; + } + + /* vertical */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case NorthEastGravity: + case NorthGravity: + break; + + case CenterGravity: + case EastGravity: + case WestGravity: + *y -= (self->size.top + self->size.bottom) / 2; + break; + + case SouthWestGravity: + case SouthEastGravity: + case SouthGravity: + *y -= self->size.top + self->size.bottom; + break; + + case ForgetGravity: + case StaticGravity: + *y -= self->size.top; + break; + } +} + +void frame_frame_gravity(Frame *self, int *x, int *y) +{ + /* horizontal */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + break; + case NorthGravity: + case CenterGravity: + case SouthGravity: + *x += (self->size.left + self->size.right) / 2; + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + *x += self->size.left + self->size.right; + break; + case StaticGravity: + case ForgetGravity: + x += self->size.left; + break; + } + + /* vertical */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + break; + case NorthGravity: + case CenterGravity: + case SouthGravity: + *y += (self->size.top + self->size.bottom) / 2; + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + *y += self->size.top + self->size.bottom; + break; + case StaticGravity: + case ForgetGravity: + *y += self->size.top; + break; + } +} diff --git a/openbox/frame.h b/openbox/frame.h new file mode 100644 index 0000000..ec53093 --- /dev/null +++ b/openbox/frame.h @@ -0,0 +1,31 @@ +#ifndef __frame_h +#define __frame_h + +#include "geom.h" +#include "client.h" + +typedef struct Frame { + Client *client; + + Window window; + Window plate; + + Strut size; + Rect area; + gboolean visible; +} Frame; + +/*! Applies gravity to the client's position to find where the frame should + be positioned. + @return The proper coordinates for the frame, based on the client. +*/ +void frame_client_gravity(Frame *self, int *x, int *y); + +/*! Reversly applies gravity to the frame's position to find where the client + should be positioned. + @return The proper coordinates for the client, based on the frame. +*/ +void frame_frame_gravity(Frame *self, int *x, int *y); + + +#endif diff --git a/openbox/geom.h b/openbox/geom.h new file mode 100644 index 0000000..89b69b1 --- /dev/null +++ b/openbox/geom.h @@ -0,0 +1,49 @@ +#ifndef __geom_h +#define __geom_h + +typedef struct Point { + int x; + int y; +} Point; + +#define POINT_SET(pt, nx, ny) {pt.x = nx; pt.y = ny;} + +typedef struct Size { + int width; + int height; +} Size; + +#define SIZE_SET(sz, w, h) {sz.width = w; sz.height = h;} + +typedef struct Rect { + int x; + int y; + int width; + int height; +} Rect; + +#define RECT_SET_POINT(r, nx, ny) \ + {r.x = ny; r.y = ny;} +#define RECT_SET_SIZE(r, w, h) \ + {r.width = w; r.height = h;} +#define RECT_SET(r, nx, ny, w, h) \ + {r.x = nx; r.y = ny; r.width = w; r.height = h;} + +#define RECT_EQUAL(r1, r2) (r1.x == r2.x && r1.y == r2.y && \ + r1.width == r2.width && r1.height == r2.height) + +typedef struct Strut { + int left; + int top; + int right; + int bottom; +} Strut; + +#define STRUT_SET(s, l, t, r, b) \ + {s.left = l; s.top = t; s.right = r; s.bottom = b; } + +#define STRUT_ADD(s1, s2) \ + {s1.left = MAX(s1.left, s2.left); s1.right = MAX(s1.right, s2.right); \ + s1.top = MAX(s1.top, s2.top); s1.bottom = MAX(s1.bottom, s2.bottom); } + +#endif diff --git a/openbox/gettext.h b/openbox/gettext.h new file mode 100644 index 0000000..7bbc6a9 --- /dev/null +++ b/openbox/gettext.h @@ -0,0 +1,73 @@ +/* Convenience header for conditional use of GNU . + Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published + by the Free Software Foundation; either version 2, or (at your option) + any later version. + + 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. */ + +#ifndef _LIBGETTEXT_H +#define _LIBGETTEXT_H 1 + + +/* NLS can be disabled through the configure --disable-nls option. */ +#if ENABLE_NLS + +/* Get declarations of GNU message catalog functions. */ +# include + +#else + +/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which + chokes if dcgettext is defined as a macro. So include it now, to make + later inclusions of a NOP. We don't include + as well because people using "gettext.h" will not include , + and also including would fail on SunOS 4, whereas + is OK. */ +#if defined(__sun) +# include +#endif + +/* Disabled NLS. + The casts to 'const char *' serve the purpose of producing warnings + for invalid uses of the value returned from these functions. + On pre-ANSI systems without 'const', the config.h file is supposed to + contain "#define const". */ +# define gettext(Msgid) ((const char *) (Msgid)) +# define dgettext(Domainname, Msgid) ((const char *) (Msgid)) +# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid)) +# define ngettext(Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dngettext(Domainname, Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define textdomain(Domainname) ((const char *) (Domainname)) +# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) +# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset)) + +#endif + +/* A pseudo function call that serves as a marker for the automated + extraction of messages, but does not call gettext(). The run-time + translation is done at a different place in the code. + The argument, String, should be a literal string. Concatenated strings + and other string expressions won't work. + The macro's expansion is not parenthesized, so that it is suitable as + initializer for static 'char[]' or 'const char[]' variables. */ +#define gettext_noop(String) String + +/* Custom macro to make life easier */ +#define _(str) gettext(str) + +#endif /* _LIBGETTEXT_H */ diff --git a/openbox/hooks.c b/openbox/hooks.c new file mode 100644 index 0000000..35d8d69 --- /dev/null +++ b/openbox/hooks.c @@ -0,0 +1,295 @@ +#include "hooks.h" +#include + +/* + * + * Define the 'Hook' class type + * + */ +#define IS_HOOK(v) ((v)->ob_type == &HookType) + +staticforward PyTypeObject HookType; + +typedef struct HookObject { + PyObject_HEAD + GSList *funcs; +} HookObject; + +static int hook_init(HookObject *self, PyObject *args, PyObject *kwds) +{ + char *keywords[] = { 0 }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, ":__init__", keywords)) + return -1; + self->funcs = NULL; + return 0; +} + +static void hook_dealloc(HookObject *self) +{ + GSList *it; + + for (it = self->funcs; it != NULL; it = it->next) + Py_DECREF((PyObject*) it->data); + + PyObject_Del((PyObject*) self); +} + +static PyObject *hook_fire(HookObject *self, PyObject *args) +{ + GSList *it; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'fire' requires a 'Hook' object"); + return NULL; + } + + for (it = self->funcs; it != NULL; it = it->next) { + PyObject *ret = PyObject_CallObject(it->data, args); + if (ret == NULL) + return NULL; + Py_DECREF(ret); + } + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *hook_append(HookObject *self, PyObject *args) +{ + PyObject *func; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'append' requires a 'Hook' object"); + return NULL; + } + if (!PyArg_ParseTuple(args, "O:append", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'append' requires a callable argument"); + return NULL; + } + self->funcs = g_slist_append(self->funcs, func); + Py_INCREF(func); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *hook_remove(HookObject *self, PyObject *args) +{ + PyObject *func; + GSList *it; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'remove' requires a 'Hook' object"); + return NULL; + } + if (!PyArg_ParseTuple(args, "O:remove", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'remove' requires a callable argument"); + return NULL; + } + + it = g_slist_find(self->funcs, func); + if (it != NULL) { + self->funcs = g_slist_delete_link(self->funcs, it); + Py_DECREF(func); + + Py_INCREF(Py_None); + return Py_None; + } + PyErr_SetString(PyExc_TypeError, + "given callable object was not found in Hook"); + return NULL; +} + +static PyObject *hook_call(HookObject *self, PyObject *args) +{ + GSList *it, *next; + gboolean stop = FALSE; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor '__call__' requires a 'Hook' object"); + return NULL; + } + + for (it = self->funcs; !stop && it != NULL;) { + next = it->next; /* incase the hook removes itself */ + + PyObject *ret = PyObject_CallObject(it->data, args); + if (ret == NULL) + return NULL; + if (ret != Py_None) + stop = TRUE; + Py_DECREF(ret); + + it = next; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static PyTypeObject HookType = { + PyObject_HEAD_INIT(NULL) + 0, + "Hook", + sizeof(HookObject), + 0, + (destructor) hook_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +static PyMethodDef HookMethods[] = { + {"append", (PyCFunction)hook_append, METH_VARARGS, + "hook.add(func) -- Add a function to the hook." }, + {"remove", (PyCFunction)hook_remove, METH_VARARGS, + "hook.remove(func) -- Remove a function from the hook." }, + { NULL, NULL, 0, NULL } +}; + + +/* + * + * Module initialization/finalization + * + */ + +static PyObject *hooks, *hooksdict; + +static PyMethodDef HooksMethods[] = { + { NULL, NULL, 0, NULL } +}; + +struct HookObject *hooks_create(char *name) +{ + HookObject *hook; + int ret; + + hook = PyObject_New(HookObject, &HookType); + hook->funcs = NULL; + + /* add it to the hooks module */ + ret = PyDict_SetItemString(hooksdict, name, (PyObject*) hook); + g_assert(ret != -1); + + return hook; +} + +void hooks_startup() +{ + HookType.ob_type = &PyType_Type; + HookType.tp_methods = HookMethods; + HookType.tp_alloc = PyType_GenericAlloc; + HookType.tp_new = PyType_GenericNew; + HookType.tp_init = (initproc) hook_init; + HookType.tp_call = (ternaryfunc) hook_call; + PyType_Ready(&HookType); + + Py_InitModule("hooks", HooksMethods); + + /* get the hooks module/dict */ + hooks = PyImport_ImportModule("hooks"); /* new */ + g_assert(hooks != NULL); + hooksdict = PyModule_GetDict(hooks); /* borrowed */ + g_assert(hooksdict != NULL); + + /* add the Hook type to the hooks module */ + PyDict_SetItemString(hooksdict, "Hook", (PyObject*) &HookType); + + hook_startup = hooks_create("startup"); + hook_shutdown = hooks_create("shutdown"); + hook_visibledesktop = hooks_create("visibledesktop"); + hook_numdesktops = hooks_create("numdesktops"); + hook_desktopnames = hooks_create("desktopnames"); + hook_showdesktop = hooks_create("showdesktop"); + hook_screenconfiguration = hooks_create("screenconfiguration"); + hook_screenarea = hooks_create("screenarea"); + hook_managed = hooks_create("managed"); + hook_closed = hooks_create("closed"); + hook_bell = hooks_create("bell"); + hook_urgent = hooks_create("urgent"); + hook_pointerenter = hooks_create("pointerenter"); + hook_pointerleave = hooks_create("pointerleave"); + hook_focused = hooks_create("focused"); + hook_requestactivate = hooks_create("requestactivate"); + hook_title = hooks_create("title"); + hook_desktop = hooks_create("desktop"); + hook_iconic = hooks_create("iconic"); + hook_shaded = hooks_create("shaded"); + hook_maximized = hooks_create("maximized"); + hook_fullscreen = hooks_create("fullscreen"); + hook_visible = hooks_create("visible"); + hook_configuration = hooks_create("configuration"); +} + +void hooks_shutdown() +{ + Py_DECREF(hook_startup); + Py_DECREF(hook_shutdown); + Py_DECREF(hook_visibledesktop); + Py_DECREF(hook_numdesktops); + Py_DECREF(hook_desktopnames); + Py_DECREF(hook_showdesktop); + Py_DECREF(hook_screenconfiguration); + Py_DECREF(hook_screenarea); + Py_DECREF(hook_managed); + Py_DECREF(hook_closed); + Py_DECREF(hook_bell); + Py_DECREF(hook_urgent); + Py_DECREF(hook_pointerenter); + Py_DECREF(hook_pointerleave); + Py_DECREF(hook_focused); + Py_DECREF(hook_requestactivate); + Py_DECREF(hook_title); + Py_DECREF(hook_desktop); + Py_DECREF(hook_iconic); + Py_DECREF(hook_shaded); + Py_DECREF(hook_maximized); + Py_DECREF(hook_fullscreen); + Py_DECREF(hook_visible); + Py_DECREF(hook_configuration); + + Py_DECREF(hooks); +} + +void hooks_fire(struct HookObject *hook, PyObject *args) +{ + PyObject *ret = hook_call(hook, args); + if (ret == NULL) + PyErr_Print(); + Py_XDECREF(ret); +} + +void hooks_fire_client(struct HookObject *hook, struct Client *client) +{ + PyObject *args; + + if (client != NULL) { + PyObject *c = clientwrap_new(client); + g_assert(c != NULL); + args = Py_BuildValue("(O)", c); + Py_DECREF(c); + } else { + args = Py_BuildValue("(O)", Py_None); + } + + g_assert(args != NULL); + hooks_fire(hook, args); + Py_DECREF(args); +} diff --git a/openbox/hooks.h b/openbox/hooks.h new file mode 100644 index 0000000..3211d7c --- /dev/null +++ b/openbox/hooks.h @@ -0,0 +1,56 @@ +#ifndef __hooks_h +#define __hooks_h + +#include "clientwrap.h" +#include + +void hooks_startup(); +void hooks_shutdown(); + +struct HookObject; + +struct HookObject *hooks_create(char *name); + +struct HookObject *hook_startup; +struct HookObject *hook_shutdown; +struct HookObject *hook_visibledesktop; +struct HookObject *hook_numdesktops; +struct HookObject *hook_desktopnames; +struct HookObject *hook_showdesktop; +struct HookObject *hook_screenconfiguration; +struct HookObject *hook_screenarea; +struct HookObject *hook_managed; +struct HookObject *hook_closed; +struct HookObject *hook_bell; +struct HookObject *hook_urgent; +struct HookObject *hook_pointerenter; +struct HookObject *hook_pointerleave; +struct HookObject *hook_focused; +struct HookObject *hook_requestactivate; +struct HookObject *hook_title; +struct HookObject *hook_desktop; +struct HookObject *hook_iconic; +struct HookObject *hook_shaded; +struct HookObject *hook_maximized; +struct HookObject *hook_fullscreen; +struct HookObject *hook_visible; +struct HookObject *hook_configuration; + +#define HOOKFIRE(hook, ...) \ +{ \ + PyObject *args = Py_BuildValue(__VA_ARGS__); \ + g_assert(args != NULL); \ + hooks_fire(hook_##hook, args); \ + Py_DECREF(args); \ +} + +#define HOOKFIRECLIENT(hook, client) \ +{ \ + hooks_fire_client(hook_##hook, client); \ +} + +void hooks_fire(struct HookObject *hook, PyObject *args); + +void hooks_fire_client(struct HookObject *hook, struct Client *client); + +#endif diff --git a/openbox/keyboard.c b/openbox/keyboard.c new file mode 100644 index 0000000..87cd503 --- /dev/null +++ b/openbox/keyboard.c @@ -0,0 +1,696 @@ +#include "focus.h" +#include "openbox.h" +#include "keyboard.h" +#include "clientwrap.h" + +#include +#include +#ifdef HAVE_STRING_H +# include +#endif + +typedef struct KeyBindingTree { + guint state; + guint key; + GList *keylist; + PyObject *func; + + /* the next binding in the tree at the same level */ + struct KeyBindingTree *next_sibling; + /* the first child of this binding (next binding in a chained sequence).*/ + struct KeyBindingTree *first_child; +} KeyBindingTree; + + +static KeyBindingTree *firstnode, *curpos; +static guint reset_key, reset_state; +static gboolean grabbed, user_grabbed; +static PyObject *grab_func; + +/*************************************************************************** + + Define the type 'KeyboardData' + + ***************************************************************************/ + +typedef struct KeyboardData { + PyObject_HEAD + PyObject *keychain; + guint state; + guint keycode; + gboolean press; +} KeyboardData; + +staticforward PyTypeObject KeyboardDataType; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +static PyObject *keybdata_new(PyObject *keychain, guint state, + guint keycode, gboolean press) +{ + KeyboardData *data = PyObject_New(KeyboardData, &KeyboardDataType); + data->keychain = keychain; + Py_INCREF(keychain); + data->state = state; + data->keycode = keycode; + data->press = press; + return (PyObject*) data; +} + +static void keybdata_dealloc(KeyboardData *self) +{ + Py_DECREF(self->keychain); + PyObject_Del((PyObject*)self); +} + +static PyObject *keybdata_getattr(KeyboardData *self, char *name) +{ + if (!strcmp(name, "keychain")) { + Py_INCREF(self->keychain); + return self->keychain; + } else if (!strcmp(name, "state")) + return PyInt_FromLong(self->state); + else if (!strcmp(name, "keycode")) + return PyInt_FromLong(self->keycode); + else if (!strcmp(name, "press")) + return PyInt_FromLong(!!self->press); + + PyErr_Format(PyExc_AttributeError, "no such attribute '%s'", name); + return NULL; +} + +static PyTypeObject KeyboardDataType = { + PyObject_HEAD_INIT(NULL) + 0, + "KeyboardData", + sizeof(KeyboardData), + 0, + (destructor) keybdata_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc) keybdata_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/***************************************************************************/ + +guint keyboard_translate_modifier(char *str) +{ + if (!strcmp("Mod1", str)) return Mod1Mask; + else if (!strcmp("Mod2", str)) return Mod2Mask; + else if (!strcmp("Mod3", str)) return Mod3Mask; + else if (!strcmp("Mod4", str)) return Mod4Mask; + else if (!strcmp("Mod5", str)) return Mod5Mask; + else if (!strcmp("C", str)) return ControlMask; + else if (!strcmp("S", str)) return ShiftMask; + g_warning("Invalid modifier '%s' in binding.", str); + return 0; +} + +static gboolean translate(char *str, guint *state, guint *keycode) +{ + char **parsed; + char *l; + int i; + gboolean ret = FALSE; + KeySym sym; + + parsed = g_strsplit(str, "-", -1); + + /* first, find the key (last token) */ + l = NULL; + for (i = 0; parsed[i] != NULL; ++i) + l = parsed[i]; + if (l == NULL) + goto translation_fail; + + /* figure out the mod mask */ + *state = 0; + for (i = 0; parsed[i] != l; ++i) { + guint m = keyboard_translate_modifier(parsed[i]); + if (!m) goto translation_fail; + *state |= m; + } + + /* figure out the keycode */ + sym = XStringToKeysym(l); + if (sym == NoSymbol) { + g_warning("Invalid key name '%s' in key binding.", l); + goto translation_fail; + } + *keycode = XKeysymToKeycode(ob_display, sym); + if (!keycode) { + g_warning("Key '%s' does not exist on the display.", l); + goto translation_fail; + } + + ret = TRUE; + +translation_fail: + g_strfreev(parsed); + return ret; +} + +static void destroytree(KeyBindingTree *tree) +{ + KeyBindingTree *c; + + while (tree) { + destroytree(tree->next_sibling); + c = tree->first_child; + if (c == NULL) { + GList *it; + for (it = tree->keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(tree->keylist); + Py_XDECREF(tree->func); + } + g_free(tree); + tree = c; + } +} + +static KeyBindingTree *buildtree(GList *keylist) +{ + GList *it; + KeyBindingTree *ret = NULL, *p; + + if (g_list_length(keylist) <= 0) + return NULL; /* nothing in the list.. */ + + for (it = g_list_last(keylist); it != NULL; it = it->prev) { + p = ret; + ret = g_new(KeyBindingTree, 1); + ret->next_sibling = NULL; + ret->func = NULL; + if (p == NULL) { + GList *it; + + /* this is the first built node, the bottom node of the tree */ + ret->keylist = g_list_copy(keylist); /* shallow copy */ + for (it = ret->keylist; it != NULL; it = it->next) /* deep copy */ + it->data = g_strdup(it->data); + } + ret->first_child = p; + if (!translate(it->data, &ret->state, &ret->key)) { + destroytree(ret); + return NULL; + } + } + return ret; +} + +static void assimilate(KeyBindingTree *node) +{ + KeyBindingTree *a, *b, *tmp, *last; + + if (firstnode == NULL) { + /* there are no nodes at this level yet */ + firstnode = node; + } else { + a = firstnode; + last = a; + b = node; + while (a) { + last = a; + if (!(a->state == b->state && a->key == b->key)) { + a = a->next_sibling; + } else { + tmp = b; + b = b->first_child; + g_free(tmp); + a = a->first_child; + } + } + if (!(last->state == b->state && last->key == b->key)) + last->next_sibling = b; + else { + last->first_child = b->first_child; + g_free(b); + } + } +} + +static KeyBindingTree *find(KeyBindingTree *search, gboolean *conflict) +{ + KeyBindingTree *a, *b; + + *conflict = FALSE; + + a = firstnode; + b = search; + while (a && b) { + if (!(a->state == b->state && a->key == b->key)) { + a = a->next_sibling; + } else { + if ((a->first_child == NULL) == (b->first_child == NULL)) { + if (a->first_child == NULL) { + /* found it! (return the actual node, not the search's) */ + return a; + } + } else { + *conflict = TRUE; + return NULL; /* the chain status' don't match (conflict!) */ + } + b = b->first_child; + a = a->first_child; + } + } + return NULL; // it just isn't in here +} + +static void grab_keys(gboolean grab) +{ + if (!grab) { + XUngrabKey(ob_display, AnyKey, AnyModifier, ob_root); + } else { + KeyBindingTree *p = firstnode; + while (p) { + XGrabKey(ob_display, p->key, p->state, ob_root, FALSE, + GrabModeAsync, GrabModeSync); + p = p->next_sibling; + } + } +} + +static void reset_chains() +{ + /* XXX kill timer */ + curpos = NULL; + if (grabbed) { + grabbed = FALSE; + g_message("reset chains. user_grabbed: %d", user_grabbed); + if (!user_grabbed) + XUngrabKeyboard(ob_display, CurrentTime); + } +} + +void keyboard_event(XKeyEvent *e) +{ + PyObject *chain, *client, *args, *keybdata, *ret; + gboolean press = e->type == KeyPress; + + if (focus_client) client = clientwrap_new(focus_client); + else client = Py_None; + + if (user_grabbed) { + GString *str = g_string_sized_new(0); + KeySym sym; + + /* build the 'chain' */ + if (e->state & ControlMask) + g_string_append(str, "C-"); + if (e->state & ShiftMask) + g_string_append(str, "S-"); + if (e->state & Mod1Mask) + g_string_append(str, "Mod1-"); + if (e->state & Mod2Mask) + g_string_append(str, "Mod2-"); + if (e->state & Mod3Mask) + g_string_append(str, "Mod3-"); + if (e->state & Mod4Mask) + g_string_append(str, "Mod4-"); + if (e->state & Mod5Mask) + g_string_append(str, "Mod5-"); + + sym = XKeycodeToKeysym(ob_display, e->keycode, 0); + if (sym == NoSymbol) + g_string_append(str, "NoSymbol"); + else { + char *name = XKeysymToString(sym); + if (name == NULL) + name = "Undefined"; + g_string_append(str, name); + } + + chain = PyTuple_New(1); + PyTuple_SET_ITEM(chain, 0, PyString_FromString(str->str)); + g_string_free(str, TRUE); + + keybdata = keybdata_new(chain, e->state, e->keycode, press); + + args = Py_BuildValue("OO", keybdata, client); + + ret = PyObject_CallObject(grab_func, args); + if (ret == NULL) PyErr_Print(); + Py_XDECREF(ret); + + Py_DECREF(args); + Py_DECREF(keybdata); + Py_DECREF(chain); + } + + if (press) { + if (e->keycode == reset_key && e->state == reset_state) { + reset_chains(); + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + } else { + KeyBindingTree *p; + if (curpos == NULL) + p = firstnode; + else + p = curpos->first_child; + while (p) { + if (p->key == e->keycode && p->state == e->state) { + if (p->first_child != NULL) { /* part of a chain */ + /* XXX TIMER */ + if (!grabbed && !user_grabbed) { + /*grab should never fail because we should have a + sync grab at this point */ + XGrabKeyboard(ob_display, ob_root, 0, + GrabModeAsync, GrabModeSync, + CurrentTime); + } + grabbed = TRUE; + curpos = p; + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + } else { + GList *it; + int i; + + chain = PyTuple_New(g_list_length(p->keylist)); + for (i = 0, it = p->keylist; it != NULL; + it = it->next, ++i) + PyTuple_SET_ITEM(chain, i, + PyString_FromString(it->data)); + + keybdata = keybdata_new(chain, e->state, e->keycode, + press); + + args = Py_BuildValue("OO", keybdata, client); + + ret = PyObject_CallObject(p->func, args); + if (ret == NULL) PyErr_Print(); + Py_XDECREF(ret); + + Py_DECREF(args); + Py_DECREF(keybdata); + Py_DECREF(chain); + + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + reset_chains(); + } + break; + } + p = p->next_sibling; + } + } + } + + if (client != Py_None) { Py_DECREF(client); } +} + +static void clearall() +{ + grab_keys(FALSE); + destroytree(firstnode); + firstnode = NULL; + grab_keys(TRUE); +} + +static gboolean grab_keyboard(gboolean grab) +{ + gboolean ret = TRUE; + + g_message("grab_keyboard(%s). grabbed: %d", (grab?"True":"False"),grabbed); + + user_grabbed = grab; + if (!grabbed) { + if (grab) + ret = XGrabKeyboard(ob_display, ob_root, 0, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess; + else + XUngrabKeyboard(ob_display, CurrentTime); + } + return ret; +} + +/*************************************************************************** + + Define the type 'Keyboard' + + ***************************************************************************/ + +#define IS_KEYBOARD(v) ((v)->ob_type == &KeyboardType) +#define CHECK_KEYBOARD(self, funcname) { \ + if (!IS_KEYBOARD(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'Keyboard' " \ + "object"); \ + return NULL; \ + } \ +} + +typedef struct Keyboard { + PyObject_HEAD +} Keyboard; + +staticforward PyTypeObject KeyboardType; + +static PyObject *keyb_bind(Keyboard *self, PyObject *args) +{ + KeyBindingTree *tree = NULL, *t; + gboolean conflict; + PyObject *item, *tuple, *func; + GList *keylist = NULL, *it; + int i, s; + + CHECK_KEYBOARD(self, "grab"); + if (!PyArg_ParseTuple(args, "OO:grab", &tuple, &func)) + return NULL; + + if (!PyTuple_Check(tuple)) { + PyErr_SetString(PyExc_ValueError, "expected a tuple of strings"); + goto binderror; + } + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_ValueError, "expected a callable object"); + goto binderror; + } + + s = PyTuple_GET_SIZE(tuple); + if (s <= 0) { + PyErr_SetString(PyExc_ValueError, "expected a tuple of strings"); + goto binderror; + } + + for (i = 0; i < s; ++i) { + item = PyTuple_GET_ITEM(tuple, i); + if (!PyString_Check(item)) { + PyErr_SetString(PyExc_ValueError, "expected a tuple of strings"); + goto binderror; + } + keylist = g_list_append(keylist, + g_strdup(PyString_AsString(item))); + } + + if (!(tree = buildtree(keylist))) { + PyErr_SetString(PyExc_ValueError, "invalid binding"); + goto binderror; + } + + t = find(tree, &conflict); + if (conflict) { + PyErr_SetString(PyExc_ValueError, "conflict with binding"); + goto binderror; + } + if (t != NULL) { + /* already bound to something */ + PyErr_SetString(PyExc_ValueError, "keychain is already bound"); + goto binderror; + } + + /* grab the server here to make sure no key pressed go missed */ + XGrabServer(ob_display); + XSync(ob_display, FALSE); + + grab_keys(FALSE); + + /* set the function */ + t = tree; + while (t->first_child) t = t->first_child; + t->func = func; + Py_INCREF(func); + + /* assimilate this built tree into the main tree */ + assimilate(tree); // assimilation destroys/uses the tree + + grab_keys(TRUE); + + XUngrabServer(ob_display); + XFlush(ob_display); + + for (it = keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(it); + + Py_INCREF(Py_None); + return Py_None; + +binderror: + if (tree != NULL) destroytree(tree); + for (it = keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(it); + return NULL; +} + +static PyObject *keyb_clearBinds(Keyboard *self, PyObject *args) +{ + CHECK_KEYBOARD(self, "clearBinds"); + if (!PyArg_ParseTuple(args, ":clearBinds")) + return NULL; + clearall(); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *keyb_grab(Keyboard *self, PyObject *args) +{ + PyObject *func; + + CHECK_KEYBOARD(self, "grab"); + if (!PyArg_ParseTuple(args, "O:grab", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_ValueError, "expected a callable object"); + return NULL; + } + if (!grab_keyboard(TRUE)) { + PyErr_SetString(PyExc_RuntimeError, "failed to grab keyboard"); + return NULL; + } + grab_func = func; + Py_INCREF(grab_func); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *keyb_ungrab(Keyboard *self, PyObject *args) +{ + CHECK_KEYBOARD(self, "ungrab"); + if (!PyArg_ParseTuple(args, ":ungrab")) + return NULL; + grab_keyboard(FALSE); + Py_XDECREF(grab_func); + grab_func = NULL; + Py_INCREF(Py_None); + return Py_None; +} + +#define METH(n, d) {#n, (PyCFunction)keyb_##n, METH_VARARGS, #d} + +static PyMethodDef KeyboardMethods[] = { + METH(bind, + "bind(keychain, func)\n\n" + "Binds a key-chain to a function. The keychain is a tuple of strings " + "which define a chain of key presses. Each member of the tuple has " + "the format [Modifier-]...[Key]. Modifiers can be 'mod1', 'mod2', " + "'mod3', 'mod4', 'mod5', 'control', and 'shift'. The keys on your " + "keyboard that are bound to each of these modifiers can be found by " + "running 'xmodmap'. The Key can be any valid key definition. Key " + "definitions can be found by running 'xev', pressing the key while " + "its window is focused, and watching its output. Here are some " + "examples of valid keychains: ('a'), ('F7'), ('control-a', 'd'), " + "('control-mod1-x', 'control-mod4-g'), ('F1', 'space'). The func " + "must have a definition similar to 'def func(keydata, client)'. A " + "keychain cannot be bound to more than one function."), + METH(clearBinds, + "clearBinds()\n\n" + "Removes all bindings that were previously made by bind()."), + METH(grab, + "grab(func)\n\n" + "Grabs the entire keyboard, causing all possible keyboard events to " + "be passed to the given function. CAUTION: Be sure when you grab() " + "that you also have an ungrab() that will execute, or you will not " + "be able to type until you restart Openbox. The func must have a " + "definition similar to 'def func(keydata)'. The keyboard cannot be " + "grabbed if it is already grabbed."), + METH(ungrab, + "ungrab()\n\n" + "Ungrabs the keyboard. The keyboard cannot be ungrabbed if it is not " + "grabbed."), + { NULL, NULL, 0, NULL } +}; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +static void keyb_dealloc(PyObject *self) +{ + PyObject_Del(self); +} + +static PyTypeObject KeyboardType = { + PyObject_HEAD_INIT(NULL) + 0, + "Keyboard", + sizeof(Keyboard), + 0, + (destructor) keyb_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/**************************************************************************/ + +void keyboard_startup() +{ + PyObject *input, *inputdict, *ptr; + gboolean b; + + curpos = firstnode = NULL; + grabbed = user_grabbed = FALSE; + + b = translate("C-G", &reset_state, &reset_key); + g_assert(b); + + KeyboardType.ob_type = &PyType_Type; + KeyboardType.tp_methods = KeyboardMethods; + PyType_Ready(&KeyboardType); + PyType_Ready(&KeyboardDataType); + + /* get the input module/dict */ + input = PyImport_ImportModule("input"); /* new */ + g_assert(input != NULL); + inputdict = PyModule_GetDict(input); /* borrowed */ + g_assert(inputdict != NULL); + + /* add a Keyboard instance to the input module */ + ptr = (PyObject*) PyObject_New(Keyboard, &KeyboardType); + PyDict_SetItemString(inputdict, "Keyboard", ptr); + Py_DECREF(ptr); + + Py_DECREF(input); +} + +void keyboard_shutdown() +{ + if (grabbed || user_grabbed) { + grabbed = FALSE; + grab_keyboard(FALSE); + } + grab_keys(FALSE); + destroytree(firstnode); + firstnode = NULL; +} + diff --git a/openbox/keyboard.h b/openbox/keyboard.h new file mode 100644 index 0000000..464606b --- /dev/null +++ b/openbox/keyboard.h @@ -0,0 +1,13 @@ +#ifndef __keyboard_h +#define __keyboard_h + +#include + +void keyboard_startup(); +void keyboard_shutdown(); + +guint keyboard_translate_modifier(char *str); + +void keyboard_event(XKeyEvent *e); + +#endif diff --git a/openbox/openbox.c b/openbox/openbox.c new file mode 100644 index 0000000..bd7585a --- /dev/null +++ b/openbox/openbox.c @@ -0,0 +1,210 @@ +#include "openbox.h" +#include "event.h" +#include "client.h" +#include "xerror.h" +#include "prop.h" +#include "screen.h" +#include "focus.h" +#include "extensions.h" +#include "gettext.h" +#include "keyboard.h" +#include "pointer.h" +#include "engine.h" +#include "python.h" +#include "hooks.h" +#include "clientwrap.h" +#include "openboxwrap.h" +#include "themerc.h" +#include "timer.h" +#include "../render/render.h" + +#ifdef HAVE_FCNTL_H +# include +#endif +#ifdef HAVE_SYS_SELECT_H +# include +#endif +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_SYS_WAIT_H +# include +# include +#endif +#ifdef HAVE_LOCALE_H +# include +#endif + +#include + +Display *ob_display = NULL; +int ob_screen; +Window ob_root; +State ob_state; +gboolean ob_shutdown = FALSE; +gboolean ob_restart = FALSE; +char *ob_restart_path = NULL; +gboolean ob_remote = FALSE; +gboolean ob_sync = TRUE; +Cursors ob_cursors; + +void signal_handler(int signal); + +int main(int argc, char **argv) +{ + struct sigaction action; + sigset_t sigset; + + ob_state = State_Starting; + + /* initialize the locale */ + if (!setlocale(LC_ALL, "")) + g_warning("Couldn't set locale from environment.\n"); + bindtextdomain(PACKAGE, LOCALEDIR); + bind_textdomain_codeset(PACKAGE, "UTF-8"); + textdomain(PACKAGE); + + /* set up signal handler */ + sigemptyset(&sigset); + action.sa_handler = signal_handler; + action.sa_mask = sigset; + action.sa_flags = SA_NOCLDSTOP | SA_NODEFER; + sigaction(SIGUSR1, &action, (struct sigaction *) NULL); + sigaction(SIGPIPE, &action, (struct sigaction *) NULL); + sigaction(SIGSEGV, &action, (struct sigaction *) NULL); + sigaction(SIGFPE, &action, (struct sigaction *) NULL); + sigaction(SIGTERM, &action, (struct sigaction *) NULL); + sigaction(SIGINT, &action, (struct sigaction *) NULL); + sigaction(SIGHUP, &action, (struct sigaction *) NULL); + sigaction(SIGCHLD, &action, (struct sigaction *) NULL); + + /* anything that died while we were restarting won't give us a SIGCHLD */ + while (waitpid(-1, NULL, WNOHANG) > 0); + + /* XXX parse out command line args */ + (void)argc;(void)argv; + + ob_display = XOpenDisplay(NULL); + if (ob_display == NULL) { + /* print a message and exit */ + g_critical("Failed to open the display."); + exit(1); + } + if (fcntl(ConnectionNumber(ob_display), F_SETFD, 1) == -1) { + /* print a message and exit */ + g_critical("Failed to set display as close-on-exec."); + exit(1); + } + + ob_screen = DefaultScreen(ob_display); + ob_root = RootWindow(ob_display, ob_screen); + + /* XXX fork self onto other screens */ + + XSynchronize(ob_display, ob_sync); + + /* check for locale support */ + if (!XSupportsLocale()) + g_warning("X server does not support locale."); + if (!XSetLocaleModifiers("")) + g_warning("Cannot set locale modifiers for the X server."); + + /* set our error handler */ + XSetErrorHandler(xerror_handler); + + /* set the DISPLAY environment variable for any lauched children, to the + display we're using, so they open in the right place. */ + putenv(g_strdup_printf("DISPLAY=%s", DisplayString(ob_display))); + + ob_cursors.left_ptr = XCreateFontCursor(ob_display, XC_left_ptr); + ob_cursors.ll_angle = XCreateFontCursor(ob_display, XC_ll_angle); + ob_cursors.lr_angle = XCreateFontCursor(ob_display, XC_lr_angle); + + prop_startup(); /* get atoms values for the display */ + extensions_query_all(); /* find which extensions are present */ + + if (screen_annex()) { /* it will be ours! */ + timer_startup(); + render_startup(); + themerc_startup(); + engine_startup(themerc_engine); + python_startup(); + openboxwrap_startup(); + clientwrap_startup(); + hooks_startup(); + event_startup(); + screen_startup(); + focus_startup(); + client_startup(); + keyboard_startup(); + pointer_startup(); + + /* load the user's settings */ + if (!python_import("rc")) + g_warning("ERROR LOADING RC FILE"); + + HOOKFIRE(startup, "()"); + + /* get all the existing windows */ + client_manage_all(); + + ob_state = State_Running; + while (!ob_shutdown) { + event_loop(); + } + ob_state = State_Exiting; + + client_unmanage_all(); + + HOOKFIRE(shutdown, "()"); + + pointer_shutdown(); + keyboard_shutdown(); + client_shutdown(); + screen_shutdown(); + event_shutdown(); + hooks_shutdown(); + clientwrap_shutdown(); + openboxwrap_shutdown(); + python_shutdown(); + engine_shutdown(); + themerc_shutdown(); + render_shutdown(); + timer_shutdown(); + } + + XCloseDisplay(ob_display); + + /* XXX if (ob_restart) */ + + return 0; +} + +void signal_handler(int signal) +{ + switch (signal) { + case SIGUSR1: + g_message("Caught SIGUSR1 signal. Restarting."); + ob_shutdown = ob_restart = TRUE; + break; + + case SIGCHLD: + wait(NULL); + break; + + case SIGHUP: + case SIGINT: + case SIGTERM: + case SIGPIPE: + g_message("Caught signal %d. Exiting.", signal); + ob_shutdown = TRUE; + break; + + case SIGFPE: + case SIGSEGV: + g_error("Caught signal %d. Aborting and dumping core.", signal); + } +} diff --git a/openbox/openbox.h b/openbox/openbox.h new file mode 100644 index 0000000..f8797d7 --- /dev/null +++ b/openbox/openbox.h @@ -0,0 +1,44 @@ +#ifndef __openbox_h +#define __openbox_h + +#include +#include + +/*! The X display */ +extern Display *ob_display; +/*! The number of the screen on which we're running */ +extern int ob_screen; +/*! The root window */ +extern Window ob_root; + +/*! States of execution for Openbox */ +typedef enum { + State_Starting, + State_Exiting, + State_Running +} State; + +/* The state of execution of the window manager */ +State ob_state; + +/*! When set to true, Openbox will exit */ +extern gboolean ob_shutdown; +/*! When set to true, Openbox will restart instead of shutting down */ +extern gboolean ob_restart; +/*! When restarting, if this is not NULL, it will be executed instead of + restarting Openbox. */ +extern char *ob_restart_path; + +/*! Runtime option to specify running on a remote display */ +extern gboolean ob_remote; +/*! Runtime option to run in synchronous mode */ +extern gboolean ob_sync; + +typedef struct Cursors { + Cursor left_ptr; + Cursor ll_angle; + Cursor lr_angle; +} Cursors; +Cursors ob_cursors; + +#endif diff --git a/openbox/openboxwrap.c b/openbox/openboxwrap.c new file mode 100644 index 0000000..c73e725 --- /dev/null +++ b/openbox/openboxwrap.c @@ -0,0 +1,502 @@ +#include "openboxwrap.h" +#include "openbox.h" +#include "screen.h" +#include "prop.h" + +/*************************************************************************** + + Define the type 'OpenboxWrap' + + ***************************************************************************/ + +#define IS_OWRAP(v) ((v)->ob_type == &OpenboxWrapType) +#define CHECK_OWRAP(self, funcname) { \ + if (!IS_OWRAP(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'Openbox' " \ + "object"); \ + return NULL; \ + } \ +} + + +staticforward PyTypeObject OpenboxWrapType; + +/*************************************************************************** + + Attribute methods + + ***************************************************************************/ + +static PyObject *owrap_shutdown(OpenboxWrap *self, PyObject *args) +{ + CHECK_OWRAP(self, "shutdown"); + if (!PyArg_ParseTuple(args, ":shutdown")) + return NULL; + ob_shutdown = TRUE; + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *owrap_restart(OpenboxWrap *self, PyObject *args) +{ + char *path = NULL; + + CHECK_OWRAP(self, "restart"); + if (!PyArg_ParseTuple(args, "|s:restart", &path)) + return NULL; + ob_shutdown = ob_restart = TRUE; + ob_restart_path = path; + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *owrap_state(OpenboxWrap *self, PyObject *args) +{ + CHECK_OWRAP(self, "state"); + if (!PyArg_ParseTuple(args, ":state")) + return NULL; + return PyInt_FromLong(ob_state); +} + +static PyObject *owrap_desktop(OpenboxWrap *self, PyObject *args) +{ + CHECK_OWRAP(self, "desktop"); + if (!PyArg_ParseTuple(args, ":desktop")) + return NULL; + return PyInt_FromLong(screen_desktop); +} + +static PyObject *owrap_setDesktop(OpenboxWrap *self, PyObject *args) +{ + int desktop; + + CHECK_OWRAP(self, "setDesktop"); + if (!PyArg_ParseTuple(args, "i:setDesktop", &desktop)) + return NULL; + if (desktop < 0 || (unsigned)desktop >= screen_num_desktops) { + PyErr_SetString(PyExc_ValueError, "invalid desktop"); + return NULL; + } + screen_set_desktop(desktop); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *owrap_setNextDesktop(OpenboxWrap *self, PyObject *args) +{ + gboolean wrap = TRUE; + guint d; + + CHECK_OWRAP(self, "setNextDesktop"); + if (!PyArg_ParseTuple(args, "|i:setNextDesktop", &wrap)) + return NULL; + d = screen_desktop + 1; + if (d >= screen_num_desktops && wrap) + d = 0; + if (d < screen_num_desktops) + screen_set_desktop(d); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *owrap_setPreviousDesktop(OpenboxWrap *self, PyObject *args) +{ + gboolean wrap = TRUE; + guint d; + + CHECK_OWRAP(self, "setPreviousDesktop"); + if (!PyArg_ParseTuple(args, "|i:setPreviousDesktop", &wrap)) + return NULL; + d = screen_desktop - 1; + if (d >= screen_num_desktops && wrap) + d = screen_num_desktops - 1; + if (d < screen_num_desktops) + screen_set_desktop(d); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *owrap_numDesktops(OpenboxWrap *self, PyObject *args) +{ + CHECK_OWRAP(self, "numDesktops"); + if (!PyArg_ParseTuple(args, ":numDesktops")) + return NULL; + return PyInt_FromLong(screen_num_desktops); +} + +static PyObject *owrap_setNumDesktops(OpenboxWrap *self, PyObject *args) +{ + int desktops; + + CHECK_OWRAP(self, "setNumDesktops"); + if (!PyArg_ParseTuple(args, "i:setNumDesktops", &desktops)) + return NULL; + if (desktops <= 0) { + PyErr_SetString(PyExc_ValueError, "invalid number of desktops"); + return NULL; + } + screen_set_num_desktops(desktops); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *owrap_desktopNames(OpenboxWrap *self, PyObject *args) +{ + PyObject *tuple; + int i, s; + + CHECK_OWRAP(self, "desktopNames"); + if (!PyArg_ParseTuple(args, ":desktopNames")) + return NULL; + s = screen_desktop_names->len; + tuple = PyTuple_New(s); + for (i = 0; i < s; ++i) + PyTuple_SET_ITEM(tuple, i, g_ptr_array_index(screen_desktop_names, i)); + return tuple; +} + +static PyObject *owrap_setDesktopNames(OpenboxWrap *self, PyObject *args) +{ + PyObject *seq; + int i, s; + GPtrArray *data; + + CHECK_OWRAP(self, "setDesktopNames"); + if (!PyArg_ParseTuple(args, "O:setDesktopNames", &seq)) + return NULL; + if (!PySequence_Check(seq)) + PyErr_SetString(PyExc_TypeError, "expected a sequence"); + return NULL; + + s = PySequence_Size(seq); + for (i = 0; i < s; ++i) { + PyObject *item; + gboolean check; + item = PySequence_GetItem(seq, i); /* new */ + check = PyString_Check(item); + Py_DECREF(item); + if (!check) { + PyErr_SetString(PyExc_TypeError, "expected a sequence of strings"); + return NULL; + } + } + + data = g_ptr_array_sized_new(s); + for (i = 0; i < s; ++i) { + PyObject *item; + item = PySequence_GetItem(seq, i); /* new */ + g_ptr_array_index(data, i) = PyString_AsString(item); /* borrowed */ + Py_DECREF(item); + } + + PROP_SETSA(ob_root, net_desktop_names, utf8, data); + g_ptr_array_free(data, TRUE); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *owrap_showingDesktop(OpenboxWrap *self, PyObject *args) +{ + CHECK_OWRAP(self, "showingDesktop"); + if (!PyArg_ParseTuple(args, ":showingDesktop")) + return NULL; + return PyInt_FromLong(!!screen_showing_desktop); +} + +static PyObject *owrap_setShowingDesktop(OpenboxWrap *self, PyObject *args) +{ + int show; + + CHECK_OWRAP(self, "setShowingDesktop"); + if (!PyArg_ParseTuple(args, "i:setShowingDesktop", &show)) + return NULL; + screen_show_desktop(show); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *owrap_screenArea(OpenboxWrap *self, PyObject *args) +{ + int desktop; + Rect *area; + PyObject *tuple; + + CHECK_OWRAP(self, "screenArea"); + if (!PyArg_ParseTuple(args, "i:screenArea", &desktop)) + return NULL; + + area = screen_area(desktop); + if (area == NULL) { + PyErr_SetString(PyExc_ValueError, "invalid desktop"); + return NULL; + } + + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(area->x)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(area->y)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(area->width)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(area->height)); + return tuple; +} + +static PyObject *owrap_screenStrut(OpenboxWrap *self, PyObject *args) +{ + int desktop; + Strut *strut; + PyObject *tuple; + + CHECK_OWRAP(self, "screenStrut"); + if (!PyArg_ParseTuple(args, "i:screenStrut", &desktop)) + return NULL; + + strut = screen_strut(desktop); + if (strut == NULL) { + PyErr_SetString(PyExc_ValueError, "invalid desktop"); + return NULL; + } + + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(strut->left)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(strut->top)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(strut->right)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(strut->bottom)); + return tuple; +} + +static PyObject *owrap_physicalSize(OpenboxWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_OWRAP(self, "physicalSize"); + if (!PyArg_ParseTuple(args, ":physicalSize")) + return NULL; + + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(screen_physical_size.width)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(screen_physical_size.height)); + return tuple; +} + +static PyObject *owrap_screenNumber(OpenboxWrap *self, PyObject *args) +{ + CHECK_OWRAP(self, "screenNumber"); + if (!PyArg_ParseTuple(args, ":screenNumber")) + return NULL; + return PyInt_FromLong(ob_screen); +} + +static PyObject *owrap_rootWindow(OpenboxWrap *self, PyObject *args) +{ + CHECK_OWRAP(self, "rootWindow"); + if (!PyArg_ParseTuple(args, ":rootWindow")) + return NULL; + return PyInt_FromLong(ob_root); +} + +static PyObject *owrap_clientList(OpenboxWrap *self, PyObject *args) +{ + CHECK_OWRAP(self, "clientList"); + if (!PyArg_ParseTuple(args, ":clientList")) + return NULL; + Py_INCREF(self->client_list); + return self->client_list; +} + +#define METH(n, d) {#n, (PyCFunction)owrap_##n, METH_VARARGS, #d} + +static PyMethodDef OpenboxWrapMethods[] = { + METH(shutdown, + "Causes Openbox to shutdown and exit."), + METH(restart, + "Causes Openbox to shutdown and restart. If path is specified, " + "Openbox will shutdown and attempt to run the specified executable " + "instead of restarting itself. If that fails, however, it will " + "restart itself."), + METH(state, + "Returns Openbox's current state, this will be one of the State " + "constants."), + METH(desktop, + "Returns the number of the currently visible desktop. This will be " + "in the range of [0, numDesktops())."), + METH(setDesktop, + "Sets the specified desktop as the visible desktop."), + METH(setNextDesktop, + "Sets the visible desktop to the next desktop, optionally wrapping " + "around when reaching the last."), + METH(setPreviousDesktop, + "Sets the visible desktop to the previous desktop, optionally " + "wrapping around when reaching the first."), + METH(numDesktops, + "Returns the number of desktops available."), + METH(desktopNames, + "Returns a tuple of names, containing a name for each desktop. The " + "tuple may have a length greater than numDesktops() if more names " + "have been specified."), + METH(setDesktopNames, + "Sets the names for the desktops."), + METH(showingDesktop, + "Returns True or False, depicting if Openbox is in 'showing the " + "desktop' mode. In 'showing the desktop' mode, all normal clients " + "are hidden and the desktop is given focus if possible."), + METH(setShowingDesktop, + "Enters or leaves 'showing the desktop' mode. See showingDesktop() " + "for a description of this mode."), + METH(screenArea, + "Returns the on-screen available area. This is the area not reserved " + "by applications' struts. Windows should be placed within this area, " + "not within the physicalSize()."), + METH(screenStrut, + "Returns the combined strut which has been reserved by all " + "applications on the desktops."), + METH(physicalSize, + "Returns the physical size of the display device (in pixels)."), + METH(screenNumber, + "Returns the number of the screen on which Openbox is running."), + METH(rootWindow, + "Return the window id of the root window."), + METH(clientList, + "Returns a all clients currently being managed by Openbox. This list " + "is updated as clients are managed and closed/destroyed/released."), + { NULL, NULL, 0, NULL } +}; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +/*static PyObject *owrap_getattr(OpenboxWrap *self, char *name) +{ + CHECK_OWRAP(self, "getattr"); + return Py_FindMethod(OpenboxWrapAttributeMethods, (PyObject*)self, name); +}*/ + +static void owrap_dealloc(OpenboxWrap *self) +{ + PyObject_Del((PyObject*) self); +} + +static PyTypeObject OpenboxWrapType = { + PyObject_HEAD_INIT(NULL) + 0, + "Openbox", + sizeof(OpenboxWrap), + 0, + (destructor) owrap_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/*************************************************************************** + + Define the type 'OpenboxState' + + ***************************************************************************/ + +#define IS_OSTATE(v) ((v)->ob_type == &OpenboxStateType) +#define CHECK_OSTATE(self, funcname) { \ + if (!IS_OSTATE(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'State' " \ + "object"); \ + return NULL; \ + } \ +} + +staticforward PyTypeObject OpenboxStateType; + +typedef struct OpenboxState { + PyObject_HEAD +} OpenboxState; + +static void ostate_dealloc(PyObject *self) +{ + PyObject_Del(self); +} + +static PyObject *ostate_getattr(OpenboxState *self, char *name) +{ + struct S { char *name; int val; }; + struct S s[] = { + { "Starting", State_Starting }, + { "Running", State_Running }, + { "Exiting", State_Exiting }, + { NULL, 0 } }; + int i; + + CHECK_OSTATE(self, "__getattr__"); + + for (i = 0; s[i].name != NULL; ++i) + if (!strcmp(s[i].name, name)) + return PyInt_FromLong(s[i].val); + PyErr_SetString(PyExc_AttributeError, "invalid attribute"); + return NULL; +} + +static PyTypeObject OpenboxStateType = { + PyObject_HEAD_INIT(NULL) + 0, + "State", + sizeof(OpenboxState), + 0, + (destructor) ostate_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc) ostate_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/*************************************************************************** + + External methods + + ***************************************************************************/ + +void openboxwrap_startup() +{ + PyObject *ob, *obdict, *state; + + OpenboxWrapType.ob_type = &PyType_Type; + OpenboxWrapType.tp_methods = OpenboxWrapMethods; + PyType_Ready(&OpenboxWrapType); + + /* get the ob module/dict */ + ob = PyImport_ImportModule("ob"); /* new */ + g_assert(ob != NULL); + obdict = PyModule_GetDict(ob); /* borrowed */ + g_assert(obdict != NULL); + + /* add an Openbox instance to the ob module */ + openboxwrap_obj = PyObject_New(OpenboxWrap, &OpenboxWrapType); + openboxwrap_obj->client_list = PyList_New(0); + + PyDict_SetItemString(obdict, "Openbox", (PyObject*) openboxwrap_obj); + + /* add an instance of OpenboxState */ + state = (PyObject*) PyObject_New(OpenboxState, &OpenboxStateType); + PyDict_SetItemString(obdict, "State", state); + Py_DECREF(state); + + Py_DECREF(ob); +} + +void openboxwrap_shutdown() +{ + Py_DECREF(openboxwrap_obj->client_list); + Py_DECREF(openboxwrap_obj); +} diff --git a/openbox/openboxwrap.h b/openbox/openboxwrap.h new file mode 100644 index 0000000..8e54f2d --- /dev/null +++ b/openbox/openboxwrap.h @@ -0,0 +1,17 @@ +#ifndef __openboxwrap_h +#define __openboxwrap_h + +#include + +/* OpenboxWrap is a PyObject */ +typedef struct OpenboxWrap { + PyObject_HEAD + PyObject *client_list; +} OpenboxWrap; + +OpenboxWrap *openboxwrap_obj; + +void openboxwrap_startup(); +void openboxwrap_shutdown(); + +#endif diff --git a/openbox/pointer.c b/openbox/pointer.c new file mode 100644 index 0000000..00720f3 --- /dev/null +++ b/openbox/pointer.c @@ -0,0 +1,729 @@ +#include "pointer.h" +#include "keyboard.h" +#include "frame.h" +#include "engine.h" +#include "openbox.h" +#include "hooks.h" + +#include +#include +#include /* for PyMemberDef stuff */ +#ifdef HAVE_STDLIB_H +# include +#endif + +typedef enum { + Action_Press, + Action_Release, + Action_Click, + Action_DoubleClick, + Action_Motion, + NUM_ACTIONS +} Action; + +/* GData of GSList*s of PointerBinding*s. */ +static GData *bound_contexts; +static gboolean grabbed; +static int double_click_rate, drag_threshold; +PyObject *grab_func; + +struct foreach_grab_temp { + Client *client; + gboolean grab; +}; + +typedef struct { + guint state; + guint button; + Action action; + char *name; + GSList *funcs[NUM_ACTIONS]; +} PointerBinding; + +/*************************************************************************** + + Define the type 'ButtonData' + + ***************************************************************************/ + +typedef struct PointerData { + PyObject_HEAD + Action action; + GQuark context; + char *button; + guint state; + guint buttonnum; + int posx, posy; + int pressposx, pressposy; + int pcareax, pcareay, pcareaw, pcareah; +} PointerData; + +staticforward PyTypeObject PointerDataType; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +static PyObject *ptrdata_new(char *button, GQuark context, Action action, + guint state, guint buttonnum, int posx, int posy, + int pressposx, int pressposy, int pcareax, + int pcareay, int pcareaw, int pcareah) +{ + PointerData *self = PyObject_New(PointerData, &PointerDataType); + self->button = g_strdup(button); + self->context = context; + self->action = action; + self->state = state; + self->buttonnum = buttonnum; + self->posx = posx; + self->posy = posy; + self->pressposx = pressposx; + self->pressposy = pressposy; + self->pcareax = pcareax; + self->pcareay = pcareay; + self->pcareaw = pcareaw; + self->pcareah = pcareah; + return (PyObject*) self; +} + +static void ptrdata_dealloc(PointerData *self) +{ + g_free(self->button); + PyObject_Del((PyObject*)self); +} + +static PyObject *ptrdata_getattr(PointerData *self, char *name) +{ + if (!strcmp(name, "button")) + return PyString_FromString(self->button); + if (!strcmp(name, "action")) + return PyInt_FromLong(self->action); + if (!strcmp(name, "context")) + return PyString_FromString(g_quark_to_string(self->context)); + if (!strcmp(name, "state")) + return PyInt_FromLong(self->state); + if (!strcmp(name, "buttonnum")) + return PyInt_FromLong(self->buttonnum); + + if (self->action == Action_Motion) { /* the rest are only for motions */ + if (!strcmp(name, "pos")) { + PyObject *pos = PyTuple_New(2); + PyTuple_SET_ITEM(pos, 0, PyInt_FromLong(self->posx)); + PyTuple_SET_ITEM(pos, 1, PyInt_FromLong(self->posy)); + return pos; + } + if (!strcmp(name, "presspos")) { + PyObject *presspos = PyTuple_New(2); + PyTuple_SET_ITEM(presspos, 0, PyInt_FromLong(self->pressposx)); + PyTuple_SET_ITEM(presspos, 1, PyInt_FromLong(self->pressposy)); + return presspos; + } + if (!strcmp(name, "pressclientarea")) { + if (self->pcareaw < 0) { /* < 0 indicates no client */ + Py_INCREF(Py_None); + return Py_None; + } else { + PyObject *ca = PyTuple_New(4); + PyTuple_SET_ITEM(ca, 0, PyInt_FromLong(self->pcareax)); + PyTuple_SET_ITEM(ca, 1, PyInt_FromLong(self->pcareay)); + PyTuple_SET_ITEM(ca, 2, PyInt_FromLong(self->pcareaw)); + PyTuple_SET_ITEM(ca, 3, PyInt_FromLong(self->pcareah)); + return ca; + } + } + } + + PyErr_Format(PyExc_AttributeError, "no such attribute '%s'", name); + return NULL; +} + +static PyTypeObject PointerDataType = { + PyObject_HEAD_INIT(NULL) + 0, + "PointerData", + sizeof(PointerData), + 0, + (destructor) ptrdata_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc) ptrdata_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/***************************************************************************/ + +static gboolean translate(char *str, guint *state, guint *button) +{ + char **parsed; + char *l; + int i; + gboolean ret = FALSE; + + parsed = g_strsplit(str, "-", -1); + + /* first, find the button (last token) */ + l = NULL; + for (i = 0; parsed[i] != NULL; ++i) + l = parsed[i]; + if (l == NULL) + goto translation_fail; + + /* figure out the mod mask */ + *state = 0; + for (i = 0; parsed[i] != l; ++i) { + guint m = keyboard_translate_modifier(parsed[i]); + if (!m) goto translation_fail; + *state |= m; + } + + /* figure out the button */ + *button = atoi(l); + if (!*button) { + g_warning("Invalid button '%s' in pointer binding.", l); + goto translation_fail; + } + + ret = TRUE; + +translation_fail: + g_strfreev(parsed); + return ret; +} + +static void grab_button(Client *client, guint state, guint button, + GQuark context, gboolean grab) +{ + Window win; + int mode = GrabModeAsync; + unsigned int mask; + + if (context == g_quark_try_string("frame")) { + win = client->frame->window; + mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask; + } else if (context == g_quark_try_string("client")) { + win = client->frame->plate; + mode = GrabModeSync; /* this is handled in pointer_event */ + mask = ButtonPressMask; /* can't catch more than this with Sync mode + the release event is manufactured in + pointer_fire */ + } else return; + + if (grab) + XGrabButton(ob_display, button, state, win, FALSE, mask, mode, + GrabModeAsync, None, None); + else + XUngrabButton(ob_display, button, state, win); +} + +static void foreach_grab(GQuark key, gpointer data, gpointer user_data) +{ + struct foreach_grab_temp *d = user_data; + GSList *it; + for (it = data; it != NULL; it = it->next) { + PointerBinding *b = it->data; + grab_button(d->client, b->state, b->button, key, d->grab); + } +} + +void pointer_grab_all(Client *client, gboolean grab) +{ + struct foreach_grab_temp bt; + bt.client = client; + bt.grab = grab; + g_datalist_foreach(&bound_contexts, foreach_grab, &bt); +} + +static void grab_all_clients(gboolean grab) +{ + GSList *it; + + for (it = client_list; it != NULL; it = it->next) + pointer_grab_all(it->data, grab); +} + +static gboolean grab_pointer(gboolean grab) +{ + gboolean ret = TRUE; + if (grab) + ret = XGrabPointer(ob_display, ob_root, FALSE, (ButtonPressMask | + ButtonReleaseMask | + ButtonMotionMask | + PointerMotionMask), + GrabModeAsync, GrabModeAsync, None, None, + CurrentTime) == GrabSuccess; + else + XUngrabPointer(ob_display, CurrentTime); + if (ret) grabbed = grab; + return ret; +} + +static void foreach_clear(GQuark key, gpointer data, gpointer user_data) +{ + GSList *it; + user_data = user_data; + for (it = data; it != NULL; it = it->next) { + int i; + + PointerBinding *b = it->data; + for (i = 0; i < NUM_ACTIONS; ++i) + while (b->funcs[i] != NULL) { + Py_DECREF((PyObject*)b->funcs[i]->data); + b->funcs[i] = g_slist_delete_link(b->funcs[i], b->funcs[i]); + } + g_free(b->name); + g_free(b); + } + g_slist_free(data); +} + +static void clearall() +{ + grab_all_clients(FALSE); + g_datalist_foreach(&bound_contexts, foreach_clear, NULL); +} + +static void fire_event(char *button, GQuark context, Action action, + guint state, guint buttonnum, int posx, int posy, + int pressposx, int pressposy, int pcareax, + int pcareay, int pcareaw, int pcareah, + PyObject *client, GSList *functions) +{ + PyObject *ptrdata, *args, *ret; + GSList *it; + + ptrdata = ptrdata_new(button, context, action, + state, buttonnum, posx, posy, pressposx, pressposy, + pcareax, pcareay, pcareaw, pcareah); + args = Py_BuildValue("OO", ptrdata, client); + + if (grabbed) { + ret = PyObject_CallObject(grab_func, args); + if (ret == NULL) PyErr_Print(); + Py_XDECREF(ret); + } else { + for (it = functions; it != NULL; it = it->next) { + ret = PyObject_CallObject(it->data, args); + if (ret == NULL) PyErr_Print(); + Py_XDECREF(ret); + } + } + + Py_DECREF(args); + Py_DECREF(ptrdata); +} + +void pointer_event(XEvent *e, Client *c) +{ + static guint button = 0, lastbutton = 0; + static Time time = 0; + static Rect carea; + static guint pressx, pressy; + GQuark contextq; + gboolean click = FALSE, dblclick = FALSE; + PyObject *client; + GString *str = g_string_sized_new(0); + guint state; + GSList *it = NULL; + PointerBinding *b = NULL; + + contextq = engine_get_context(c, e->xany.window); + + /* pick a button, figure out clicks/double clicks */ + switch (e->type) { + case ButtonPress: + if (!button) { + button = e->xbutton.button; + if (c != NULL) carea = c->frame->area; + else carea.width = -1; /* indicates no client */ + pressx = e->xbutton.x_root; + pressy = e->xbutton.y_root; + } + state = e->xbutton.state; + break; + case ButtonRelease: + state = e->xbutton.state; + break; + case MotionNotify: + state = e->xmotion.state; + break; + default: + g_assert_not_reached(); + return; + } + + if (!grabbed) { + for (it = g_datalist_id_get_data(&bound_contexts, contextq); + it != NULL; it = it->next) { + b = it->data; + if (b->state == state && b->button == button) + break; + } + /* if not grabbed and not bound, then nothing to do! */ + if (it == NULL) return; + } + + if (c) client = clientwrap_new(c); + else client = Py_None; + + /* build the button string */ + if (state & ControlMask) g_string_append(str, "C-"); + if (state & ShiftMask) g_string_append(str, "S-"); + if (state & Mod1Mask) g_string_append(str, "Mod1-"); + if (state & Mod2Mask) g_string_append(str, "Mod2-"); + if (state & Mod3Mask) g_string_append(str, "Mod3-"); + if (state & Mod4Mask) g_string_append(str, "Mod4-"); + if (state & Mod5Mask) g_string_append(str, "Mod5-"); + g_string_append_printf(str, "%d", button); + + /* figure out clicks/double clicks */ + switch (e->type) { + case ButtonRelease: + if (button == e->xbutton.button) { + /* determine if this is a valid 'click'. Its not if the release is + not over the window, or if a drag occured. */ + if (ABS(e->xbutton.x_root - pressx) < (unsigned)drag_threshold && + ABS(e->xbutton.y_root - pressy) < (unsigned)drag_threshold && + e->xbutton.x >= 0 && e->xbutton.y >= 0) { + int junk; + Window wjunk; + guint ujunk, w, h; + XGetGeometry(ob_display, e->xany.window, &wjunk, &junk, &junk, + &w, &h, &ujunk, &ujunk); + if (e->xbutton.x < (signed)w && e->xbutton.y < (signed)h) + click =TRUE; + } + + /* determine if this is a valid 'double-click' */ + if (click) { + if (lastbutton == button && + e->xbutton.time - double_click_rate < time) { + dblclick = TRUE; + lastbutton = 0; + } else + lastbutton = button; + } else + lastbutton = 0; + time = e->xbutton.time; + pressx = pressy = 0; + button = 0; + carea.x = carea.y = carea.width = carea.height = 0; + } + break; + } + + /* fire off the events */ + switch (e->type) { + case ButtonPress: + fire_event(str->str, contextq, Action_Press, + state, button, 0, 0, 0, 0, 0, 0, 0, 0, + client, b == NULL ? NULL : b->funcs[Action_Press]); + break; + case ButtonRelease: + fire_event(str->str, contextq, Action_Release, + state, button, 0, 0, 0, 0, 0, 0, 0, 0, + client, b == NULL ? NULL : b->funcs[Action_Release]); + break; + case MotionNotify: + /* watch out for the drag threshold */ + if (ABS(e->xmotion.x_root - pressx) < (unsigned)drag_threshold && + ABS(e->xmotion.y_root - pressy) < (unsigned)drag_threshold) + break; + fire_event(str->str, contextq, Action_Motion, + state, button, e->xmotion.x_root, + e->xmotion.y_root, pressx, pressy, + carea.x, carea.y, carea.width, carea.height, + client, b == NULL ? NULL : b->funcs[Action_Motion]); + break; + } + + if (click) + fire_event(str->str, contextq, Action_Click, + state, button, 0, 0, 0, 0, 0, 0, 0, 0, + client, b == NULL ? NULL : b->funcs[Action_Click]); + if (dblclick) + fire_event(str->str, contextq, Action_DoubleClick, + state, button, 0, 0, 0, 0, 0, 0, 0, 0, + client, b == NULL ? NULL : b->funcs[Action_DoubleClick]); + + g_string_free(str, TRUE); + if (client != Py_None) { Py_DECREF(client); } + + if (contextq == g_quark_try_string("client")) { + /* Replay the event, so it goes to the client*/ + XAllowEvents(ob_display, ReplayPointer, CurrentTime); + /* generate a release event since we don't get real ones */ + if (e->type == ButtonPress) { + e->type = ButtonRelease; + pointer_event(e, c); + } + } +} + +/*************************************************************************** + + Define the type 'Pointer' + + ***************************************************************************/ + +#define IS_POINTER(v) ((v)->ob_type == &PointerType) +#define CHECK_POINTER(self, funcname) { \ + if (!IS_POINTER(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'Pointer' " \ + "object"); \ + return NULL; \ + } \ +} + +typedef struct Pointer { + PyObject_HEAD + Action press; + Action release; + Action click; + Action doubleclick; + Action motion; +} Pointer; + +staticforward PyTypeObject PointerType; + +static PyObject *ptr_bind(Pointer *self, PyObject *args) +{ + char *buttonstr; + char *contextstr; + guint state, button; + PointerBinding *b; + GSList *it; + GQuark context; + PyObject *func; + Action action; + int i; + + CHECK_POINTER(self, "grab"); + if (!PyArg_ParseTuple(args, "ssiO:grab", + &buttonstr, &contextstr, &action, &func)) + return NULL; + + if (!translate(buttonstr, &state, &button)) { + PyErr_SetString(PyExc_ValueError, "invalid button"); + return NULL; + } + + context = g_quark_try_string(contextstr); + if (!context) { + PyErr_SetString(PyExc_ValueError, "invalid context"); + return NULL; + } + + if (action < 0 || action >= NUM_ACTIONS) { + PyErr_SetString(PyExc_ValueError, "invalid action"); + return NULL; + } + + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_ValueError, "expected a callable object"); + return NULL; + } + + for (it = g_datalist_id_get_data(&bound_contexts, context); + it != NULL; it = it->next){ + b = it->data; + if (b->state == state && b->button == button) { + /* already bound */ + b->funcs[action] = g_slist_append(b->funcs[action], func); + Py_INCREF(Py_None); + return Py_None; + } + } + + grab_all_clients(FALSE); + + /* add the binding */ + b = g_new(PointerBinding, 1); + b->state = state; + b->button = button; + b->name = g_strdup(buttonstr); + for (i = 0; i < NUM_ACTIONS; ++i) + if (i != (signed)action) b->funcs[i] = NULL; + b->funcs[action] = g_slist_append(NULL, func); + g_datalist_id_set_data(&bound_contexts, context, + g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b)); + grab_all_clients(TRUE); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *ptr_clearBinds(Pointer *self, PyObject *args) +{ + CHECK_POINTER(self, "clearBinds"); + if (!PyArg_ParseTuple(args, ":clearBinds")) + return NULL; + clearall(); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *ptr_grab(Pointer *self, PyObject *args) +{ + PyObject *func; + + CHECK_POINTER(self, "grab"); + if (!PyArg_ParseTuple(args, "O:grab", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_ValueError, "expected a callable object"); + return NULL; + } + if (!grab_pointer(TRUE)) { + PyErr_SetString(PyExc_RuntimeError, "failed to grab pointer"); + return NULL; + } + grab_func = func; + Py_INCREF(grab_func); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *ptr_ungrab(Pointer *self, PyObject *args) +{ + CHECK_POINTER(self, "ungrab"); + if (!PyArg_ParseTuple(args, ":ungrab")) + return NULL; + grab_pointer(FALSE); + Py_XDECREF(grab_func); + grab_func = NULL; + Py_INCREF(Py_None); + return Py_None; +} + +#define METH(n, d) {#n, (PyCFunction)ptr_##n, METH_VARARGS, #d} + +static PyMethodDef PointerMethods[] = { + METH(bind, + "bind(button, context, func)\n\n" + "Binds a pointer button for a context to a function. See the " + "Terminology section for a decription and list of common contexts. " + "The button is a string which defines a modifier and button " + "combination with the format [Modifier-]...[Button]. Modifiers can " + "be 'mod1', 'mod2', 'mod3', 'mod4', 'mod5', 'control', and 'shift'. " + "The keys on your keyboard that are bound to each of these modifiers " + "can be found by running 'xmodmap'. The button is the number of the " + "button. Button numbers can be found by running 'xev', pressing the " + "button with the pointer over its window, and watching its output. " + "Here are some examples of valid buttons: 'control-1', '2', " + "'mod1-shift-5'. The func must have a definition similar to " + "'def func(keydata, client)'. A button and context may be bound to " + "more than one function."), + METH(clearBinds, + "clearBinds()\n\n" + "Removes all bindings that were previously made by bind()."), + METH(grab, + "grab(func)\n\n" + "Grabs the pointer device, causing all possible pointer events to be " + "sent to the given function. CAUTION: Be sure when you grab() that " + "you also have an ungrab() that will execute, or you will not be " + "able to use the pointer device until you restart Openbox. The func " + "must have a definition similar to 'def func(keydata)'. The pointer " + "cannot be grabbed if it is already grabbed."), + METH(ungrab, + "ungrab()\n\n" + "Ungrabs the pointer. The pointer cannot be ungrabbed if it is not " + "grabbed."), + { NULL, NULL, 0, NULL } +}; + +static PyMemberDef PointerMembers[] = { + {"Action_Press", T_INT, offsetof(Pointer, press), READONLY, + "a pointer button press"}, + {"Action_Release", T_INT, offsetof(Pointer, release), READONLY, + "a pointer button release"}, + {"Action_Click", T_INT, offsetof(Pointer, click), READONLY, + "a pointer button click (press-release)"}, + {"Action_DoubleClick", T_INT, offsetof(Pointer, doubleclick), READONLY, + "a pointer button double-click"}, + {"Action_Motion", T_INT, offsetof(Pointer, motion), READONLY, + "a pointer drag"}, + {NULL} +}; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +static void ptr_dealloc(PyObject *self) +{ + PyObject_Del(self); +} + +static PyTypeObject PointerType = { + PyObject_HEAD_INIT(NULL) + 0, + "Pointer", + sizeof(Pointer), + 0, + (destructor) ptr_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/**************************************************************************/ + +void pointer_startup() +{ + PyObject *input, *inputdict; + Pointer *ptr; + + grabbed = FALSE; + double_click_rate = 300; + drag_threshold = 3; + g_datalist_init(&bound_contexts); + + PointerType.ob_type = &PyType_Type; + PointerType.tp_methods = PointerMethods; + PointerType.tp_members = PointerMembers; + PyType_Ready(&PointerType); + PyType_Ready(&PointerDataType); + + /* get the input module/dict */ + input = PyImport_ImportModule("input"); /* new */ + g_assert(input != NULL); + inputdict = PyModule_GetDict(input); /* borrowed */ + g_assert(inputdict != NULL); + + /* add a Pointer instance to the input module */ + ptr = PyObject_New(Pointer, &PointerType); + ptr->press = Action_Press; + ptr->release = Action_Release; + ptr->click = Action_Click; + ptr->doubleclick = Action_DoubleClick; + ptr->motion = Action_Motion; + PyDict_SetItemString(inputdict, "Pointer", (PyObject*) ptr); + Py_DECREF(ptr); + + Py_DECREF(input); +} + +void pointer_shutdown() +{ + if (grabbed) + grab_pointer(FALSE); + clearall(); + g_datalist_clear(&bound_contexts); +} + diff --git a/openbox/pointer.h b/openbox/pointer.h new file mode 100644 index 0000000..2f06897 --- /dev/null +++ b/openbox/pointer.h @@ -0,0 +1,14 @@ +#ifndef __pointer_h +#define __pointer_h + +#include "client.h" +#include + +void pointer_startup(); +void pointer_shutdown(); + +void pointer_grab_all(Client *client, gboolean grab); + +void pointer_event(XEvent *e, Client *c); + +#endif diff --git a/openbox/prop.c b/openbox/prop.c new file mode 100644 index 0000000..9a44850 --- /dev/null +++ b/openbox/prop.c @@ -0,0 +1,288 @@ +#include "prop.h" +#include "openbox.h" +#include + +Atoms prop_atoms; + +#define CREATE(var, name) (prop_atoms.var = \ + XInternAtom(ob_display, name, FALSE)) + +void prop_startup() +{ + g_assert(ob_display != NULL); + + CREATE(cardinal, "CARDINAL"); + CREATE(window, "WINDOW"); + CREATE(pixmap, "PIXMAP"); + CREATE(atom, "ATOM"); + CREATE(string, "STRING"); + CREATE(utf8, "UTF8_STRING"); + + CREATE(wm_colormap_windows, "WM_COLORMAP_WINDOWS"); + CREATE(wm_protocols, "WM_PROTOCOLS"); + CREATE(wm_state, "WM_STATE"); + CREATE(wm_change_state, "WM_CHANGE_STATE"); + CREATE(wm_delete_window, "WM_DELETE_WINDOW"); + CREATE(wm_take_focus, "WM_TAKE_FOCUS"); + CREATE(wm_name, "WM_NAME"); + CREATE(wm_icon_name, "WM_ICON_NAME"); + CREATE(wm_class, "WM_CLASS"); + CREATE(wm_window_role, "WM_WINDOW_ROLE"); + CREATE(motif_wm_hints, "_MOTIF_WM_HINTS"); + + CREATE(net_supported, "_NET_SUPPORTED"); + CREATE(net_client_list, "_NET_CLIENT_LIST"); + CREATE(net_client_list_stacking, "_NET_CLIENT_LIST_STACKING"); + CREATE(net_number_of_desktops, "_NET_NUMBER_OF_DESKTOPS"); + CREATE(net_desktop_geometry, "_NET_DESKTOP_GEOMETRY"); + CREATE(net_desktop_viewport, "_NET_DESKTOP_VIEWPORT"); + CREATE(net_current_desktop, "_NET_CURRENT_DESKTOP"); + CREATE(net_desktop_names, "_NET_DESKTOP_NAMES"); + CREATE(net_active_window, "_NET_ACTIVE_WINDOW"); + CREATE(net_workarea, "_NET_WORKAREA"); + CREATE(net_supporting_wm_check, "_NET_SUPPORTING_WM_CHECK"); +/* CREATE(net_virtual_roots, "_NET_VIRTUAL_ROOTS"); */ + CREATE(net_desktop_layout, "_NET_DESKTOP_LAYOUT"); + CREATE(net_showing_desktop, "_NET_SHOWING_DESKTOP"); + + CREATE(net_close_window, "_NET_CLOSE_WINDOW"); + CREATE(net_wm_moveresize, "_NET_WM_MOVERESIZE"); + +/* CREATE(net_properties, "_NET_PROPERTIES"); */ + CREATE(net_wm_name, "_NET_WM_NAME"); + CREATE(net_wm_visible_name, "_NET_WM_VISIBLE_NAME"); + CREATE(net_wm_icon_name, "_NET_WM_ICON_NAME"); + CREATE(net_wm_visible_icon_name, "_NET_WM_VISIBLE_ICON_NAME"); + CREATE(net_wm_desktop, "_NET_WM_DESKTOP"); + CREATE(net_wm_window_type, "_NET_WM_WINDOW_TYPE"); + CREATE(net_wm_state, "_NET_WM_STATE"); + CREATE(net_wm_strut, "_NET_WM_STRUT"); +/* CREATE(net_wm_icon_geometry, "_NET_WM_ICON_GEOMETRY"); */ + CREATE(net_wm_icon, "_NET_WM_ICON"); +/* CREATE(net_wm_pid, "_NET_WM_PID"); */ +/* CREATE(net_wm_handled_icons, "_NET_WM_HANDLED_ICONS"); */ + CREATE(net_wm_allowed_actions, "_NET_WM_ALLOWED_ACTIONS"); + +/* CREATE(net_wm_ping, "_NET_WM_PING"); */ + + CREATE(net_wm_window_type_desktop, "_NET_WM_WINDOW_TYPE_DESKTOP"); + CREATE(net_wm_window_type_dock, "_NET_WM_WINDOW_TYPE_DOCK"); + CREATE(net_wm_window_type_toolbar, "_NET_WM_WINDOW_TYPE_TOOLBAR"); + CREATE(net_wm_window_type_menu, "_NET_WM_WINDOW_TYPE_MENU"); + CREATE(net_wm_window_type_utility, "_NET_WM_WINDOW_TYPE_UTILITY"); + CREATE(net_wm_window_type_splash, "_NET_WM_WINDOW_TYPE_SPLASH"); + CREATE(net_wm_window_type_dialog, "_NET_WM_WINDOW_TYPE_DIALOG"); + CREATE(net_wm_window_type_normal, "_NET_WM_WINDOW_TYPE_NORMAL"); + + CREATE(net_wm_moveresize_size_topleft, "_NET_WM_MOVERESIZE_SIZE_TOPLEFT"); + CREATE(net_wm_moveresize_size_topright, + "_NET_WM_MOVERESIZE_SIZE_TOPRIGHT"); + CREATE(net_wm_moveresize_size_bottomleft, + "_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT"); + CREATE(net_wm_moveresize_size_bottomright, + "_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT"); + CREATE(net_wm_moveresize_move, "_NET_WM_MOVERESIZE_MOVE"); + + CREATE(net_wm_action_move, "_NET_WM_ACTION_MOVE"); + CREATE(net_wm_action_resize, "_NET_WM_ACTION_RESIZE"); + CREATE(net_wm_action_minimize, "_NET_WM_ACTION_MINIMIZE"); + CREATE(net_wm_action_shade, "_NET_WM_ACTION_SHADE"); + CREATE(net_wm_action_stick, "_NET_WM_ACTION_STICK"); + CREATE(net_wm_action_maximize_horz, "_NET_WM_ACTION_MAXIMIZE_HORZ"); + CREATE(net_wm_action_maximize_vert, "_NET_WM_ACTION_MAXIMIZE_VERT"); + CREATE(net_wm_action_fullscreen, "_NET_WM_ACTION_FULLSCREEN"); + CREATE(net_wm_action_change_desktop, "_NET_WM_ACTION_CHANGE_DESKTOP"); + CREATE(net_wm_action_close, "_NET_WM_ACTION_CLOSE"); + CREATE(net_wm_state_modal, "_NET_WM_STATE_MODAL"); + CREATE(net_wm_state_sticky, "_NET_WM_STATE_STICKY"); + CREATE(net_wm_state_maximized_vert, "_NET_WM_STATE_MAXIMIZED_VERT"); + CREATE(net_wm_state_maximized_horz, "_NET_WM_STATE_MAXIMIZED_HORZ"); + CREATE(net_wm_state_shaded, "_NET_WM_STATE_SHADED"); + CREATE(net_wm_state_skip_taskbar, "_NET_WM_STATE_SKIP_TASKBAR"); + CREATE(net_wm_state_skip_pager, "_NET_WM_STATE_SKIP_PAGER"); + CREATE(net_wm_state_hidden, "_NET_WM_STATE_HIDDEN"); + CREATE(net_wm_state_fullscreen, "_NET_WM_STATE_FULLSCREEN"); + CREATE(net_wm_state_above, "_NET_WM_STATE_ABOVE"); + CREATE(net_wm_state_below, "_NET_WM_STATE_BELOW"); + + prop_atoms.net_wm_state_add = 1; + prop_atoms.net_wm_state_remove = 0; + prop_atoms.net_wm_state_toggle = 2; + + prop_atoms.net_wm_orientation_horz = 0; + prop_atoms.net_wm_orientation_vert = 1; + prop_atoms.net_wm_topleft = 0; + prop_atoms.net_wm_topright = 1; + prop_atoms.net_wm_bottomright = 2; + prop_atoms.net_wm_bottomleft = 3; + + CREATE(kde_net_system_tray_windows, "_KDE_NET_SYSTEM_TRAY_WINDOWS"); + CREATE(kde_net_wm_system_tray_window_for, + "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR"); + CREATE(kde_net_wm_window_type_override, + "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"); + + CREATE(kwm_win_icon, "KWM_WIN_ICON"); + + CREATE(rootpmapid, "_XROOTPMAP_ID"); + CREATE(esetrootid, "ESETROOT_PMAP_ID"); + + CREATE(openbox_pid, "_OPENBOX_PID"); + CREATE(openbox_premax, "_OPENBOX_PREMAX"); +} + +gboolean prop_get(Window win, Atom prop, Atom type, int size, + guchar **data, gulong num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + long num32 = 32 / size * num; /* num in 32-bit elements */ + + res = XGetWindowProperty(ob_display, win, prop, 0l, num32, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success && ret_items && xdata) { + if (ret_size == size && ret_items >= num) { + *data = g_memdup(xdata, num * (size / 8)); + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_prealloc(Window win, Atom prop, Atom type, int size, + guchar *data, gulong num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + long num32 = 32 / size * num; /* num in 32-bit elements */ + + res = XGetWindowProperty(ob_display, win, prop, 0l, num32, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success && ret_items && xdata) { + if (ret_size == size && ret_items >= num) { + gulong i; + for (i = 0; i < num; ++i) + switch (size) { + case 8: + data[i] = xdata[i]; + break; + case 16: + ((guint16*)data)[i] = ((guint16*)xdata)[i]; + break; + case 32: + ((guint32*)data)[i] = ((guint32*)xdata)[i]; + break; + default: + g_assert_not_reached(); /* unhandled size */ + } + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_all(Window win, Atom prop, Atom type, int size, + guchar **data, gulong *num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + + res = XGetWindowProperty(ob_display, win, prop, 0l, G_MAXLONG, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success) { + if (ret_size == size && ret_items > 0) { + *data = g_memdup(xdata, ret_items * (size / 8)); + *num = ret_items; + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_string(Window win, Atom prop, Atom type, guchar **data) +{ + guchar *raw; + gulong num; + GString *str; + + if (prop_get_all(win, prop, type, 8, &raw, &num)) { + str = g_string_new_len((char*)raw, num); + g_assert(str->str[num] == '\0'); + + g_free(raw); + + *data = (guchar*)g_string_free(str, FALSE); + return TRUE; + } + return FALSE; +} + +gboolean prop_get_strings(Window win, Atom prop, Atom type, + GPtrArray *data) +{ + guchar *raw; + gulong num; + GString *str, *str2; + guint i, start; + + if (prop_get_all(win, prop, type, 8, &raw, &num)) { + str = g_string_new_len((gchar*)raw, num); + g_assert(str->str[num] == '\0'); /* assuming this is always true.. */ + + g_free(raw); + + /* split it into the list */ + for (start = 0, i = 0; i < str->len; ++i) { + if (str->str[i] == '\0') { + str2 = g_string_new_len(&str->str[start], i - start); + g_ptr_array_add(data, g_string_free(str2, FALSE)); + start = i + 1; + } + } + g_string_free(str, TRUE); + + if (data->len > 0) + return TRUE; + } + return FALSE; +} + +void prop_erase(Window win, Atom prop) +{ + XDeleteProperty(ob_display, win, prop); +} + +void prop_message(Window about, Atom messagetype, long data0, long data1, + long data2, long data3) +{ + XEvent ce; + ce.xclient.type = ClientMessage; + ce.xclient.message_type = messagetype; + ce.xclient.display = ob_display; + ce.xclient.window = about; + ce.xclient.format = 32; + ce.xclient.data.l[0] = data0; + ce.xclient.data.l[1] = data1; + ce.xclient.data.l[2] = data2; + ce.xclient.data.l[3] = data3; + XSendEvent(ob_display, ob_root, FALSE, + SubstructureNotifyMask | SubstructureRedirectMask, &ce); +} diff --git a/openbox/prop.h b/openbox/prop.h new file mode 100644 index 0000000..c2de3b2 --- /dev/null +++ b/openbox/prop.h @@ -0,0 +1,214 @@ +#ifndef __atoms_h +#define __atoms_h + +#include +#include +#ifdef HAVE_STRING_H +# include +#endif + +#include "openbox.h" + +/*! The atoms on the X server which this class will cache */ +typedef struct Atoms { + /* types */ + Atom cardinal; /*!< The atom which represents the Cardinal data type */ + Atom window; /*!< The atom which represents window ids */ + Atom pixmap; /*!< The atom which represents pixmap ids */ + Atom atom; /*!< The atom which represents atom values */ + Atom string; /*!< The atom which represents ascii strings */ + Atom utf8; /*!< The atom which represents utf8-encoded strings */ + + /* window hints */ + Atom wm_colormap_windows; + Atom wm_protocols; + Atom wm_state; + Atom wm_delete_window; + Atom wm_take_focus; + Atom wm_change_state; + Atom wm_name; + Atom wm_icon_name; + Atom wm_class; + Atom wm_window_role; + Atom motif_wm_hints; + + /* NETWM atoms */ + + /* root window properties */ + Atom net_supported; + Atom net_client_list; + Atom net_client_list_stacking; + Atom net_number_of_desktops; + Atom net_desktop_geometry; + Atom net_desktop_viewport; + Atom net_current_desktop; + Atom net_desktop_names; + Atom net_active_window; + Atom net_workarea; + Atom net_supporting_wm_check; +/* Atom net_virtual_roots; */ + Atom net_desktop_layout; + Atom net_showing_desktop; + /* root window messages */ + Atom net_close_window; + Atom net_wm_moveresize; + /* application window properties */ +/* Atom net_properties; */ + Atom net_wm_name; + Atom net_wm_visible_name; + Atom net_wm_icon_name; + Atom net_wm_visible_icon_name; + Atom net_wm_desktop; + Atom net_wm_window_type; + Atom net_wm_state; + Atom net_wm_strut; +/* Atom net_wm_icon_geometry; */ + Atom net_wm_icon; +/* Atom net_wm_pid; */ +/* Atom net_wm_handled_icons; */ + Atom net_wm_allowed_actions; + /* application protocols */ +/* Atom Atom net_wm_ping; */ + + Atom net_wm_window_type_desktop; + Atom net_wm_window_type_dock; + Atom net_wm_window_type_toolbar; + Atom net_wm_window_type_menu; + Atom net_wm_window_type_utility; + Atom net_wm_window_type_splash; + Atom net_wm_window_type_dialog; + Atom net_wm_window_type_normal; + + Atom net_wm_moveresize_size_topleft; + Atom net_wm_moveresize_size_topright; + Atom net_wm_moveresize_size_bottomleft; + Atom net_wm_moveresize_size_bottomright; + Atom net_wm_moveresize_move; + + Atom net_wm_action_move; + Atom net_wm_action_resize; + Atom net_wm_action_minimize; + Atom net_wm_action_shade; + Atom net_wm_action_stick; + Atom net_wm_action_maximize_horz; + Atom net_wm_action_maximize_vert; + Atom net_wm_action_fullscreen; + Atom net_wm_action_change_desktop; + Atom net_wm_action_close; + + Atom net_wm_state_modal; + Atom net_wm_state_sticky; + Atom net_wm_state_maximized_vert; + Atom net_wm_state_maximized_horz; + Atom net_wm_state_shaded; + Atom net_wm_state_skip_taskbar; + Atom net_wm_state_skip_pager; + Atom net_wm_state_hidden; + Atom net_wm_state_fullscreen; + Atom net_wm_state_above; + Atom net_wm_state_below; + + Atom net_wm_state_add; + Atom net_wm_state_remove; + Atom net_wm_state_toggle; + + Atom net_wm_orientation_horz; + Atom net_wm_orientation_vert; + Atom net_wm_topleft; + Atom net_wm_topright; + Atom net_wm_bottomright; + Atom net_wm_bottomleft; + + /* Extra atoms */ + + Atom kde_net_system_tray_windows; + Atom kde_net_wm_system_tray_window_for; + Atom kde_net_wm_window_type_override; + + Atom kwm_win_icon; + + Atom rootpmapid; + Atom esetrootid; + + /* Openbox specific atoms */ + + Atom openbox_pid; + Atom openbox_premax; +} Atoms; +Atoms prop_atoms; + +void prop_startup(); + +gboolean prop_get(Window win, Atom prop, Atom type, int size, + guchar **data, gulong num); + +gboolean prop_get_prealloc(Window win, Atom prop, Atom type, int size, + guchar *data, gulong num); + +gboolean prop_get_all(Window win, Atom prop, Atom type, int size, + guchar **data, gulong *num); + +gboolean prop_get_string(Window win, Atom prop, Atom type, guchar **data); +gboolean prop_get_strings(Window win, Atom prop, Atom type, + GPtrArray *data); + +void prop_set_strings(Window win, Atom prop, Atom type, GPtrArray *data); + +void prop_erase(Window win, Atom prop); + +void prop_message(Window about, Atom messagetype, long data0, long data1, + long data2, long data3); + +#define PROP_MSG(about, msgtype, data0, data1, data2, data3) \ + (prop_message(about, prop_atoms.msgtype, data0, data1, data2, data3)) + +/* Set an 8-bit property from a string */ +#define PROP_SETS(win, prop, type, value) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 8, \ + PropModeReplace, (guchar*)value, strlen(value))) +/* Set an 8-bit property array from a GPtrArray of strings */ +#define PROP_SETSA(win, prop, type, value) \ + (prop_set_strings(win, prop_atoms.prop, prop_atoms.type, value)) + +/* Set a 32-bit property from a single value */ +#define PROP_SET32(win, prop, type, value) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 32, \ + PropModeReplace, (guchar*)&value, 1)) +/* Set a 32-bit property from an array */ +#define PROP_SET32A(win, prop, type, value, num) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 32, \ + PropModeReplace, (guchar*)value, num)) + +/* Get an 8-bit property into a string */ +#define PROP_GETS(win, prop, type, value) \ + (prop_get_string(win, prop_atoms.prop, prop_atoms.type, \ + (guchar**)&value)) +/* Get an 8-bit property into a GPtrArray of strings + (The strings must be freed, the GPtrArray must already be created.) */ +#define PROP_GETSA(win, prop, type, value) \ + (prop_get_strings(win, prop_atoms.prop, prop_atoms.type, \ + value)) + +/* Get an entire 8-bit property into an array (which must be freed) */ +#define PROP_GET8U(win, prop, type, value, num) \ + (prop_get_all(win, prop_atoms.prop, prop_atoms.type, 8, \ + (guchar**)&value, &num)) + +/* Get 1 element of a 32-bit property into a given variable */ +#define PROP_GET32(win, prop, type, value) \ + (prop_get_prealloc(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar*)&value, 1)) + +/* Get an amount of a 32-bit property into an array (which must be freed) */ +#define PROP_GET32A(win, prop, type, value, num) \ + (prop_get(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar**)&value, num)) + +/* Get an entire 32-bit property into an array (which must be freed) */ +#define PROP_GET32U(win, prop, type, value, num) \ + (prop_get_all(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar**)&value, &num)) + +#define PROP_ERASE(win, prop) (prop_erase(win, prop_atoms.prop)) + +#endif diff --git a/openbox/python.c b/openbox/python.c new file mode 100644 index 0000000..52f19f3 --- /dev/null +++ b/openbox/python.c @@ -0,0 +1,60 @@ +#include +#include + +static PyMethodDef ObMethods[] = { + { NULL, NULL, 0, NULL } +}; + +static PyMethodDef InputMethods[] = { + { NULL, NULL, 0, NULL } +}; + +void python_startup() +{ + PyObject *sys, *sysdict, *syspath, *path1, *path2; + char *homescriptdir; + + Py_Initialize(); + + /* fix up the system path */ + + sys = PyImport_ImportModule("sys"); /* new */ + sysdict = PyModule_GetDict(sys); /* borrowed */ + syspath = PyDict_GetItemString(sysdict, "path"); /* borrowed */ + + path1 = PyString_FromString(SCRIPTDIR); /* new */ + PyList_Insert(syspath, 0, path1); + Py_DECREF(path1); + + homescriptdir = g_build_filename(g_get_home_dir(), ".openbox", NULL); + path2 = PyString_FromString(homescriptdir); /* new */ + PyList_Insert(syspath, 0, path2); + Py_DECREF(path2); + g_free(homescriptdir); + + Py_DECREF(sys); + + /* create the 'ob' module */ + Py_InitModule("ob", ObMethods); + + /* create the 'input' module */ + Py_InitModule("input", InputMethods); +} + +void python_shutdown() +{ + Py_Finalize(); +} + +gboolean python_import(char *module) +{ + PyObject *mod; + + mod = PyImport_ImportModule(module); /* new */ + if (mod == NULL) { + PyErr_Print(); + return FALSE; + } + Py_DECREF(mod); + return TRUE; +} diff --git a/openbox/python.h b/openbox/python.h new file mode 100644 index 0000000..9036631 --- /dev/null +++ b/openbox/python.h @@ -0,0 +1,10 @@ +#ifndef __python_h +#define __python_h + +void python_startup(); +void python_shutdown(); + +/*! Import a python module */ +gboolean python_import(char *module); + +#endif diff --git a/openbox/screen.c b/openbox/screen.c new file mode 100644 index 0000000..d077c49 --- /dev/null +++ b/openbox/screen.c @@ -0,0 +1,526 @@ +#include "openbox.h" +#include "prop.h" +#include "screen.h" +#include "client.h" +#include "focus.h" + +#include +#ifdef HAVE_UNISTD_H +# include +# include +#endif + +/*! The event mask to grab on the root window */ +#define ROOT_EVENTMASK (/*ColormapChangeMask |*/ PropertyChangeMask | \ + EnterWindowMask | LeaveWindowMask | \ + SubstructureNotifyMask | SubstructureRedirectMask | \ + ButtonPressMask | ButtonReleaseMask | ButtonMotionMask) + +guint screen_num_desktops = 1; +guint screen_desktop = 0; +Size screen_physical_size; +gboolean screen_showing_desktop; +DesktopLayout screen_desktop_layout; +GPtrArray *screen_desktop_names; + +static Rect *area = NULL; +static Strut *strut = NULL; + +static void screen_update_area(); + +static gboolean running; +static int another_running(Display *d, XErrorEvent *e) +{ + (void)d;(void)e; + g_message("A window manager is already running on screen %d", + ob_screen); + running = TRUE; + return -1; +} + +gboolean screen_annex() +{ + XErrorHandler old; + Window support; + pid_t pid; + int i, num_support; + Atom *supported; + + running = FALSE; + old = XSetErrorHandler(another_running); + XSelectInput(ob_display, ob_root, ROOT_EVENTMASK); + XSync(ob_display, FALSE); + XSetErrorHandler(old); + if (running) + return FALSE; + + g_message("Managing screen %d", ob_screen); + + /* set the mouse cursor for the root window (the default cursor) */ + XDefineCursor(ob_display, ob_root, ob_cursors.left_ptr); + + /* set the OPENBOX_PID hint */ + pid = getpid(); + PROP_SET32(ob_root, openbox_pid, cardinal, pid); + + /* create the netwm support window */ + support = XCreateSimpleWindow(ob_display, ob_root, 0, 0, 1, 1, 0, 0, 0); + + /* set supporting window */ + PROP_SET32(ob_root, net_supporting_wm_check, window, support); + + /* set properties on the supporting window */ + PROP_SETS(support, net_wm_name, utf8, "Openbox"); + PROP_SET32(support, net_supporting_wm_check, window, support); + + /* set the _NET_SUPPORTED_ATOMS hint */ + num_support = 48; + i = 0; + supported = g_new(Atom, num_support); + supported[i++] = prop_atoms.net_current_desktop; + supported[i++] = prop_atoms.net_number_of_desktops; + supported[i++] = prop_atoms.net_desktop_geometry; + supported[i++] = prop_atoms.net_desktop_viewport; + supported[i++] = prop_atoms.net_active_window; + supported[i++] = prop_atoms.net_workarea; + supported[i++] = prop_atoms.net_client_list; + supported[i++] = prop_atoms.net_client_list_stacking; + supported[i++] = prop_atoms.net_desktop_names; + supported[i++] = prop_atoms.net_close_window; + supported[i++] = prop_atoms.net_desktop_layout; + supported[i++] = prop_atoms.net_showing_desktop; + supported[i++] = prop_atoms.net_wm_name; + supported[i++] = prop_atoms.net_wm_visible_name; + supported[i++] = prop_atoms.net_wm_icon_name; + supported[i++] = prop_atoms.net_wm_visible_icon_name; + supported[i++] = prop_atoms.net_wm_desktop; + supported[i++] = prop_atoms.net_wm_strut; + supported[i++] = prop_atoms.net_wm_window_type; + supported[i++] = prop_atoms.net_wm_window_type_desktop; + supported[i++] = prop_atoms.net_wm_window_type_dock; + supported[i++] = prop_atoms.net_wm_window_type_toolbar; + supported[i++] = prop_atoms.net_wm_window_type_menu; + supported[i++] = prop_atoms.net_wm_window_type_utility; + supported[i++] = prop_atoms.net_wm_window_type_splash; + supported[i++] = prop_atoms.net_wm_window_type_dialog; + supported[i++] = prop_atoms.net_wm_window_type_normal; + supported[i++] = prop_atoms.net_wm_allowed_actions; + supported[i++] = prop_atoms.net_wm_action_move; + supported[i++] = prop_atoms.net_wm_action_resize; + supported[i++] = prop_atoms.net_wm_action_minimize; + supported[i++] = prop_atoms.net_wm_action_shade; + supported[i++] = prop_atoms.net_wm_action_maximize_horz; + supported[i++] = prop_atoms.net_wm_action_maximize_vert; + supported[i++] = prop_atoms.net_wm_action_fullscreen; + supported[i++] = prop_atoms.net_wm_action_change_desktop; + supported[i++] = prop_atoms.net_wm_action_close; + supported[i++] = prop_atoms.net_wm_state; + supported[i++] = prop_atoms.net_wm_state_modal; + supported[i++] = prop_atoms.net_wm_state_maximized_vert; + supported[i++] = prop_atoms.net_wm_state_maximized_horz; + supported[i++] = prop_atoms.net_wm_state_shaded; + supported[i++] = prop_atoms.net_wm_state_skip_taskbar; + supported[i++] = prop_atoms.net_wm_state_skip_pager; + supported[i++] = prop_atoms.net_wm_state_hidden; + supported[i++] = prop_atoms.net_wm_state_fullscreen; + supported[i++] = prop_atoms.net_wm_state_above; + supported[i++] = prop_atoms.net_wm_state_below; + g_assert(i == num_support); +/* + supported[] = prop_atoms.net_wm_moveresize; + supported[] = prop_atoms.net_wm_moveresize_size_topleft; + supported[] = prop_atoms.net_wm_moveresize_size_topright; + supported[] = prop_atoms.net_wm_moveresize_size_bottomleft; + supported[] = prop_atoms.net_wm_moveresize_size_bottomright; + supported[] = prop_atoms.net_wm_moveresize_move; + supported[] = prop_atoms.net_wm_action_stick; +*/ + + PROP_SET32A(ob_root, net_supported, atom, supported, num_support); + g_free(supported); + + return TRUE; +} + +void screen_startup() +{ + screen_desktop_names = g_ptr_array_new(); + + /* get the initial size */ + screen_resize(); + + screen_set_num_desktops(4); + screen_set_desktop(0); + + /* don't start in showing-desktop mode */ + screen_showing_desktop = FALSE; + PROP_SET32(ob_root, net_showing_desktop, cardinal, screen_showing_desktop); + + screen_update_layout(); +} + +void screen_shutdown() +{ + guint i; + for (i = 0; i < screen_desktop_names->len; ++i) + g_free(g_ptr_array_index(screen_desktop_names, i)); + g_ptr_array_free(screen_desktop_names, TRUE); + g_free(strut); + g_free(area); +} + +void screen_resize() +{ + /* Set the _NET_DESKTOP_GEOMETRY hint */ + /* XXX RandR support here? */ + int geometry[2]; + + geometry[0] = WidthOfScreen(ScreenOfDisplay(ob_display, ob_screen)); + geometry[1] = HeightOfScreen(ScreenOfDisplay(ob_display, ob_screen)); + PROP_SET32A(ob_root, net_desktop_geometry, cardinal, geometry, 2); + screen_physical_size.width = geometry[0]; + screen_physical_size.height = geometry[1]; + + if (ob_state == State_Starting) + return; + + screen_update_struts(); + + /* XXX adjust more stuff ? */ +} + +void screen_set_num_desktops(guint num) +{ + unsigned long *viewport; + + g_assert(num > 0); + + /* move windows on desktops that will no longer exist! */ + /* XXX + std::list::iterator it, end = clients.end(); + for (it = clients.begin(); it != end; ++it) { + unsigned int d = (*it)->desktop(); + if (d >= num && d != 0xffffffff) { + XEvent ce; + ce.xclient.type = ClientMessage; + ce.xclient.message_type = otk::Property::atoms.net_wm_desktop; + ce.xclient.display = **otk::display; + ce.xclient.window = (*it)->window(); + ce.xclient.format = 32; + ce.xclient.data.l[0] = num - 1; + XSendEvent(**otk::display, _info->rootWindow(), false, + SubstructureNotifyMask | SubstructureRedirectMask, &ce); + } + } + */ + + screen_num_desktops = num; + PROP_SET32(ob_root, net_number_of_desktops, cardinal, num); + + /* set the viewport hint */ + viewport = g_new0(unsigned long, num * 2); + PROP_SET32A(ob_root, net_desktop_viewport, cardinal, viewport, num * 2); + g_free(viewport); + + /* change our struts/area to match */ + screen_update_struts(); + + /* the number of rows/columns will differ */ + screen_update_layout(); + + /* may be some unnamed desktops that we need to fill in with names */ + screen_update_desktop_names(); + + /* change our desktop if we're on one that no longer exists! */ + if (screen_desktop >= screen_num_desktops) + screen_set_desktop(num - 1); +} + +void screen_set_desktop(guint num) +{ + GList *it; + + guint old = screen_desktop; + + g_assert(num < screen_num_desktops); + + g_message("Moving to desktop %u", num); + + screen_desktop = num; + PROP_SET32(ob_root, net_current_desktop, cardinal, num); + + if (old == num) return; + + for (it = stacking_list; it != NULL; it = it->next) + client_showhide(it->data, FALSE); + + /* force the callbacks to fire */ + if (focus_client == NULL) + focus_set_client(NULL); +} + +void screen_update_layout() +{ + unsigned long *data = NULL; + + /* defaults */ + screen_desktop_layout.orientation = prop_atoms.net_wm_orientation_horz; + screen_desktop_layout.start_corner = prop_atoms.net_wm_topleft; + screen_desktop_layout.rows = 1; + screen_desktop_layout.columns = screen_num_desktops; + + if (PROP_GET32A(ob_root, net_desktop_layout, cardinal, data, 4)) { + if (data[0] == prop_atoms.net_wm_orientation_vert) + screen_desktop_layout.orientation = data[0]; + if (data[3] == prop_atoms.net_wm_topright) + screen_desktop_layout.start_corner = data[3]; + else if (data[3] == prop_atoms.net_wm_bottomright) + screen_desktop_layout.start_corner = data[3]; + else if (data[3] == prop_atoms.net_wm_bottomleft) + screen_desktop_layout.start_corner = data[3]; + + /* fill in a zero rows/columns */ + if (!(data[1] == 0 && data[2] == 0)) { /* both 0's is bad data.. */ + if (data[1] == 0) { + data[1] = (screen_num_desktops + + screen_num_desktops % data[2]) / data[2]; + } else if (data[2] == 0) { + data[2] = (screen_num_desktops + + screen_num_desktops % data[1]) / data[1]; + } + screen_desktop_layout.columns = data[1]; + screen_desktop_layout.rows = data[2]; + } + + /* bounds checking */ + if (screen_desktop_layout.orientation == + prop_atoms.net_wm_orientation_horz) { + if (screen_desktop_layout.rows > screen_num_desktops) + screen_desktop_layout.rows = screen_num_desktops; + if (screen_desktop_layout.columns > ((screen_num_desktops + + screen_num_desktops % + screen_desktop_layout.rows) / + screen_desktop_layout.rows)) + screen_desktop_layout.columns = + (screen_num_desktops + screen_num_desktops % + screen_desktop_layout.rows) / + screen_desktop_layout.rows; + } else { + if (screen_desktop_layout.columns > screen_num_desktops) + screen_desktop_layout.columns = screen_num_desktops; + if (screen_desktop_layout.rows > ((screen_num_desktops + + screen_num_desktops % + screen_desktop_layout.columns) / + screen_desktop_layout.columns)) + screen_desktop_layout.rows = + (screen_num_desktops + screen_num_desktops % + screen_desktop_layout.columns) / + screen_desktop_layout.columns; + } + g_free(data); + } +} + +void screen_update_desktop_names() +{ + guint i; + + /* empty the array */ + for (i = 0; i < screen_desktop_names->len; ++i) + g_free(g_ptr_array_index(screen_desktop_names, i)); + g_ptr_array_set_size(screen_desktop_names, 0); + + PROP_GETSA(ob_root, net_desktop_names, utf8, screen_desktop_names); + + while (screen_desktop_names->len < screen_num_desktops) + g_ptr_array_add(screen_desktop_names, g_strdup("Unnamed Desktop")); +} + +void screen_show_desktop(gboolean show) +{ + GList *it; + static Window saved_focus = 0; + + if (show == screen_showing_desktop) return; /* no change */ + + /* save the window focus, and restore it when leaving the show-desktop + mode */ + if (show && focus_client) + saved_focus = focus_client->window; + + screen_showing_desktop = show; + + if (show) { + /* bottom to top */ + for (it = g_list_last(stacking_list); it != NULL; it = it->prev) { + Client *client = it->data; + if (client->type == Type_Desktop) + client_focus(client); + else + client_showhide(client, FALSE); + } + } else { + /* top to bottom */ + for (it = stacking_list; it != NULL; it = it->next) { + Client *client = it->data; + if (client->type != Type_Desktop) + client_showhide(client, FALSE); + } + } + + if (!show) { + Client *f = focus_client; + if (!f || f->type == Type_Desktop) { + Client *c = g_hash_table_lookup(client_map, + (gpointer)saved_focus); + if (c) client_focus(c); + } + } + + show = show ? 1 : 0; /* make it boolean */ + PROP_SET32(ob_root, net_showing_desktop, cardinal, show); +} + +void screen_install_colormap(Client *client, gboolean install) +{ + if (client == NULL) { + /* XXX DONT USE THE DEFAULT SHIT HERE */ + if (install) + XInstallColormap(ob_display, + DefaultColormap(ob_display, ob_screen)); + else + XUninstallColormap(ob_display, + DefaultColormap(ob_display, ob_screen)); + } else { + XWindowAttributes wa; + if (XGetWindowAttributes(ob_display, client->window, &wa)) { + if (install) + XInstallColormap(ob_display, wa.colormap); + else + XUninstallColormap(ob_display, wa.colormap); + } + } +} + +void screen_update_struts() +{ + GSList *it; + guint i; + + if (strut != NULL) + g_free(strut); + strut = g_new0(Strut, screen_num_desktops + 1); + + for (it = client_list; it; it = it->next) { + Client *c = it->data; + if (c->iconic) continue; /* these dont count in the strut */ + + if (c->desktop == 0xffffffff) { + for (i = 0; i < screen_num_desktops; ++i) + STRUT_ADD(strut[i], c->strut); + } else { + g_assert(c->desktop < screen_num_desktops); + STRUT_ADD(strut[c->desktop], c->strut); + } + /* apply to the 'all desktops' strut */ + STRUT_ADD(strut[screen_num_desktops], c->strut); + } + screen_update_area(); +} + +static void screen_update_area() +{ + guint i; + gulong *dims; + + if (area != NULL) + g_free(area); + area = g_new0(Rect, screen_num_desktops + 1); + + dims = g_new(unsigned long, 4 * screen_num_desktops); + for (i = 0; i < screen_num_desktops + 1; ++i) { + Rect old_area = area[i]; +/* + #ifdef XINERAMA + // reset to the full areas + if (isXineramaActive()) + xineramaUsableArea = getXineramaAreas(); + #endif // XINERAMA +*/ + + RECT_SET(area[i], strut[i].left, strut[i].top, + screen_physical_size.width - (strut[i].left + + strut[i].right), + screen_physical_size.height - (strut[i].top + + strut[i].bottom)); + +/* + #ifdef XINERAMA + if (isXineramaActive()) { + // keep each of the ximerama-defined areas inside the strut + RectList::iterator xit, xend = xineramaUsableArea.end(); + for (xit = xineramaUsableArea.begin(); xit != xend; ++xit) { + if (xit->x() < usableArea.x()) { + xit->setX(usableArea.x()); + xit->setWidth(xit->width() - usableArea.x()); + } + if (xit->y() < usableArea.y()) { + xit->setY(usableArea.y()); + xit->setHeight(xit->height() - usableArea.y()); + } + if (xit->x() + xit->width() > usableArea.width()) + xit->setWidth(usableArea.width() - xit->x()); + if (xit->y() + xit->height() > usableArea.height()) + xit->setHeight(usableArea.height() - xit->y()); + } + } + #endif // XINERAMA +*/ + if (!RECT_EQUAL(old_area, area[i])) { + /* the area has changed, adjust all the maximized windows */ + GSList *it; + for (it = client_list; it; it = it->next) { + Client *c = it->data; + if (i < screen_num_desktops) { + if (c->desktop == i) + client_remaximize(c); + } else { + /* the 'all desktops' size */ + if (c->desktop == DESKTOP_ALL) + client_remaximize(c); + } + } + } + + /* don't set these for the 'all desktops' area */ + if (i < screen_num_desktops) { + dims[(i * 4) + 0] = area[i].x; + dims[(i * 4) + 1] = area[i].y; + dims[(i * 4) + 2] = area[i].width; + dims[(i * 4) + 3] = area[i].height; + } + } + PROP_SET32A(ob_root, net_workarea, cardinal, + dims, 4 * screen_num_desktops); + g_free(dims); +} + +Rect *screen_area(guint desktop) +{ + if (desktop >= screen_num_desktops) { + if (desktop == DESKTOP_ALL) + return &area[screen_num_desktops]; + return NULL; + } + return &area[desktop]; +} + +Strut *screen_strut(guint desktop) +{ + if (desktop >= screen_num_desktops) { + if (desktop == DESKTOP_ALL) + return &strut[screen_num_desktops]; + return NULL; + } + return &strut[desktop]; +} diff --git a/openbox/screen.h b/openbox/screen.h new file mode 100644 index 0000000..1c0a4f7 --- /dev/null +++ b/openbox/screen.h @@ -0,0 +1,72 @@ +#ifndef __screen_h +#define __screen_h + +#include "geom.h" + +struct Client; + +#define DESKTOP_ALL (0xffffffff) + +/*! The number of available desktops */ +extern guint screen_num_desktops; +/*! The current desktop */ +extern guint screen_desktop; +/*! The size of the screen */ +extern Size screen_physical_size; +/*! Are we in showing-desktop mode? */ +extern gboolean screen_showing_desktop; + +/*! Orientation of the desktops */ +typedef enum { + Orientation_Horz, + Orientation_Vert +} Orientation; + +typedef struct DesktopLayout { + guint orientation; + guint start_corner; + guint rows; + guint columns; +} DesktopLayout; +extern DesktopLayout screen_desktop_layout; + +/*! An array of gchar*'s which are desktop names in UTF-8 format */ +extern GPtrArray *screen_desktop_names; + +/*! Take over the screen, set the basic hints on it claming it as ours */ +gboolean screen_annex(); + +/*! Once the screen is ours, set up its initial state */ +void screen_startup(); +/*! Free resources */ +void screen_shutdown(); + +/*! Figure out the new size of the screen and adjust stuff for it */ +void screen_resize(); + +/*! Change the number of available desktops */ +void screen_set_num_desktops(guint num); +/*! Change the current desktop */ +void screen_set_desktop(guint num); + +/*! Shows and focuses the desktop and hides all the client windows, or + returns to the normal state, showing client windows. */ +void screen_show_desktop(gboolean show); + +/*! Updates the desktop layout from the root property if available */ +void screen_update_layout(); + +/*! Get desktop names from the root window property */ +void screen_update_desktop_names(); + +/*! Installs or uninstalls a colormap for a client. If client is NULL, then + it handles the root colormap. */ +void screen_install_colormap(struct Client *client, gboolean install); + +void screen_update_struts(); + +Rect *screen_area(guint desktop); + +Strut *screen_strut(guint desktop); + +#endif diff --git a/openbox/stacking.c b/openbox/stacking.c new file mode 100644 index 0000000..081bde9 --- /dev/null +++ b/openbox/stacking.c @@ -0,0 +1,116 @@ +#include "openbox.h" +#include "prop.h" +#include "focus.h" +#include "client.h" +#include "frame.h" +#include + +GList *stacking_list = NULL; + +void stacking_set_list() +{ + Window *windows, *win_it; + GList *it; + guint size = g_list_length(stacking_list); + + /* create an array of the window ids (from bottom to top, + reverse order!) */ + if (size > 0) { + windows = g_new(Window, size); + win_it = windows; + for (it = g_list_last(stacking_list); it; it = it->prev, ++win_it) + *win_it = ((Client*)it->data)->window; + } else + windows = NULL; + + PROP_SET32A(ob_root, net_client_list_stacking, window, windows, size); + + if (windows) + g_free(windows); +} + +void stacking_raise(Client *client) +{ + Window wins[2]; /* only ever restack 2 windows. */ + GList *it; + Client *m; + + g_assert(stacking_list != NULL); /* this would be bad */ + + m = client_find_modal_child(client); + /* if we have a modal child, raise it instead, we'll go along tho later */ + if (m) stacking_raise(m); + + /* remove the client before looking so we can't run into ourselves */ + stacking_list = g_list_remove(stacking_list, client); + + /* the stacking list is from highest to lowest */ + it = stacking_list; + while (it) { + Client *c = it->data; + if (client->layer >= c->layer && m != c) + break; + it = it->next; + } + + /* + if our new position is the top, we want to stack under the focus_backup. + otherwise, we want to stack under the previous window in the stack. + */ + if (it == stacking_list) + wins[0] = focus_backup; + else if (it != NULL) + wins[0] = ((Client*)it->prev->data)->frame->window; + else + wins[0] = ((Client*)g_list_last(stacking_list)->data)->frame->window; + wins[1] = client->frame->window; + + stacking_list = g_list_insert_before(stacking_list, it, client); + + XRestackWindows(ob_display, wins, 2); + + stacking_set_list(); +} + +void stacking_lower(Client *client) +{ + Window wins[2]; /* only ever restack 2 windows. */ + GList *it; + + g_assert(stacking_list != NULL); /* this would be bad */ + + it = g_list_last(stacking_list); + + if (client->modal && client->transient_for) { + /* don't let a modal window lower below its transient_for */ + it = g_list_find(stacking_list, client->transient_for); + g_assert(it != NULL); + + wins[0] = (it == stacking_list ? focus_backup : + ((Client*)it->prev->data)->frame->window); + wins[1] = client->frame->window; + if (wins[0] == wins[1]) return; /* already right above the window */ + + stacking_list = g_list_remove(stacking_list, client); + stacking_list = g_list_insert_before(stacking_list, it, client); + } else { + while (it != stacking_list) { + Client *c = it->data; + if (client->layer >= c->layer) + break; + it = it->prev; + } + if (it->data == client) return; /* already the bottom, return */ + + wins[0] = ((Client*)it->data)->frame->window; + wins[1] = client->frame->window; + + stacking_list = g_list_remove(stacking_list, client); + stacking_list = g_list_insert_before(stacking_list, + it->next, client); + } + + XRestackWindows(ob_display, wins, 2); + stacking_set_list(); +} + diff --git a/openbox/stacking.h b/openbox/stacking.h new file mode 100644 index 0000000..6286181 --- /dev/null +++ b/openbox/stacking.h @@ -0,0 +1,39 @@ +#ifndef __stacking_h +#define __stacking_h + +#include + +struct Client; + +/*! The possible stacking layers a client window can be a part of */ +typedef enum { + Layer_Icon, /*!< 0 - iconified windows, in any order at all */ + Layer_Desktop, /*!< 1 - desktop windows */ + Layer_Below, /*!< 2 - normal windows w/ below */ + Layer_Normal, /*!< 3 - normal windows */ + Layer_Above, /*!< 4 - normal windows w/ above */ + Layer_Top, /*!< 5 - always-on-top-windows (docks?) */ + Layer_Fullscreen, /*!< 6 - fullscreeen windows */ + Layer_Internal /*!< 7 - openbox windows/menus */ +} StackLayer; + +/* list of Client*s in stacking order from highest to lowest */ +extern GList *stacking_list; + +/*! Sets the client stacking list on the root window from the + stacking_clientlist */ +void stacking_set_list(); + +/*! Raises a client window above all others in its stacking layer + raiseWindow has a couple of constraints that lowerWindow does not.
+ 1) raiseWindow can be called after changing a Client's stack layer, and + the list will be reorganized properly.
+ 2) raiseWindow guarantees that XRestackWindows() will always be + called for the specified client. +*/ +void stacking_raise(struct Client *client); + +/*! Lowers a client window below all others in its stacking layer */ +void stacking_lower(struct Client *client); + +#endif diff --git a/openbox/themerc.c b/openbox/themerc.c new file mode 100644 index 0000000..7f587af --- /dev/null +++ b/openbox/themerc.c @@ -0,0 +1,156 @@ +#include +#ifdef HAVE_STRING_H +# include +#endif + +char *themerc_engine; +char *themerc_theme; +char *themerc_font; +char *themerc_titlebar_layout; + +GError *error; + +static void parse(char *path, int fd) +{ + GScanner *scanner; + + scanner = g_scanner_new(NULL); + g_scanner_input_file(scanner, fd); + + while (g_scanner_get_next_token(scanner) != G_TOKEN_EOF) { + char *name, *val; + + if (scanner->token != G_TOKEN_IDENTIFIER) { + g_scanner_unexp_token(scanner, scanner->token, NULL, NULL, NULL, + NULL, TRUE); + return; + } + name = g_strdup(scanner->value.v_identifier); + + g_scanner_get_next_token(scanner); + if (scanner->token != G_TOKEN_EQUAL_SIGN) { + g_scanner_unexp_token(scanner, scanner->token, NULL, NULL, NULL, + NULL, TRUE); + g_free(name); + return; + } + + g_scanner_get_next_token(scanner); + if (scanner->token == G_TOKEN_STRING) { + val = g_strdup(scanner->value.v_identifier); + + if (!g_ascii_strcasecmp(name, "engine")) { + if (themerc_engine != NULL) { + g_warning("%s:%d: '%s' already defined", path, + scanner->line, name); + g_free(name); + g_free(val); + } else + themerc_engine = val; + } else if (!g_ascii_strcasecmp(name, "theme")) { + if (themerc_theme != NULL) { + g_warning("%s:%d: '%s' already defined", path, + scanner->line, name); + g_free(name); + g_free(val); + } else + themerc_theme = val; + } else if (!g_ascii_strcasecmp(name, "font")) { + if (themerc_font != NULL) { + g_warning("%s:%d: '%s' already defined", path, + scanner->line, name); + g_free(name); + g_free(val); + } else + themerc_font = val; + } else if (!g_ascii_strcasecmp(name, "titlebarlayout")) { + if (themerc_titlebar_layout != NULL) { + g_warning("%s:%d: '%s' already defined", path, + scanner->line, name); + g_free(name); + g_free(val); + } else { + char *lowval = g_ascii_strup(val, -1); + int i, len = strlen(lowval); + g_free(val); + for (i = 0; i < len; ++i) { + gboolean valid = FALSE; + switch(lowval[i]) { + case 'I': + case 'L': + case 'M': + case 'C': + case 'N': + case 'D': + valid = TRUE; + } + if (!valid) { + g_warning("%s:%d: invalid titlebarlayout element " + "'%c'", path, scanner->line, lowval[i]); + break; + } + } + if (i == len) + themerc_titlebar_layout = lowval; + else { + g_free(name); + g_free(val); + } + } + } else { + g_warning("%s:%d: invalid option '%s'", path, + scanner->line, name); + g_free(name); + g_free(val); + } + } else { + g_scanner_unexp_token(scanner, scanner->token, NULL, NULL, NULL, + NULL, TRUE); + g_free(name); + return; + } + } +} + +void themerc_startup() +{ + GIOChannel *chan = NULL; + char *path = NULL; + + /* defaults */ + themerc_engine = NULL; + themerc_theme = NULL; + themerc_font = NULL; + themerc_titlebar_layout = NULL; + + path = g_build_filename(g_get_home_dir(), ".openbox", "themerc", NULL); + error = NULL; + chan = g_io_channel_new_file(path, "r", &error); + + if (chan == NULL) { + g_free(path); + path = g_build_filename(THEMERCDIR, "themerc", NULL); + error = NULL; + chan = g_io_channel_new_file(path, "r", &error); + } + + if (chan != NULL) { + parse(path, g_io_channel_unix_get_fd(chan)); + g_free(path); + g_io_channel_close(chan); + } + + /* non-NULL defaults */ + if (themerc_titlebar_layout == NULL) + themerc_titlebar_layout = g_strdup("NDLIMC"); + if (themerc_font == NULL) + themerc_font = g_strdup("sans-8"); +} + +void themerc_shutdown() +{ + if (themerc_engine != NULL) g_free(themerc_engine); + if (themerc_theme != NULL) g_free(themerc_theme); + if (themerc_font != NULL) g_free(themerc_font); + if (themerc_titlebar_layout != NULL) g_free(themerc_titlebar_layout); +} diff --git a/openbox/themerc.h b/openbox/themerc.h new file mode 100644 index 0000000..89e4b07 --- /dev/null +++ b/openbox/themerc.h @@ -0,0 +1,12 @@ +#ifndef __themerc_h +#define __themerc_h + +extern char *themerc_engine; /* NULL to use the default engine */ +extern char *themerc_theme; /* NULL to use the default theme for the engine */ +extern char *themerc_font; /* always non-NULL */ +extern char *themerc_titlebar_layout; /* always non-NULL */ + +void themerc_startup(); +void themerc_shutdown(); + +#endif diff --git a/openbox/timer.c b/openbox/timer.c new file mode 100644 index 0000000..0cec366 --- /dev/null +++ b/openbox/timer.c @@ -0,0 +1,127 @@ +#include "timer.h" + +#ifdef HAVE_SYS_TIME_H +# include +#endif + +static GTimeVal now; +static GTimeVal ret_wait; +static GSList *timers; /* nearest timer is at the top */ + +#define NEAREST_TIMEOUT (((Timer*)timers->data)->timeout) + +static void insert_timer(Timer *self) +{ + GSList *it; + for (it = timers; it != NULL; it = it->next) { + Timer *t = it->data; + if (!timercmp(&self->timeout, &t->timeout, >)) { + timers = g_slist_insert_before(timers, it, self); + break; + } + } + if (it == NULL) /* didnt fit anywhere in the list */ + timers = g_slist_append(timers, self); +} + +void timer_startup() +{ + g_get_current_time(&now); + timers = NULL; +} + +void timer_shutdown() +{ + GSList *it; + for (it = timers; it != NULL; it = it->next) { + g_free(it->data); + } + g_slist_free(timers); + timers = NULL; +} + +Timer *timer_start(long delay, TimeoutHandler cb, void *data) +{ + Timer *self = g_new(Timer, 1); + self->delay = delay; + self->action = cb; + self->data = data; + self->del_me = FALSE; + self->last = self->timeout = now; + g_time_val_add(&self->timeout, delay); + + insert_timer(self); + + return self; +} + +void timer_stop(Timer *self) +{ + self->del_me = TRUE; +} + +/* find the time to wait for the nearest timeout */ +static gboolean nearest_timeout_wait(GTimeVal *tm) +{ + if (timers == NULL) + return FALSE; + + tm->tv_sec = NEAREST_TIMEOUT.tv_sec - now.tv_sec; + tm->tv_usec = NEAREST_TIMEOUT.tv_usec - now.tv_usec; + + while (tm->tv_usec < 0) { + tm->tv_usec += G_USEC_PER_SEC; + tm->tv_sec--; + } + tm->tv_sec += tm->tv_usec / G_USEC_PER_SEC; + tm->tv_usec %= G_USEC_PER_SEC; + if (tm->tv_sec < 0) + tm->tv_sec = 0; + + return TRUE; +} + + +void timer_dispatch(GTimeVal **wait) +{ + g_get_current_time(&now); + + while (timers != NULL) { + Timer *curr = timers->data; /* get the top element */ + /* since timer_stop doesn't actually free the timer, we have to do our + real freeing in here. + */ + if (curr->del_me) { + timers = g_slist_delete_link(timers, timers); /* delete the top */ + g_free(curr); + continue; + } + + /* the queue is sorted, so if this timer shouldn't fire, none are + ready */ + if (!timercmp(&now, &NEAREST_TIMEOUT, >)) + break; + + /* we set the last fired time to delay msec after the previous firing, + then re-insert. timers maintain their order and may trigger more + than once if they've waited more than one delay's worth of time. + */ + timers = g_slist_delete_link(timers, timers); + g_time_val_add(&curr->last, curr->delay); + curr->action(curr->data); + g_time_val_add(&curr->timeout, curr->delay); + insert_timer(curr); + + /* if at least one timer fires, then don't wait on X events, as there + may already be some in the queue from the timer callbacks. + */ + ret_wait.tv_sec = ret_wait.tv_usec = 0; + *wait = &ret_wait; + return; + } + + if (nearest_timeout_wait(&ret_wait)) + *wait = &ret_wait; + else + *wait = NULL; +} diff --git a/openbox/timer.h b/openbox/timer.h new file mode 100644 index 0000000..da6c864 --- /dev/null +++ b/openbox/timer.h @@ -0,0 +1,38 @@ +#ifndef __timer_h +#define __timer_h + +#include + +/*! Data type of Timer callback */ +typedef void (*TimeoutHandler)(void *data); + +typedef struct Timer { + /*! Milliseconds between timer firings */ + long delay; + /*! Callback for timer expiry */ + TimeoutHandler action; + /*! Data sent to callback */ + void *data; + /*! We overload the delete operator to just set this to true */ + gboolean del_me; + /*! The time the last fire should've been at */ + GTimeVal last; + /*! When this timer will next trigger */ + GTimeVal timeout; +} Timer; + +/*! Initializes the timer subsection */ +void timer_startup(); +/*! Destroys the timer subsection */ +void timer_shutdown(); + +/* Creates a new timer with a given delay */ +Timer *timer_start(long delay, TimeoutHandler cb, void *data); +/* Stops and frees a timer */ +void timer_stop(Timer *self); + +/*! Dispatch all pending timers. Sets wait to the amount of time to wait for + the next timer, or NULL if there are no timers to wait for */ +void timer_dispatch(GTimeVal **wait); + +#endif diff --git a/openbox/xerror.c b/openbox/xerror.c new file mode 100644 index 0000000..49a795f --- /dev/null +++ b/openbox/xerror.c @@ -0,0 +1,32 @@ +#include "openbox.h" +#include +#include + +static gboolean xerror_ignore = FALSE; + +int xerror_handler(Display *d, XErrorEvent *e) +{ +#ifdef DEBUG + if (!xerror_ignore) { + char errtxt[128]; + + /*if (e->error_code != BadWindow) */ + { + XGetErrorText(d, e->error_code, errtxt, 127); + if (e->error_code == BadWindow) + g_warning("X Error: %s", errtxt); + else + g_error("X Error: %s", errtxt); + } + } +#else + (void)d; (void)e; +#endif + return 0; +} + +void xerror_set_ignore(gboolean ignore) +{ + XSync(ob_display, FALSE); + xerror_ignore = ignore; +} diff --git a/openbox/xerror.h b/openbox/xerror.h new file mode 100644 index 0000000..74b236f --- /dev/null +++ b/openbox/xerror.h @@ -0,0 +1,11 @@ +#ifndef __xerror_h +#define __xerror_h + +#include +#include + +int xerror_handler(Display *, XErrorEvent *); + +void xerror_set_ignore(gboolean ignore); + +#endif diff --git a/otk/Makefile.am b/otk/Makefile.am index 566fafc..58fe305d 100644 --- a/otk/Makefile.am +++ b/otk/Makefile.am @@ -8,7 +8,7 @@ lib_LTLIBRARIES=libotk.la libotk_la_SOURCES=rendercontrol.cc truerendercontrol.cc surface.cc util.cc \ renderstyle.cc rendercolor.cc pseudorendercontrol.cc \ - display.cc font.cc screeninfo.cc property.cc timer.cc \ + display.cc font.cc property.cc timer.cc \ eventdispatcher.cc eventhandler.cc ustring.cc \ widget.cc application.cc label.cc appwidget.cc button.cc \ otk.cc messagedialog.cc @@ -19,7 +19,7 @@ includeotk_HEADERS=application.hh appwidget.hh assassin.hh button.hh \ display.hh eventdispatcher.hh eventhandler.hh font.hh \ label.hh otk.hh point.hh property.hh pseudorendercontrol.hh\ rect.hh rendercolor.hh rendercontrol.hh renderstyle.hh \ - rendertexture.hh screeninfo.hh size.hh strut.hh surface.hh \ + rendertexture.hh size.hh strut.hh surface.hh \ timer.hh truerendercontrol.hh ustring.hh util.hh widget.hh \ messagedialog.hh ../config.h diff --git a/otk/display.cc b/otk/display.cc index 9817b81..d4580a0 100644 --- a/otk/display.cc +++ b/otk/display.cc @@ -3,8 +3,6 @@ #include "config.h" #include "display.hh" -#include "screeninfo.hh" -#include "rendercontrol.hh" #include "util.hh" extern "C" { @@ -68,8 +66,8 @@ static int xerrorHandler(::Display *d, XErrorEvent *e) } -Display::Display() - : _display(0), +Display::Display(::Display *d) + : _display(d), _xkb(false), _xkb_event_basep(0), _shape(false), @@ -79,21 +77,15 @@ Display::Display() _mask_list(), _num_lock_mask(0), _scroll_lock_mask(0), - _grab_count(0), - _screeninfo_list(0), - _rendercontrol_list(0) + _grab_count(0) { int junk; (void)junk; + assert(_display); + display = this; - // Open the X display - if (!(_display = XOpenDisplay(NULL))) { - printf(_("Unable to open connection to the X server. Please set the \n\ -DISPLAY environment variable approriately.\n\n")); - ::exit(1); - } if (fcntl(ConnectionNumber(_display), F_SETFD, 1) == -1) { printf(_("Couldn't mark display connection as close-on-exec.\n\n")); ::exit(1); @@ -159,13 +151,51 @@ DISPLAY environment variable approriately.\n\n")); _mask_list[6] = _scroll_lock_mask | _num_lock_mask; _mask_list[7] = _scroll_lock_mask | LockMask | _num_lock_mask; - // Get information on all the screens which are available, and create their - // RenderControl - _screeninfo_list = new ScreenInfo*[ScreenCount(_display)]; - _rendercontrol_list = new RenderControl*[ScreenCount(_display)]; - for (int i = 0; i < ScreenCount(_display); ++i) { - _screeninfo_list[i] = new ScreenInfo(i); - _rendercontrol_list[i] = RenderControl::createRenderControl(i); + /* + If the default depth is at least 8 we will use that, + otherwise we try to find the largest TrueColor visual. + Preference is given to 24 bit over larger depths if 24 bit is an option. + */ + + int screen = DefaultScreen(_display); + _depth = DefaultDepth(_display, screen); + _visual = DefaultVisual(_display, screen); + _colormap = DefaultColormap(_display, screen); + + if (_depth < 8) { + // search for a TrueColor Visual... if we can't find one... + // we will use the default visual for the screen + XVisualInfo vinfo_template, *vinfo_return; + int vinfo_nitems; + int best = -1; + + vinfo_template.screen = screen; + vinfo_template.c_class = TrueColor; + + vinfo_return = XGetVisualInfo(_display, + VisualScreenMask | VisualClassMask, + &vinfo_template, &vinfo_nitems); + if (vinfo_return) { + int max_depth = 1; + for (int i = 0; i < vinfo_nitems; ++i) { + if (vinfo_return[i].depth > max_depth) { + if (max_depth == 24 && vinfo_return[i].depth > 24) + break; // prefer 24 bit over 32 + max_depth = vinfo_return[i].depth; + best = i; + } + } + if (max_depth < _depth) best = -1; + } + + if (best != -1) { + _depth = vinfo_return[best].depth; + _visual = vinfo_return[best].visual; + _colormap = XCreateColormap(_display, RootWindow(_display, screen), + _visual, AllocNone); + } + + XFree(vinfo_return); } } @@ -177,42 +207,10 @@ Display::~Display() XFreeModifiermap(_modmap); - for (int i = 0; i < ScreenCount(_display); ++i) { - delete _rendercontrol_list[i]; - delete _screeninfo_list[i]; - } - delete [] _rendercontrol_list; - delete [] _screeninfo_list; - XCloseDisplay(_display); } -const ScreenInfo* Display::screenInfo(int snum) const -{ - assert(snum >= 0); - assert(snum < (signed) ScreenCount(_display)); - return _screeninfo_list[snum]; -} - - -const ScreenInfo* Display::findScreen(Window root) const -{ - for (int i = 0; i < ScreenCount(_display); ++i) - if (_screeninfo_list[i]->rootWindow() == root) - return _screeninfo_list[i]; - return 0; -} - - -const RenderControl *Display::renderControl(int snum) const -{ - assert(snum >= 0); - assert(snum < (signed) ScreenCount(_display)); - return _rendercontrol_list[snum]; -} - - void Display::setIgnoreErrors(bool t) { // sync up so that anything already sent is/isn't ignored! diff --git a/otk/display.hh b/otk/display.hh index 81dd27c..25c1b45 100644 --- a/otk/display.hh +++ b/otk/display.hh @@ -8,7 +8,6 @@ extern "C" { namespace otk { -class ScreenInfo; class RenderControl; class Display; @@ -56,38 +55,24 @@ private: //! When true, X errors will be ignored. Use with care. bool _ignore_errors; - //! A list of information for all screens on the display - ScreenInfo** _screeninfo_list; + //! The optimal visual for the display + Visual *_visual; - //! A list of RenderControl objects, which are used for all graphics on a - //! screen - RenderControl** _rendercontrol_list; + //! Our colormap built for the optimal visual + Colormap _colormap; + //! The depth of our optimal visual + int _depth; + public: - //! Initializes the class, opens the X display + //! Wraps an open Display connection /*! - The DISPLAY environment variable is used to choose the display. - @see Display::display + @param d An open Display connection. */ - Display(); + Display(::Display *d); //! Destroys the class, closes the X display ~Display(); - //! Gets information on a specific screen - /*! - Returns a ScreenInfo class, which contains information for a screen on the - display. - @param snum The screen number of the screen to retrieve info on - @return Info on the requested screen, in a ScreenInfo class - */ - const ScreenInfo* screenInfo(int snum) const; - - //! Find a ScreenInfo based on a root window - const ScreenInfo* findScreen(Window root) const; - - //! Gets the RenderControl for a screen - const RenderControl *renderControl(int snum) const; - //! Returns if the display has the xkb extension available inline bool xkb() const { return _xkb; } //! Returns the xkb extension's event base diff --git a/otk/screeninfo.cc b/otk/screeninfo.cc index 689837d..368ae98 100644 --- a/otk/screeninfo.cc +++ b/otk/screeninfo.cc @@ -26,52 +26,6 @@ ScreenInfo::ScreenInfo(int num) { _screen)), HeightOfScreen(ScreenOfDisplay(**display, _screen))); - /* - If the default depth is at least 8 we will use that, - otherwise we try to find the largest TrueColor visual. - Preference is given to 24 bit over larger depths if 24 bit is an option. - */ - - _depth = DefaultDepth(**display, _screen); - _visual = DefaultVisual(**display, _screen); - _colormap = DefaultColormap(**display, _screen); - - if (_depth < 8) { - // search for a TrueColor Visual... if we can't find one... - // we will use the default visual for the screen - XVisualInfo vinfo_template, *vinfo_return; - int vinfo_nitems; - int best = -1; - - vinfo_template.screen = _screen; - vinfo_template.c_class = TrueColor; - - vinfo_return = XGetVisualInfo(**display, - VisualScreenMask | VisualClassMask, - &vinfo_template, &vinfo_nitems); - if (vinfo_return) { - int max_depth = 1; - for (int i = 0; i < vinfo_nitems; ++i) { - if (vinfo_return[i].depth > max_depth) { - if (max_depth == 24 && vinfo_return[i].depth > 24) - break; // prefer 24 bit over 32 - max_depth = vinfo_return[i].depth; - best = i; - } - } - if (max_depth < _depth) best = -1; - } - - if (best != -1) { - _depth = vinfo_return[best].depth; - _visual = vinfo_return[best].visual; - _colormap = XCreateColormap(**display, _root_window, _visual, - AllocNone); - } - - XFree(vinfo_return); - } - // get the default display string and strip the screen number string default_string = DisplayString(**display); const string::size_type pos = default_string.rfind("."); diff --git a/otk/screeninfo.hh b/otk/screeninfo.hh index 17063e3..93e835f 100644 --- a/otk/screeninfo.hh +++ b/otk/screeninfo.hh @@ -16,11 +16,6 @@ namespace otk { class ScreenInfo { private: - Visual *_visual; - Window _root_window; - Colormap _colormap; - - int _depth; int _screen; std::string _display_string; Size _size; diff --git a/python/.cvsignore b/python/.cvsignore new file mode 100644 index 0000000..282522d --- /dev/null +++ b/python/.cvsignore @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff --git a/python/Makefile.am b/python/Makefile.am new file mode 100644 index 0000000..ebb3812 --- /dev/null +++ b/python/Makefile.am @@ -0,0 +1,11 @@ +scriptdir=$(libdir)/openbox/python + +SUBDIRS = + +script_PYTHON = rc.py keymap.py buttonmap.py config.py motion.py \ + historyplacement.py windowplacement.py stackedcycle.py focus.py + +MAINTAINERCLEANFILES= Makefile.in + +distclean-local: + $(RM) *\~ *.orig *.rej .\#* diff --git a/python/buttonmap.py b/python/buttonmap.py new file mode 100644 index 0000000..1a20281 --- /dev/null +++ b/python/buttonmap.py @@ -0,0 +1,76 @@ +from input import Pointer + +def set(map): + """Set your buttonmap. Functions in the button map should all take a single + argument, a Client object, except for functions for Action_Motion events, + who should take 2 arguments, a PointerData object and a Client object.""" + global _press_map, _release_map, _click_map, _doubleclick_map, _motion_map + Pointer.clearBinds() + _press_map = [] + _release_map = [] + _click_map = [] + _doubleclick_map = [] + _motion_map = [] + for button, context, action, func in map: + if (action == Pointer.Action_Press): + _press_map.append((button, context, func)) + mapfunc = press_run + if (action == Pointer.Action_Release): + _release_map.append((button, context, func)) + mapfunc = release_run + if (action == Pointer.Action_Click): + _click_map.append((button, context, func)) + mapfunc = click_run + if (action == Pointer.Action_DoubleClick): + _doubleclick_map.append((button, context, func)) + mapfunc = doubleclick_run + if (action == Pointer.Action_Motion): + _motion_map.append((button, context, func)) + mapfunc = motion_run + Pointer.bind(button, context, action, mapfunc) + +def press_run(ptrdata, client): + """Run a button press event through the buttonmap""" + button = ptrdata.button + context = ptrdata.context + for but, cont, func in _press_map: + if (but == button and cont == context): + func(client) + +def release_run(ptrdata, client): + """Run a button release event through the buttonmap""" + button = ptrdata.button + context = ptrdata.context + for but, cont, func in _release_map: + if (but == button and cont == context): + func(client) + +def click_run(ptrdata, client): + """Run a button click event through the buttonmap""" + button = ptrdata.button + context = ptrdata.context + for but, cont, func in _click_map: + if (but == button and cont == context): + func(client) + +def doubleclick_run(ptrdata, client): + """Run a button doubleclick event through the buttonmap""" + button = ptrdata.button + context = ptrdata.context + for but, cont, func in _doubleclick_map: + if (but == button and cont == context): + func(client) + +def motion_run(ptrdata, client): + """Run a pointer motion event through the buttonmap""" + button = ptrdata.button + context = ptrdata.context + for but, cont, func in _motion_map: + if (but == button and cont == context): + func(ptrdata, client) + +_press_map = () +_release_map = () +_click_map = () +_doubleclick_map = () +_motion_map = () diff --git a/python/config.py b/python/config.py new file mode 100644 index 0000000..2ab22f9 --- /dev/null +++ b/python/config.py @@ -0,0 +1,242 @@ +# Openbox's config system. Please use the defined functions instead of +# accessing the internal data structures directly, for the sake of us all. + +def add(modulename, name, friendlyname, description, type, default, + **keywords): + """Add a variable to the configuration system. + + Add a variable to the configuration system for a module. + modulename - The name of the module, e.g. 'focus' + name - The name of the variable, e.g. 'my_variable' + friendlyname - The user-friendly name of the variable, e.g. 'My Variable' + description - The detailed destription of the variable, e.g. 'Does Things' + type - The type of the variable, one of: + - 'boolean' + - 'enum' + - 'integer' + - 'string' + - 'file' + - 'function' + - 'object' + default - The default value for the variable, e.g. 300 + keywords - Extra keyword=value pairs to further define the variable. These + can be: + - For 'enum' types: + - options : A list of possible options for the variable. + This *must* be set for all enum variables. + - For 'integer' types: + - min : The minimum value for the variable. + - max : The maximum value for the variable. + """ + modulename = str(modulename).lower() + name = str(name).lower() + friendlyname = str(friendlyname) + description = str(description) + type = str(type).lower() + + # make sure the sub-dicts exist + try: + _settings[modulename] + try: + _settings[modulename][name] + except KeyError: + _settings[modulename][name] = {} + except KeyError: + _settings[modulename] = {} + _settings[modulename][name] = {} + + # add the keywords first as they are used for the tests in set() + for key,value in zip(keywords.keys(), keywords.values()): + _settings[modulename][name][key] = value + + _settings[modulename][name]['name'] = friendlyname + _settings[modulename][name]['description'] = description + _settings[modulename][name]['type'] = type + _settings[modulename][name]['default'] = default + + # put it through the tests + try: + set(modulename, name, default) + except: + del _settings[modulename][name] + import sys + raise sys.exc_info()[0], sys.exc_info()[1] # re-raise it + +def set(modulename, name, value): + """Set a variable's value. + + Sets the value for a variable of the specified module. + modulename - The name of the module, e.g. 'focus' + name - The name of the variable, e.g. 'my_variable' + value - The new value for the variable. + """ + modulename = str(modulename).lower() + name = str(name).lower() + + # proper value checking for 'boolean's + if _settings[modulename][name]['type'] == 'boolean': + if not (value == 0 or value == 1): + raise ValueError, 'Attempted to set ' + name + ' to a value of '+\ + str(value) + ' but boolean variables can only contain 0 or'+\ + ' 1.' + + # proper value checking for 'enum's + elif _settings[modulename][name]['type'] == 'enum': + options = _settings[modulename][name]['options'] + if not value in options: + raise ValueError, 'Attempted to set ' + name + ' to a value of '+\ + str(value) + ' but this is not one of the possible values '+\ + 'for this enum variable. Possible values are: ' +\ + str(options) + "." + + # min/max checking for 'integer's + elif _settings[modulename][name]['type'] == 'integer': + try: + min = _settings[modulename][name]['min'] + if value < min: + raise ValueError, 'Attempted to set ' + name + ' to a value '+\ + ' of ' + str(value) + ' but it has a minimum value ' +\ + ' of ' + str(min) + '.' + except KeyError: pass + try: + max = _settings[modulename][name]['max'] + if value > max: + raise ValueError, 'Attempted to set ' + name + ' to a value '+\ + ' of ' + str(value) + ' but it has a maximum value ' +\ + ' of ' + str(min) + '.' + except KeyError: pass + + _settings[modulename][name]['value'] = value + +def reset(modulename, name): + """Reset a variable to its default value. + + Resets the value for a variable in the specified module back to its + original (default) value. + modulename - The name of the module, e.g. 'focus' + name - The name of the variable, e.g. 'my_variable' + """ + modulename = str(modulename).lower() + name = str(name).lower() + _settings[modulename][name]['value'] = \ + _settings[modulename][name]['default'] + +def get(modulename, name): + """Returns the value of a variable. + + Returns the current value for a variable in the specified module. + modulename - The name of the module, e.g. 'focus' + name - The name of the variable, e.g. 'my variable' + """ + modulename = str(modulename).lower() + name = str(name).lower() + return _settings[modulename][name]['value'] + +#---------------------------- Internals --------------------------- + +"""The main configuration dictionary, which holds sub-dictionaries for each + module. + + The format for entries in here like this (for a string): + _settings['modulename']['varname']['name'] = 'Text Label' + _settings['modulename']['varname']['description'] = 'Does this' + _settings['modulename']['varname']['type'] = 'string' + _settings['modulename']['varname']['default'] = 'Foo' + _settings['modulename']['varname']['value'] = 'Foo' + # 'value' should always be initialized to the same + # value as the 'default' field! + + Here's an example of an enum: + _settings['modulename']['varname']['name'] = 'My Enum Variable' + _settings['modulename']['varname']['description'] = 'Does Enum-like things.' + _settings['modulename']['varname']['type'] = 'enum' + _settings['modulename']['varname']['default'] = \ + _settings['modulename']['varname']['value'] = [ 'Blue', 'Green', 'Pink' ] + + And Here's an example of an integer with bounds: + _settings['modulename']['varname']['name'] = 'A Bounded Integer' + _settings['modulename']['varname']['description'] = 'A fierce party animal!' + _settings['modulename']['varname']['type'] = 'integer' + _settings['modulename']['varname']['default'] = \ + _settings['modulename']['varname']['value'] = 0 + _settings['modulename']['varname']['min'] = 0 + _settings['modulename']['varname']['max'] = 49 + + Hopefully you get the idea. + """ +_settings = {} + +"""Valid values for a variable's type.""" +_types = [ 'boolean', # Boolean types can only hold a value of 0 or 1. + + 'enum', # Enum types hold a value from a list of possible values. + # An 'options' field *must* be provided for enums, + # containing a list of possible values for the variable. + + 'integer', # Integer types hold a single number, as well as a 'min' + # and 'max' property. + # If the 'min' or 'max' is ignore then bounds checking + # will not be performed in that direction. + + 'string', # String types hold a text string. + + 'file', # File types hold a file object. + + 'function',# Function types hold any callable object. + + 'object' # Object types can hold any python object. + ]; + + + + + + + + + + + + + + + + + +############################################################################# +### Options that can be changed to adjust the behavior of Openbox. ### +############################################################################# + +THEME = "/usr/local/share/openbox/styles/fieron2" +"""The theme used to decorate everything.""" + +#TITLEBAR_LAYOUT = [ "icon", "title", "alldesktops", "iconify", "maximize", "close" ] +TITLEBAR_LAYOUT = "DITMC" +"""The layout of the buttons/label on client titlebars, can be made up of the +following: + I - iconify button + L - text label + M - maximize button, + D - all-desktops button + C - close button +If no 'L' is included in the string, one will be added to the end by +Openbox.""" + +DOUBLE_CLICK_DELAY = 300 +"""The number of milliseconds in which 2 clicks are perceived as a +double-click.""" + +DRAG_THRESHOLD = 3 +"""The amount of pixels that you have to drag the mouse before motion events +will start occuring.""" + +DESKTOP_NAMES = ["one", "two", "three", "four", "five", "six", "seven", \ + "eight", "nine", "ten", "eleven", "twelve"] +"""The name of each desktop.""" + +NUMBER_OF_DESKTOPS = 4 +"""The number of desktops/workspaces which can be scrolled between.""" + +############################################################################# + +print "Loaded config.py" diff --git a/python/focus.py b/python/focus.py new file mode 100644 index 0000000..9cf37e6 --- /dev/null +++ b/python/focus.py @@ -0,0 +1,66 @@ +########################################################################### +### Functions for helping out with your window focus. ### +########################################################################### + +import config, ob, hooks + +export_functions = () + +config.add('focus', + 'avoid_skip_taskbar', + 'Avoid SkipTaskbar Windows', + "Don't focus windows which have requested to not be displayed " + \ + "in taskbars. You will still be able to focus the windows, but " + \ + "not through cycling, and they won't be focused as a fallback " + \ + "if 'Focus Fallback' is enabled.", + 'boolean', + 1) + +config.add('focus', + 'fallback', + 'Focus Fallback', + "Send focus somewhere when nothing is left with the focus, if " + \ + "possible.", + 'boolean', + 1) + +# maintain a list of clients, stacked in focus order +_clients = [] +_skip = 0 + +def focusable(client, desktop): + if not client.normal(): return False + if not (client.canFocus() or client.focusNotify()): return False + if client.iconic(): return False + if config.get('focus', 'avoid_skip_taskbar') and \ + client.skipTaskbar(): return False + + desk = client.desktop() + if not (desk == 0xffffffff or desk == desktop): return False + + return True + +def _focused(client): + global _clients, _skip + + if _skip: + _skip -= 1 + return + + if client: + # move it to the top + _clients.remove(client) + _clients.insert(0, client) + elif config.get('focus', 'fallback'): + # pass around focus + desktop = ob.Openbox.desktop() + for c in _clients: + if focusable(c, desktop): + c.focus() + break + +hooks.managed.append(lambda c: _clients.append(c)) +hooks.closed.append(lambda c: _clients.remove(c)) +hooks.focused.append(_focused) + +print "Loaded focus.py" diff --git a/python/historyplacement.py b/python/historyplacement.py new file mode 100644 index 0000000..0421a22 --- /dev/null +++ b/python/historyplacement.py @@ -0,0 +1,164 @@ +############################################################################## +### The history window placement algorithm. ebind historyplacement.place ### +### to the ob.EventAction.PlaceWindow event to use it. ### +############################################################################## + +import windowplacement, config, hooks + +def place(data): + """Place a window usingthe history placement algorithm.""" + _place(data) + +export_functions = place + +############################################################################## + +config.add('historyplacement', + 'ignore_requested_positions', + 'Ignore Requested Positions', + "When true, the placement algorithm will attempt to place " + \ + "windows even when they request a position (like XMMS can)." + \ + "Note this only applies to 'normal' windows, not to special " + \ + "cases like desktops and docks.", + 'boolean', + 0) +config.add('historyplacement', + 'dont_duplicate', + "Don't Diplicate", + "When true, if 2 copies of the same match in history are to be " + \ + "placed before one of them is closed (so it would be placed " + \ + "over-top of the last one), this will cause the second window to "+\ + "not be placed via history, and the 'Fallback Algorithm' will be "+\ + "used instead.", + 'boolean', + 1) +config.add('historyplacement', + 'filename', + 'History Database Filename', + "The name of the file where history data will be stored. The " + \ + "number of the screen is appended onto this name. The file will " +\ + "be placed in ~/.openbox/.", + 'string', + 'historydb') +config.add('historyplacement', + 'fallback', + 'Fallback Algorithm', + "The window placement algorithm that will be used when history " + \ + "placement does not have a place for the window.", + 'enum', + windowplacement.random, + options = windowplacement.export_functions) + +########################################################################### + +########################################################################### +### Internal stuff, should not be accessed outside the module. ### +########################################################################### + +import ob, os, string + +_data = [] + +class _State: + def __init__(self, resname, resclass, role, x, y): + self.resname = resname + self.resclass = resclass + self.role = role + self.x = x + self.y = y + self.placed = 0 + def __eq__(self, other): + if self.resname == other.resname and \ + self.resclass == other.resclass and \ + self.role == other.role: + return 1 + return 0 + +def _load(): + global _data + try: + file = open(os.environ['HOME'] + '/.openbox/' + \ + config.get('historyplacement', 'filename') + \ + "." + str(ob.Openbox.screenNumber()), 'r') + # read data + for line in file.readlines(): + line = line[:-1] # drop the '\n' + try: + s = string.split(line, '\0') + state = _State(s[0], s[1], s[2], + int(s[3]), int(s[4])) + + _data.append(state) + + except ValueError: pass + except IndexError: pass + file.close() + except IOError: pass + +def _save(): + file = open(os.environ['HOME'] + '/.openbox/'+ \ + config.get('historyplacement', 'filename') + \ + "." + str(ob.Openbox.screenNumber()), 'w') + if file: + for i in _data: + file.write(i.resname + '\0' + + i.resclass + '\0' + + i.role + '\0' + + str(i.x) + '\0' + + str(i.y) + '\n') + file.close() + +def _create_state(client): + area = client.area() + return _State(client.resName(), client.resClass(), + client.role(), area[0], area[1]) + +def _place(client): + state = _create_state(client) + try: + print "looking for : " + state.resname + " : " + \ + state.resclass + " : " + state.role + try: + i = _data.index(state) + except ValueError: + print "No match in history" + else: + coords = _data[i] # get the equal element + print "Found in history ("+str(coords.x)+","+\ + str(coords.y)+")" + if not (config.get('historyplacement', 'dont_duplicate') \ + and coords.placed): + coords.placed = 1 + if ob.Openbox.state() != ob.State.Starting: +# if not (config.get('historyplacement', 'ignore_requested_positions') \ +# and data.client.normal()): +# if data.client.positionRequested(): return + ca = client.area() + client.setArea((coords.x, coords.y, ca[2], ca[3])) + return + else: + print "Already placed another window there" + except TypeError: + pass + fallback = config.get('historyplacement', 'fallback') + if fallback: fallback(client) + +def _save_window(client): + global _data + state = _create_state(client) + print "looking for : " + state.resname + " : " + state.resclass + \ + " : " + state.role + + try: + print "replacing" + i = _data.index(state) + _data[i] = state # replace it + except ValueError: + print "appending" + _data.append(state) + +hooks.startup.append(_load) +hooks.shutdown.append(_save) +hooks.closed.append(_save_window) + +print "Loaded historyplacement.py" diff --git a/python/keymap.py b/python/keymap.py new file mode 100644 index 0000000..569dc92 --- /dev/null +++ b/python/keymap.py @@ -0,0 +1,17 @@ +from input import Keyboard + +def set(map): + """Set your keymap""" + global _map + Keyboard.clearBinds() + for key, func in map: + Keyboard.bind(key, run) + _map = map + +def run(keydata, client): + """Run a key press event through the keymap""" + for key, func in _map: + if (keydata.keychain == key): + func(keydata, client) + +_map = () diff --git a/python/motion.py b/python/motion.py new file mode 100644 index 0000000..64e0494 --- /dev/null +++ b/python/motion.py @@ -0,0 +1,81 @@ +import config, hooks, ob +from input import Pointer + +config.add('motion', + 'edge_resistance', + 'Edge Resistance', + "The amount of resistance to provide to moving a window past a " + \ + "screen boundary. Specify a value of 0 to disable edge resistance.", + 'integer', + 10, + min = 0) + +def move(ptrdata, client): + def mymove(ptrdata, client): + global _moving, _last_pos + if ptrdata.action == Pointer.Action_Release: + _moveclient.setArea(_moveclient.area(), True) # finalize the move + _moving = False + Pointer.ungrab() + elif ptrdata.action == Pointer.Action_Motion: + pos = ptrdata.pos + + x = _pcarea[0] + pos[0] - _presspos[0] + y = _pcarea[1] + pos[1] - _presspos[1] + + resist = config.get('motion', 'edge_resistance') + if resist: + ca = _moveclient.area() + w, h = ca[2], ca[3] + # use the area based on the struts + sa = ob.Openbox.screenArea(_moveclient.desktop()) + l, t = sa[0], sa[1] + r = l+ sa[2] - w + b = t+ sa[3] - h + # left screen edge + if _last_pos[0] >= pos[0] and x < l and x >= l - resist: + x = l + # right screen edge + if _last_pos[0] <= pos[0] and x > r and x <= r + resist: + x = r + # top screen edge + if _last_pos[1] >= pos[1] and y < t and y >= t - resist: + y = t + # right screen edge + if _last_pos[1] <= pos[1] and y > b and y <= b + resist: + y = b + + _moveclient.setArea((x, y, _pcarea[2], _pcarea[3]), False) + _last_pos = pos + + global _last_pos, _moving, _pcarea, _presspos, _moveclient + if not _moving: + _moving = True + _pcarea = ptrdata.pressclientarea + _presspos = ptrdata.presspos + _last_pos = _presspos + _moveclient = client + Pointer.grab(mymove) + mymove(ptrdata, client) + +def resize(ptrdata, client): + x, y = ptrdata.pos + px, py = ptrdata.presspos + cx, cy, cw, ch = ptrdata.pressclientarea + dx = x - px + dy = y - py + if px < cx + cw / 2: # left side + dx *= -1 + cx -= dx + if py < cy + ch / 2: # top side + dy *= -1 + cy -= dy + cw += dx + ch += dy + client.setArea((cx, cy, cw, ch)) + +_moving = False +_moveclient = 0 +_last_pos = () +_pcarea = () +_presspos = () diff --git a/python/rc.py b/python/rc.py new file mode 100644 index 0000000..e3cf76f --- /dev/null +++ b/python/rc.py @@ -0,0 +1,114 @@ +import hooks, ob, keymap, buttonmap, os, sys, input, motion, historyplacement +import stackedcycle +from input import Pointer + +hooks.managed.append(historyplacement.place) + +_grab = 0 +def printshit(keydata, client): + global _grab + print "shit" + _grab = not _grab + print _grab + def gfunc(data, client=None): pass + if _grab: + input.Keyboard.grab(gfunc) + input.Pointer.grab(gfunc) + else: + input.Keyboard.ungrab() + input.Pointer.ungrab() + +def myexec(prog): + print "execing: ", prog + if (os.fork() == 0): + try: + os.setsid() + os.execl("/bin/sh", "/bin/sh", "-c", prog) + except: + print str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1]) + try: + print "failed to execute '" + prog + "'" + except: + print str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1]) + os._exit(0) + +def myactivate(c): + if ob.Openbox.showingDesktop(): + ob.Openbox.setShowingDesktop(False) + if c.iconic(): + c.setIconic(False) + elif not c.visible(): + # if its not visible for other reasons, then don't mess with it + return + if c.shaded(): + c.setShaded(False) + c.focus() + c.raiseWindow() + +hooks.requestactivate.append(myactivate) + +def myfocus(c): + if c and c.normal(): c.focus() + +#hooks.showwindow.append(myfocus) +hooks.pointerenter.append(myfocus) + +hooks.visible.append(myfocus) + +mykmap=((("C-a", "d"), printshit), + (("C-Tab",), stackedcycle.next), + (("C-S-Tab",), stackedcycle.previous), + (("C-space",), lambda k, c: myexec("xterm"))) +keymap.set(mykmap) + +def mytogglesticky(client): + if client.desktop() == 0xffffffff: d = ob.Openbox.desktop() + else: d = 0xffffffff + client.setDesktop(d) + +mybmap=(("1", "maximize", Pointer.Action_Click, + lambda c: c.setMaximized(not c.maximized())), + ("2", "maximize", Pointer.Action_Click, + lambda c: c.setMaximizedVert(not c.maximizedVert())), + ("3", "maximize", Pointer.Action_Click, + lambda c: c.setMaximizedHorz(not c.maximizedHorz())), + ("1", "alldesktops", Pointer.Action_Click, mytogglesticky), + ("1", "iconify", Pointer.Action_Click, + lambda c: c.setIconic(True)), + ("1", "icon", Pointer.Action_DoubleClick, ob.Client.close), + ("1", "close", Pointer.Action_Click, ob.Client.close), + ("1", "titlebar", Pointer.Action_Motion, motion.move), + ("1", "handle", Pointer.Action_Motion, motion.move), + ("Mod1-1", "frame", Pointer.Action_Click, ob.Client.raiseWindow), + ("Mod1-1", "frame", Pointer.Action_Motion, motion.move), + ("1", "titlebar", Pointer.Action_Press, ob.Client.raiseWindow), + ("1", "handle", Pointer.Action_Press, ob.Client.raiseWindow), + ("1", "client", Pointer.Action_Press, ob.Client.raiseWindow), + ("2", "titlebar", Pointer.Action_Press, ob.Client.lowerWindow), + ("2", "handle", Pointer.Action_Press, ob.Client.lowerWindow), + ("Mod1-3", "frame", Pointer.Action_Click, ob.Client.lowerWindow), + ("Mod1-3", "frame", Pointer.Action_Motion, motion.resize), + ("1", "blcorner", Pointer.Action_Motion, motion.resize), + ("1", "brcorner", Pointer.Action_Motion, motion.resize), + ("1", "titlebar", Pointer.Action_Press, ob.Client.focus), + ("1", "handle", Pointer.Action_Press, ob.Client.focus), + ("1", "client", Pointer.Action_Press, ob.Client.focus), + ("1", "titlebar", Pointer.Action_DoubleClick, + lambda c: c.setShaded(not c.shaded())), + ("4", "titlebar", Pointer.Action_Click, + lambda c: c.setShaded(True)), + ("5", "titlebar", Pointer.Action_Click, + lambda c: c.setShaded(False)), + ("4", "root", Pointer.Action_Click, + lambda c: ob.Openbox.setNextDesktop()), + ("5", "root", Pointer.Action_Click, + lambda c: ob.Openbox.setPreviousDesktop()), + ("Mod1-4", "frame", Pointer.Action_Click, + lambda c: ob.Openbox.setNextDesktop()), + ("Mod1-5", "frame", Pointer.Action_Click, + lambda c: ob.Openbox.setPreviousDesktop()), + ("Mod1-4", "root", Pointer.Action_Click, + lambda c: ob.Openbox.setNextDesktop()), + ("Mod1-5", "root", Pointer.Action_Click, + lambda c: ob.Openbox.setPreviousDesktop())) +buttonmap.set(mybmap) diff --git a/python/stackedcycle.py b/python/stackedcycle.py new file mode 100644 index 0000000..4f7f71d --- /dev/null +++ b/python/stackedcycle.py @@ -0,0 +1,216 @@ +import ob, config, hooks, focus +from input import Pointer, Keyboard + +config.add('stackedcycle', + 'activate_while_cycling', + 'Activate While Cycling', + "If this is True then windows will be activated as they are" + \ + "highlighted in the cycling list (except iconified windows).", + 'boolean', + True) + +config.add('stackedcycle', + 'raise_window', + 'Raise After Cycling', + "If this is True, the selected window will be raised as well as " +\ + "focused.", + 'boolean', + True) + +config.add('stackedcycle', + 'include_all_desktops', + 'Include Windows From All Desktops', + "If this is True then windows from all desktops will be included" +\ + " in the stacking list.", + 'boolean', + False) + +config.add('stackedcycle', + 'include_icons', + 'Include Icons', + "If this is True then windows which are iconified on the current" +\ + " desktop will be included in the stacking list.", + 'boolean', + False) + +config.add('stackedcycle', + 'include_icons_all_desktops', + 'Include Icons From All Desktops', + "If this is True then windows which are iconified from all " +\ + "desktops will be included in the stacking list (if Include Icons"+\ + " is also True).", + 'boolean', + True) + +config.add('stackedcycle', + 'include_omnipresent', + 'Include Omnipresent Windows', + "If this is True then windows which are on all-desktops at once " +\ + "will be included in the stacking list.", + 'boolean', + True) + +def next(keydata, client): _cycle(keydata, client, True) +def previous(keydata, client): _cycle(keydata, client, False) + +def _shouldAdd(client): + """Determines if a client should be added to the cycling list.""" + curdesk = ob.Openbox.desktop() + desk = client.desktop() + + if not (client.normal() and client.canFocus()): return False + if config.get('focus', 'avoid_skip_taskbar') and client.skipTaskbar(): + return False + + if client.iconic(): + if config.get('stackedcycle', 'include_icons'): + if config.get('stackedcycle', 'include_icons_all_desktops'): + return True + if desk == curdesk: return True + return False + if config.get('stackedcycle', 'include_omnipresent') and \ + desk == 0xffffffff: return True + if config.get('stackedcycle', 'include_all_desktops'): return True + if desk == curdesk: return True + + return False + +def _populateItems(): + global _items + # get the list of clients, keeping iconic windows at the bottom + _items = [] + iconic_clients = [] + for c in focus._clients: + if _shouldAdd(c): + if c.iconic(): iconic_clients.append(c) + else: _items.append(c) + _items.extend(iconic_clients) + +def _populate(): + global _pos, _items + try: + current = _items[_pos] + except IndexError: + current = None + oldpos = _pos + _pos = -1 + + _populateItems() + + i = 0 + for item in _items: + # current item might have shifted after a populateItems() + # call, so we need to do this test. + if current == item: + _pos = i + i += 1 + + # The item we were on might be gone entirely + if _pos < 0: + # try stay at the same spot in the menu + if oldpos >= len(_items): + _pos = len(_items) - 1 + else: + _pos = oldpos + + +def _activate(final): + """ + Activates (focuses and, if the user requested it, raises a window). + If final is True, then this is the very last window we're activating + and the user has finished cycling. + """ + print "_activate" + try: + client = _items[_pos] + except IndexError: return # empty list + print client + + # move the to client's desktop if required + if not (client.iconic() or client.desktop() == 0xffffffff or \ + client.desktop() == ob.Openbox.desktop()): + ob.Openbox.setDesktop(client.desktop()) + + if final or not client.iconic(): + if final: r = config.get('stackedcycle', 'raise_window') + else: r = False + client.focus(True) + if final and client.shaded(): client.setShaded(False) + print "final", final, "raising", r + if r: client.raiseWindow() + if not final: + focus._skip += 1 + +def _cycle(keydata, client, forward): + global _cycling, _state, _pos, _inititem, _items + + if not _cycling: + _items = [] # so it doesnt try start partway through the list + _populate() + + if not _items: return # don't bother doing anything + + Keyboard.grab(_grabfunc) + # the pointer grab causes pointer events during the keyboard grab + # to go away, which means we don't get enter notifies when the + # popup disappears, screwing up the focus + Pointer.grabPointer(True) + + _cycling = True + _state = keydata.state + _pos = 0 + _inititem = _items[_pos] + + if forward: + _pos += 1 + else: + _pos -= 1 + # wrap around + if _pos < 0: _pos = len(_items) - 1 + elif _pos >= len(_items): _pos = 0 + if config.get('stackedcycle', 'activate_while_cycling'): + _activate(False) # activate, but dont deiconify/unshade/raise + +def _grabfunc(keydata, client): + global _cycling + + done = False + notreverting = True + # have all the modifiers this started with been released? + if not _state & keydata.state: + done = True + elif keydata.press: + # has Escape been pressed? + if keydata.keychain == "Escape": + done = True + notreverting = False + # revert + try: + _pos = _items.index(_inititem) + except: + _pos = -1 + # has Enter been pressed? + elif keydata.keychain == "Return": + done = True + + if done: + # activate, and deiconify/unshade/raise + _activate(notreverting) + _cycling = False + Keyboard.ungrab() + Pointer.grabPointer(False) + + +_cycling = False +_pos = 0 +_inititem = None +_items = [] +_state = 0 + +def _newwin(data): + if _cycling: _populate() +def _closewin(data): + if _cycling: _populate() + +hooks.managed.append(_newwin) +hooks.closed.append(_closewin) diff --git a/python/windowplacement.py b/python/windowplacement.py new file mode 100644 index 0000000..56b5320 --- /dev/null +++ b/python/windowplacement.py @@ -0,0 +1,47 @@ +############################################################################ +### Window placement algorithms, choose one of these and ebind it to the ### +### ob.EventAction.PlaceWindow event. ### +### ### +### Also see historyplacement.py for the history placement module which ### +### provides an algorithm that can be used in place of, or alongside, ### +### these. ### +############################################################################ + +import ob +from random import Random + +def random(client): + """Place windows randomly around the screen.""" + if ob.Openbox.state() == ob.State.Starting: return + #if data.client.positionRequested(): return + cx, cy, cw, ch = client.area() + sx, sy, sw, sh = ob.Openbox.screenArea(client.desktop()) + global _rand + x = Random().randrange(sx, sw - cw - 1) + y = Random().randrange(sy, sh - ch - 1) + client.setArea((x, y, cw, ch)) + +def cascade(client): + """Place windows in a cascading order from top-left to bottom-right.""" + if ob.Openbox.state() == ob.State.Starting: return + #if data.client.positionRequested(): return + cx, cy, cw, ch = client.area() + sx, sy, sw, sh = ob.Openbox.screenArea(client.desktop()) + width = sw - cw + height = sh - ch + global _cascade_x, _cascade_y + if _cascade_x < sx or _cascade_y < sy or \ + _cascade_x >= width or _cascade_y >= height: + _cascade_x = sx + _cascade_y = sy + client.setArea((_cascade_x, _cascade_y, cw, ch)) + frame_size = client.frameSize() + _cascade_x += frame_size[1] + _cascade_y += frame_size[1] + +_cascade_x = 0 +_cascade_y = 0 + +export_functions = random, cascade + +print "Loaded windowplacement.py" diff --git a/render/.cvsignore b/render/.cvsignore new file mode 100644 index 0000000..8ce2c9d --- /dev/null +++ b/render/.cvsignore @@ -0,0 +1,6 @@ +? .deps +? .libs +? Makefile +? Makefile.in +? rendertest + diff --git a/render/Makefile.am b/render/Makefile.am new file mode 100644 index 0000000..dfe8d5f --- /dev/null +++ b/render/Makefile.am @@ -0,0 +1,17 @@ +CPPFLAGS=$(XFT_CFLAGS) $(PYTHON_CFLAGS) $(GLIB_CFLAGS) @CPPFLAGS@ \ +-DG_LOG_DOMAIN=\"Render\" + +LIBS=$(XFT_LIBS) $(PYTHON_LIBS) $(GLIB_LIBS) @LIBS@ + +noinst_PROGRAMS=rendertest +rendertest_SOURCES=$(librender_a_SOURCES) + +noinst_LIBRARIES=librender.a +librender_a_SOURCES=render.c test.c gradient.c color.c + +noinst_HEADERS=render.h gradient.h color.h + +MAINTAINERCLEANFILES= Makefile.in + +distclean-local: + $(RM) *\~ *.orig *.rej .\#* diff --git a/render/color.c b/render/color.c new file mode 100644 index 0000000..83c2aa3 --- /dev/null +++ b/render/color.c @@ -0,0 +1,57 @@ +#include +#include +#include "render.h" +#include "color.h" +#include "../kernel/openbox.h" +void color_allocate_gc(color_rgb *in) +{ + XGCValues gcv; + + gcv.foreground = in->pixel; + gcv.cap_style = CapProjecting; + in->gc = XCreateGC(ob_display, ob_root, GCForeground | GCCapStyle, &gcv); +} + +color_rgb *color_parse(char *colorname) +{ + g_assert(colorname != NULL); + // get rgb values from colorname + + XColor xcol; + xcol.red = 0; + xcol.green = 0; + xcol.blue = 0; + xcol.pixel = 0; + if (!XParseColor(ob_display, render_colormap, colorname, &xcol)) { + g_warning("unable to parse color '%s'", colorname); + return NULL; + } + return color_new(xcol.red >> 8, xcol.green >> 8, xcol.blue >> 8); +} + +color_rgb *color_new(int r, int g, int b) +{ +/* this should be replaced with something far cooler */ + color_rgb *out; + XColor xcol; + xcol.red = (r << 8) | r; + xcol.green = (g << 8) | g; + xcol.blue = (b << 8) | b; + if (XAllocColor(ob_display, render_colormap, &xcol)) { + out = g_new(color_rgb, 1); + out->r = xcol.red >> 8; + out->g = xcol.green >> 8; + out->b = xcol.blue >> 8; + out->gc = None; + out->pixel = xcol.pixel; + return out; + } + return NULL; +} + +void color_free(color_rgb *c) +{ + if (c->gc != None) + XFreeGC(ob_display, c->gc); + free(c); +} diff --git a/render/color.h b/render/color.h new file mode 100644 index 0000000..1e0cc57 --- /dev/null +++ b/render/color.h @@ -0,0 +1,19 @@ +#ifndef __color_h +#define __color_h + +#include + +typedef struct color_rgb { + int r; + int g; + int b; + unsigned long pixel; + GC gc; +} color_rgb; + +void color_allocate_gc(color_rgb *in); +color_rgb *color_parse(char *colorname); +color_rgb *color_new(int r, int g, int b); +void color_free(color_rgb *in); + +#endif /* __color_h */ diff --git a/render/font.c b/render/font.c new file mode 100644 index 0000000..eb72feb --- /dev/null +++ b/render/font.c @@ -0,0 +1,75 @@ +#include "font.h" + +#include "../src/gettext.h" +#define _(str) gettext(str) + +font_open(const std::string &fontstring, + bool shadow, unsigned char offset, unsigned char tint) +{ + assert(screen_num >= 0); + assert(tint <= CHAR_MAX); + + if (!_xft_init) { + if (!XftInit(0)) { + printf(_("Couldn't initialize Xft.\n\n")); + ::exit(3); + } +#ifdef DEBUG + int version = XftGetVersion(); + printf("Using Xft %d.%d.%d (Built against %d.%d.%d).\n", + version / 10000 % 100, version / 100 % 100, version % 100, + XFT_MAJOR, XFT_MINOR, XFT_REVISION); +#endif + _xft_init = true; + } + + if ((_xftfont = XftFontOpenName(ob_display, _screen_num, + fontstring))) + return; + + printf(_("Unable to load font: %s\n"), _fontstring.c_str()); + printf(_("Trying fallback font: %s\n"), "fixed"); + + if ((_xftfont = XftFontOpenName(ob_display, _screen_num, + "fixed"))) + return; + + printf(_("Unable to load font: %s\n"), "fixed"); + printf(_("Aborting!.\n")); + + exit(3); // can't continue without a font +} + + +destroy_fonts(void) +{ + if (_xftfont) + XftFontClose(ob_display, _xftfont); +} + + +int font_measure_string(const char *) +{ + XGlyphInfo info; + + if (string.utf8()) + XftTextExtentsUtf8(**display, _xftfont, + (FcChar8*)string.c_str(), string.bytes(), &info); + else + XftTextExtents8(ob_display, _xftfont, + (FcChar8*)string.c_str(), string.bytes(), &info); + + return (signed) info.xOff + (_shadow ? _offset : 0); +} + + +int font_height(void) +{ + return (signed) _xftfont->height + (_shadow ? _offset : 0); +} + + +int font_max_char_width(void) +{ + return (signed) _xftfont->max_advance_width; +} diff --git a/render/gradient.c b/render/gradient.c new file mode 100644 index 0000000..8ea86aa --- /dev/null +++ b/render/gradient.c @@ -0,0 +1,320 @@ +#include +#include +#include "render.h" +#include "gradient.h" +#include "../kernel/openbox.h" +#include "color.h" + +void gradient_render(Surface *sf, int w, int h) +{ + pixel32 *data = sf->data.planar.pixel_data; + pixel32 current; + unsigned int r,g,b; + int off, x; + + switch (sf->data.planar.grad) { + case Background_Solid: /* already handled */ + return; + case Background_Vertical: + gradient_vertical(sf, w, h); + break; + case Background_Horizontal: + gradient_horizontal(sf, w, h); + break; + case Background_Diagonal: + gradient_diagonal(sf, w, h); + break; + case Background_CrossDiagonal: + gradient_crossdiagonal(sf, w, h); + break; + default: + printf("unhandled gradient\n"); + } + + if (sf->data.planar.relief == Flat && sf->data.planar.border) { + r = sf->data.planar.border_color->r; + g = sf->data.planar.border_color->g; + b = sf->data.planar.border_color->b; + current = (r << default_red_shift) + + (g << default_green_shift) + + (b << default_blue_shift); + for (off = 0, x = 0; x < w; ++x, off++) { + *(data + off) = current; + *(data + off + ((h-1) * w)) = current; + } + for (off = 0, x = 0; x < h; ++x, off++) { + *(data + (off * w)) = current; + *(data + (off * w) + w - 1) = current; + } + } + + if (sf->data.planar.relief != Flat) { + if (sf->data.planar.bevel == Bevel1) { + for (off = 1, x = 1; x < w - 1; ++x, off++) + highlight(data + off, + data + off + (h-1) * w, + sf->data.planar.relief==Raised); + for (off = 0, x = 0; x < h; ++x, off++) + highlight(data + off * w, + data + off * w + w - 1, + sf->data.planar.relief==Raised); + } + + if (sf->data.planar.bevel == Bevel2) { + for (off = 2, x = 2; x < w - 2; ++x, off++) + highlight(data + off + w, + data + off + (h-2) * w, + sf->data.planar.relief==Raised); + for (off = 1, x = 1; x < h-1; ++x, off++) + highlight(data + off * w + 1, + data + off * w + w - 2, + sf->data.planar.relief==Raised); + } + } +} + + + +void gradient_vertical(Surface *sf, int w, int h) +{ + pixel32 *data = sf->data.planar.pixel_data; + pixel32 current; + float dr, dg, db; + unsigned int r,g,b; + int x, y; + + dr = (float)(sf->data.planar.secondary->r - sf->data.planar.primary->r); + dr/= (float)h; + + dg = (float)(sf->data.planar.secondary->g - sf->data.planar.primary->g); + dg/= (float)h; + + db = (float)(sf->data.planar.secondary->b - sf->data.planar.primary->b); + db/= (float)h; + + for (y = 0; y < h; ++y) { + r = sf->data.planar.primary->r + (int)(dr * y); + g = sf->data.planar.primary->g + (int)(dg * y); + b = sf->data.planar.primary->b + (int)(db * y); + current = (r << default_red_shift) + + (g << default_green_shift) + + (b << default_blue_shift); + for (x = 0; x < w; ++x, ++data) + *data = current; + } +} + +void gradient_horizontal(Surface *sf, int w, int h) +{ + pixel32 *data = sf->data.planar.pixel_data; + pixel32 current; + float dr, dg, db; + unsigned int r,g,b; + int x, y; + + dr = (float)(sf->data.planar.secondary->r - sf->data.planar.primary->r); + dr/= (float)w; + + dg = (float)(sf->data.planar.secondary->g - sf->data.planar.primary->g); + dg/= (float)w; + + db = (float)(sf->data.planar.secondary->b - sf->data.planar.primary->b); + db/= (float)w; + + for (x = 0; x < w; ++x, ++data) { + r = sf->data.planar.primary->r + (int)(dr * x); + g = sf->data.planar.primary->g + (int)(dg * x); + b = sf->data.planar.primary->b + (int)(db * x); + current = (r << default_red_shift) + + (g << default_green_shift) + + (b << default_blue_shift); + for (y = 0; y < h; ++y) + *(data + y*w) = current; + } +} + +void gradient_diagonal(Surface *sf, int w, int h) +{ + pixel32 *data = sf->data.planar.pixel_data; + pixel32 current; + float drx, dgx, dbx, dry, dgy, dby; + unsigned int r,g,b; + int x, y; + + for (y = 0; y < h; ++y) { + drx = (float)(sf->data.planar.secondary->r - sf->data.planar.primary->r); + dry = drx/(float)h; + drx/= (float)w; + + dgx = (float)(sf->data.planar.secondary->g - sf->data.planar.primary->g); + dgy = dgx/(float)h; + dgx/= (float)w; + + dbx = (float)(sf->data.planar.secondary->b - sf->data.planar.primary->b); + dby = dbx/(float)h; + dbx/= (float)w; + for (x = 0; x < w; ++x, ++data) { + r = sf->data.planar.primary->r + ((int)(drx * x) + (int)(dry * y))/2; + g = sf->data.planar.primary->g + ((int)(dgx * x) + (int)(dgy * y))/2; + b = sf->data.planar.primary->b + ((int)(dbx * x) + (int)(dby * y))/2; + current = (r << default_red_shift) + + (g << default_green_shift) + + (b << default_blue_shift); + *data = current; + } + } +} + +void gradient_crossdiagonal(Surface *sf, int w, int h) +{ + pixel32 *data = sf->data.planar.pixel_data; + pixel32 current; + float drx, dgx, dbx, dry, dgy, dby; + unsigned int r,g,b; + int x, y; + + for (y = 0; y < h; ++y) { + drx = (float)(sf->data.planar.secondary->r - sf->data.planar.primary->r); + dry = drx/(float)h; + drx/= (float)w; + + dgx = (float)(sf->data.planar.secondary->g - sf->data.planar.primary->g); + dgy = dgx/(float)h; + dgx/= (float)w; + + dbx = (float)(sf->data.planar.secondary->b - sf->data.planar.primary->b); + dby = dbx/(float)h; + dbx/= (float)w; + for (x = w; x > 0; --x, ++data) { + r = sf->data.planar.primary->r + ((int)(drx * (x-1)) + (int)(dry * y))/2; + g = sf->data.planar.primary->g + ((int)(dgx * (x-1)) + (int)(dgy * y))/2; + b = sf->data.planar.primary->b + ((int)(dbx * (x-1)) + (int)(dby * y))/2; + current = (r << default_red_shift) + + (g << default_green_shift) + + (b << default_blue_shift); + *data = current; + } + } +} + +void highlight(pixel32 *x, pixel32 *y, gboolean raised) +{ + int r, g, b; + + pixel32 *up, *down; + if (raised) { + up = x; + down = y; + } else { + up = y; + down = x; + } + r = (*up >> default_red_shift) & 0xFF; + r += r >> 1; + g = (*up >> default_green_shift) & 0xFF; + g += g >> 1; + b = (*up >> default_blue_shift) & 0xFF; + b += b >> 1; + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + *up = (r << default_red_shift) + (g << default_green_shift) + + (b << default_blue_shift); + + r = (*down >> default_red_shift) & 0xFF; + r = (r >> 1) + (r >> 2); + g = (*down >> default_green_shift) & 0xFF; + g = (g >> 1) + (g >> 2); + b = (*down >> default_blue_shift) & 0xFF; + b = (b >> 1) + (b >> 2); + *down = (r << default_red_shift) + (g << default_green_shift) + + (b << default_blue_shift); +} + +void gradient_solid(Appearance *l, int w, int h) +{ + int i; + PlanarSurface *sp = &l->surface.data.planar; + int left = 0, top = 0, right = w - 1, bottom = h - 1; + + if (sp->primary->gc == None) + color_allocate_gc(sp->primary); + XFillRectangle(ob_display, l->pixmap, sp->primary->gc + , 0, 0, w, h); + + if (l->surface.data.planar.interlaced) { + if (sp->secondary->gc == None) + color_allocate_gc(sp->secondary); + for (i = 0; i < h; i += 2) + XDrawLine(ob_display, l->pixmap, sp->secondary->gc, + 0, i, w, i); + } +/* + switch (texture.relief()) { + case RenderTexture::Raised: + switch (texture.bevel()) { + case RenderTexture::Bevel1: + XDrawLine(ob_display, l->pixmap, texture.bevelDarkColor().gc(), + left, bottom, right, bottom); + XDrawLine(ob_display, l->pixmap, texture.bevelDarkColor().gc(), + right, bottom, right, top); + + XDrawLine(ob_display, l->pixmap, texture.bevelLightColor().gc(), + left, top, right, top); + XDrawLine(ob_display, l->pixmap, texture.bevelLightColor().gc(), + left, bottom, left, top); + break; + case RenderTexture::Bevel2: + XDrawLine(ob_display, l->pixmap, texture.bevelDarkColor().gc(), + left + 1, bottom - 2, right - 2, bottom - 2); + XDrawLine(ob_display, l->pixmap, texture.bevelDarkColor().gc(), + right - 2, bottom - 2, right - 2, top + 1); + + XDrawLine(ob_display, l->pixmap, texture.bevelLightColor().gc(), + left + 1, top + 1, right - 2, top + 1); + XDrawLine(**display, sf.pixmap(), texture.bevelLightColor().gc(), + left + 1, bottom - 2, left + 1, top + 1); + break; + default: + assert(false); // unhandled RenderTexture::BevelType + } + break; + case RenderTexture::Sunken: + switch (texture.bevel()) { + case RenderTexture::Bevel1: + XDrawLine(**display, sf.pixmap(), texture.bevelLightColor().gc(), + left, bottom, right, bottom); + XDrawLine(**display, sf.pixmap(), texture.bevelLightColor().gc(), + right, bottom, right, top); + + XDrawLine(**display, sf.pixmap(), texture.bevelDarkColor().gc(), + left, top, right, top); + XDrawLine(**display, sf.pixmap(), texture.bevelDarkColor().gc(), + left, bottom, left, top); + break; + case RenderTexture::Bevel2: + XDrawLine(**display, sf.pixmap(), texture.bevelLightColor().gc(), + left + 1, bottom - 2, right - 2, bottom - 2); + XDrawLine(**display, sf.pixmap(), texture.bevelLightColor().gc(), + right - 2, bottom - 2, right - 2, top + 1); + + XDrawLine(**display, sf.pixmap(), texture.bevelDarkColor().gc(), + left + 1, top + 1, right - 2, top + 1); + XDrawLine(**display, sf.pixmap(), texture.bevelDarkColor().gc(), + left + 1, bottom - 2, left + 1, top + 1); + + break; + default: + assert(false); // unhandled RenderTexture::BevelType + } + break; + case RenderTexture::Flat: + if (texture.border()) + XDrawRectangle(**display, sf.pixmap(), texture.borderColor().gc(), + left, top, right, bottom); + break; + default: + assert(false); // unhandled RenderTexture::ReliefType + } +*/ +} diff --git a/render/gradient.h b/render/gradient.h new file mode 100644 index 0000000..626278f --- /dev/null +++ b/render/gradient.h @@ -0,0 +1,14 @@ +#ifndef __gradient_h +#define __gradient_h + +#include "render.h" + +void gradient_render(Surface *sf, int w, int h); +void gradient_vertical(Surface *sf, int w, int h); +void gradient_horizontal(Surface *sf, int w, int h); +void gradient_diagonal(Surface *sf, int w, int h); +void gradient_crossdiagonal(Surface *sf, int w, int h); +void gradient_solid(Appearance *l, int w, int h); /* needs access to pixmap */ +void highlight(pixel32 *x, pixel32 *y, gboolean raised); + +#endif /* __gradient_h */ diff --git a/render/render.c b/render/render.c new file mode 100644 index 0000000..cdbf01f --- /dev/null +++ b/render/render.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include "render.h" +#include "gradient.h" +#include "../kernel/openbox.h" + +int render_depth; +Visual *render_visual; +Colormap render_colormap; + +void render_startup(void) +{ + paint = x_paint; + + render_depth = DefaultDepth(ob_display, ob_screen); + render_visual = DefaultVisual(ob_display, ob_screen); + render_colormap = DefaultColormap(ob_display, ob_screen); + + if (render_depth < 8) { + XVisualInfo vinfo_template, *vinfo_return; + // search for a TrueColor Visual... if we can't find one... + // we will use the default visual for the screen + int vinfo_nitems; + int best = -1; + + vinfo_template.screen = ob_screen; + vinfo_template.class = TrueColor; + vinfo_return = XGetVisualInfo(ob_display, + VisualScreenMask | VisualClassMask, + &vinfo_template, &vinfo_nitems); + if (vinfo_return) { + int i; + int max_depth = 1; + for (i = 0; i < vinfo_nitems; ++i) { + if (vinfo_return[i].depth > max_depth) { + if (max_depth == 24 && vinfo_return[i].depth > 24) + break; // prefer 24 bit over 32 + max_depth = vinfo_return[i].depth; + best = i; + } + } + if (max_depth < render_depth) best = -1; + } + if (best != -1) { + render_depth = vinfo_return[best].depth; + render_visual = vinfo_return[best].visual; + render_colormap = XCreateColormap(ob_display, ob_root, render_visual, + AllocNone); + } + XFree(vinfo_return); + } +} + +void x_paint(Window win, Appearance *l, int w, int h) +{ + int i; + XImage *im; + + if (w <= 0 || h <= 0) return; + + g_assert(l->surface.type == Surface_Planar); +// printf("painting window %ld\n", win); + + if (l->pixmap != None) XFreePixmap(ob_display, l->pixmap); + l->pixmap = XCreatePixmap(ob_display, ob_root, w, h, render_depth); + g_assert(l->pixmap != None); + + if (l->xftdraw != NULL) + XftDrawDestroy(l->xftdraw); + l->xftdraw = XftDrawCreate(ob_display, l->pixmap, render_visual, + render_colormap); + g_assert(l->xftdraw != NULL); + + if (l->surface.data.planar.pixel_data != NULL) + g_free(l->surface.data.planar.pixel_data); + l->surface.data.planar.pixel_data = g_new(pixel32, w * h); + + if (l->surface.data.planar.grad == Background_Solid) + gradient_solid(l, w, h); + else gradient_render(&l->surface, w, h); + for (i = 0; i < l->textures; i++) { + printf("I AM DOING SOMETHING NOW\n"); + } +//reduce depth + if (l->surface.data.planar.grad != Background_Solid) { + im = XCreateImage(ob_display, render_visual, render_depth, + ZPixmap, 0, NULL, w, h, 32, 0); + g_assert(im != None); + im->byte_order = endian; + im->data = l->surface.data.planar.pixel_data; + XPutImage(ob_display, l->pixmap, DefaultGC(ob_display, ob_screen), + im, 0, 0, 0, 0, w, h); + im->data = NULL; + XDestroyImage(im); + } + XSetWindowBackgroundPixmap(ob_display, win, l->pixmap); + XClearWindow(ob_display, win); +} + +/* +void gl_paint(Window win, Appearance *l) +{ + glXMakeCurrent(ob_display, win, gl_context); +} +*/ + +void render_shutdown(void) +{ +} + +Appearance *appearance_new(SurfaceType type, int numtex) +{ + PlanarSurface *p; + Appearance *out; + + out = g_new(Appearance, 1); + out->surface.type = type; + out->textures = numtex; + out->xftdraw = NULL; + if (numtex) out->texture = g_new(Texture, numtex); + out->pixmap = None; + + switch (type) { + case Surface_Planar: + p = &out->surface.data.planar; + p->primary = NULL; + p->secondary = NULL; + p->border_color = NULL; + p->pixel_data = NULL; + break; + } + return out; +} + +Appearance *appearance_copy(Appearance *orig) +{ + PlanarSurface *spo, *spc; + Appearance *copy = g_new(Appearance, 1); + copy->surface.type = orig->surface.type; + switch (orig->surface.type) { + case Surface_Planar: + spo = &(orig->surface.data.planar); + spc = &(copy->surface.data.planar); + spc->grad = spo->grad; + spc->relief = spo->relief; + spc->bevel = spo->bevel; + if (spo->primary != NULL) + spc->primary = color_new(spo->primary->r, + spo->primary->g, + spo->primary->b); + else spc->primary = NULL; + + if (spo->secondary != NULL) + spc->secondary = color_new(spo->secondary->r, + spo->secondary->g, + spo->secondary->b); + else spc->secondary = NULL; + + if (spo->border_color != NULL) + spc->border_color = color_new(spo->border_color->r, + spo->border_color->g, + spo->border_color->b); + else spc->border_color = NULL; + + spc->interlaced = spo->interlaced; + spc->border = spo->border; + spc->pixel_data = NULL; + break; + } + copy->textures = orig->textures; + copy->texture = NULL; /* XXX FIX ME */ + copy->pixmap = None; + copy->xftdraw = NULL; + return copy; +} + +void appearance_free(Appearance *a) +{ + PlanarSurface *p; + if (a->textures) + g_free(a->texture); + if (a->surface.type == Surface_Planar) { + p = &a->surface.data.planar; + if (p->primary != NULL) color_free(p->primary); + if (p->secondary != NULL) color_free(p->secondary); + if (p->border_color != NULL) color_free(p->border_color); + } + g_free(a); +} diff --git a/render/render.h b/render/render.h new file mode 100644 index 0000000..b79fae5 --- /dev/null +++ b/render/render.h @@ -0,0 +1,145 @@ +#ifndef __render_h +#define __render_h + +#include +#define _XFT_NO_COMPAT_ /* no Xft 1 API */ +#include +#include +#include "color.h" + +#ifdef HAVE_STDINT_H +# include +#else +# ifdef HAVE_SYS_TYPES_H +# include +# endif +#endif + +#ifdef HAVE_STDINT_H +typedef uint32_t pixel32; +typedef uint16_t pixel16; +#else +typedef u_int32_t pixel32; +typedef u_int16_t pixel16; +#endif /* HAVE_STDINT_H */ + +#if (G_ENDIAN == G_BIG_ENDIAN) +#define default_red_shift 0 +#define default_green_shift 8 +#define default_blue_shift 16 +#define endian MSBFirst +#else +#define default_red_shift 16 +#define default_green_shift 8 +#define default_blue_shift 0 +#define endian LSBFirst +#endif /* G_ENDIAN == G_BIG_ENDIAN */ + +typedef enum { + Surface_Planar, + Surface_Nonplanar +} SurfaceType; + +typedef enum { + Flat, + Raised, + Sunken +} ReliefType; + +typedef enum { + Bevel1, + Bevel2 +} BevelType; + +typedef enum { + Background_ParentRelative, + Background_Solid, + Background_Horizontal, + Background_Vertical, + Background_Diagonal, + Background_CrossDiagonal, + Background_PipeCross, + Background_Rectangle, + Background_Pyramid, + Background_Elliptic +} SurfaceColorType; + +typedef enum { + Bitmask, + Text, + RGBA +} TextureType; + +typedef struct PlanarSurface { + SurfaceColorType grad; + ReliefType relief; + BevelType bevel; + color_rgb *primary; + color_rgb *secondary; + color_rgb *border_color; + gboolean interlaced; + gboolean border; + pixel32 *pixel_data; +} PlanarSurface; + +typedef struct NonplanarSurface { + int poo; +} NonplanarSurface; + +typedef union { + PlanarSurface planar; + NonplanarSurface nonplanar; +} SurfaceData; + +typedef struct Surface { + SurfaceType type; + SurfaceColorType colortype; + SurfaceData data; +} Surface; + +typedef struct TextureText { + color_rgb *color; + char *string; +} TextureText; + +typedef struct TextureMask { + color_rgb *color; +} TextureMask; + +typedef struct TextureRGBA { + int poo; +} TextureRGBA; + +typedef union { + TextureRGBA rgba; + TextureText text; + TextureMask mask; +} TextureData; + +typedef struct Texture { + TextureType type; + TextureData data; +} Texture; + +typedef struct Appearance { + Surface surface; + int textures; + Texture *texture; + Pixmap pixmap; + XftDraw *xftdraw; +} Appearance; + +extern Visual *render_visual; +extern int render_depth; +extern Colormap render_colormap; + +void (*paint)(Window win, Appearance *l, int w, int h); + +void render_startup(void); +void init_appearance(Appearance *l); +void x_paint(Window win, Appearance *l, int w, int h); +void render_shutdown(void); +Appearance *appearance_new(SurfaceType type, int numtex); +Appearance *appearance_copy(Appearance *a); +void appearance_free(Appearance *a); +#endif /*__render_h*/ diff --git a/render/test.c b/render/test.c new file mode 100644 index 0000000..114819a --- /dev/null +++ b/render/test.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include +#include "render.h" +#include + +static int x_error_handler(Display * disp, XErrorEvent * error) +{ + char buf[1024]; + XGetErrorText(disp, error->error_code, buf, 1024); + printf("%s\n", buf); + return 0; +} + +Display *ob_display; +int ob_screen; +Window ob_root; + +int main() +{ + Window win; + GC gc; + Pixmap pm; + Appearance *look; + + int grabbed = 0; + Window root; + XGCValues values; + XEvent report; + int h = 500, w = 500, tmp; + XVisualInfo *vi; + int i; + + ob_display = XOpenDisplay(NULL); + XSetErrorHandler(x_error_handler); + ob_screen = DefaultScreen(ob_display); + ob_root = RootWindow(ob_display, ob_screen); + win = + XCreateWindow(ob_display, RootWindow(ob_display, 0) + , 10, 10, w, h, 10, + CopyFromParent, // depth + CopyFromParent, // class + CopyFromParent, // visual + 0, // valuemask + 0); // attributes + XMapWindow(ob_display, win); + XSelectInput(ob_display, win, ExposureMask | StructureNotifyMask); + root = RootWindow (ob_display, DefaultScreen (ob_display)); + render_startup(); + + look = appearance_new(Surface_Planar, 0); + look->surface.data.planar.grad = Background_Solid; + look->surface.data.planar.secondary = color_new(0xFF, 0xFF, 0xFF); + look->surface.data.planar.primary = color_parse("Red"); + look->surface.data.planar.interlaced = FALSE; + if (ob_display == NULL) { + fprintf(stderr, "couldn't connect to X server :0\n"); + return 0; + } + + paint(win, look, 500, 500); + while (1) { + XNextEvent(ob_display, &report); + switch (report.type) { + case Expose: + break; + case ConfigureNotify: + w = report.xconfigure.width; + h = report.xconfigure.height; + paint(win, look, w, h); + printf("confignotify %i:%i\n", w, h); + break; + } + + } + + return 1; +} diff --git a/scripts/historyplacement.py b/scripts/historyplacement.py index cb9fb96..fb1dd35 100644 --- a/scripts/historyplacement.py +++ b/scripts/historyplacement.py @@ -48,17 +48,6 @@ config.add('historyplacement', 'enum', windowplacement.random, options = windowplacement.export_functions) -config.add('historyplacement', - 'confirm_callback', - 'Confirm Placement Callback', - "A function which will be called before attempting to place a " + \ - "window via history. If the function returns true, then an " + \ - "attempt will be made to place the window. If it returns false, " +\ - "the 'Fallback Algorithm' will be directly applied instead. The " +\ - "function must take 1 argument, which will be the callback data " +\ - "which was passed to invoke the window placement.", - 'function', - None) ########################################################################### @@ -152,25 +141,23 @@ def _place(data): if data.client.positionRequested(): return state = _create_state(data) try: - confirm = config.get('historyplacement', 'confirm_callback') - if not confirm or confirm(data): - print "looking for : " + state.appname + " : " + \ - state.appclass + " : " + state.role - - i = _find(data.screen, state) - if i >= 0: - coords = _data[data.screen][i] - print "Found in history ("+str(coords.x)+","+\ - str(coords.y)+")" - if not (config.get('historyplacement', 'dont_duplicate') \ - and coords.placed): - data.client.move(coords.x, coords.y) - coords.placed = 1 - return - else: - print "Already placed another window there" + print "looking for : " + state.appname + " : " + \ + state.appclass + " : " + state.role + + i = _find(data.screen, state) + if i >= 0: + coords = _data[data.screen][i] + print "Found in history ("+str(coords.x)+","+\ + str(coords.y)+")" + if not (config.get('historyplacement', 'dont_duplicate') \ + and coords.placed): + data.client.move(coords.x, coords.y) + coords.placed = 1 + return else: - print "No match in history" + print "Already placed another window there" + else: + print "No match in history" except TypeError: pass fallback = config.get('historyplacement', 'fallback') diff --git a/src/actions.cc b/src/actions.cc index c629006..36b4f11 100644 --- a/src/actions.cc +++ b/src/actions.cc @@ -290,8 +290,8 @@ void Actions::motionHandler(const XMotionEvent &e) // compress changes to a window into a single change XEvent ce; while (XCheckTypedWindowEvent(**otk::display, e.window, e.type, &ce)) { - x_root = e.x_root; - y_root = e.y_root; + x_root = ce.x_root; + y_root = ce.y_root; } int screen; diff --git a/themes/.cvsignore b/themes/.cvsignore new file mode 100644 index 0000000..282522d --- /dev/null +++ b/themes/.cvsignore @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff --git a/themes/Makefile.am b/themes/Makefile.am new file mode 100644 index 0000000..db3bc8d --- /dev/null +++ b/themes/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = openbox diff --git a/themes/openbox/.cvsignore b/themes/openbox/.cvsignore new file mode 100644 index 0000000..282522d --- /dev/null +++ b/themes/openbox/.cvsignore @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff --git a/themes/openbox/Makefile.am b/themes/openbox/Makefile.am new file mode 100644 index 0000000..f2543bf --- /dev/null +++ b/themes/openbox/Makefile.am @@ -0,0 +1,15 @@ +themedir=$(datadir)/openbox/themes/openbox + +theme_DATA = artwiz bbs bluebox cthulhain deep fieron fieron2 flux frobozz \ + frobust mbdtex miklos nyz nyzclone ob20 operation outcomes paper \ + purplehaaze shade steelblue steelblue2 the_orange trisb twice \ + warp-xp + +EXTRA_DIST = $(theme_DATA) + +MAINTAINERCLEANFILES = Makefile.in + +distclean-local: + $(RM) *\~ .\#* +uninstall-am: + -rmdir -p $(DESTDIR)$(themedir) diff --git a/data/styles/artwiz b/themes/openbox/artwiz similarity index 100% rename from data/styles/artwiz rename to themes/openbox/artwiz diff --git a/data/styles/bbs b/themes/openbox/bbs similarity index 100% rename from data/styles/bbs rename to themes/openbox/bbs diff --git a/data/styles/bluebox b/themes/openbox/bluebox similarity index 100% rename from data/styles/bluebox rename to themes/openbox/bluebox diff --git a/data/styles/cthulhain b/themes/openbox/cthulhain similarity index 100% rename from data/styles/cthulhain rename to themes/openbox/cthulhain diff --git a/data/styles/deep b/themes/openbox/deep similarity index 100% rename from data/styles/deep rename to themes/openbox/deep diff --git a/data/styles/fieron b/themes/openbox/fieron similarity index 100% rename from data/styles/fieron rename to themes/openbox/fieron diff --git a/data/styles/fieron2 b/themes/openbox/fieron2 similarity index 100% rename from data/styles/fieron2 rename to themes/openbox/fieron2 diff --git a/data/styles/flux b/themes/openbox/flux similarity index 100% rename from data/styles/flux rename to themes/openbox/flux diff --git a/data/styles/frobozz b/themes/openbox/frobozz similarity index 100% rename from data/styles/frobozz rename to themes/openbox/frobozz diff --git a/data/styles/frobust b/themes/openbox/frobust similarity index 100% rename from data/styles/frobust rename to themes/openbox/frobust diff --git a/data/styles/mbdtex b/themes/openbox/mbdtex similarity index 100% rename from data/styles/mbdtex rename to themes/openbox/mbdtex diff --git a/data/styles/miklos b/themes/openbox/miklos similarity index 100% rename from data/styles/miklos rename to themes/openbox/miklos diff --git a/data/styles/nyz b/themes/openbox/nyz similarity index 100% rename from data/styles/nyz rename to themes/openbox/nyz diff --git a/data/styles/nyzclone b/themes/openbox/nyzclone similarity index 100% rename from data/styles/nyzclone rename to themes/openbox/nyzclone diff --git a/data/styles/ob20 b/themes/openbox/ob20 similarity index 100% rename from data/styles/ob20 rename to themes/openbox/ob20 diff --git a/data/styles/operation b/themes/openbox/operation similarity index 100% rename from data/styles/operation rename to themes/openbox/operation diff --git a/data/styles/outcomes b/themes/openbox/outcomes similarity index 100% rename from data/styles/outcomes rename to themes/openbox/outcomes diff --git a/data/styles/paper b/themes/openbox/paper similarity index 100% rename from data/styles/paper rename to themes/openbox/paper diff --git a/data/styles/purplehaaze b/themes/openbox/purplehaaze similarity index 100% rename from data/styles/purplehaaze rename to themes/openbox/purplehaaze diff --git a/data/styles/shade b/themes/openbox/shade similarity index 100% rename from data/styles/shade rename to themes/openbox/shade diff --git a/data/styles/steelblue b/themes/openbox/steelblue similarity index 100% rename from data/styles/steelblue rename to themes/openbox/steelblue diff --git a/data/styles/steelblue2 b/themes/openbox/steelblue2 similarity index 100% rename from data/styles/steelblue2 rename to themes/openbox/steelblue2 diff --git a/data/styles/the_orange b/themes/openbox/the_orange similarity index 100% rename from data/styles/the_orange rename to themes/openbox/the_orange diff --git a/data/styles/trisb b/themes/openbox/trisb similarity index 100% rename from data/styles/trisb rename to themes/openbox/trisb diff --git a/data/styles/twice b/themes/openbox/twice similarity index 100% rename from data/styles/twice rename to themes/openbox/twice diff --git a/data/styles/warp-xp b/themes/openbox/warp-xp similarity index 100% rename from data/styles/warp-xp rename to themes/openbox/warp-xp