summaryrefslogtreecommitdiff
path: root/openbox
diff options
context:
space:
mode:
authorDana Jansens <danakj@orodu.net>2003-03-16 21:11:39 +0000
committerDana Jansens <danakj@orodu.net>2003-03-16 21:11:39 +0000
commitf8a47de5ec444c452093371e3db16857eb39a490 (patch)
tree31db2567842d98232775f9980f7a8d2586c0ac71 /openbox
parent8ba0586bcbdc7fe9648f1063812126d71a041670 (diff)
merge the C branch into HEAD
Diffstat (limited to 'openbox')
-rw-r--r--openbox/.cvsignore5
-rw-r--r--openbox/Makefile.am33
-rw-r--r--openbox/client.c1896
-rw-r--r--openbox/client.h448
-rw-r--r--openbox/clientwrap.c1029
-rw-r--r--openbox/clientwrap.h19
-rw-r--r--openbox/engine.c84
-rw-r--r--openbox/engine.h27
-rw-r--r--openbox/event.c598
-rw-r--r--openbox/event.h12
-rw-r--r--openbox/extensions.c34
-rw-r--r--openbox/extensions.h33
-rw-r--r--openbox/focus.c57
-rw-r--r--openbox/focus.h20
-rw-r--r--openbox/frame.c105
-rw-r--r--openbox/frame.h31
-rw-r--r--openbox/geom.h49
-rw-r--r--openbox/gettext.h73
-rw-r--r--openbox/hooks.c295
-rw-r--r--openbox/hooks.h56
-rw-r--r--openbox/keyboard.c696
-rw-r--r--openbox/keyboard.h13
-rw-r--r--openbox/openbox.c210
-rw-r--r--openbox/openbox.h44
-rw-r--r--openbox/openboxwrap.c502
-rw-r--r--openbox/openboxwrap.h17
-rw-r--r--openbox/pointer.c729
-rw-r--r--openbox/pointer.h14
-rw-r--r--openbox/prop.c288
-rw-r--r--openbox/prop.h214
-rw-r--r--openbox/python.c60
-rw-r--r--openbox/python.h10
-rw-r--r--openbox/screen.c526
-rw-r--r--openbox/screen.h72
-rw-r--r--openbox/stacking.c116
-rw-r--r--openbox/stacking.h39
-rw-r--r--openbox/themerc.c156
-rw-r--r--openbox/themerc.h12
-rw-r--r--openbox/timer.c127
-rw-r--r--openbox/timer.h38
-rw-r--r--openbox/xerror.c32
-rw-r--r--openbox/xerror.h11
42 files changed, 8830 insertions, 0 deletions
diff --git a/openbox/.cvsignore b/openbox/.cvsignore
new file mode 100644
index 00000000..4d46e8cf
--- /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 00000000..5eff8fdd
--- /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 00000000..40c61208
--- /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 <X11/Xutil.h>
+
+/*! 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 00000000..0f9eaa4b
--- /dev/null
+++ b/openbox/client.h
@@ -0,0 +1,448 @@
+#ifndef __client_h
+#define __client_h
+
+#include "geom.h"
+#include "stacking.h"
+#include <glib.h>
+#include <X11/Xlib.h>
+
+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.<br>
+ 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 00000000..fe3783ed
--- /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 <glib.h>
+
+/***************************************************************************
+
+ 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, &current))
+ 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 00000000..b20ca914
--- /dev/null
+++ b/openbox/clientwrap.h
@@ -0,0 +1,19 @@
+#ifndef __clientwrap_h
+#define __clientwrap_h
+
+#include <Python.h>
+
+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 00000000..3457da18
--- /dev/null
+++ b/openbox/engine.c
@@ -0,0 +1,84 @@
+#include "engine.h"
+
+#include <glib.h>
+#include <gmodule.h>
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#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 00000000..067f02fb
--- /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 00000000..00b2857c
--- /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 <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <X11/Xatom.h>
+
+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 00000000..9153116e
--- /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 00000000..3fe43194
--- /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 00000000..3c11076a
--- /dev/null
+++ b/openbox/extensions.h
@@ -0,0 +1,33 @@
+#ifndef __extensions_h
+#define __extensions_h
+
+#include <X11/Xlib.h>
+#ifdef XKB
+#include <X11/XKBlib.h>
+#endif
+#ifdef SHAPE
+#include <X11/extensions/shape.h>
+#endif
+#ifdef XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+#include <glib.h>
+
+/*! 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 00000000..3c7b635b
--- /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 <X11/Xlib.h>
+
+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 00000000..9db52026
--- /dev/null
+++ b/openbox/focus.h
@@ -0,0 +1,20 @@
+#ifndef __focus_h
+#define __focus_h
+
+#include <X11/Xlib.h>
+
+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 00000000..eee01e48
--- /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 00000000..ec530934
--- /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 00000000..89b69b12
--- /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 00000000..7bbc6a94
--- /dev/null
+++ b/openbox/gettext.h
@@ -0,0 +1,73 @@
+/* Convenience header for conditional use of GNU <libintl.h>.
+ 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 <libintl.h>
+
+#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 <locale.h> a NOP. We don't include <libintl.h>
+ as well because people using "gettext.h" will not include <libintl.h>,
+ and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
+ is OK. */
+#if defined(__sun)
+# include <locale.h>
+#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 00000000..35d8d694
--- /dev/null
+++ b/openbox/hooks.c
@@ -0,0 +1,295 @@
+#include "hooks.h"
+#include <glib.h>
+
+/*
+ *
+ * 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 00000000..3211d7cf
--- /dev/null
+++ b/openbox/hooks.h
@@ -0,0 +1,56 @@
+#ifndef __hooks_h
+#define __hooks_h
+
+#include "clientwrap.h"
+#include <Python.h>
+
+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 00000000..87cd5036
--- /dev/null
+++ b/openbox/keyboard.c
@@ -0,0 +1,696 @@
+#include "focus.h"
+#include "openbox.h"
+#include "keyboard.h"
+#include "clientwrap.h"
+
+#include <Python.h>
+#include <glib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#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 00000000..464606b2
--- /dev/null
+++ b/openbox/keyboard.h
@@ -0,0 +1,13 @@
+#ifndef __keyboard_h
+#define __keyboard_h
+
+#include <glib.h>
+
+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 00000000..bd7585ad
--- /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 <fcntl.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/types.h>
+# include <sys/wait.h>
+#endif
+#ifdef HAVE_LOCALE_H
+# include <locale.h>
+#endif
+
+#include <X11/cursorfont.h>
+
+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 00000000..f8797d7f
--- /dev/null
+++ b/openbox/openbox.h
@@ -0,0 +1,44 @@
+#ifndef __openbox_h
+#define __openbox_h
+
+#include <glib.h>
+#include <X11/Xlib.h>
+
+/*! 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 00000000..c73e725a
--- /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 00000000..8e54f2d5
--- /dev/null
+++ b/openbox/openboxwrap.h
@@ -0,0 +1,17 @@
+#ifndef __openboxwrap_h
+#define __openboxwrap_h
+
+#include <Python.h>
+
+/* 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 00000000..00720f3b
--- /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 <glib.h>
+#include <Python.h>
+#include <structmember.h> /* for PyMemberDef stuff */
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#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 00000000..2f068972
--- /dev/null
+++ b/openbox/pointer.h
@@ -0,0 +1,14 @@
+#ifndef __pointer_h
+#define __pointer_h
+
+#include "client.h"
+#include <glib.h>
+
+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 00000000..9a448500
--- /dev/null
+++ b/openbox/prop.c
@@ -0,0 +1,288 @@
+#include "prop.h"
+#include "openbox.h"
+#include <X11/Xatom.h>
+
+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 00000000..c2de3b20
--- /dev/null
+++ b/openbox/prop.h
@@ -0,0 +1,214 @@
+#ifndef __atoms_h
+#define __atoms_h
+
+#include <X11/Xlib.h>
+#include <glib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#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 00000000..52f19f3f
--- /dev/null
+++ b/openbox/python.c
@@ -0,0 +1,60 @@
+#include <Python.h>
+#include <glib.h>
+
+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 00000000..90366315
--- /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 00000000..d077c495
--- /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 <X11/Xlib.h>
+#ifdef HAVE_UNISTD_H
+# include <sys/types.h>
+# include <unistd.h>
+#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<Client*>::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 00000000..1c0a4f77
--- /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 00000000..081bde93
--- /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 <glib.h>
+
+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 00000000..6286181c
--- /dev/null
+++ b/openbox/stacking.h
@@ -0,0 +1,39 @@
+#ifndef __stacking_h
+#define __stacking_h
+
+#include <glib.h>
+
+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.<br>
+ 1) raiseWindow can be called after changing a Client's stack layer, and
+ the list will be reorganized properly.<br>
+ 2) raiseWindow guarantees that XRestackWindows() will <i>always</i> 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 00000000..7f587afe
--- /dev/null
+++ b/openbox/themerc.c
@@ -0,0 +1,156 @@
+#include <glib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#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 00000000..89e4b072
--- /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 00000000..0cec366f
--- /dev/null
+++ b/openbox/timer.c
@@ -0,0 +1,127 @@
+#include "timer.h"
+
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#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 00000000..da6c8642
--- /dev/null
+++ b/openbox/timer.h
@@ -0,0 +1,38 @@
+#ifndef __timer_h
+#define __timer_h
+
+#include <glib.h>
+
+/*! 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 00000000..49a795f8
--- /dev/null
+++ b/openbox/xerror.c
@@ -0,0 +1,32 @@
+#include "openbox.h"
+#include <glib.h>
+#include <X11/Xlib.h>
+
+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 00000000..74b236fb
--- /dev/null
+++ b/openbox/xerror.h
@@ -0,0 +1,11 @@
+#ifndef __xerror_h
+#define __xerror_h
+
+#include <X11/Xlib.h>
+#include <glib.h>
+
+int xerror_handler(Display *, XErrorEvent *);
+
+void xerror_set_ignore(gboolean ignore);
+
+#endif