From f8a47de5ec444c452093371e3db16857eb39a490 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 16 Mar 2003 21:11:39 +0000 Subject: merge the C branch into HEAD --- openbox/.cvsignore | 5 + openbox/Makefile.am | 33 + openbox/client.c | 1896 +++++++++++++++++++++++++++++++++++++++++++++++++ openbox/client.h | 448 ++++++++++++ openbox/clientwrap.c | 1029 +++++++++++++++++++++++++++ openbox/clientwrap.h | 19 + openbox/engine.c | 84 +++ openbox/engine.h | 27 + openbox/event.c | 598 ++++++++++++++++ openbox/event.h | 12 + openbox/extensions.c | 34 + openbox/extensions.h | 33 + openbox/focus.c | 57 ++ openbox/focus.h | 20 + openbox/frame.c | 105 +++ openbox/frame.h | 31 + openbox/geom.h | 49 ++ openbox/gettext.h | 73 ++ openbox/hooks.c | 295 ++++++++ openbox/hooks.h | 56 ++ openbox/keyboard.c | 696 ++++++++++++++++++ openbox/keyboard.h | 13 + openbox/openbox.c | 210 ++++++ openbox/openbox.h | 44 ++ openbox/openboxwrap.c | 502 +++++++++++++ openbox/openboxwrap.h | 17 + openbox/pointer.c | 729 +++++++++++++++++++ openbox/pointer.h | 14 + openbox/prop.c | 288 ++++++++ openbox/prop.h | 214 ++++++ openbox/python.c | 60 ++ openbox/python.h | 10 + openbox/screen.c | 526 ++++++++++++++ openbox/screen.h | 72 ++ openbox/stacking.c | 116 +++ openbox/stacking.h | 39 + openbox/themerc.c | 156 ++++ openbox/themerc.h | 12 + openbox/timer.c | 127 ++++ openbox/timer.h | 38 + openbox/xerror.c | 32 + openbox/xerror.h | 11 + 42 files changed, 8830 insertions(+) create mode 100644 openbox/.cvsignore create mode 100644 openbox/Makefile.am create mode 100644 openbox/client.c create mode 100644 openbox/client.h create mode 100644 openbox/clientwrap.c create mode 100644 openbox/clientwrap.h create mode 100644 openbox/engine.c create mode 100644 openbox/engine.h create mode 100644 openbox/event.c create mode 100644 openbox/event.h create mode 100644 openbox/extensions.c create mode 100644 openbox/extensions.h create mode 100644 openbox/focus.c create mode 100644 openbox/focus.h create mode 100644 openbox/frame.c create mode 100644 openbox/frame.h create mode 100644 openbox/geom.h create mode 100644 openbox/gettext.h create mode 100644 openbox/hooks.c create mode 100644 openbox/hooks.h create mode 100644 openbox/keyboard.c create mode 100644 openbox/keyboard.h create mode 100644 openbox/openbox.c create mode 100644 openbox/openbox.h create mode 100644 openbox/openboxwrap.c create mode 100644 openbox/openboxwrap.h create mode 100644 openbox/pointer.c create mode 100644 openbox/pointer.h create mode 100644 openbox/prop.c create mode 100644 openbox/prop.h create mode 100644 openbox/python.c create mode 100644 openbox/python.h create mode 100644 openbox/screen.c create mode 100644 openbox/screen.h create mode 100644 openbox/stacking.c create mode 100644 openbox/stacking.h create mode 100644 openbox/themerc.c create mode 100644 openbox/themerc.h create mode 100644 openbox/timer.c create mode 100644 openbox/timer.h create mode 100644 openbox/xerror.c create mode 100644 openbox/xerror.h (limited to 'openbox') 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 + +/*! 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 +#include + +struct ClientWrap; +struct Frame; + + +/*! Holds an icon in ARGB format */ +typedef struct Icon { + unsigned long w, h; + unsigned long *data; +} Icon; + +/*! The MWM Hints as retrieved from the window property + This structure only contains 3 elements, even though the Motif 2.0 + structure contains 5. We only use the first 3, so that is all gets + defined. +*/ +typedef struct MwmHints { + /*! A bitmask of Client::MwmFlags values */ + unsigned long flags; + /*! A bitmask of Client::MwmFunctions values */ + unsigned long functions; + /*! A bitmask of Client::MwmDecorations values */ + unsigned long decorations; +} MwmHints; +/*! The number of elements in the Client::MwmHints struct */ +#define MWM_ELEMENTS 3 + +/*! Possible flags for MWM Hints (defined by Motif 2.0) */ +typedef enum { + MwmFlag_Functions = 1 << 0, /*!< The MMW Hints define funcs */ + MwmFlag_Decorations = 1 << 1 /*!< The MWM Hints define decor */ +} MwmFlags; + +/*! Possible functions for MWM Hints (defined by Motif 2.0) */ +typedef enum { + MwmFunc_All = 1 << 0, /*!< All functions */ + MwmFunc_Resize = 1 << 1, /*!< Allow resizing */ + MwmFunc_Move = 1 << 2, /*!< Allow moving */ + MwmFunc_Iconify = 1 << 3, /*!< Allow to be iconfied */ + MwmFunc_Maximize = 1 << 4 /*!< Allow to be maximized */ + /*MwmFunc_Close = 1 << 5 /!< Allow to be closed */ +} MwmFunctions; + +/*! Possible decorations for MWM Hints (defined by Motif 2.0) */ +typedef enum { + MwmDecor_All = 1 << 0, /*!< All decorations */ + MwmDecor_Border = 1 << 1, /*!< Show a border */ + MwmDecor_Handle = 1 << 2, /*!< Show a handle (bottom) */ + MwmDecor_Title = 1 << 3, /*!< Show a titlebar */ + /*MwmDecor_Menu = 1 << 4, /!< Show a menu */ + MwmDecor_Iconify = 1 << 5, /*!< Show an iconify button */ + MwmDecor_Maximize = 1 << 6 /*!< Show a maximize button */ +} MemDecorations; + +/*! Corners of the client window, used for anchor positions */ +typedef enum { + Corner_TopLeft, + Corner_TopRight, + Corner_BottomLeft, + Corner_BottomRight +} Corner; + +/*! Possible window types */ +typedef enum { + Type_Desktop, /*!< A desktop (bottom-most window) */ + Type_Dock, /*!< A dock bar/panel window */ + Type_Toolbar, /*!< A toolbar window, pulled off an app */ + Type_Menu, /*!< An unpinned menu from an app */ + Type_Utility, /*!< A small utility window such as a palette */ + Type_Splash, /*!< A splash screen window */ + Type_Dialog, /*!< A dialog window */ + Type_Normal /*!< A normal application window */ +} WindowType; + +/*! The things the user can do to the client window */ +typedef enum { + Func_Resize = 1 << 0, /*!< Allow resizing */ + Func_Move = 1 << 1, /*!< Allow moving */ + Func_Iconify = 1 << 2, /*!< Allow to be iconified */ + Func_Maximize = 1 << 3, /*!< Allow to be maximized */ + Func_Shade = 1 << 4, /*!< Allow to be shaded */ + Func_Fullscreen = 1 << 5, /*!< Allow to be made fullscreen */ + Func_Close = 1 << 6 /*!< Allow to be closed */ +} Function; + +/*! The decorations the client window wants to be displayed on it */ +typedef enum { + Decor_Titlebar = 1 << 0, /*!< Display a titlebar */ + Decor_Handle = 1 << 1, /*!< Display a handle (bottom) */ + Decor_Border = 1 << 2, /*!< Display a border */ + Decor_Icon = 1 << 3, /*!< Display the window's icon */ + Decor_Iconify = 1 << 4, /*!< Display an iconify button */ + Decor_Maximize = 1 << 5, /*!< Display a maximize button */ + /*! Display a button to toggle the window's placement on + all desktops */ + Decor_AllDesktops = 1 << 6, + Decor_Close = 1 << 7 /*!< Display a close button */ +} Decoration; + + +typedef struct Client { + Window window; + + struct Frame *frame; + + /*! The number of unmap events to ignore on the window */ + int ignore_unmaps; + + /*! The id of the group the window belongs to */ + Window group; + /*! Whether or not the client is a transient window. This is guaranteed to + be TRUE if transient_for != NULL, but not guaranteed to be FALSE if + transient_for == NULL. */ + gboolean transient; + /*! The client which this client is a transient (child) for */ + struct Client *transient_for; + /*! The clients which are transients (children) of this client */ + GSList *transients; + /*! The desktop on which the window resides (0xffffffff for all + desktops) */ + unsigned int desktop; + + /*! Normal window title */ + gchar *title; + /*! Window title when iconified */ + gchar *icon_title; + + /*! The application that created the window */ + gchar *res_name; + /*! The class of the window, can used for grouping */ + gchar *res_class; + /*! The specified role of the window, used for identification */ + gchar *role; + + /*! The type of window (what its function is) */ + WindowType type; + + /*! Position and size of the window + This will not always be the actual position of the window on screen, it + is, rather, the position requested by the client, to which the window's + gravity is applied. + */ + Rect area; + + /*! The window's strut + The strut defines areas of the screen that are marked off-bounds for + window placement. In theory, where this window exists. + */ + Strut strut; + + /*! The logical size of the window + The "logical" size of the window is refers to the user's perception of + the size of the window, and is the value that should be displayed to the + user. For example, with xterms, this value it the number of characters + being displayed in the terminal, instead of the number of pixels. + */ + Size logical_size; + + /*! Width of the border on the window. + The window manager will set this to 0 while the window is being managed, + but needs to restore it afterwards, so it is saved here. + */ + guint border_width; + + /*! The minimum aspect ratio the client window can be sized to. + A value of 0 means this is ignored. + */ + float min_ratio; + /*! The maximum aspect ratio the client window can be sized to. + A value of 0 means this is ignored. + */ + float max_ratio; + + /*! The minimum size of the client window + If the min is > the max, then the window is not resizable + */ + Size min_size; + /*! The maximum size of the client window + If the min is > the max, then the window is not resizable + */ + Size max_size; + /*! The size of increments to resize the client window by */ + Size size_inc; + /*! The base size of the client window + This value should be subtracted from the window's actual size when + displaying its size to the user, or working with its min/max size + */ + Size base_size; + + /*! Window decoration and functionality hints */ + MwmHints mwmhints; + + /*! Where to place the decorated window in relation to the undecorated + window */ + int gravity; + + /*! The state of the window, one of WithdrawnState, IconicState, or + NormalState */ + long wmstate; + + /*! True if the client supports the delete_window protocol */ + gboolean delete_window; + + /*! Was the window's position requested by the application? if not, we + should place the window ourselves when it first appears */ + gboolean positioned; + + /*! Can the window receive input focus? */ + gboolean can_focus; + /*! Urgency flag */ + gboolean urgent; + /*! Notify the window when it receives focus? */ + gboolean focus_notify; + /*! Does the client window have the input focus? */ + gboolean focused; + + /*! The window uses shape extension to be non-rectangular? */ + gboolean shaped; + + /*! The window is modal, so it must be processed before any windows it is + related to can be focused */ + gboolean modal; + /*! Only the window's titlebar is displayed */ + gboolean shaded; + /*! The window is iconified */ + gboolean iconic; + /*! The window is maximized to fill the screen vertically */ + gboolean max_vert; + /*! The window is maximized to fill the screen horizontally */ + gboolean max_horz; + /*! The window should not be displayed by pagers */ + gboolean skip_pager; + /*! The window should not be displayed by taskbars */ + gboolean skip_taskbar; + /*! The window is a 'fullscreen' window, and should be on top of all + others */ + gboolean fullscreen; + /*! The window should be on top of other windows of the same type. + above takes priority over below. */ + gboolean above; + /*! The window should be underneath other windows of the same type. + above takes priority over below. */ + gboolean below; + + /*! The layer in which the window will be stacked, windows in lower layers + are always below windows in higher layers. */ + StackLayer layer; + + /*! A bitmask of values in the Decoration enum + The values in the variable are the decorations that the client wants to + be displayed around it. + */ + int decorations; + + /*! A bitmask of values in the Decoration enum. + Specifies the decorations that should NOT be displayed on the client. + */ + int disabled_decorations; + + /*! A bitmask of values in the Function enum + The values in the variable specify the ways in which the user is allowed + to modify this window. + */ + int functions; + + /*! Icons for the client as specified on the client window */ + Icon *icons; + /*! The number of icons in icons */ + int nicons; + + /*! The icon for the client specified in the WMHints or the KWM hints */ + Pixmap pixmap_icon; + /*! The mask for the pixmap_icon, or None if its not masked */ + Pixmap pixmap_icon_mask; + + /* The instance of the wrapper class if one exists */ + struct ClientWrap *wrap; +} Client; + +extern GSList *client_list; +extern GHashTable *client_map; + +void client_startup(); +void client_shutdown(); + +/*! Manages all existing windows */ +void client_manage_all(); +/*! Manages a given window */ +void client_manage(Window win); +/*! Unmanages all managed windows */ +void client_unmanage_all(); +/*! Unmanages a given client */ +void client_unmanage(Client *client); + +/*! Sets the client list on the root window from the client_list */ +void client_set_list(); + +/*! Reapplies the maximized state to the window + Use this to make the window readjust its maximized size to new + surroundings (struts, etc). */ +void client_remaximize(Client *self); + +/*! Shows the window if it should be shown, or hides it + Used when changing desktops, the window's state, etc. */ +void client_showhide(Client *self, gboolean firehook); + +/*! Returns if the window should be treated as a normal window. + Some windows (desktops, docks, splash screens) have special rules applied + to them in a number of places regarding focus or user interaction. */ +gboolean client_normal(Client *self); + +/*! Move and/or resize the window. + This also maintains things like the client's minsize, and size increments. + @param anchor The corner to keep in the same position when resizing. + @param x The x coordiante of the new position for the client. + @param y The y coordiante of the new position for the client. + @param w The width component of the new size for the client. + @param h The height component of the new size for the client. + @param user Specifies whether this is a user-requested change or a + program requested change. For program requested changes, the + constraints are not checked. + @param final If user is true, then this should specify if this is a final + configuration. e.g. Final should be FALSE if doing an + interactive move/resize, and then be TRUE for the last call + only. +*/ +void client_configure(Client *self, Corner anchor, int x, int y, int w, int h, + gboolean user, gboolean final); + +/*! Fullscreen's or unfullscreen's the client window + @param fs true if the window should be made fullscreen; false if it should + be returned to normal state. + @param savearea true to have the client's current size and position saved; + otherwise, they are not. You should not save when mapping a + new window that is set to fullscreen. This has no effect + when restoring a window from fullscreen. +*/ +void client_fullscreen(Client *self, gboolean fs, gboolean savearea); + +/*! Iconifies or uniconifies the client window + @param iconic true if the window should be iconified; false if it should be + restored. + @param curdesk If iconic is FALSE, then this determines if the window will + be uniconified to the current viewable desktop (true) or to + its previous desktop (false) +*/ +void client_iconify(Client *self, gboolean iconic, gboolean curdesk); + +/*! Maximize or unmaximize the client window + @param max true if the window should be maximized; false if it should be + returned to normal size. + @param dir 0 to set both horz and vert, 1 to set horz, 2 to set vert. + @param savearea true to have the client's current size and position saved; + otherwise, they are not. You should not save when mapping a + new window that is set to fullscreen. This has no effect + when unmaximizing a window. +*/ +void client_maximize(Client *self, gboolean max, int dir, + gboolean savearea); + +/*! Shades or unshades the client window + @param shade true if the window should be shaded; false if it should be + unshaded. +*/ +void client_shade(Client *self, gboolean shade); + +/*! Request the client to close its window. */ +void client_close(Client *self); + +/*! Sends the window to the specified desktop */ +void client_set_desktop(Client *self, unsigned int target); + +/*! Return a modal child of the client window + @return A modal child of the client window, or 0 if none was found. +*/ +Client *client_find_modal_child(Client *self); + +/*! Validate client, by making sure no Destroy or Unmap events exist in + the event queue for the window. + @return true if the client is valid; false if the client has already + been unmapped/destroyed, and so is invalid. +*/ +gboolean client_validate(Client *self); + +/*! Sets the wm_state to the specified value */ +void client_set_wm_state(Client *self, long state); + +/*! Adjusts the window's net_state + This should not be called as part of the window mapping process! It is for + use when updating the state post-mapping.
+ client_apply_startup_state is used to do the same things during the mapping + process. +*/ +void client_set_state(Client *self, Atom action, long data1, long data2); + +/*! Attempt to focus the client window */ +gboolean client_focus(Client *self); + +/*! Remove focus from the client window */ +void client_unfocus(Client *self); + +/*! Calculates the stacking layer for the client window */ +void client_calc_layer(Client *self); + +/*! Updates the window's transient status, and any parents of it */ +void client_update_transient_for(Client *self); +/*! Update the protocols that the window supports and adjusts things if they + change */ +void client_update_protocols(Client *self); +/*! Updates the WMNormalHints and adjusts things if they change */ +void client_update_normal_hints(Client *self); + +/*! Updates the WMHints and adjusts things if they change + @param initstate Whether to read the initial_state property from the + WMHints. This should only be used during the mapping + process. +*/ +void client_update_wmhints(Client *self); +/*! Updates the window's title */ +void client_update_title(Client *self); +/*! Updates the window's icon title */ +void client_update_icon_title(Client *self); +/*! Updates the window's application name and class */ +void client_update_class(Client *self); +/*! Updates the strut for the client */ +void client_update_strut(Client *self); +/*! Updates the window's icons */ +void client_update_icons(Client *self); +/*! Updates the window's kwm icon */ +void client_update_kwm_icon(Client *self); + +/*! Set up what decor should be shown on the window and what functions should + be allowed (Client::decorations and Client::functions). + This also updates the NET_WM_ALLOWED_ACTIONS hint. +*/ +void client_setup_decor_and_functions(Client *self); + +/*! Retrieves the window's type and sets Client->type */ +void client_get_type(Client *self); + +#endif diff --git a/openbox/clientwrap.c b/openbox/clientwrap.c new file mode 100644 index 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 + +/*************************************************************************** + + Define the type 'ClientWrap' + + ***************************************************************************/ + +#define IS_CWRAP(v) ((v)->ob_type == &ClientWrapType) +#define IS_VALID_CWRAP(v) ((v)->client != NULL) +#define CHECK_CWRAP(self, funcname) { \ + if (!IS_CWRAP(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'Client' " \ + "object"); \ + return NULL; \ + } \ + if (!IS_VALID_CWRAP(self)) { \ + PyErr_SetString(PyExc_ValueError, \ + "This 'Client' is wrapping a client which no longer "\ + "exists."); \ + return NULL; \ + } \ +} + + +staticforward PyTypeObject ClientWrapType; + +/*************************************************************************** + + Attribute methods + + ***************************************************************************/ + +static PyObject *cwrap_valid(ClientWrap *self, PyObject *args) +{ + if (!IS_CWRAP(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'valid' requires a 'Client' object"); + return NULL; + } + return PyInt_FromLong(self->client != NULL); +} + +static PyObject *cwrap_title(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "title"); + if (!PyArg_ParseTuple(args, ":title")) + return NULL; + return PyString_FromString(self->client->title); +} + +static PyObject *cwrap_setTitle(ClientWrap *self, PyObject *args) +{ + char *title; + + CHECK_CWRAP(self, "setTitle"); + if (!PyArg_ParseTuple(args, "s:setTitle", &title)) + return NULL; + + PROP_SETS(self->client->window, net_wm_name, utf8, title); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_iconTitle(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "iconTitle"); + if (!PyArg_ParseTuple(args, ":iconTitle")) + return NULL; + return PyString_FromString(self->client->icon_title); +} + +static PyObject *cwrap_setIconTitle(ClientWrap *self, PyObject *args) +{ + char *title; + + CHECK_CWRAP(self, "setIconTitle"); + if (!PyArg_ParseTuple(args, "s:setIconTitle", &title)) + return NULL; + + PROP_SETS(self->client->window, net_wm_icon_name, utf8, title); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_desktop(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "desktop"); + if (!PyArg_ParseTuple(args, ":desktop")) + return NULL; + return PyInt_FromLong(self->client->desktop); +} + +static PyObject *cwrap_setDesktop(ClientWrap *self, PyObject *args) +{ + int desktop; + CHECK_CWRAP(self, "setDesktop"); + if (!PyArg_ParseTuple(args, "i:setDesktop", &desktop)) + return NULL; + client_set_desktop(self->client, desktop); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_resName(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "resName"); + if (!PyArg_ParseTuple(args, ":resName")) + return NULL; + return PyString_FromString(self->client->res_name); +} + +static PyObject *cwrap_resClass(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "resClass"); + if (!PyArg_ParseTuple(args, ":resClass")) + return NULL; + return PyString_FromString(self->client->res_class); +} + +static PyObject *cwrap_role(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "role"); + if (!PyArg_ParseTuple(args, ":role")) + return NULL; + return PyString_FromString(self->client->role); +} + +static PyObject *cwrap_transient(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "transient"); + if (!PyArg_ParseTuple(args, ":transient")) + return NULL; + return PyInt_FromLong(!!self->client->transient); +} + +static PyObject *cwrap_transientFor(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "transientFor"); + if (!PyArg_ParseTuple(args, ":transientFor")) + return NULL; + if (self->client->transient_for != NULL) + return clientwrap_new(self->client->transient_for); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_transients(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + GSList *it; + guint i, s; + + CHECK_CWRAP(self, "transients"); + if (!PyArg_ParseTuple(args, ":transients")) + return NULL; + s = g_slist_length(self->client->transients); + tuple = PyTuple_New(s); + for (i = 0, it = self->client->transients; i < s; ++i, it = it->next) + PyTuple_SET_ITEM(tuple, i, clientwrap_new(it->data)); + return tuple; +} + +static PyObject *cwrap_type(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "type"); + if (!PyArg_ParseTuple(args, ":type")) + return NULL; + return PyInt_FromLong(self->client->type); +} + +static PyObject *cwrap_normal(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "normal"); + if (!PyArg_ParseTuple(args, ":normal")) + return NULL; + return PyInt_FromLong(!!client_normal(self->client)); +} + +static PyObject *cwrap_area(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "area"); + if (!PyArg_ParseTuple(args, ":area")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->frame->area.x)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->frame->area.y)); + PyTuple_SET_ITEM(tuple, 2, + PyInt_FromLong(self->client->frame->area.width)); + PyTuple_SET_ITEM(tuple, 3, + PyInt_FromLong(self->client->frame->area.height)); + return tuple; +} + +static PyObject *cwrap_setArea(ClientWrap *self, PyObject *args) +{ + int x, y, w, h, final = TRUE; + + CHECK_CWRAP(self, "setArea"); + if (!PyArg_ParseTuple(args, "(iiii)|i:setArea", &x, &y, &w, &h, &final)) + return NULL; + + frame_frame_gravity(self->client->frame, &x, &y); + w -= self->client->frame->size.left + self->client->frame->size.right; + h -= self->client->frame->size.top + self->client->frame->size.bottom; + client_configure(self->client, Corner_TopLeft, x, y, w, h, TRUE, final); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_clientArea(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "clientArea"); + if (!PyArg_ParseTuple(args, ":clientArea")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->area.x)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->area.y)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(self->client->area.width)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(self->client->area.height)); + return tuple; +} + +static PyObject *cwrap_setClientArea(ClientWrap *self, PyObject *args) +{ + int x, y, w, h; + + CHECK_CWRAP(self, "setClientArea"); + if (!PyArg_ParseTuple(args, "(iiii)|i:setClientArea", &x, &y, &w, &h)) + return NULL; + + client_configure(self->client, Corner_TopLeft, x, y, w, h, TRUE, TRUE); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_frameSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "frameSize"); + if (!PyArg_ParseTuple(args, ":frameSize")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->frame->size.left)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->frame->size.top)); + PyTuple_SET_ITEM(tuple, 2, + PyInt_FromLong(self->client->frame->size.right)); + PyTuple_SET_ITEM(tuple, 3, + PyInt_FromLong(self->client->frame->size.bottom)); + return tuple; +} + +static PyObject *cwrap_strut(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "strut"); + if (!PyArg_ParseTuple(args, ":strut")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->strut.left)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->strut.top)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(self->client->strut.right)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(self->client->strut.bottom)); + return tuple; +} + +static PyObject *cwrap_logicalSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "logicalSize"); + if (!PyArg_ParseTuple(args, ":logicalSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, + PyInt_FromLong(self->client->logical_size.width)); + PyTuple_SET_ITEM(tuple, 1, + PyInt_FromLong(self->client->logical_size.height)); + return tuple; +} + +static PyObject *cwrap_canFocus(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "canFocus"); + if (!PyArg_ParseTuple(args, ":canFocus")) + return NULL; + return PyInt_FromLong(!!self->client->can_focus); +} + +static PyObject *cwrap_focus(ClientWrap *self, PyObject *args) +{ + int focus = TRUE; + + CHECK_CWRAP(self, "focus"); + if (!PyArg_ParseTuple(args, "|i:focus", &focus)) + return NULL; + if (focus) + return PyInt_FromLong(!!client_focus(self->client)); + else { + if (focus_client == self->client) + client_unfocus(self->client); + Py_INCREF(Py_None); + return Py_None; + } +} + +static PyObject *cwrap_focused(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "focused"); + if (!PyArg_ParseTuple(args, ":focused")) + return NULL; + return PyInt_FromLong(!!self->client->focused); +} + +static PyObject *cwrap_visible(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "visible"); + if (!PyArg_ParseTuple(args, ":visible")) + return NULL; + return PyInt_FromLong(!!self->client->frame->visible); +} + +static PyObject *cwrap_setVisible(ClientWrap *self, PyObject *args) +{ + int show; + + CHECK_CWRAP(self, "setVisible"); + if (!PyArg_ParseTuple(args, "i:setVisible", &show)) + return NULL; + if (show) + engine_frame_show(self->client->frame); + else + engine_frame_hide(self->client->frame); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_modal(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "modal"); + if (!PyArg_ParseTuple(args, ":modal")) + return NULL; + return PyInt_FromLong(!!self->client->modal); +} + +static PyObject *cwrap_setModal(ClientWrap *self, PyObject *args) +{ + int modal; + + CHECK_CWRAP(self, "setModal"); + if (!PyArg_ParseTuple(args, "i:setModal", &modal)) + return NULL; + + client_set_state(self->client, + (modal ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_modal, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_shaded(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "shaded"); + if (!PyArg_ParseTuple(args, ":shaded")) + return NULL; + return PyInt_FromLong(!!self->client->shaded); +} + +static PyObject *cwrap_setShaded(ClientWrap *self, PyObject *args) +{ + int shaded; + + CHECK_CWRAP(self, "setShaded"); + if (!PyArg_ParseTuple(args, "i:setShaded", &shaded)) + return NULL; + + client_shade(self->client, shaded); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_iconic(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "iconic"); + if (!PyArg_ParseTuple(args, ":iconic")) + return NULL; + return PyInt_FromLong(!!self->client->iconic); +} + +static PyObject *cwrap_setIconic(ClientWrap *self, PyObject *args) +{ + int iconify, current = TRUE; + + CHECK_CWRAP(self, "setIconic"); + if (!PyArg_ParseTuple(args, "i|i:setIconic", &iconify, ¤t)) + return NULL; + + client_iconify(self->client, iconify, current); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_maximizedHorz(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maximizedHorz"); + if (!PyArg_ParseTuple(args, ":maximizedHorz")) + return NULL; + return PyInt_FromLong(!!self->client->max_horz); +} + +static PyObject *cwrap_setMaximizedHorz(ClientWrap *self, PyObject *args) +{ + int max; + + CHECK_CWRAP(self, "setMaximizedHorz"); + if (!PyArg_ParseTuple(args, "i:setMaximizedHorz", &max)) + return NULL; + + client_set_state(self->client, + (max ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_maximized_horz, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_maximizedVert(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maximizedVert"); + if (!PyArg_ParseTuple(args, ":maximizedVert")) + return NULL; + return PyInt_FromLong(!!self->client->max_vert); +} + +static PyObject *cwrap_setMaximizedVert(ClientWrap *self, PyObject *args) +{ + int max; + + CHECK_CWRAP(self, "setMaximizedVert"); + if (!PyArg_ParseTuple(args, "i:setMaximizedVert", &max)) + return NULL; + + client_set_state(self->client, + (max ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_maximized_vert, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_maximized(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maximized"); + if (!PyArg_ParseTuple(args, ":maximized")) + return NULL; + return PyInt_FromLong(self->client->max_vert || self->client->max_horz); +} + +static PyObject *cwrap_setMaximized(ClientWrap *self, PyObject *args) +{ + int max; + + CHECK_CWRAP(self, "setMaximized"); + if (!PyArg_ParseTuple(args, "i:setMaximized", &max)) + return NULL; + + client_set_state(self->client, + (max ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_maximized_vert, + prop_atoms.net_wm_state_maximized_horz); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_fullscreen(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "fullscreen"); + if (!PyArg_ParseTuple(args, ":fullscreen")) + return NULL; + return PyInt_FromLong(!!self->client->fullscreen); +} + +static PyObject *cwrap_setFullscreen(ClientWrap *self, PyObject *args) +{ + int fs; + + CHECK_CWRAP(self, "setFullscreen"); + if (!PyArg_ParseTuple(args, "i:setFullscreen", &fs)) + return NULL; + + client_set_state(self->client, + (fs ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_fullscreen, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_stacking(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "stacking"); + if (!PyArg_ParseTuple(args, ":stacking")) + return NULL; + return PyInt_FromLong(self->client->above ? 1 : + self->client->below ? -1 : 0); +} + +static PyObject *cwrap_setStacking(ClientWrap *self, PyObject *args) +{ + int stack; + + CHECK_CWRAP(self, "setStacking"); + if (!PyArg_ParseTuple(args, "i:setStacking", &stack)) + return NULL; + client_set_state(self->client, + (stack > 0 ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_above, 0); + client_set_state(self->client, + (stack < 0 ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_below, 0); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_raiseWindow(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "raiseWindow"); + if (!PyArg_ParseTuple(args, ":raiseWindow")) + return NULL; + stacking_raise(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_lowerWindow(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "lowerWindow"); + if (!PyArg_ParseTuple(args, ":lowerWindow")) + return NULL; + stacking_lower(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_skipPager(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "skipPager"); + if (!PyArg_ParseTuple(args, ":skipPager")) + return NULL; + return PyInt_FromLong(!!self->client->skip_pager); +} + +static PyObject *cwrap_setSkipPager(ClientWrap *self, PyObject *args) +{ + int skip; + + CHECK_CWRAP(self, "setSkipPager"); + if (!PyArg_ParseTuple(args, "i:setSkipPager", &skip)) + return NULL; + + client_set_state(self->client, + (skip ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_skip_pager, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_skipTaskbar(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "skipTaskbar"); + if (!PyArg_ParseTuple(args, ":skipTaskbar")) + return NULL; + return PyInt_FromLong(!!self->client->skip_taskbar); +} + +static PyObject *cwrap_setSkipTaskbar(ClientWrap *self, PyObject *args) +{ + int skip; + + CHECK_CWRAP(self, "setSkipTaskbar"); + if (!PyArg_ParseTuple(args, "i:setSkipTaskbar", &skip)) + return NULL; + + client_set_state(self->client, + (skip ? prop_atoms.net_wm_state_add : + prop_atoms.net_wm_state_remove), + prop_atoms.net_wm_state_skip_taskbar, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_disableDecorations(ClientWrap *self, PyObject *args) +{ + int title, handle, border; + + CHECK_CWRAP(self, "disableDecorations"); + if (!PyArg_ParseTuple(args, "iii:disableDecorations", &title, &handle, + &border)) + return NULL; + + self->client->disabled_decorations = 0; + if (title) self->client->disabled_decorations |= Decor_Titlebar; + if (handle) self->client->disabled_decorations |= Decor_Handle; + if (border) self->client->disabled_decorations |= Decor_Border; + client_setup_decor_and_functions(self->client); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_close(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "close"); + if (!PyArg_ParseTuple(args, ":close")) + return NULL; + client_close(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_window(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "window"); + if (!PyArg_ParseTuple(args, ":window")) + return NULL; + return PyInt_FromLong(self->client->window); +} + +#define METH(n, d) {#n, (PyCFunction)cwrap_##n, METH_VARARGS, #d} + +static PyMethodDef ClientWrapMethods[] = { + METH(valid, + "Returns if the Client instance is still valid. Client instances are " + "marked as invalid when the Client they are associated is " + "closed/destroyed/released."), + METH(title, + "Returns the client's title."), + METH(setTitle, + "Change the client's title to the given string. This change will be " + "overwritten if/when the client changes its title."), + METH(iconTitle, + "Returns's the client's icon title. The icon title is the title to " + "be displayed when the client is iconified."), + METH(setIconTitle, + "Change the client's icon title to the given string. This change " + "will be overwritten if/when the client changes its icon title."), + METH(desktop, + "Returns the desktop on which the client is visible. This value will " + "always be in the range [0, ob.Openbox.numDesktops()), unless it is " + "0xffffffff. A value of 0xffffffff indicates the client is visible " + "on all desktops."), + METH(setDesktop, + "Moves the client to the specified desktop. The desktop must be in " + "the range [0, ob.Openbox.numDesktops()), unless it is 0xffffffff. A " + "value of 0xffffffff indicates the client is visible on all " + "desktops."), + METH(resName, + "Returns the resouce name of the client. The resource name is meant " + "to provide an instance name for the client."), + METH(resClass, + "Returns the resouce class of the client. The resource class is " + "meant to provide the genereal class of the application. e.g. " + "'Emacs', 'Xterm', 'XClock', 'XLoad', and so on."), + METH(role, + "Returns the client's role. The role is meant to distinguish between " + "different windows of an application. Each window should have a " + "unique role."), + METH(transient, + "Returns True or False describing if the client is a transient " + "window. Transient windows are 'temporary' windows, such as " + "preference dialogs, and usually have a parent window, which can be " + "found from transientFor()."), + METH(transientFor, + "Returns the client for which this client is a transient. See " + "transient() for a description of transience."), + METH(transients, + "Returns a tuple containing all the Clients which are transients of " + "this window. See transient() for a description of transience."), + METH(type, + "Returns the logical type of the window. This is one of the " + "ClientType constants. See also normal()."), + METH(normal, + "Returns True or False for if the client is a 'normal' window. " + "Normal windows make up most applications. Non-normal windows have " + "special rules applied to them at times such as for focus handling. " + "An example of a non-normal window is 'gnome-panel'. This value is " + "determined from the client's type(), but does not imply that the " + "window is ClientType.Normal. Rather this is a more generic " + "definition of 'normal' windows, and includes dialogs and others."), + METH(area, + "Returns the area of the screen which the client occupies. It may be " + "important to note that this is the position and size of the client " + "*with* its decorations. If you want the underlying position and " + "size of the client itself, you should use clientArea(). See also " + "logicalSize()."), + + METH(setArea, + "Sets the client's area, moving and resizing it as specified (or as " + "close as can be accomidated)."), + METH(clientArea, + "Returns the area of the screen which the client considers itself to " + "be occupying. This value is not what you see and should not be used " + "for most things (it should, for example, be used for persisting a " + "client's dimentions across sessions). See also area()."), + METH(setClientArea, + "Sets the area of the screen which the client considers itself to be " + "occupying. This is not the on-screen visible position and size, and " + "should be used with care. You probably want to use setArea() to " + "adjust the client. This should be used if you want the client " + "window (inside the decorations) to be a specific size. Adjusting " + "the client's position with this function is probably always a bad " + "idea, because of window gravity."), + METH(strut, + "Returns the application's specified strut. The strut is the amount " + "of space that should be reserved for the application on each side " + "of the screen."), + METH(frameSize, + "Returns the size of the decorations around the client window."), + METH(logicalSize, + "Returns the client's logical size. The logical size is the client's " + "size in more user friendly terms. For many apps this is simply the " + "size of the client in pixels, however for some apps this will " + "differ (e.g. terminal emulators). This value should be used when " + "displaying an applications size to the user."), + METH(canFocus, + "Returns True or False for if the client can be focused."), + METH(focus, + "Focuses (or unfocuses) the client window. Windows which return " + "False for canFocus() or visible() cannot be focused. When this " + "function returns, the client's focused() state will not be changed " + "yet. This only sends the request through the X server. You should " + "wait for the hooks.focused hook to fire, and not assume the client " + "has been focused."), + METH(focused, + "Returns True or False for if the client has the input focus."), + METH(visible, + "Returns True or False for if the client is visible. A client is not " + "visible if it is iconic() or if its desktop() is not visible."), + METH(setVisible, + "Shows or hides the client. This has no effect if its current " + "visible() state is requested."), + METH(modal, + "Returns True or False for if the client is a modal window. Modal " + "windows indicate that they must be dealt with before the program " + "can continue. When a modal window is a transient(), its " + "transientFor() client cannot be focused or raised above it."), + METH(setModal, + "Make the client window modal or non-modal."), + METH(shaded, + "Returns True or False for if the client is shaded. Shaded windows " + "have only their titlebar decorations showing."), + METH(setShaded, + "Shade or unshade the client. Shaded windows have only their " + "titlebar decorations showing. Windows which do not have a titlebar " + "cannot be shaded."), + METH(iconic, + "Returns True or False for if the window is iconified. Iconified " + "windows are not visible on any desktops."), + METH(setIconic, + "Iconifies or restores the client window. Iconified windows are not " + "visible on any desktops. Iconified windows can be restored to the " + "currently visible desktop or to their original (native) desktop."), + METH(maximizedHorz, + "Returns whether the client is maximized in the horizontal " + "direction."), + METH(setMaximizedHorz, + "Maximizes or restores a client horizontally."), + METH(maximizedVert, + "Returns whether the client is maximized in the vertical direction."), + METH(setMaximizedVert, + "Maximizes or restores a client vertically."), + METH(maximized, + "Returns whether the client is maximized in the horizontal or " + "vertical direction."), + METH(setMaximized, + "Maximizes or restores a client vertically and horzontally."), + METH(fullscreen, + "Returns if the client is in fullscreen mode. Fullscreen windows are " + "kept above all other windows and are stretched to fill the entire " + "physical display."), + METH(setFullscreen, + "Set a client into or out of fullscreen mode. Fullscreen windows are " + "kept above all other windows and are stretched to fill the entire " + "physical display."), + METH(stacking, + "Returns if the client will be stacked above/below other clients in " + "the same layer."), + METH(setStacking, + "Set how the client will be stacked according to other clients in " + "its layer."), + METH(raiseWindow, + "Raises the window to the top of its stacking layer."), + METH(lowerWindow, + "Lowers the window to the bottom of its stacking layer."), + METH(skipPager, + "Returns if the client has requested to be skipped (not displayed) " + "by pagers."), + METH(setSkipPager, + "Set whether the client should be skipped (not displayed) by " + "pagers."), + METH(skipTaskbar, + "Returns if the client has requested to be skipped (not displayed) " + "by taskbars."), + METH(setSkipTaskbar, + "Set whether the client should be skipped (not displayed) by " + "taskbars."), + METH(disableDecorations, + "Choose which decorations to disable on the client. Note that " + "decorations can only be disabled, and decorations that would " + "normally not be shown cannot be added. These values may have " + "slightly different meanings in different theme engines."), + METH(close, + "Requests the client to close its window."), + METH(window, + "Returns the client's window id. This is the id by which the X " + "server knows the client."), + { NULL, NULL, 0, NULL } +}; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +/*static PyObject *cwrap_getattr(ClientWrap *self, char *name) +{ + CHECK_CWRAP(self, "getattr"); + return Py_FindMethod(ClientWrapAttributeMethods, (PyObject*)self, name); +}*/ + +static void cwrap_dealloc(ClientWrap *self) +{ + if (self->client != NULL) + self->client->wrap = NULL; + PyObject_Del((PyObject*) self); +} + +static PyObject *cwrap_repr(ClientWrap *self) +{ + CHECK_CWRAP(self, "repr"); + return PyString_FromFormat("Client: 0x%x", (guint)self->client->window); +} + +static int cwrap_compare(ClientWrap *self, ClientWrap *other) +{ + Window w1, w2; + if (!IS_VALID_CWRAP(self)) { + PyErr_SetString(PyExc_ValueError, + "This 'Client' is wrapping a client which no longer " + "exists."); + } + + w1 = self->client->window; + w2 = other->client->window; + return w1 > w2 ? 1 : w1 < w2 ? -1 : 0; +} + +static PyTypeObject ClientWrapType = { + PyObject_HEAD_INIT(NULL) + 0, + "Client", + sizeof(ClientWrap), + 0, + (destructor) cwrap_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + (cmpfunc) cwrap_compare, /*tp_compare*/ + (reprfunc) cwrap_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + + +/*************************************************************************** + + Define the type 'ClientType' + + ***************************************************************************/ + +#define IS_CTYPE(v) ((v)->ob_type == &ClientTypeType) +#define CHECK_CTYPE(self, funcname) { \ + if (!IS_CTYPE(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'ClientType' " \ + "object"); \ + return NULL; \ + } \ +} + +staticforward PyTypeObject ClientTypeType; + +typedef struct ClientType { + PyObject_HEAD +} ClientType; + +static void ctype_dealloc(PyObject *self) +{ + PyObject_Del(self); +} + +static PyObject *ctype_getattr(ClientType *self, char *name) +{ + struct S { char *name; int val; }; + struct S s[] = { + { "Normal", Type_Normal }, + { "Dialog", Type_Dialog }, + { "Desktop", Type_Desktop }, + { "Dock", Type_Dock }, + { "Toolbar", Type_Toolbar }, + { "Menu", Type_Menu }, + { "Utility", Type_Utility }, + { "Splash", Type_Splash }, + { NULL, 0 } }; + int i; + + CHECK_CTYPE(self, "__getattr__"); + + for (i = 0; s[i].name != NULL; ++i) + if (!strcmp(s[i].name, name)) + return PyInt_FromLong(s[i].val); + PyErr_SetString(PyExc_AttributeError, "invalid attribute"); + return NULL; +} + +static PyTypeObject ClientTypeType = { + PyObject_HEAD_INIT(NULL) + 0, + "Type", + sizeof(ClientType), + 0, + (destructor) ctype_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc) ctype_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/*************************************************************************** + + External methods + + ***************************************************************************/ + +void clientwrap_startup() +{ + PyObject *ob, *obdict, *type; + + ClientWrapType.ob_type = &PyType_Type; + ClientWrapType.tp_methods = ClientWrapMethods; + PyType_Ready(&ClientWrapType); + + ClientTypeType.ob_type = &PyType_Type; + PyType_Ready(&ClientTypeType); + + /* get the ob module/dict */ + ob = PyImport_ImportModule("ob"); /* new */ + g_assert(ob != NULL); + obdict = PyModule_GetDict(ob); /* borrowed */ + g_assert(obdict != NULL); + + /* add the Client type to the ob module */ + PyDict_SetItemString(obdict, "Client", (PyObject*) &ClientWrapType); + + /* add an instance of ClientType */ + type = (PyObject*) PyObject_New(ClientType, &ClientTypeType); + PyDict_SetItemString(obdict, "ClientType", type); + Py_DECREF(type); + + Py_DECREF(ob); +} + +void clientwrap_shutdown() +{ +} + +PyObject *clientwrap_new(Client *client) +{ + g_assert(client != NULL); + + if (client->wrap != NULL) { + /* already has a wrapper! */ + Py_INCREF((PyObject*) client->wrap); + } else { + client->wrap = PyObject_New(ClientWrap, &ClientWrapType); + client->wrap->client = client; + } + return (PyObject*) client->wrap; +} diff --git a/openbox/clientwrap.h b/openbox/clientwrap.h new file mode 100644 index 00000000..b20ca914 --- /dev/null +++ b/openbox/clientwrap.h @@ -0,0 +1,19 @@ +#ifndef __clientwrap_h +#define __clientwrap_h + +#include + +struct Client; + +/* ClientWrap is a PyObject */ +typedef struct ClientWrap { + PyObject_HEAD + struct Client *client; +} ClientWrap; + +void clientwrap_startup(); +void clientwrap_shutdown(); + +PyObject *clientwrap_new(struct Client *client); + +#endif diff --git a/openbox/engine.c b/openbox/engine.c new file mode 100644 index 00000000..3457da18 --- /dev/null +++ b/openbox/engine.c @@ -0,0 +1,84 @@ +#include "engine.h" + +#include +#include +#ifdef HAVE_STDLIB_H +# include +#endif + +static GModule *module; +static EngineStartup *estartup; +static EngineShutdown *eshutdown; + +#define LOADSYM(name, var) \ + if (!g_module_symbol(module, #name, (gpointer*)&var)) { \ + g_warning("Failed to load symbol %s from engine", #name); \ + return FALSE; \ + } + +static gboolean load(char *name) +{ + char *path; + + g_assert(module == NULL); + + path = g_build_filename(ENGINEDIR, name, NULL); + module = g_module_open(path, G_MODULE_BIND_LAZY); + g_free(path); + + if (module == NULL) { + path = g_build_filename(g_get_home_dir(), ".openbox", "engines", name, + NULL); + module = g_module_open(path, G_MODULE_BIND_LAZY); + g_free(path); + } + + if (module == NULL) + return FALSE; + + /* load the engine's symbols */ + LOADSYM(startup, estartup); + LOADSYM(shutdown, eshutdown); + LOADSYM(frame_new, engine_frame_new); + LOADSYM(frame_grab_client, engine_frame_grab_client); + LOADSYM(frame_release_client, engine_frame_release_client); + LOADSYM(frame_adjust_size, engine_frame_adjust_size); + LOADSYM(frame_adjust_position, engine_frame_adjust_position); + LOADSYM(frame_adjust_shape, engine_frame_adjust_shape); + LOADSYM(frame_adjust_state, engine_frame_adjust_state); + LOADSYM(frame_adjust_focus, engine_frame_adjust_focus); + LOADSYM(frame_adjust_title, engine_frame_adjust_title); + LOADSYM(frame_adjust_icon, engine_frame_adjust_icon); + LOADSYM(frame_show, engine_frame_show); + LOADSYM(frame_hide, engine_frame_hide); + LOADSYM(get_context, engine_get_context); + + if (!estartup()) + return FALSE; + + return TRUE; +} + +void engine_startup(char *engine) +{ + module = NULL; + + if (engine != NULL) { + if (load(engine)) + return; + g_warning("Failed to load the engine '%s'", engine); + g_message("Falling back to the default: '%s'", DEFAULT_ENGINE); + } + if (!load(DEFAULT_ENGINE)) { + g_critical("Failed to load the engine '%s'. Aborting", DEFAULT_ENGINE); + exit(1); + } +} + +void engine_shutdown() +{ + if (module != NULL) { + eshutdown(); + g_module_close(module); + } +} diff --git a/openbox/engine.h b/openbox/engine.h new file mode 100644 index 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 +#include +#include + +static void event_process(XEvent *e); +static void event_handle_root(XEvent *e); +static void event_handle_client(Client *c, XEvent *e); + +Time event_lasttime = 0; + +/*! A list of all possible combinations of keyboard lock masks */ +static unsigned int mask_list[8]; +/*! The value of the mask for the NumLock modifier */ +static unsigned int NumLockMask; +/*! The value of the mask for the ScrollLock modifier */ +static unsigned int ScrollLockMask; +/*! The key codes for the modifier keys */ +static XModifierKeymap *modmap; +/*! Table of the constant modifier masks */ +static const int mask_table[] = { + ShiftMask, LockMask, ControlMask, Mod1Mask, + Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask +}; +static int mask_table_size; + +void event_startup() +{ + mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]); + + /* get lock masks that are defined by the display (not constant) */ + modmap = XGetModifierMapping(ob_display); + g_assert(modmap); + if (modmap && modmap->max_keypermod > 0) { + size_t cnt; + const size_t size = mask_table_size * modmap->max_keypermod; + /* get the values of the keyboard lock modifiers + Note: Caps lock is not retrieved the same way as Scroll and Num + lock since it doesn't need to be. */ + const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock); + const KeyCode scroll_lock = XKeysymToKeycode(ob_display, + XK_Scroll_Lock); + + for (cnt = 0; cnt < size; ++cnt) { + if (! modmap->modifiermap[cnt]) continue; + + if (num_lock == modmap->modifiermap[cnt]) + NumLockMask = mask_table[cnt / modmap->max_keypermod]; + if (scroll_lock == modmap->modifiermap[cnt]) + ScrollLockMask = mask_table[cnt / modmap->max_keypermod]; + } + } + + mask_list[0] = 0; + mask_list[1] = LockMask; + mask_list[2] = NumLockMask; + mask_list[3] = LockMask | NumLockMask; + mask_list[4] = ScrollLockMask; + mask_list[5] = ScrollLockMask | LockMask; + mask_list[6] = ScrollLockMask | NumLockMask; + mask_list[7] = ScrollLockMask | LockMask | NumLockMask; +} + +void event_shutdown() +{ + XFreeModifiermap(modmap); +} + +void event_loop() +{ + fd_set selset; + XEvent e; + int x_fd; + struct timeval *wait; + + while (TRUE) { + /* + There are slightly different event retrieval semantics here for + local (or high bandwidth) versus remote (or low bandwidth) + connections to the display/Xserver. + */ + if (ob_remote) { + if (!XPending(ob_display)) + break; + } else { + /* + This XSync allows for far more compression of events, which + makes things like Motion events perform far far better. Since + it also means network traffic for every event instead of every + X events (where X is the number retrieved at a time), it + probably should not be used for setups where Openbox is + running on a remote/low bandwidth display/Xserver. + */ + XSync(ob_display, FALSE); + if (!XEventsQueued(ob_display, QueuedAlready)) + break; + } + XNextEvent(ob_display, &e); + + event_process(&e); + } + + timer_dispatch((GTimeVal**)&wait); + x_fd = ConnectionNumber(ob_display); + FD_ZERO(&selset); + FD_SET(x_fd, &selset); + select(x_fd + 1, &selset, NULL, NULL, wait); +} + +void event_process(XEvent *e) +{ + XEvent ce; + KeyCode *kp; + Window window; + int i, k; + Client *client; + + /* pick a window */ + switch (e->type) { + case UnmapNotify: + window = e->xunmap.window; + break; + case DestroyNotify: + window = e->xdestroywindow.window; + break; + case ConfigureRequest: + window = e->xconfigurerequest.window; + break; + default: + /* XKB events */ + if (e->type == extensions_xkb_event_basep) { + switch (((XkbAnyEvent*)&e)->xkb_type) { + case XkbBellNotify: + window = ((XkbBellNotifyEvent*)&e)->window; + default: + window = None; + } + } else + window = e->xany.window; + } + + /* grab the lasttime and hack up the state */ + switch (e->type) { + case ButtonPress: + case ButtonRelease: + event_lasttime = e->xbutton.time; + e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + break; + case KeyPress: + event_lasttime = e->xkey.time; + e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* add to the state the mask of the modifier being pressed, if it is + a modifier key being pressed (this is a little ugly..) */ +/* I'm commenting this out cuz i don't want "C-Control_L" being returned. */ +/* kp = modmap->modifiermap;*/ +/* for (i = 0; i < mask_table_size; ++i) {*/ +/* for (k = 0; k < modmap->max_keypermod; ++k) {*/ +/* if (*kp == e->xkey.keycode) {*/ /* found the keycode */ + /* add the mask for it */ +/* e->xkey.state |= mask_table[i];*/ + /* cause the first loop to break; */ +/* i = mask_table_size;*/ +/* break;*/ /* get outta here! */ +/* }*/ +/* ++kp;*/ +/* }*/ +/* }*/ + + break; + case KeyRelease: + event_lasttime = e->xkey.time; + e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* remove from the state the mask of the modifier being released, if + it is a modifier key being released (this is a little ugly..) */ + kp = modmap->modifiermap; + for (i = 0; i < mask_table_size; ++i) { + for (k = 0; k < modmap->max_keypermod; ++k) { + if (*kp == e->xkey.keycode) { /* found the keycode */ + /* remove the mask for it */ + e->xkey.state &= ~mask_table[i]; + /* cause the first loop to break; */ + i = mask_table_size; + break; /* get outta here! */ + } + ++kp; + } + } + break; + case MotionNotify: + event_lasttime = e->xmotion.time; + e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* compress events */ + while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) { + e->xmotion.x_root = ce.xmotion.x_root; + e->xmotion.y_root = ce.xmotion.y_root; + } + break; + case PropertyNotify: + event_lasttime = e->xproperty.time; + break; + case FocusIn: + case FocusOut: + if (e->xfocus.mode == NotifyGrab) + /*|| e.xfocus.mode == NotifyUngrab ||*/ + + /* From Metacity, from WindowMaker, ignore all funky pointer + root events. Its commented out cuz I don't think we need this + at all. If problems arise we can look into it */ + /*e.xfocus.detail > NotifyNonlinearVirtual) */ + return; /* skip me! */ + if (e->type == FocusOut) { + /* FocusOut events just make us look for FocusIn events. They + are mostly ignored otherwise. */ + XEvent fi; + if (XCheckTypedEvent(ob_display, FocusIn, &fi)) { + event_process(&fi); + /* dont unfocus the window we just focused! */ + if (fi.xfocus.window == e->xfocus.window) + return; + } + } + break; + case EnterNotify: + case LeaveNotify: + event_lasttime = e->xcrossing.time; + if (e->xcrossing.mode != NotifyNormal) + return; /* skip me! */ + break; + } + + client = g_hash_table_lookup(client_map, (gpointer)window); + + if (client) { + event_handle_client(client, e); + } else if (window == ob_root) + event_handle_root(e); + else if (e->type == ConfigureRequest) { + /* unhandled configure requests must be used to configure the + window directly */ + XWindowChanges xwc; + + xwc.x = e->xconfigurerequest.x; + xwc.y = e->xconfigurerequest.y; + xwc.width = e->xconfigurerequest.width; + xwc.height = e->xconfigurerequest.height; + xwc.border_width = e->xconfigurerequest.border_width; + xwc.sibling = e->xconfigurerequest.above; + xwc.stack_mode = e->xconfigurerequest.detail; + + g_message("Proxying configure event for 0x%lx", window); + + /* we are not to be held responsible if someone sends us an + invalid request! */ + xerror_set_ignore(TRUE); + XConfigureWindow(ob_display, window, + e->xconfigurerequest.value_mask, &xwc); + xerror_set_ignore(FALSE); + } + + /* dispatch Crossing, Pointer and Key events to the hooks */ + switch(e->type) { + case EnterNotify: + HOOKFIRECLIENT(pointerenter, client); + break; + case LeaveNotify: + HOOKFIRECLIENT(pointerleave, client); + break; + case ButtonPress: + case ButtonRelease: + case MotionNotify: + pointer_event(e, client); + break; + case KeyPress: + case KeyRelease: + keyboard_event(&e->xkey); + break; + default: + /* XKB events */ + if (e->type == extensions_xkb_event_basep) { + switch (((XkbAnyEvent*)&e)->xkb_type) { + case XkbBellNotify: + HOOKFIRECLIENT(bell, client); + break; + } + } + } +} + +static void event_handle_root(XEvent *e) +{ + Atom msgtype; + + switch(e->type) { + case MapRequest: + g_message("MapRequest on root"); + client_manage(e->xmap.window); + break; + case ClientMessage: + if (e->xclient.format != 32) break; + + msgtype = e->xclient.message_type; + if (msgtype == prop_atoms.net_current_desktop) { + unsigned int d = e->xclient.data.l[0]; + if (d <= screen_num_desktops) + screen_set_desktop(d); + } else if (msgtype == prop_atoms.net_number_of_desktops) { + unsigned int d = e->xclient.data.l[0]; + if (d > 0) + screen_set_num_desktops(d); + } else if (msgtype == prop_atoms.net_showing_desktop) { + screen_show_desktop(e->xclient.data.l[0] != 0); + } + break; + case PropertyNotify: + if (e->xproperty.atom == prop_atoms.net_desktop_names) + screen_update_desktop_names(); + else if (e->xproperty.atom == prop_atoms.net_desktop_layout) + screen_update_layout(); + break; + } +} + +static void event_handle_client(Client *client, XEvent *e) +{ + XEvent ce; + Atom msgtype; + + switch (e->type) { + case FocusIn: + client->focused = TRUE; + engine_frame_adjust_focus(client->frame); + + /* focus state can affect the stacking layer */ + client_calc_layer(client); + + focus_set_client(client); + break; + case FocusOut: + client->focused = FALSE; + engine_frame_adjust_focus(client->frame); + + /* focus state can affect the stacking layer */ + client_calc_layer(client); + + if (focus_client == client) + focus_set_client(NULL); + break; + case ConfigureRequest: + g_message("ConfigureRequest for window %lx", client->window); + /* compress these */ + while (XCheckTypedWindowEvent(ob_display, client->window, + ConfigureRequest, &ce)) { + /* XXX if this causes bad things.. we can compress config req's + with the same mask. */ + e->xconfigurerequest.value_mask |= + ce.xconfigurerequest.value_mask; + if (ce.xconfigurerequest.value_mask & CWX) + e->xconfigurerequest.x = ce.xconfigurerequest.x; + if (ce.xconfigurerequest.value_mask & CWY) + e->xconfigurerequest.y = ce.xconfigurerequest.y; + if (ce.xconfigurerequest.value_mask & CWWidth) + e->xconfigurerequest.width = ce.xconfigurerequest.width; + if (ce.xconfigurerequest.value_mask & CWHeight) + e->xconfigurerequest.height = ce.xconfigurerequest.height; + if (ce.xconfigurerequest.value_mask & CWBorderWidth) + e->xconfigurerequest.border_width = + ce.xconfigurerequest.border_width; + if (ce.xconfigurerequest.value_mask & CWStackMode) + e->xconfigurerequest.detail = ce.xconfigurerequest.detail; + } + + /* if we are iconic (or shaded (fvwm does this)) ignore the event */ + if (client->iconic || client->shaded) return; + + if (e->xconfigurerequest.value_mask & CWBorderWidth) + client->border_width = e->xconfigurerequest.border_width; + + /* resize, then move, as specified in the EWMH section 7.7 */ + if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight | + CWX | CWY)) { + int x, y, w, h; + Corner corner; + + x = (e->xconfigurerequest.value_mask & CWX) ? + e->xconfigurerequest.x : client->area.x; + y = (e->xconfigurerequest.value_mask & CWY) ? + e->xconfigurerequest.y : client->area.y; + w = (e->xconfigurerequest.value_mask & CWWidth) ? + e->xconfigurerequest.width : client->area.width; + h = (e->xconfigurerequest.value_mask & CWHeight) ? + e->xconfigurerequest.height : client->area.height; + + switch (client->gravity) { + case NorthEastGravity: + case EastGravity: + corner = Corner_TopRight; + break; + case SouthWestGravity: + case SouthGravity: + corner = Corner_BottomLeft; + break; + case SouthEastGravity: + corner = Corner_BottomRight; + break; + default: /* NorthWest, Static, etc */ + corner = Corner_TopLeft; + } + + client_configure(client, corner, x, y, w, h, FALSE, FALSE); + } + + if (e->xconfigurerequest.value_mask & CWStackMode) { + switch (e->xconfigurerequest.detail) { + case Below: + case BottomIf: + stacking_lower(client); + break; + + case Above: + case TopIf: + default: + stacking_raise(client); + break; + } + } + break; + case UnmapNotify: + if (client->ignore_unmaps) { + client->ignore_unmaps--; + break; + } + g_message("UnmapNotify for %lx", client->window); + client_unmanage(client); + break; + case DestroyNotify: + g_message("DestroyNotify for %lx", client->window); + client_unmanage(client); + break; + case ReparentNotify: + /* this is when the client is first taken captive in the frame */ + if (e->xreparent.parent == client->frame->plate) break; + + /* + This event is quite rare and is usually handled in unmapHandler. + However, if the window is unmapped when the reparent event occurs, + the window manager never sees it because an unmap event is not sent + to an already unmapped window. + */ + + /* we don't want the reparent event, put it back on the stack for the + X server to deal with after we unmanage the window */ + XPutBackEvent(ob_display, e); + + client_unmanage(client); + break; + case MapRequest: + /* we shouldn't be able to get this unless we're iconic */ + g_assert(client->iconic); + + HOOKFIRECLIENT(requestactivate, client); + break; + case ClientMessage: + /* validate cuz we query stuff off the client here */ + if (!client_validate(client)) break; + + if (e->xclient.format != 32) return; + + msgtype = e->xclient.message_type; + if (msgtype == prop_atoms.wm_change_state) { + /* compress changes into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL messages of a + type, not just messages in a row without other + message types between. */ + if (ce.xclient.message_type != msgtype) { + XPutBackEvent(ob_display, &ce); + break; + } + e->xclient = ce.xclient; + } + client_set_wm_state(client, e->xclient.data.l[0]); + } else if (msgtype == prop_atoms.net_wm_desktop) { + /* compress changes into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL messages of a + type, not just messages in a row without other + message types between. */ + if (ce.xclient.message_type != msgtype) { + XPutBackEvent(ob_display, &ce); + break; + } + e->xclient = ce.xclient; + } + client_set_desktop(client, e->xclient.data.l[0]); + } else if (msgtype == prop_atoms.net_wm_state) { + /* can't compress these */ + g_message("net_wm_state %s %ld %ld for 0x%lx\n", + (e->xclient.data.l[0] == 0 ? "Remove" : + e->xclient.data.l[0] == 1 ? "Add" : + e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"), + e->xclient.data.l[1], e->xclient.data.l[2], + client->window); + client_set_state(client, e->xclient.data.l[0], + e->xclient.data.l[1], e->xclient.data.l[2]); + } else if (msgtype == prop_atoms.net_close_window) { + g_message("net_close_window for 0x%lx\n", client->window); + client_close(client); + } else if (msgtype == prop_atoms.net_active_window) { + g_message("net_active_window for 0x%lx\n", client->window); + if (screen_showing_desktop) + screen_show_desktop(FALSE); + if (client->iconic) + client_iconify(client, FALSE, TRUE); + else if (!client->frame->visible) + /* if its not visible for other reasons, then don't mess + with it */ + return; + HOOKFIRECLIENT(requestactivate, client); + } + break; + case PropertyNotify: + /* validate cuz we query stuff off the client here */ + if (!client_validate(client)) break; + + /* compress changes to a single property into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL changes to a property, + not just changes in a row without other props between. */ + if (ce.xproperty.atom != e->xproperty.atom) { + XPutBackEvent(ob_display, &ce); + break; + } + } + + msgtype = e->xproperty.atom; + if (msgtype == XA_WM_NORMAL_HINTS) { + client_update_normal_hints(client); + /* normal hints can make a window non-resizable */ + client_setup_decor_and_functions(client); + } + else if (msgtype == XA_WM_HINTS) + client_update_wmhints(client); + else if (msgtype == XA_WM_TRANSIENT_FOR) { + client_update_transient_for(client); + client_get_type(client); + /* type may have changed, so update the layer */ + client_calc_layer(client); + client_setup_decor_and_functions(client); + } + else if (msgtype == prop_atoms.net_wm_name || + msgtype == prop_atoms.wm_name) + client_update_title(client); + else if (msgtype == prop_atoms.net_wm_icon_name || + msgtype == prop_atoms.wm_icon_name) + client_update_icon_title(client); + else if (msgtype == prop_atoms.wm_class) + client_update_class(client); + else if (msgtype == prop_atoms.wm_protocols) { + client_update_protocols(client); + client_setup_decor_and_functions(client); + } + else if (msgtype == prop_atoms.net_wm_strut) + client_update_strut(client); + else if (msgtype == prop_atoms.net_wm_icon) + client_update_icons(client); + else if (msgtype == prop_atoms.kwm_win_icon) + client_update_kwm_icon(client); + } +} diff --git a/openbox/event.h b/openbox/event.h new file mode 100644 index 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 +#ifdef XKB +#include +#endif +#ifdef SHAPE +#include +#endif +#ifdef XINERAMA +#include +#endif +#include + +/*! Does the display have the XKB extension? */ +extern gboolean extensions_xkb; +/*! Base for events for the XKB extension */ +extern int extensions_xkb_event_basep; + +/*! Does the display have the Shape extension? */ +extern gboolean extensions_shape; +/*! Base for events for the Shape extension */ +extern int extensions_shape_event_basep; + +/*! Does the display have the Xinerama extension? */ +extern gboolean extensions_xinerama; +/*! Base for events for the Xinerama extension */ +extern int extensions_xinerama_event_basep; + +void extensions_query_all(); + +#endif diff --git a/openbox/focus.c b/openbox/focus.c new file mode 100644 index 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 + +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 + +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 . + Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published + by the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. */ + +#ifndef _LIBGETTEXT_H +#define _LIBGETTEXT_H 1 + + +/* NLS can be disabled through the configure --disable-nls option. */ +#if ENABLE_NLS + +/* Get declarations of GNU message catalog functions. */ +# include + +#else + +/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which + chokes if dcgettext is defined as a macro. So include it now, to make + later inclusions of a NOP. We don't include + as well because people using "gettext.h" will not include , + and also including would fail on SunOS 4, whereas + is OK. */ +#if defined(__sun) +# include +#endif + +/* Disabled NLS. + The casts to 'const char *' serve the purpose of producing warnings + for invalid uses of the value returned from these functions. + On pre-ANSI systems without 'const', the config.h file is supposed to + contain "#define const". */ +# define gettext(Msgid) ((const char *) (Msgid)) +# define dgettext(Domainname, Msgid) ((const char *) (Msgid)) +# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid)) +# define ngettext(Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dngettext(Domainname, Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define textdomain(Domainname) ((const char *) (Domainname)) +# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) +# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset)) + +#endif + +/* A pseudo function call that serves as a marker for the automated + extraction of messages, but does not call gettext(). The run-time + translation is done at a different place in the code. + The argument, String, should be a literal string. Concatenated strings + and other string expressions won't work. + The macro's expansion is not parenthesized, so that it is suitable as + initializer for static 'char[]' or 'const char[]' variables. */ +#define gettext_noop(String) String + +/* Custom macro to make life easier */ +#define _(str) gettext(str) + +#endif /* _LIBGETTEXT_H */ diff --git a/openbox/hooks.c b/openbox/hooks.c new file mode 100644 index 00000000..35d8d694 --- /dev/null +++ b/openbox/hooks.c @@ -0,0 +1,295 @@ +#include "hooks.h" +#include + +/* + * + * Define the 'Hook' class type + * + */ +#define IS_HOOK(v) ((v)->ob_type == &HookType) + +staticforward PyTypeObject HookType; + +typedef struct HookObject { + PyObject_HEAD + GSList *funcs; +} HookObject; + +static int hook_init(HookObject *self, PyObject *args, PyObject *kwds) +{ + char *keywords[] = { 0 }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, ":__init__", keywords)) + return -1; + self->funcs = NULL; + return 0; +} + +static void hook_dealloc(HookObject *self) +{ + GSList *it; + + for (it = self->funcs; it != NULL; it = it->next) + Py_DECREF((PyObject*) it->data); + + PyObject_Del((PyObject*) self); +} + +static PyObject *hook_fire(HookObject *self, PyObject *args) +{ + GSList *it; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'fire' requires a 'Hook' object"); + return NULL; + } + + for (it = self->funcs; it != NULL; it = it->next) { + PyObject *ret = PyObject_CallObject(it->data, args); + if (ret == NULL) + return NULL; + Py_DECREF(ret); + } + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *hook_append(HookObject *self, PyObject *args) +{ + PyObject *func; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'append' requires a 'Hook' object"); + return NULL; + } + if (!PyArg_ParseTuple(args, "O:append", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'append' requires a callable argument"); + return NULL; + } + self->funcs = g_slist_append(self->funcs, func); + Py_INCREF(func); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *hook_remove(HookObject *self, PyObject *args) +{ + PyObject *func; + GSList *it; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'remove' requires a 'Hook' object"); + return NULL; + } + if (!PyArg_ParseTuple(args, "O:remove", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'remove' requires a callable argument"); + return NULL; + } + + it = g_slist_find(self->funcs, func); + if (it != NULL) { + self->funcs = g_slist_delete_link(self->funcs, it); + Py_DECREF(func); + + Py_INCREF(Py_None); + return Py_None; + } + PyErr_SetString(PyExc_TypeError, + "given callable object was not found in Hook"); + return NULL; +} + +static PyObject *hook_call(HookObject *self, PyObject *args) +{ + GSList *it, *next; + gboolean stop = FALSE; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor '__call__' requires a 'Hook' object"); + return NULL; + } + + for (it = self->funcs; !stop && it != NULL;) { + next = it->next; /* incase the hook removes itself */ + + PyObject *ret = PyObject_CallObject(it->data, args); + if (ret == NULL) + return NULL; + if (ret != Py_None) + stop = TRUE; + Py_DECREF(ret); + + it = next; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static PyTypeObject HookType = { + PyObject_HEAD_INIT(NULL) + 0, + "Hook", + sizeof(HookObject), + 0, + (destructor) hook_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +static PyMethodDef HookMethods[] = { + {"append", (PyCFunction)hook_append, METH_VARARGS, + "hook.add(func) -- Add a function to the hook." }, + {"remove", (PyCFunction)hook_remove, METH_VARARGS, + "hook.remove(func) -- Remove a function from the hook." }, + { NULL, NULL, 0, NULL } +}; + + +/* + * + * Module initialization/finalization + * + */ + +static PyObject *hooks, *hooksdict; + +static PyMethodDef HooksMethods[] = { + { NULL, NULL, 0, NULL } +}; + +struct HookObject *hooks_create(char *name) +{ + HookObject *hook; + int ret; + + hook = PyObject_New(HookObject, &HookType); + hook->funcs = NULL; + + /* add it to the hooks module */ + ret = PyDict_SetItemString(hooksdict, name, (PyObject*) hook); + g_assert(ret != -1); + + return hook; +} + +void hooks_startup() +{ + HookType.ob_type = &PyType_Type; + HookType.tp_methods = HookMethods; + HookType.tp_alloc = PyType_GenericAlloc; + HookType.tp_new = PyType_GenericNew; + HookType.tp_init = (initproc) hook_init; + HookType.tp_call = (ternaryfunc) hook_call; + PyType_Ready(&HookType); + + Py_InitModule("hooks", HooksMethods); + + /* get the hooks module/dict */ + hooks = PyImport_ImportModule("hooks"); /* new */ + g_assert(hooks != NULL); + hooksdict = PyModule_GetDict(hooks); /* borrowed */ + g_assert(hooksdict != NULL); + + /* add the Hook type to the hooks module */ + PyDict_SetItemString(hooksdict, "Hook", (PyObject*) &HookType); + + hook_startup = hooks_create("startup"); + hook_shutdown = hooks_create("shutdown"); + hook_visibledesktop = hooks_create("visibledesktop"); + hook_numdesktops = hooks_create("numdesktops"); + hook_desktopnames = hooks_create("desktopnames"); + hook_showdesktop = hooks_create("showdesktop"); + hook_screenconfiguration = hooks_create("screenconfiguration"); + hook_screenarea = hooks_create("screenarea"); + hook_managed = hooks_create("managed"); + hook_closed = hooks_create("closed"); + hook_bell = hooks_create("bell"); + hook_urgent = hooks_create("urgent"); + hook_pointerenter = hooks_create("pointerenter"); + hook_pointerleave = hooks_create("pointerleave"); + hook_focused = hooks_create("focused"); + hook_requestactivate = hooks_create("requestactivate"); + hook_title = hooks_create("title"); + hook_desktop = hooks_create("desktop"); + hook_iconic = hooks_create("iconic"); + hook_shaded = hooks_create("shaded"); + hook_maximized = hooks_create("maximized"); + hook_fullscreen = hooks_create("fullscreen"); + hook_visible = hooks_create("visible"); + hook_configuration = hooks_create("configuration"); +} + +void hooks_shutdown() +{ + Py_DECREF(hook_startup); + Py_DECREF(hook_shutdown); + Py_DECREF(hook_visibledesktop); + Py_DECREF(hook_numdesktops); + Py_DECREF(hook_desktopnames); + Py_DECREF(hook_showdesktop); + Py_DECREF(hook_screenconfiguration); + Py_DECREF(hook_screenarea); + Py_DECREF(hook_managed); + Py_DECREF(hook_closed); + Py_DECREF(hook_bell); + Py_DECREF(hook_urgent); + Py_DECREF(hook_pointerenter); + Py_DECREF(hook_pointerleave); + Py_DECREF(hook_focused); + Py_DECREF(hook_requestactivate); + Py_DECREF(hook_title); + Py_DECREF(hook_desktop); + Py_DECREF(hook_iconic); + Py_DECREF(hook_shaded); + Py_DECREF(hook_maximized); + Py_DECREF(hook_fullscreen); + Py_DECREF(hook_visible); + Py_DECREF(hook_configuration); + + Py_DECREF(hooks); +} + +void hooks_fire(struct HookObject *hook, PyObject *args) +{ + PyObject *ret = hook_call(hook, args); + if (ret == NULL) + PyErr_Print(); + Py_XDECREF(ret); +} + +void hooks_fire_client(struct HookObject *hook, struct Client *client) +{ + PyObject *args; + + if (client != NULL) { + PyObject *c = clientwrap_new(client); + g_assert(c != NULL); + args = Py_BuildValue("(O)", c); + Py_DECREF(c); + } else { + args = Py_BuildValue("(O)", Py_None); + } + + g_assert(args != NULL); + hooks_fire(hook, args); + Py_DECREF(args); +} diff --git a/openbox/hooks.h b/openbox/hooks.h new file mode 100644 index 00000000..3211d7cf --- /dev/null +++ b/openbox/hooks.h @@ -0,0 +1,56 @@ +#ifndef __hooks_h +#define __hooks_h + +#include "clientwrap.h" +#include + +void hooks_startup(); +void hooks_shutdown(); + +struct HookObject; + +struct HookObject *hooks_create(char *name); + +struct HookObject *hook_startup; +struct HookObject *hook_shutdown; +struct HookObject *hook_visibledesktop; +struct HookObject *hook_numdesktops; +struct HookObject *hook_desktopnames; +struct HookObject *hook_showdesktop; +struct HookObject *hook_screenconfiguration; +struct HookObject *hook_screenarea; +struct HookObject *hook_managed; +struct HookObject *hook_closed; +struct HookObject *hook_bell; +struct HookObject *hook_urgent; +struct HookObject *hook_pointerenter; +struct HookObject *hook_pointerleave; +struct HookObject *hook_focused; +struct HookObject *hook_requestactivate; +struct HookObject *hook_title; +struct HookObject *hook_desktop; +struct HookObject *hook_iconic; +struct HookObject *hook_shaded; +struct HookObject *hook_maximized; +struct HookObject *hook_fullscreen; +struct HookObject *hook_visible; +struct HookObject *hook_configuration; + +#define HOOKFIRE(hook, ...) \ +{ \ + PyObject *args = Py_BuildValue(__VA_ARGS__); \ + g_assert(args != NULL); \ + hooks_fire(hook_##hook, args); \ + Py_DECREF(args); \ +} + +#define HOOKFIRECLIENT(hook, client) \ +{ \ + hooks_fire_client(hook_##hook, client); \ +} + +void hooks_fire(struct HookObject *hook, PyObject *args); + +void hooks_fire_client(struct HookObject *hook, struct Client *client); + +#endif diff --git a/openbox/keyboard.c b/openbox/keyboard.c new file mode 100644 index 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 +#include +#ifdef HAVE_STRING_H +# include +#endif + +typedef struct KeyBindingTree { + guint state; + guint key; + GList *keylist; + PyObject *func; + + /* the next binding in the tree at the same level */ + struct KeyBindingTree *next_sibling; + /* the first child of this binding (next binding in a chained sequence).*/ + struct KeyBindingTree *first_child; +} KeyBindingTree; + + +static KeyBindingTree *firstnode, *curpos; +static guint reset_key, reset_state; +static gboolean grabbed, user_grabbed; +static PyObject *grab_func; + +/*************************************************************************** + + Define the type 'KeyboardData' + + ***************************************************************************/ + +typedef struct KeyboardData { + PyObject_HEAD + PyObject *keychain; + guint state; + guint keycode; + gboolean press; +} KeyboardData; + +staticforward PyTypeObject KeyboardDataType; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +static PyObject *keybdata_new(PyObject *keychain, guint state, + guint keycode, gboolean press) +{ + KeyboardData *data = PyObject_New(KeyboardData, &KeyboardDataType); + data->keychain = keychain; + Py_INCREF(keychain); + data->state = state; + data->keycode = keycode; + data->press = press; + return (PyObject*) data; +} + +static void keybdata_dealloc(KeyboardData *self) +{ + Py_DECREF(self->keychain); + PyObject_Del((PyObject*)self); +} + +static PyObject *keybdata_getattr(KeyboardData *self, char *name) +{ + if (!strcmp(name, "keychain")) { + Py_INCREF(self->keychain); + return self->keychain; + } else if (!strcmp(name, "state")) + return PyInt_FromLong(self->state); + else if (!strcmp(name, "keycode")) + return PyInt_FromLong(self->keycode); + else if (!strcmp(name, "press")) + return PyInt_FromLong(!!self->press); + + PyErr_Format(PyExc_AttributeError, "no such attribute '%s'", name); + return NULL; +} + +static PyTypeObject KeyboardDataType = { + PyObject_HEAD_INIT(NULL) + 0, + "KeyboardData", + sizeof(KeyboardData), + 0, + (destructor) keybdata_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc) keybdata_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/***************************************************************************/ + +guint keyboard_translate_modifier(char *str) +{ + if (!strcmp("Mod1", str)) return Mod1Mask; + else if (!strcmp("Mod2", str)) return Mod2Mask; + else if (!strcmp("Mod3", str)) return Mod3Mask; + else if (!strcmp("Mod4", str)) return Mod4Mask; + else if (!strcmp("Mod5", str)) return Mod5Mask; + else if (!strcmp("C", str)) return ControlMask; + else if (!strcmp("S", str)) return ShiftMask; + g_warning("Invalid modifier '%s' in binding.", str); + return 0; +} + +static gboolean translate(char *str, guint *state, guint *keycode) +{ + char **parsed; + char *l; + int i; + gboolean ret = FALSE; + KeySym sym; + + parsed = g_strsplit(str, "-", -1); + + /* first, find the key (last token) */ + l = NULL; + for (i = 0; parsed[i] != NULL; ++i) + l = parsed[i]; + if (l == NULL) + goto translation_fail; + + /* figure out the mod mask */ + *state = 0; + for (i = 0; parsed[i] != l; ++i) { + guint m = keyboard_translate_modifier(parsed[i]); + if (!m) goto translation_fail; + *state |= m; + } + + /* figure out the keycode */ + sym = XStringToKeysym(l); + if (sym == NoSymbol) { + g_warning("Invalid key name '%s' in key binding.", l); + goto translation_fail; + } + *keycode = XKeysymToKeycode(ob_display, sym); + if (!keycode) { + g_warning("Key '%s' does not exist on the display.", l); + goto translation_fail; + } + + ret = TRUE; + +translation_fail: + g_strfreev(parsed); + return ret; +} + +static void destroytree(KeyBindingTree *tree) +{ + KeyBindingTree *c; + + while (tree) { + destroytree(tree->next_sibling); + c = tree->first_child; + if (c == NULL) { + GList *it; + for (it = tree->keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(tree->keylist); + Py_XDECREF(tree->func); + } + g_free(tree); + tree = c; + } +} + +static KeyBindingTree *buildtree(GList *keylist) +{ + GList *it; + KeyBindingTree *ret = NULL, *p; + + if (g_list_length(keylist) <= 0) + return NULL; /* nothing in the list.. */ + + for (it = g_list_last(keylist); it != NULL; it = it->prev) { + p = ret; + ret = g_new(KeyBindingTree, 1); + ret->next_sibling = NULL; + ret->func = NULL; + if (p == NULL) { + GList *it; + + /* this is the first built node, the bottom node of the tree */ + ret->keylist = g_list_copy(keylist); /* shallow copy */ + for (it = ret->keylist; it != NULL; it = it->next) /* deep copy */ + it->data = g_strdup(it->data); + } + ret->first_child = p; + if (!translate(it->data, &ret->state, &ret->key)) { + destroytree(ret); + return NULL; + } + } + return ret; +} + +static void assimilate(KeyBindingTree *node) +{ + KeyBindingTree *a, *b, *tmp, *last; + + if (firstnode == NULL) { + /* there are no nodes at this level yet */ + firstnode = node; + } else { + a = firstnode; + last = a; + b = node; + while (a) { + last = a; + if (!(a->state == b->state && a->key == b->key)) { + a = a->next_sibling; + } else { + tmp = b; + b = b->first_child; + g_free(tmp); + a = a->first_child; + } + } + if (!(last->state == b->state && last->key == b->key)) + last->next_sibling = b; + else { + last->first_child = b->first_child; + g_free(b); + } + } +} + +static KeyBindingTree *find(KeyBindingTree *search, gboolean *conflict) +{ + KeyBindingTree *a, *b; + + *conflict = FALSE; + + a = firstnode; + b = search; + while (a && b) { + if (!(a->state == b->state && a->key == b->key)) { + a = a->next_sibling; + } else { + if ((a->first_child == NULL) == (b->first_child == NULL)) { + if (a->first_child == NULL) { + /* found it! (return the actual node, not the search's) */ + return a; + } + } else { + *conflict = TRUE; + return NULL; /* the chain status' don't match (conflict!) */ + } + b = b->first_child; + a = a->first_child; + } + } + return NULL; // it just isn't in here +} + +static void grab_keys(gboolean grab) +{ + if (!grab) { + XUngrabKey(ob_display, AnyKey, AnyModifier, ob_root); + } else { + KeyBindingTree *p = firstnode; + while (p) { + XGrabKey(ob_display, p->key, p->state, ob_root, FALSE, + GrabModeAsync, GrabModeSync); + p = p->next_sibling; + } + } +} + +static void reset_chains() +{ + /* XXX kill timer */ + curpos = NULL; + if (grabbed) { + grabbed = FALSE; + g_message("reset chains. user_grabbed: %d", user_grabbed); + if (!user_grabbed) + XUngrabKeyboard(ob_display, CurrentTime); + } +} + +void keyboard_event(XKeyEvent *e) +{ + PyObject *chain, *client, *args, *keybdata, *ret; + gboolean press = e->type == KeyPress; + + if (focus_client) client = clientwrap_new(focus_client); + else client = Py_None; + + if (user_grabbed) { + GString *str = g_string_sized_new(0); + KeySym sym; + + /* build the 'chain' */ + if (e->state & ControlMask) + g_string_append(str, "C-"); + if (e->state & ShiftMask) + g_string_append(str, "S-"); + if (e->state & Mod1Mask) + g_string_append(str, "Mod1-"); + if (e->state & Mod2Mask) + g_string_append(str, "Mod2-"); + if (e->state & Mod3Mask) + g_string_append(str, "Mod3-"); + if (e->state & Mod4Mask) + g_string_append(str, "Mod4-"); + if (e->state & Mod5Mask) + g_string_append(str, "Mod5-"); + + sym = XKeycodeToKeysym(ob_display, e->keycode, 0); + if (sym == NoSymbol) + g_string_append(str, "NoSymbol"); + else { + char *name = XKeysymToString(sym); + if (name == NULL) + name = "Undefined"; + g_string_append(str, name); + } + + chain = PyTuple_New(1); + PyTuple_SET_ITEM(chain, 0, PyString_FromString(str->str)); + g_string_free(str, TRUE); + + keybdata = keybdata_new(chain, e->state, e->keycode, press); + + args = Py_BuildValue("OO", keybdata, client); + + ret = PyObject_CallObject(grab_func, args); + if (ret == NULL) PyErr_Print(); + Py_XDECREF(ret); + + Py_DECREF(args); + Py_DECREF(keybdata); + Py_DECREF(chain); + } + + if (press) { + if (e->keycode == reset_key && e->state == reset_state) { + reset_chains(); + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + } else { + KeyBindingTree *p; + if (curpos == NULL) + p = firstnode; + else + p = curpos->first_child; + while (p) { + if (p->key == e->keycode && p->state == e->state) { + if (p->first_child != NULL) { /* part of a chain */ + /* XXX TIMER */ + if (!grabbed && !user_grabbed) { + /*grab should never fail because we should have a + sync grab at this point */ + XGrabKeyboard(ob_display, ob_root, 0, + GrabModeAsync, GrabModeSync, + CurrentTime); + } + grabbed = TRUE; + curpos = p; + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + } else { + GList *it; + int i; + + chain = PyTuple_New(g_list_length(p->keylist)); + for (i = 0, it = p->keylist; it != NULL; + it = it->next, ++i) + PyTuple_SET_ITEM(chain, i, + PyString_FromString(it->data)); + + keybdata = keybdata_new(chain, e->state, e->keycode, + press); + + args = Py_BuildValue("OO", keybdata, client); + + ret = PyObject_CallObject(p->func, args); + if (ret == NULL) PyErr_Print(); + Py_XDECREF(ret); + + Py_DECREF(args); + Py_DECREF(keybdata); + Py_DECREF(chain); + + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + reset_chains(); + } + break; + } + p = p->next_sibling; + } + } + } + + if (client != Py_None) { Py_DECREF(client); } +} + +static void clearall() +{ + grab_keys(FALSE); + destroytree(firstnode); + firstnode = NULL; + grab_keys(TRUE); +} + +static gboolean grab_keyboard(gboolean grab) +{ + gboolean ret = TRUE; + + g_message("grab_keyboard(%s). grabbed: %d", (grab?"True":"False"),grabbed); + + user_grabbed = grab; + if (!grabbed) { + if (grab) + ret = XGrabKeyboard(ob_display, ob_root, 0, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess; + else + XUngrabKeyboard(ob_display, CurrentTime); + } + return ret; +} + +/*************************************************************************** + + Define the type 'Keyboard' + + ***************************************************************************/ + +#define IS_KEYBOARD(v) ((v)->ob_type == &KeyboardType) +#define CHECK_KEYBOARD(self, funcname) { \ + if (!IS_KEYBOARD(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'Keyboard' " \ + "object"); \ + return NULL; \ + } \ +} + +typedef struct Keyboard { + PyObject_HEAD +} Keyboard; + +staticforward PyTypeObject KeyboardType; + +static PyObject *keyb_bind(Keyboard *self, PyObject *args) +{ + KeyBindingTree *tree = NULL, *t; + gboolean conflict; + PyObject *item, *tuple, *func; + GList *keylist = NULL, *it; + int i, s; + + CHECK_KEYBOARD(self, "grab"); + if (!PyArg_ParseTuple(args, "OO:grab", &tuple, &func)) + return NULL; + + if (!PyTuple_Check(tuple)) { + PyErr_SetString(PyExc_ValueError, "expected a tuple of strings"); + goto binderror; + } + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_ValueError, "expected a callable object"); + goto binderror; + } + + s = PyTuple_GET_SIZE(tuple); + if (s <= 0) { + PyErr_SetString(PyExc_ValueError, "expected a tuple of strings"); + goto binderror; + } + + for (i = 0; i < s; ++i) { + item = PyTuple_GET_ITEM(tuple, i); + if (!PyString_Check(item)) { + PyErr_SetString(PyExc_ValueError, "expected a tuple of strings"); + goto binderror; + } + keylist = g_list_append(keylist, + g_strdup(PyString_AsString(item))); + } + + if (!(tree = buildtree(keylist))) { + PyErr_SetString(PyExc_ValueError, "invalid binding"); + goto binderror; + } + + t = find(tree, &conflict); + if (conflict) { + PyErr_SetString(PyExc_ValueError, "conflict with binding"); + goto binderror; + } + if (t != NULL) { + /* already bound to something */ + PyErr_SetString(PyExc_ValueError, "keychain is already bound"); + goto binderror; + } + + /* grab the server here to make sure no key pressed go missed */ + XGrabServer(ob_display); + XSync(ob_display, FALSE); + + grab_keys(FALSE); + + /* set the function */ + t = tree; + while (t->first_child) t = t->first_child; + t->func = func; + Py_INCREF(func); + + /* assimilate this built tree into the main tree */ + assimilate(tree); // assimilation destroys/uses the tree + + grab_keys(TRUE); + + XUngrabServer(ob_display); + XFlush(ob_display); + + for (it = keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(it); + + Py_INCREF(Py_None); + return Py_None; + +binderror: + if (tree != NULL) destroytree(tree); + for (it = keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(it); + return NULL; +} + +static PyObject *keyb_clearBinds(Keyboard *self, PyObject *args) +{ + CHECK_KEYBOARD(self, "clearBinds"); + if (!PyArg_ParseTuple(args, ":clearBinds")) + return NULL; + clearall(); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *keyb_grab(Keyboard *self, PyObject *args) +{ + PyObject *func; + + CHECK_KEYBOARD(self, "grab"); + if (!PyArg_ParseTuple(args, "O:grab", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_ValueError, "expected a callable object"); + return NULL; + } + if (!grab_keyboard(TRUE)) { + PyErr_SetString(PyExc_RuntimeError, "failed to grab keyboard"); + return NULL; + } + grab_func = func; + Py_INCREF(grab_func); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *keyb_ungrab(Keyboard *self, PyObject *args) +{ + CHECK_KEYBOARD(self, "ungrab"); + if (!PyArg_ParseTuple(args, ":ungrab")) + return NULL; + grab_keyboard(FALSE); + Py_XDECREF(grab_func); + grab_func = NULL; + Py_INCREF(Py_None); + return Py_None; +} + +#define METH(n, d) {#n, (PyCFunction)keyb_##n, METH_VARARGS, #d} + +static PyMethodDef KeyboardMethods[] = { + METH(bind, + "bind(keychain, func)\n\n" + "Binds a key-chain to a function. The keychain is a tuple of strings " + "which define a chain of key presses. Each member of the tuple has " + "the format [Modifier-]...[Key]. Modifiers can be 'mod1', 'mod2', " + "'mod3', 'mod4', 'mod5', 'control', and 'shift'. The keys on your " + "keyboard that are bound to each of these modifiers can be found by " + "running 'xmodmap'. The Key can be any valid key definition. Key " + "definitions can be found by running 'xev', pressing the key while " + "its window is focused, and watching its output. Here are some " + "examples of valid keychains: ('a'), ('F7'), ('control-a', 'd'), " + "('control-mod1-x', 'control-mod4-g'), ('F1', 'space'). The func " + "must have a definition similar to 'def func(keydata, client)'. A " + "keychain cannot be bound to more than one function."), + METH(clearBinds, + "clearBinds()\n\n" + "Removes all bindings that were previously made by bind()."), + METH(grab, + "grab(func)\n\n" + "Grabs the entire keyboard, causing all possible keyboard events to " + "be passed to the given function. CAUTION: Be sure when you grab() " + "that you also have an ungrab() that will execute, or you will not " + "be able to type until you restart Openbox. The func must have a " + "definition similar to 'def func(keydata)'. The keyboard cannot be " + "grabbed if it is already grabbed."), + METH(ungrab, + "ungrab()\n\n" + "Ungrabs the keyboard. The keyboard cannot be ungrabbed if it is not " + "grabbed."), + { NULL, NULL, 0, NULL } +}; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +static void keyb_dealloc(PyObject *self) +{ + PyObject_Del(self); +} + +static PyTypeObject KeyboardType = { + PyObject_HEAD_INIT(NULL) + 0, + "Keyboard", + sizeof(Keyboard), + 0, + (destructor) keyb_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/**************************************************************************/ + +void keyboard_startup() +{ + PyObject *input, *inputdict, *ptr; + gboolean b; + + curpos = firstnode = NULL; + grabbed = user_grabbed = FALSE; + + b = translate("C-G", &reset_state, &reset_key); + g_assert(b); + + KeyboardType.ob_type = &PyType_Type; + KeyboardType.tp_methods = KeyboardMethods; + PyType_Ready(&KeyboardType); + PyType_Ready(&KeyboardDataType); + + /* get the input module/dict */ + input = PyImport_ImportModule("input"); /* new */ + g_assert(input != NULL); + inputdict = PyModule_GetDict(input); /* borrowed */ + g_assert(inputdict != NULL); + + /* add a Keyboard instance to the input module */ + ptr = (PyObject*) PyObject_New(Keyboard, &KeyboardType); + PyDict_SetItemString(inputdict, "Keyboard", ptr); + Py_DECREF(ptr); + + Py_DECREF(input); +} + +void keyboard_shutdown() +{ + if (grabbed || user_grabbed) { + grabbed = FALSE; + grab_keyboard(FALSE); + } + grab_keys(FALSE); + destroytree(firstnode); + firstnode = NULL; +} + diff --git a/openbox/keyboard.h b/openbox/keyboard.h new file mode 100644 index 00000000..464606b2 --- /dev/null +++ b/openbox/keyboard.h @@ -0,0 +1,13 @@ +#ifndef __keyboard_h +#define __keyboard_h + +#include + +void keyboard_startup(); +void keyboard_shutdown(); + +guint keyboard_translate_modifier(char *str); + +void keyboard_event(XKeyEvent *e); + +#endif diff --git a/openbox/openbox.c b/openbox/openbox.c new file mode 100644 index 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 +#endif +#ifdef HAVE_SYS_SELECT_H +# include +#endif +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_SYS_WAIT_H +# include +# include +#endif +#ifdef HAVE_LOCALE_H +# include +#endif + +#include + +Display *ob_display = NULL; +int ob_screen; +Window ob_root; +State ob_state; +gboolean ob_shutdown = FALSE; +gboolean ob_restart = FALSE; +char *ob_restart_path = NULL; +gboolean ob_remote = FALSE; +gboolean ob_sync = TRUE; +Cursors ob_cursors; + +void signal_handler(int signal); + +int main(int argc, char **argv) +{ + struct sigaction action; + sigset_t sigset; + + ob_state = State_Starting; + + /* initialize the locale */ + if (!setlocale(LC_ALL, "")) + g_warning("Couldn't set locale from environment.\n"); + bindtextdomain(PACKAGE, LOCALEDIR); + bind_textdomain_codeset(PACKAGE, "UTF-8"); + textdomain(PACKAGE); + + /* set up signal handler */ + sigemptyset(&sigset); + action.sa_handler = signal_handler; + action.sa_mask = sigset; + action.sa_flags = SA_NOCLDSTOP | SA_NODEFER; + sigaction(SIGUSR1, &action, (struct sigaction *) NULL); + sigaction(SIGPIPE, &action, (struct sigaction *) NULL); + sigaction(SIGSEGV, &action, (struct sigaction *) NULL); + sigaction(SIGFPE, &action, (struct sigaction *) NULL); + sigaction(SIGTERM, &action, (struct sigaction *) NULL); + sigaction(SIGINT, &action, (struct sigaction *) NULL); + sigaction(SIGHUP, &action, (struct sigaction *) NULL); + sigaction(SIGCHLD, &action, (struct sigaction *) NULL); + + /* anything that died while we were restarting won't give us a SIGCHLD */ + while (waitpid(-1, NULL, WNOHANG) > 0); + + /* XXX parse out command line args */ + (void)argc;(void)argv; + + ob_display = XOpenDisplay(NULL); + if (ob_display == NULL) { + /* print a message and exit */ + g_critical("Failed to open the display."); + exit(1); + } + if (fcntl(ConnectionNumber(ob_display), F_SETFD, 1) == -1) { + /* print a message and exit */ + g_critical("Failed to set display as close-on-exec."); + exit(1); + } + + ob_screen = DefaultScreen(ob_display); + ob_root = RootWindow(ob_display, ob_screen); + + /* XXX fork self onto other screens */ + + XSynchronize(ob_display, ob_sync); + + /* check for locale support */ + if (!XSupportsLocale()) + g_warning("X server does not support locale."); + if (!XSetLocaleModifiers("")) + g_warning("Cannot set locale modifiers for the X server."); + + /* set our error handler */ + XSetErrorHandler(xerror_handler); + + /* set the DISPLAY environment variable for any lauched children, to the + display we're using, so they open in the right place. */ + putenv(g_strdup_printf("DISPLAY=%s", DisplayString(ob_display))); + + ob_cursors.left_ptr = XCreateFontCursor(ob_display, XC_left_ptr); + ob_cursors.ll_angle = XCreateFontCursor(ob_display, XC_ll_angle); + ob_cursors.lr_angle = XCreateFontCursor(ob_display, XC_lr_angle); + + prop_startup(); /* get atoms values for the display */ + extensions_query_all(); /* find which extensions are present */ + + if (screen_annex()) { /* it will be ours! */ + timer_startup(); + render_startup(); + themerc_startup(); + engine_startup(themerc_engine); + python_startup(); + openboxwrap_startup(); + clientwrap_startup(); + hooks_startup(); + event_startup(); + screen_startup(); + focus_startup(); + client_startup(); + keyboard_startup(); + pointer_startup(); + + /* load the user's settings */ + if (!python_import("rc")) + g_warning("ERROR LOADING RC FILE"); + + HOOKFIRE(startup, "()"); + + /* get all the existing windows */ + client_manage_all(); + + ob_state = State_Running; + while (!ob_shutdown) { + event_loop(); + } + ob_state = State_Exiting; + + client_unmanage_all(); + + HOOKFIRE(shutdown, "()"); + + pointer_shutdown(); + keyboard_shutdown(); + client_shutdown(); + screen_shutdown(); + event_shutdown(); + hooks_shutdown(); + clientwrap_shutdown(); + openboxwrap_shutdown(); + python_shutdown(); + engine_shutdown(); + themerc_shutdown(); + render_shutdown(); + timer_shutdown(); + } + + XCloseDisplay(ob_display); + + /* XXX if (ob_restart) */ + + return 0; +} + +void signal_handler(int signal) +{ + switch (signal) { + case SIGUSR1: + g_message("Caught SIGUSR1 signal. Restarting."); + ob_shutdown = ob_restart = TRUE; + break; + + case SIGCHLD: + wait(NULL); + break; + + case SIGHUP: + case SIGINT: + case SIGTERM: + case SIGPIPE: + g_message("Caught signal %d. Exiting.", signal); + ob_shutdown = TRUE; + break; + + case SIGFPE: + case SIGSEGV: + g_error("Caught signal %d. Aborting and dumping core.", signal); + } +} diff --git a/openbox/openbox.h b/openbox/openbox.h new file mode 100644 index 00000000..f8797d7f --- /dev/null +++ b/openbox/openbox.h @@ -0,0 +1,44 @@ +#ifndef __openbox_h +#define __openbox_h + +#include +#include + +/*! The X display */ +extern Display *ob_display; +/*! The number of the screen on which we're running */ +extern int ob_screen; +/*! The root window */ +extern Window ob_root; + +/*! States of execution for Openbox */ +typedef enum { + State_Starting, + State_Exiting, + State_Running +} State; + +/* The state of execution of the window manager */ +State ob_state; + +/*! When set to true, Openbox will exit */ +extern gboolean ob_shutdown; +/*! When set to true, Openbox will restart instead of shutting down */ +extern gboolean ob_restart; +/*! When restarting, if this is not NULL, it will be executed instead of + restarting Openbox. */ +extern char *ob_restart_path; + +/*! Runtime option to specify running on a remote display */ +extern gboolean ob_remote; +/*! Runtime option to run in synchronous mode */ +extern gboolean ob_sync; + +typedef struct Cursors { + Cursor left_ptr; + Cursor ll_angle; + Cursor lr_angle; +} Cursors; +Cursors ob_cursors; + +#endif diff --git a/openbox/openboxwrap.c b/openbox/openboxwrap.c new file mode 100644 index 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 + +/* 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 +#include +#include /* for PyMemberDef stuff */ +#ifdef HAVE_STDLIB_H +# include +#endif + +typedef enum { + Action_Press, + Action_Release, + Action_Click, + Action_DoubleClick, + Action_Motion, + NUM_ACTIONS +} Action; + +/* GData of GSList*s of PointerBinding*s. */ +static GData *bound_contexts; +static gboolean grabbed; +static int double_click_rate, drag_threshold; +PyObject *grab_func; + +struct foreach_grab_temp { + Client *client; + gboolean grab; +}; + +typedef struct { + guint state; + guint button; + Action action; + char *name; + GSList *funcs[NUM_ACTIONS]; +} PointerBinding; + +/*************************************************************************** + + Define the type 'ButtonData' + + ***************************************************************************/ + +typedef struct PointerData { + PyObject_HEAD + Action action; + GQuark context; + char *button; + guint state; + guint buttonnum; + int posx, posy; + int pressposx, pressposy; + int pcareax, pcareay, pcareaw, pcareah; +} PointerData; + +staticforward PyTypeObject PointerDataType; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +static PyObject *ptrdata_new(char *button, GQuark context, Action action, + guint state, guint buttonnum, int posx, int posy, + int pressposx, int pressposy, int pcareax, + int pcareay, int pcareaw, int pcareah) +{ + PointerData *self = PyObject_New(PointerData, &PointerDataType); + self->button = g_strdup(button); + self->context = context; + self->action = action; + self->state = state; + self->buttonnum = buttonnum; + self->posx = posx; + self->posy = posy; + self->pressposx = pressposx; + self->pressposy = pressposy; + self->pcareax = pcareax; + self->pcareay = pcareay; + self->pcareaw = pcareaw; + self->pcareah = pcareah; + return (PyObject*) self; +} + +static void ptrdata_dealloc(PointerData *self) +{ + g_free(self->button); + PyObject_Del((PyObject*)self); +} + +static PyObject *ptrdata_getattr(PointerData *self, char *name) +{ + if (!strcmp(name, "button")) + return PyString_FromString(self->button); + if (!strcmp(name, "action")) + return PyInt_FromLong(self->action); + if (!strcmp(name, "context")) + return PyString_FromString(g_quark_to_string(self->context)); + if (!strcmp(name, "state")) + return PyInt_FromLong(self->state); + if (!strcmp(name, "buttonnum")) + return PyInt_FromLong(self->buttonnum); + + if (self->action == Action_Motion) { /* the rest are only for motions */ + if (!strcmp(name, "pos")) { + PyObject *pos = PyTuple_New(2); + PyTuple_SET_ITEM(pos, 0, PyInt_FromLong(self->posx)); + PyTuple_SET_ITEM(pos, 1, PyInt_FromLong(self->posy)); + return pos; + } + if (!strcmp(name, "presspos")) { + PyObject *presspos = PyTuple_New(2); + PyTuple_SET_ITEM(presspos, 0, PyInt_FromLong(self->pressposx)); + PyTuple_SET_ITEM(presspos, 1, PyInt_FromLong(self->pressposy)); + return presspos; + } + if (!strcmp(name, "pressclientarea")) { + if (self->pcareaw < 0) { /* < 0 indicates no client */ + Py_INCREF(Py_None); + return Py_None; + } else { + PyObject *ca = PyTuple_New(4); + PyTuple_SET_ITEM(ca, 0, PyInt_FromLong(self->pcareax)); + PyTuple_SET_ITEM(ca, 1, PyInt_FromLong(self->pcareay)); + PyTuple_SET_ITEM(ca, 2, PyInt_FromLong(self->pcareaw)); + PyTuple_SET_ITEM(ca, 3, PyInt_FromLong(self->pcareah)); + return ca; + } + } + } + + PyErr_Format(PyExc_AttributeError, "no such attribute '%s'", name); + return NULL; +} + +static PyTypeObject PointerDataType = { + PyObject_HEAD_INIT(NULL) + 0, + "PointerData", + sizeof(PointerData), + 0, + (destructor) ptrdata_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc) ptrdata_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/***************************************************************************/ + +static gboolean translate(char *str, guint *state, guint *button) +{ + char **parsed; + char *l; + int i; + gboolean ret = FALSE; + + parsed = g_strsplit(str, "-", -1); + + /* first, find the button (last token) */ + l = NULL; + for (i = 0; parsed[i] != NULL; ++i) + l = parsed[i]; + if (l == NULL) + goto translation_fail; + + /* figure out the mod mask */ + *state = 0; + for (i = 0; parsed[i] != l; ++i) { + guint m = keyboard_translate_modifier(parsed[i]); + if (!m) goto translation_fail; + *state |= m; + } + + /* figure out the button */ + *button = atoi(l); + if (!*button) { + g_warning("Invalid button '%s' in pointer binding.", l); + goto translation_fail; + } + + ret = TRUE; + +translation_fail: + g_strfreev(parsed); + return ret; +} + +static void grab_button(Client *client, guint state, guint button, + GQuark context, gboolean grab) +{ + Window win; + int mode = GrabModeAsync; + unsigned int mask; + + if (context == g_quark_try_string("frame")) { + win = client->frame->window; + mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask; + } else if (context == g_quark_try_string("client")) { + win = client->frame->plate; + mode = GrabModeSync; /* this is handled in pointer_event */ + mask = ButtonPressMask; /* can't catch more than this with Sync mode + the release event is manufactured in + pointer_fire */ + } else return; + + if (grab) + XGrabButton(ob_display, button, state, win, FALSE, mask, mode, + GrabModeAsync, None, None); + else + XUngrabButton(ob_display, button, state, win); +} + +static void foreach_grab(GQuark key, gpointer data, gpointer user_data) +{ + struct foreach_grab_temp *d = user_data; + GSList *it; + for (it = data; it != NULL; it = it->next) { + PointerBinding *b = it->data; + grab_button(d->client, b->state, b->button, key, d->grab); + } +} + +void pointer_grab_all(Client *client, gboolean grab) +{ + struct foreach_grab_temp bt; + bt.client = client; + bt.grab = grab; + g_datalist_foreach(&bound_contexts, foreach_grab, &bt); +} + +static void grab_all_clients(gboolean grab) +{ + GSList *it; + + for (it = client_list; it != NULL; it = it->next) + pointer_grab_all(it->data, grab); +} + +static gboolean grab_pointer(gboolean grab) +{ + gboolean ret = TRUE; + if (grab) + ret = XGrabPointer(ob_display, ob_root, FALSE, (ButtonPressMask | + ButtonReleaseMask | + ButtonMotionMask | + PointerMotionMask), + GrabModeAsync, GrabModeAsync, None, None, + CurrentTime) == GrabSuccess; + else + XUngrabPointer(ob_display, CurrentTime); + if (ret) grabbed = grab; + return ret; +} + +static void foreach_clear(GQuark key, gpointer data, gpointer user_data) +{ + GSList *it; + user_data = user_data; + for (it = data; it != NULL; it = it->next) { + int i; + + PointerBinding *b = it->data; + for (i = 0; i < NUM_ACTIONS; ++i) + while (b->funcs[i] != NULL) { + Py_DECREF((PyObject*)b->funcs[i]->data); + b->funcs[i] = g_slist_delete_link(b->funcs[i], b->funcs[i]); + } + g_free(b->name); + g_free(b); + } + g_slist_free(data); +} + +static void clearall() +{ + grab_all_clients(FALSE); + g_datalist_foreach(&bound_contexts, foreach_clear, NULL); +} + +static void fire_event(char *button, GQuark context, Action action, + guint state, guint buttonnum, int posx, int posy, + int pressposx, int pressposy, int pcareax, + int pcareay, int pcareaw, int pcareah, + PyObject *client, GSList *functions) +{ + PyObject *ptrdata, *args, *ret; + GSList *it; + + ptrdata = ptrdata_new(button, context, action, + state, buttonnum, posx, posy, pressposx, pressposy, + pcareax, pcareay, pcareaw, pcareah); + args = Py_BuildValue("OO", ptrdata, client); + + if (grabbed) { + ret = PyObject_CallObject(grab_func, args); + if (ret == NULL) PyErr_Print(); + Py_XDECREF(ret); + } else { + for (it = functions; it != NULL; it = it->next) { + ret = PyObject_CallObject(it->data, args); + if (ret == NULL) PyErr_Print(); + Py_XDECREF(ret); + } + } + + Py_DECREF(args); + Py_DECREF(ptrdata); +} + +void pointer_event(XEvent *e, Client *c) +{ + static guint button = 0, lastbutton = 0; + static Time time = 0; + static Rect carea; + static guint pressx, pressy; + GQuark contextq; + gboolean click = FALSE, dblclick = FALSE; + PyObject *client; + GString *str = g_string_sized_new(0); + guint state; + GSList *it = NULL; + PointerBinding *b = NULL; + + contextq = engine_get_context(c, e->xany.window); + + /* pick a button, figure out clicks/double clicks */ + switch (e->type) { + case ButtonPress: + if (!button) { + button = e->xbutton.button; + if (c != NULL) carea = c->frame->area; + else carea.width = -1; /* indicates no client */ + pressx = e->xbutton.x_root; + pressy = e->xbutton.y_root; + } + state = e->xbutton.state; + break; + case ButtonRelease: + state = e->xbutton.state; + break; + case MotionNotify: + state = e->xmotion.state; + break; + default: + g_assert_not_reached(); + return; + } + + if (!grabbed) { + for (it = g_datalist_id_get_data(&bound_contexts, contextq); + it != NULL; it = it->next) { + b = it->data; + if (b->state == state && b->button == button) + break; + } + /* if not grabbed and not bound, then nothing to do! */ + if (it == NULL) return; + } + + if (c) client = clientwrap_new(c); + else client = Py_None; + + /* build the button string */ + if (state & ControlMask) g_string_append(str, "C-"); + if (state & ShiftMask) g_string_append(str, "S-"); + if (state & Mod1Mask) g_string_append(str, "Mod1-"); + if (state & Mod2Mask) g_string_append(str, "Mod2-"); + if (state & Mod3Mask) g_string_append(str, "Mod3-"); + if (state & Mod4Mask) g_string_append(str, "Mod4-"); + if (state & Mod5Mask) g_string_append(str, "Mod5-"); + g_string_append_printf(str, "%d", button); + + /* figure out clicks/double clicks */ + switch (e->type) { + case ButtonRelease: + if (button == e->xbutton.button) { + /* determine if this is a valid 'click'. Its not if the release is + not over the window, or if a drag occured. */ + if (ABS(e->xbutton.x_root - pressx) < (unsigned)drag_threshold && + ABS(e->xbutton.y_root - pressy) < (unsigned)drag_threshold && + e->xbutton.x >= 0 && e->xbutton.y >= 0) { + int junk; + Window wjunk; + guint ujunk, w, h; + XGetGeometry(ob_display, e->xany.window, &wjunk, &junk, &junk, + &w, &h, &ujunk, &ujunk); + if (e->xbutton.x < (signed)w && e->xbutton.y < (signed)h) + click =TRUE; + } + + /* determine if this is a valid 'double-click' */ + if (click) { + if (lastbutton == button && + e->xbutton.time - double_click_rate < time) { + dblclick = TRUE; + lastbutton = 0; + } else + lastbutton = button; + } else + lastbutton = 0; + time = e->xbutton.time; + pressx = pressy = 0; + button = 0; + carea.x = carea.y = carea.width = carea.height = 0; + } + break; + } + + /* fire off the events */ + switch (e->type) { + case ButtonPress: + fire_event(str->str, contextq, Action_Press, + state, button, 0, 0, 0, 0, 0, 0, 0, 0, + client, b == NULL ? NULL : b->funcs[Action_Press]); + break; + case ButtonRelease: + fire_event(str->str, contextq, Action_Release, + state, button, 0, 0, 0, 0, 0, 0, 0, 0, + client, b == NULL ? NULL : b->funcs[Action_Release]); + break; + case MotionNotify: + /* watch out for the drag threshold */ + if (ABS(e->xmotion.x_root - pressx) < (unsigned)drag_threshold && + ABS(e->xmotion.y_root - pressy) < (unsigned)drag_threshold) + break; + fire_event(str->str, contextq, Action_Motion, + state, button, e->xmotion.x_root, + e->xmotion.y_root, pressx, pressy, + carea.x, carea.y, carea.width, carea.height, + client, b == NULL ? NULL : b->funcs[Action_Motion]); + break; + } + + if (click) + fire_event(str->str, contextq, Action_Click, + state, button, 0, 0, 0, 0, 0, 0, 0, 0, + client, b == NULL ? NULL : b->funcs[Action_Click]); + if (dblclick) + fire_event(str->str, contextq, Action_DoubleClick, + state, button, 0, 0, 0, 0, 0, 0, 0, 0, + client, b == NULL ? NULL : b->funcs[Action_DoubleClick]); + + g_string_free(str, TRUE); + if (client != Py_None) { Py_DECREF(client); } + + if (contextq == g_quark_try_string("client")) { + /* Replay the event, so it goes to the client*/ + XAllowEvents(ob_display, ReplayPointer, CurrentTime); + /* generate a release event since we don't get real ones */ + if (e->type == ButtonPress) { + e->type = ButtonRelease; + pointer_event(e, c); + } + } +} + +/*************************************************************************** + + Define the type 'Pointer' + + ***************************************************************************/ + +#define IS_POINTER(v) ((v)->ob_type == &PointerType) +#define CHECK_POINTER(self, funcname) { \ + if (!IS_POINTER(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'Pointer' " \ + "object"); \ + return NULL; \ + } \ +} + +typedef struct Pointer { + PyObject_HEAD + Action press; + Action release; + Action click; + Action doubleclick; + Action motion; +} Pointer; + +staticforward PyTypeObject PointerType; + +static PyObject *ptr_bind(Pointer *self, PyObject *args) +{ + char *buttonstr; + char *contextstr; + guint state, button; + PointerBinding *b; + GSList *it; + GQuark context; + PyObject *func; + Action action; + int i; + + CHECK_POINTER(self, "grab"); + if (!PyArg_ParseTuple(args, "ssiO:grab", + &buttonstr, &contextstr, &action, &func)) + return NULL; + + if (!translate(buttonstr, &state, &button)) { + PyErr_SetString(PyExc_ValueError, "invalid button"); + return NULL; + } + + context = g_quark_try_string(contextstr); + if (!context) { + PyErr_SetString(PyExc_ValueError, "invalid context"); + return NULL; + } + + if (action < 0 || action >= NUM_ACTIONS) { + PyErr_SetString(PyExc_ValueError, "invalid action"); + return NULL; + } + + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_ValueError, "expected a callable object"); + return NULL; + } + + for (it = g_datalist_id_get_data(&bound_contexts, context); + it != NULL; it = it->next){ + b = it->data; + if (b->state == state && b->button == button) { + /* already bound */ + b->funcs[action] = g_slist_append(b->funcs[action], func); + Py_INCREF(Py_None); + return Py_None; + } + } + + grab_all_clients(FALSE); + + /* add the binding */ + b = g_new(PointerBinding, 1); + b->state = state; + b->button = button; + b->name = g_strdup(buttonstr); + for (i = 0; i < NUM_ACTIONS; ++i) + if (i != (signed)action) b->funcs[i] = NULL; + b->funcs[action] = g_slist_append(NULL, func); + g_datalist_id_set_data(&bound_contexts, context, + g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b)); + grab_all_clients(TRUE); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *ptr_clearBinds(Pointer *self, PyObject *args) +{ + CHECK_POINTER(self, "clearBinds"); + if (!PyArg_ParseTuple(args, ":clearBinds")) + return NULL; + clearall(); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *ptr_grab(Pointer *self, PyObject *args) +{ + PyObject *func; + + CHECK_POINTER(self, "grab"); + if (!PyArg_ParseTuple(args, "O:grab", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_ValueError, "expected a callable object"); + return NULL; + } + if (!grab_pointer(TRUE)) { + PyErr_SetString(PyExc_RuntimeError, "failed to grab pointer"); + return NULL; + } + grab_func = func; + Py_INCREF(grab_func); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *ptr_ungrab(Pointer *self, PyObject *args) +{ + CHECK_POINTER(self, "ungrab"); + if (!PyArg_ParseTuple(args, ":ungrab")) + return NULL; + grab_pointer(FALSE); + Py_XDECREF(grab_func); + grab_func = NULL; + Py_INCREF(Py_None); + return Py_None; +} + +#define METH(n, d) {#n, (PyCFunction)ptr_##n, METH_VARARGS, #d} + +static PyMethodDef PointerMethods[] = { + METH(bind, + "bind(button, context, func)\n\n" + "Binds a pointer button for a context to a function. See the " + "Terminology section for a decription and list of common contexts. " + "The button is a string which defines a modifier and button " + "combination with the format [Modifier-]...[Button]. Modifiers can " + "be 'mod1', 'mod2', 'mod3', 'mod4', 'mod5', 'control', and 'shift'. " + "The keys on your keyboard that are bound to each of these modifiers " + "can be found by running 'xmodmap'. The button is the number of the " + "button. Button numbers can be found by running 'xev', pressing the " + "button with the pointer over its window, and watching its output. " + "Here are some examples of valid buttons: 'control-1', '2', " + "'mod1-shift-5'. The func must have a definition similar to " + "'def func(keydata, client)'. A button and context may be bound to " + "more than one function."), + METH(clearBinds, + "clearBinds()\n\n" + "Removes all bindings that were previously made by bind()."), + METH(grab, + "grab(func)\n\n" + "Grabs the pointer device, causing all possible pointer events to be " + "sent to the given function. CAUTION: Be sure when you grab() that " + "you also have an ungrab() that will execute, or you will not be " + "able to use the pointer device until you restart Openbox. The func " + "must have a definition similar to 'def func(keydata)'. The pointer " + "cannot be grabbed if it is already grabbed."), + METH(ungrab, + "ungrab()\n\n" + "Ungrabs the pointer. The pointer cannot be ungrabbed if it is not " + "grabbed."), + { NULL, NULL, 0, NULL } +}; + +static PyMemberDef PointerMembers[] = { + {"Action_Press", T_INT, offsetof(Pointer, press), READONLY, + "a pointer button press"}, + {"Action_Release", T_INT, offsetof(Pointer, release), READONLY, + "a pointer button release"}, + {"Action_Click", T_INT, offsetof(Pointer, click), READONLY, + "a pointer button click (press-release)"}, + {"Action_DoubleClick", T_INT, offsetof(Pointer, doubleclick), READONLY, + "a pointer button double-click"}, + {"Action_Motion", T_INT, offsetof(Pointer, motion), READONLY, + "a pointer drag"}, + {NULL} +}; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +static void ptr_dealloc(PyObject *self) +{ + PyObject_Del(self); +} + +static PyTypeObject PointerType = { + PyObject_HEAD_INIT(NULL) + 0, + "Pointer", + sizeof(Pointer), + 0, + (destructor) ptr_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/**************************************************************************/ + +void pointer_startup() +{ + PyObject *input, *inputdict; + Pointer *ptr; + + grabbed = FALSE; + double_click_rate = 300; + drag_threshold = 3; + g_datalist_init(&bound_contexts); + + PointerType.ob_type = &PyType_Type; + PointerType.tp_methods = PointerMethods; + PointerType.tp_members = PointerMembers; + PyType_Ready(&PointerType); + PyType_Ready(&PointerDataType); + + /* get the input module/dict */ + input = PyImport_ImportModule("input"); /* new */ + g_assert(input != NULL); + inputdict = PyModule_GetDict(input); /* borrowed */ + g_assert(inputdict != NULL); + + /* add a Pointer instance to the input module */ + ptr = PyObject_New(Pointer, &PointerType); + ptr->press = Action_Press; + ptr->release = Action_Release; + ptr->click = Action_Click; + ptr->doubleclick = Action_DoubleClick; + ptr->motion = Action_Motion; + PyDict_SetItemString(inputdict, "Pointer", (PyObject*) ptr); + Py_DECREF(ptr); + + Py_DECREF(input); +} + +void pointer_shutdown() +{ + if (grabbed) + grab_pointer(FALSE); + clearall(); + g_datalist_clear(&bound_contexts); +} + diff --git a/openbox/pointer.h b/openbox/pointer.h new file mode 100644 index 00000000..2f068972 --- /dev/null +++ b/openbox/pointer.h @@ -0,0 +1,14 @@ +#ifndef __pointer_h +#define __pointer_h + +#include "client.h" +#include + +void pointer_startup(); +void pointer_shutdown(); + +void pointer_grab_all(Client *client, gboolean grab); + +void pointer_event(XEvent *e, Client *c); + +#endif diff --git a/openbox/prop.c b/openbox/prop.c new file mode 100644 index 00000000..9a448500 --- /dev/null +++ b/openbox/prop.c @@ -0,0 +1,288 @@ +#include "prop.h" +#include "openbox.h" +#include + +Atoms prop_atoms; + +#define CREATE(var, name) (prop_atoms.var = \ + XInternAtom(ob_display, name, FALSE)) + +void prop_startup() +{ + g_assert(ob_display != NULL); + + CREATE(cardinal, "CARDINAL"); + CREATE(window, "WINDOW"); + CREATE(pixmap, "PIXMAP"); + CREATE(atom, "ATOM"); + CREATE(string, "STRING"); + CREATE(utf8, "UTF8_STRING"); + + CREATE(wm_colormap_windows, "WM_COLORMAP_WINDOWS"); + CREATE(wm_protocols, "WM_PROTOCOLS"); + CREATE(wm_state, "WM_STATE"); + CREATE(wm_change_state, "WM_CHANGE_STATE"); + CREATE(wm_delete_window, "WM_DELETE_WINDOW"); + CREATE(wm_take_focus, "WM_TAKE_FOCUS"); + CREATE(wm_name, "WM_NAME"); + CREATE(wm_icon_name, "WM_ICON_NAME"); + CREATE(wm_class, "WM_CLASS"); + CREATE(wm_window_role, "WM_WINDOW_ROLE"); + CREATE(motif_wm_hints, "_MOTIF_WM_HINTS"); + + CREATE(net_supported, "_NET_SUPPORTED"); + CREATE(net_client_list, "_NET_CLIENT_LIST"); + CREATE(net_client_list_stacking, "_NET_CLIENT_LIST_STACKING"); + CREATE(net_number_of_desktops, "_NET_NUMBER_OF_DESKTOPS"); + CREATE(net_desktop_geometry, "_NET_DESKTOP_GEOMETRY"); + CREATE(net_desktop_viewport, "_NET_DESKTOP_VIEWPORT"); + CREATE(net_current_desktop, "_NET_CURRENT_DESKTOP"); + CREATE(net_desktop_names, "_NET_DESKTOP_NAMES"); + CREATE(net_active_window, "_NET_ACTIVE_WINDOW"); + CREATE(net_workarea, "_NET_WORKAREA"); + CREATE(net_supporting_wm_check, "_NET_SUPPORTING_WM_CHECK"); +/* CREATE(net_virtual_roots, "_NET_VIRTUAL_ROOTS"); */ + CREATE(net_desktop_layout, "_NET_DESKTOP_LAYOUT"); + CREATE(net_showing_desktop, "_NET_SHOWING_DESKTOP"); + + CREATE(net_close_window, "_NET_CLOSE_WINDOW"); + CREATE(net_wm_moveresize, "_NET_WM_MOVERESIZE"); + +/* CREATE(net_properties, "_NET_PROPERTIES"); */ + CREATE(net_wm_name, "_NET_WM_NAME"); + CREATE(net_wm_visible_name, "_NET_WM_VISIBLE_NAME"); + CREATE(net_wm_icon_name, "_NET_WM_ICON_NAME"); + CREATE(net_wm_visible_icon_name, "_NET_WM_VISIBLE_ICON_NAME"); + CREATE(net_wm_desktop, "_NET_WM_DESKTOP"); + CREATE(net_wm_window_type, "_NET_WM_WINDOW_TYPE"); + CREATE(net_wm_state, "_NET_WM_STATE"); + CREATE(net_wm_strut, "_NET_WM_STRUT"); +/* CREATE(net_wm_icon_geometry, "_NET_WM_ICON_GEOMETRY"); */ + CREATE(net_wm_icon, "_NET_WM_ICON"); +/* CREATE(net_wm_pid, "_NET_WM_PID"); */ +/* CREATE(net_wm_handled_icons, "_NET_WM_HANDLED_ICONS"); */ + CREATE(net_wm_allowed_actions, "_NET_WM_ALLOWED_ACTIONS"); + +/* CREATE(net_wm_ping, "_NET_WM_PING"); */ + + CREATE(net_wm_window_type_desktop, "_NET_WM_WINDOW_TYPE_DESKTOP"); + CREATE(net_wm_window_type_dock, "_NET_WM_WINDOW_TYPE_DOCK"); + CREATE(net_wm_window_type_toolbar, "_NET_WM_WINDOW_TYPE_TOOLBAR"); + CREATE(net_wm_window_type_menu, "_NET_WM_WINDOW_TYPE_MENU"); + CREATE(net_wm_window_type_utility, "_NET_WM_WINDOW_TYPE_UTILITY"); + CREATE(net_wm_window_type_splash, "_NET_WM_WINDOW_TYPE_SPLASH"); + CREATE(net_wm_window_type_dialog, "_NET_WM_WINDOW_TYPE_DIALOG"); + CREATE(net_wm_window_type_normal, "_NET_WM_WINDOW_TYPE_NORMAL"); + + CREATE(net_wm_moveresize_size_topleft, "_NET_WM_MOVERESIZE_SIZE_TOPLEFT"); + CREATE(net_wm_moveresize_size_topright, + "_NET_WM_MOVERESIZE_SIZE_TOPRIGHT"); + CREATE(net_wm_moveresize_size_bottomleft, + "_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT"); + CREATE(net_wm_moveresize_size_bottomright, + "_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT"); + CREATE(net_wm_moveresize_move, "_NET_WM_MOVERESIZE_MOVE"); + + CREATE(net_wm_action_move, "_NET_WM_ACTION_MOVE"); + CREATE(net_wm_action_resize, "_NET_WM_ACTION_RESIZE"); + CREATE(net_wm_action_minimize, "_NET_WM_ACTION_MINIMIZE"); + CREATE(net_wm_action_shade, "_NET_WM_ACTION_SHADE"); + CREATE(net_wm_action_stick, "_NET_WM_ACTION_STICK"); + CREATE(net_wm_action_maximize_horz, "_NET_WM_ACTION_MAXIMIZE_HORZ"); + CREATE(net_wm_action_maximize_vert, "_NET_WM_ACTION_MAXIMIZE_VERT"); + CREATE(net_wm_action_fullscreen, "_NET_WM_ACTION_FULLSCREEN"); + CREATE(net_wm_action_change_desktop, "_NET_WM_ACTION_CHANGE_DESKTOP"); + CREATE(net_wm_action_close, "_NET_WM_ACTION_CLOSE"); + CREATE(net_wm_state_modal, "_NET_WM_STATE_MODAL"); + CREATE(net_wm_state_sticky, "_NET_WM_STATE_STICKY"); + CREATE(net_wm_state_maximized_vert, "_NET_WM_STATE_MAXIMIZED_VERT"); + CREATE(net_wm_state_maximized_horz, "_NET_WM_STATE_MAXIMIZED_HORZ"); + CREATE(net_wm_state_shaded, "_NET_WM_STATE_SHADED"); + CREATE(net_wm_state_skip_taskbar, "_NET_WM_STATE_SKIP_TASKBAR"); + CREATE(net_wm_state_skip_pager, "_NET_WM_STATE_SKIP_PAGER"); + CREATE(net_wm_state_hidden, "_NET_WM_STATE_HIDDEN"); + CREATE(net_wm_state_fullscreen, "_NET_WM_STATE_FULLSCREEN"); + CREATE(net_wm_state_above, "_NET_WM_STATE_ABOVE"); + CREATE(net_wm_state_below, "_NET_WM_STATE_BELOW"); + + prop_atoms.net_wm_state_add = 1; + prop_atoms.net_wm_state_remove = 0; + prop_atoms.net_wm_state_toggle = 2; + + prop_atoms.net_wm_orientation_horz = 0; + prop_atoms.net_wm_orientation_vert = 1; + prop_atoms.net_wm_topleft = 0; + prop_atoms.net_wm_topright = 1; + prop_atoms.net_wm_bottomright = 2; + prop_atoms.net_wm_bottomleft = 3; + + CREATE(kde_net_system_tray_windows, "_KDE_NET_SYSTEM_TRAY_WINDOWS"); + CREATE(kde_net_wm_system_tray_window_for, + "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR"); + CREATE(kde_net_wm_window_type_override, + "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"); + + CREATE(kwm_win_icon, "KWM_WIN_ICON"); + + CREATE(rootpmapid, "_XROOTPMAP_ID"); + CREATE(esetrootid, "ESETROOT_PMAP_ID"); + + CREATE(openbox_pid, "_OPENBOX_PID"); + CREATE(openbox_premax, "_OPENBOX_PREMAX"); +} + +gboolean prop_get(Window win, Atom prop, Atom type, int size, + guchar **data, gulong num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + long num32 = 32 / size * num; /* num in 32-bit elements */ + + res = XGetWindowProperty(ob_display, win, prop, 0l, num32, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success && ret_items && xdata) { + if (ret_size == size && ret_items >= num) { + *data = g_memdup(xdata, num * (size / 8)); + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_prealloc(Window win, Atom prop, Atom type, int size, + guchar *data, gulong num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + long num32 = 32 / size * num; /* num in 32-bit elements */ + + res = XGetWindowProperty(ob_display, win, prop, 0l, num32, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success && ret_items && xdata) { + if (ret_size == size && ret_items >= num) { + gulong i; + for (i = 0; i < num; ++i) + switch (size) { + case 8: + data[i] = xdata[i]; + break; + case 16: + ((guint16*)data)[i] = ((guint16*)xdata)[i]; + break; + case 32: + ((guint32*)data)[i] = ((guint32*)xdata)[i]; + break; + default: + g_assert_not_reached(); /* unhandled size */ + } + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_all(Window win, Atom prop, Atom type, int size, + guchar **data, gulong *num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + + res = XGetWindowProperty(ob_display, win, prop, 0l, G_MAXLONG, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success) { + if (ret_size == size && ret_items > 0) { + *data = g_memdup(xdata, ret_items * (size / 8)); + *num = ret_items; + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_string(Window win, Atom prop, Atom type, guchar **data) +{ + guchar *raw; + gulong num; + GString *str; + + if (prop_get_all(win, prop, type, 8, &raw, &num)) { + str = g_string_new_len((char*)raw, num); + g_assert(str->str[num] == '\0'); + + g_free(raw); + + *data = (guchar*)g_string_free(str, FALSE); + return TRUE; + } + return FALSE; +} + +gboolean prop_get_strings(Window win, Atom prop, Atom type, + GPtrArray *data) +{ + guchar *raw; + gulong num; + GString *str, *str2; + guint i, start; + + if (prop_get_all(win, prop, type, 8, &raw, &num)) { + str = g_string_new_len((gchar*)raw, num); + g_assert(str->str[num] == '\0'); /* assuming this is always true.. */ + + g_free(raw); + + /* split it into the list */ + for (start = 0, i = 0; i < str->len; ++i) { + if (str->str[i] == '\0') { + str2 = g_string_new_len(&str->str[start], i - start); + g_ptr_array_add(data, g_string_free(str2, FALSE)); + start = i + 1; + } + } + g_string_free(str, TRUE); + + if (data->len > 0) + return TRUE; + } + return FALSE; +} + +void prop_erase(Window win, Atom prop) +{ + XDeleteProperty(ob_display, win, prop); +} + +void prop_message(Window about, Atom messagetype, long data0, long data1, + long data2, long data3) +{ + XEvent ce; + ce.xclient.type = ClientMessage; + ce.xclient.message_type = messagetype; + ce.xclient.display = ob_display; + ce.xclient.window = about; + ce.xclient.format = 32; + ce.xclient.data.l[0] = data0; + ce.xclient.data.l[1] = data1; + ce.xclient.data.l[2] = data2; + ce.xclient.data.l[3] = data3; + XSendEvent(ob_display, ob_root, FALSE, + SubstructureNotifyMask | SubstructureRedirectMask, &ce); +} diff --git a/openbox/prop.h b/openbox/prop.h new file mode 100644 index 00000000..c2de3b20 --- /dev/null +++ b/openbox/prop.h @@ -0,0 +1,214 @@ +#ifndef __atoms_h +#define __atoms_h + +#include +#include +#ifdef HAVE_STRING_H +# include +#endif + +#include "openbox.h" + +/*! The atoms on the X server which this class will cache */ +typedef struct Atoms { + /* types */ + Atom cardinal; /*!< The atom which represents the Cardinal data type */ + Atom window; /*!< The atom which represents window ids */ + Atom pixmap; /*!< The atom which represents pixmap ids */ + Atom atom; /*!< The atom which represents atom values */ + Atom string; /*!< The atom which represents ascii strings */ + Atom utf8; /*!< The atom which represents utf8-encoded strings */ + + /* window hints */ + Atom wm_colormap_windows; + Atom wm_protocols; + Atom wm_state; + Atom wm_delete_window; + Atom wm_take_focus; + Atom wm_change_state; + Atom wm_name; + Atom wm_icon_name; + Atom wm_class; + Atom wm_window_role; + Atom motif_wm_hints; + + /* NETWM atoms */ + + /* root window properties */ + Atom net_supported; + Atom net_client_list; + Atom net_client_list_stacking; + Atom net_number_of_desktops; + Atom net_desktop_geometry; + Atom net_desktop_viewport; + Atom net_current_desktop; + Atom net_desktop_names; + Atom net_active_window; + Atom net_workarea; + Atom net_supporting_wm_check; +/* Atom net_virtual_roots; */ + Atom net_desktop_layout; + Atom net_showing_desktop; + /* root window messages */ + Atom net_close_window; + Atom net_wm_moveresize; + /* application window properties */ +/* Atom net_properties; */ + Atom net_wm_name; + Atom net_wm_visible_name; + Atom net_wm_icon_name; + Atom net_wm_visible_icon_name; + Atom net_wm_desktop; + Atom net_wm_window_type; + Atom net_wm_state; + Atom net_wm_strut; +/* Atom net_wm_icon_geometry; */ + Atom net_wm_icon; +/* Atom net_wm_pid; */ +/* Atom net_wm_handled_icons; */ + Atom net_wm_allowed_actions; + /* application protocols */ +/* Atom Atom net_wm_ping; */ + + Atom net_wm_window_type_desktop; + Atom net_wm_window_type_dock; + Atom net_wm_window_type_toolbar; + Atom net_wm_window_type_menu; + Atom net_wm_window_type_utility; + Atom net_wm_window_type_splash; + Atom net_wm_window_type_dialog; + Atom net_wm_window_type_normal; + + Atom net_wm_moveresize_size_topleft; + Atom net_wm_moveresize_size_topright; + Atom net_wm_moveresize_size_bottomleft; + Atom net_wm_moveresize_size_bottomright; + Atom net_wm_moveresize_move; + + Atom net_wm_action_move; + Atom net_wm_action_resize; + Atom net_wm_action_minimize; + Atom net_wm_action_shade; + Atom net_wm_action_stick; + Atom net_wm_action_maximize_horz; + Atom net_wm_action_maximize_vert; + Atom net_wm_action_fullscreen; + Atom net_wm_action_change_desktop; + Atom net_wm_action_close; + + Atom net_wm_state_modal; + Atom net_wm_state_sticky; + Atom net_wm_state_maximized_vert; + Atom net_wm_state_maximized_horz; + Atom net_wm_state_shaded; + Atom net_wm_state_skip_taskbar; + Atom net_wm_state_skip_pager; + Atom net_wm_state_hidden; + Atom net_wm_state_fullscreen; + Atom net_wm_state_above; + Atom net_wm_state_below; + + Atom net_wm_state_add; + Atom net_wm_state_remove; + Atom net_wm_state_toggle; + + Atom net_wm_orientation_horz; + Atom net_wm_orientation_vert; + Atom net_wm_topleft; + Atom net_wm_topright; + Atom net_wm_bottomright; + Atom net_wm_bottomleft; + + /* Extra atoms */ + + Atom kde_net_system_tray_windows; + Atom kde_net_wm_system_tray_window_for; + Atom kde_net_wm_window_type_override; + + Atom kwm_win_icon; + + Atom rootpmapid; + Atom esetrootid; + + /* Openbox specific atoms */ + + Atom openbox_pid; + Atom openbox_premax; +} Atoms; +Atoms prop_atoms; + +void prop_startup(); + +gboolean prop_get(Window win, Atom prop, Atom type, int size, + guchar **data, gulong num); + +gboolean prop_get_prealloc(Window win, Atom prop, Atom type, int size, + guchar *data, gulong num); + +gboolean prop_get_all(Window win, Atom prop, Atom type, int size, + guchar **data, gulong *num); + +gboolean prop_get_string(Window win, Atom prop, Atom type, guchar **data); +gboolean prop_get_strings(Window win, Atom prop, Atom type, + GPtrArray *data); + +void prop_set_strings(Window win, Atom prop, Atom type, GPtrArray *data); + +void prop_erase(Window win, Atom prop); + +void prop_message(Window about, Atom messagetype, long data0, long data1, + long data2, long data3); + +#define PROP_MSG(about, msgtype, data0, data1, data2, data3) \ + (prop_message(about, prop_atoms.msgtype, data0, data1, data2, data3)) + +/* Set an 8-bit property from a string */ +#define PROP_SETS(win, prop, type, value) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 8, \ + PropModeReplace, (guchar*)value, strlen(value))) +/* Set an 8-bit property array from a GPtrArray of strings */ +#define PROP_SETSA(win, prop, type, value) \ + (prop_set_strings(win, prop_atoms.prop, prop_atoms.type, value)) + +/* Set a 32-bit property from a single value */ +#define PROP_SET32(win, prop, type, value) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 32, \ + PropModeReplace, (guchar*)&value, 1)) +/* Set a 32-bit property from an array */ +#define PROP_SET32A(win, prop, type, value, num) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 32, \ + PropModeReplace, (guchar*)value, num)) + +/* Get an 8-bit property into a string */ +#define PROP_GETS(win, prop, type, value) \ + (prop_get_string(win, prop_atoms.prop, prop_atoms.type, \ + (guchar**)&value)) +/* Get an 8-bit property into a GPtrArray of strings + (The strings must be freed, the GPtrArray must already be created.) */ +#define PROP_GETSA(win, prop, type, value) \ + (prop_get_strings(win, prop_atoms.prop, prop_atoms.type, \ + value)) + +/* Get an entire 8-bit property into an array (which must be freed) */ +#define PROP_GET8U(win, prop, type, value, num) \ + (prop_get_all(win, prop_atoms.prop, prop_atoms.type, 8, \ + (guchar**)&value, &num)) + +/* Get 1 element of a 32-bit property into a given variable */ +#define PROP_GET32(win, prop, type, value) \ + (prop_get_prealloc(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar*)&value, 1)) + +/* Get an amount of a 32-bit property into an array (which must be freed) */ +#define PROP_GET32A(win, prop, type, value, num) \ + (prop_get(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar**)&value, num)) + +/* Get an entire 32-bit property into an array (which must be freed) */ +#define PROP_GET32U(win, prop, type, value, num) \ + (prop_get_all(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar**)&value, &num)) + +#define PROP_ERASE(win, prop) (prop_erase(win, prop_atoms.prop)) + +#endif diff --git a/openbox/python.c b/openbox/python.c new file mode 100644 index 00000000..52f19f3f --- /dev/null +++ b/openbox/python.c @@ -0,0 +1,60 @@ +#include +#include + +static PyMethodDef ObMethods[] = { + { NULL, NULL, 0, NULL } +}; + +static PyMethodDef InputMethods[] = { + { NULL, NULL, 0, NULL } +}; + +void python_startup() +{ + PyObject *sys, *sysdict, *syspath, *path1, *path2; + char *homescriptdir; + + Py_Initialize(); + + /* fix up the system path */ + + sys = PyImport_ImportModule("sys"); /* new */ + sysdict = PyModule_GetDict(sys); /* borrowed */ + syspath = PyDict_GetItemString(sysdict, "path"); /* borrowed */ + + path1 = PyString_FromString(SCRIPTDIR); /* new */ + PyList_Insert(syspath, 0, path1); + Py_DECREF(path1); + + homescriptdir = g_build_filename(g_get_home_dir(), ".openbox", NULL); + path2 = PyString_FromString(homescriptdir); /* new */ + PyList_Insert(syspath, 0, path2); + Py_DECREF(path2); + g_free(homescriptdir); + + Py_DECREF(sys); + + /* create the 'ob' module */ + Py_InitModule("ob", ObMethods); + + /* create the 'input' module */ + Py_InitModule("input", InputMethods); +} + +void python_shutdown() +{ + Py_Finalize(); +} + +gboolean python_import(char *module) +{ + PyObject *mod; + + mod = PyImport_ImportModule(module); /* new */ + if (mod == NULL) { + PyErr_Print(); + return FALSE; + } + Py_DECREF(mod); + return TRUE; +} diff --git a/openbox/python.h b/openbox/python.h new file mode 100644 index 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 +#ifdef HAVE_UNISTD_H +# include +# include +#endif + +/*! The event mask to grab on the root window */ +#define ROOT_EVENTMASK (/*ColormapChangeMask |*/ PropertyChangeMask | \ + EnterWindowMask | LeaveWindowMask | \ + SubstructureNotifyMask | SubstructureRedirectMask | \ + ButtonPressMask | ButtonReleaseMask | ButtonMotionMask) + +guint screen_num_desktops = 1; +guint screen_desktop = 0; +Size screen_physical_size; +gboolean screen_showing_desktop; +DesktopLayout screen_desktop_layout; +GPtrArray *screen_desktop_names; + +static Rect *area = NULL; +static Strut *strut = NULL; + +static void screen_update_area(); + +static gboolean running; +static int another_running(Display *d, XErrorEvent *e) +{ + (void)d;(void)e; + g_message("A window manager is already running on screen %d", + ob_screen); + running = TRUE; + return -1; +} + +gboolean screen_annex() +{ + XErrorHandler old; + Window support; + pid_t pid; + int i, num_support; + Atom *supported; + + running = FALSE; + old = XSetErrorHandler(another_running); + XSelectInput(ob_display, ob_root, ROOT_EVENTMASK); + XSync(ob_display, FALSE); + XSetErrorHandler(old); + if (running) + return FALSE; + + g_message("Managing screen %d", ob_screen); + + /* set the mouse cursor for the root window (the default cursor) */ + XDefineCursor(ob_display, ob_root, ob_cursors.left_ptr); + + /* set the OPENBOX_PID hint */ + pid = getpid(); + PROP_SET32(ob_root, openbox_pid, cardinal, pid); + + /* create the netwm support window */ + support = XCreateSimpleWindow(ob_display, ob_root, 0, 0, 1, 1, 0, 0, 0); + + /* set supporting window */ + PROP_SET32(ob_root, net_supporting_wm_check, window, support); + + /* set properties on the supporting window */ + PROP_SETS(support, net_wm_name, utf8, "Openbox"); + PROP_SET32(support, net_supporting_wm_check, window, support); + + /* set the _NET_SUPPORTED_ATOMS hint */ + num_support = 48; + i = 0; + supported = g_new(Atom, num_support); + supported[i++] = prop_atoms.net_current_desktop; + supported[i++] = prop_atoms.net_number_of_desktops; + supported[i++] = prop_atoms.net_desktop_geometry; + supported[i++] = prop_atoms.net_desktop_viewport; + supported[i++] = prop_atoms.net_active_window; + supported[i++] = prop_atoms.net_workarea; + supported[i++] = prop_atoms.net_client_list; + supported[i++] = prop_atoms.net_client_list_stacking; + supported[i++] = prop_atoms.net_desktop_names; + supported[i++] = prop_atoms.net_close_window; + supported[i++] = prop_atoms.net_desktop_layout; + supported[i++] = prop_atoms.net_showing_desktop; + supported[i++] = prop_atoms.net_wm_name; + supported[i++] = prop_atoms.net_wm_visible_name; + supported[i++] = prop_atoms.net_wm_icon_name; + supported[i++] = prop_atoms.net_wm_visible_icon_name; + supported[i++] = prop_atoms.net_wm_desktop; + supported[i++] = prop_atoms.net_wm_strut; + supported[i++] = prop_atoms.net_wm_window_type; + supported[i++] = prop_atoms.net_wm_window_type_desktop; + supported[i++] = prop_atoms.net_wm_window_type_dock; + supported[i++] = prop_atoms.net_wm_window_type_toolbar; + supported[i++] = prop_atoms.net_wm_window_type_menu; + supported[i++] = prop_atoms.net_wm_window_type_utility; + supported[i++] = prop_atoms.net_wm_window_type_splash; + supported[i++] = prop_atoms.net_wm_window_type_dialog; + supported[i++] = prop_atoms.net_wm_window_type_normal; + supported[i++] = prop_atoms.net_wm_allowed_actions; + supported[i++] = prop_atoms.net_wm_action_move; + supported[i++] = prop_atoms.net_wm_action_resize; + supported[i++] = prop_atoms.net_wm_action_minimize; + supported[i++] = prop_atoms.net_wm_action_shade; + supported[i++] = prop_atoms.net_wm_action_maximize_horz; + supported[i++] = prop_atoms.net_wm_action_maximize_vert; + supported[i++] = prop_atoms.net_wm_action_fullscreen; + supported[i++] = prop_atoms.net_wm_action_change_desktop; + supported[i++] = prop_atoms.net_wm_action_close; + supported[i++] = prop_atoms.net_wm_state; + supported[i++] = prop_atoms.net_wm_state_modal; + supported[i++] = prop_atoms.net_wm_state_maximized_vert; + supported[i++] = prop_atoms.net_wm_state_maximized_horz; + supported[i++] = prop_atoms.net_wm_state_shaded; + supported[i++] = prop_atoms.net_wm_state_skip_taskbar; + supported[i++] = prop_atoms.net_wm_state_skip_pager; + supported[i++] = prop_atoms.net_wm_state_hidden; + supported[i++] = prop_atoms.net_wm_state_fullscreen; + supported[i++] = prop_atoms.net_wm_state_above; + supported[i++] = prop_atoms.net_wm_state_below; + g_assert(i == num_support); +/* + supported[] = prop_atoms.net_wm_moveresize; + supported[] = prop_atoms.net_wm_moveresize_size_topleft; + supported[] = prop_atoms.net_wm_moveresize_size_topright; + supported[] = prop_atoms.net_wm_moveresize_size_bottomleft; + supported[] = prop_atoms.net_wm_moveresize_size_bottomright; + supported[] = prop_atoms.net_wm_moveresize_move; + supported[] = prop_atoms.net_wm_action_stick; +*/ + + PROP_SET32A(ob_root, net_supported, atom, supported, num_support); + g_free(supported); + + return TRUE; +} + +void screen_startup() +{ + screen_desktop_names = g_ptr_array_new(); + + /* get the initial size */ + screen_resize(); + + screen_set_num_desktops(4); + screen_set_desktop(0); + + /* don't start in showing-desktop mode */ + screen_showing_desktop = FALSE; + PROP_SET32(ob_root, net_showing_desktop, cardinal, screen_showing_desktop); + + screen_update_layout(); +} + +void screen_shutdown() +{ + guint i; + for (i = 0; i < screen_desktop_names->len; ++i) + g_free(g_ptr_array_index(screen_desktop_names, i)); + g_ptr_array_free(screen_desktop_names, TRUE); + g_free(strut); + g_free(area); +} + +void screen_resize() +{ + /* Set the _NET_DESKTOP_GEOMETRY hint */ + /* XXX RandR support here? */ + int geometry[2]; + + geometry[0] = WidthOfScreen(ScreenOfDisplay(ob_display, ob_screen)); + geometry[1] = HeightOfScreen(ScreenOfDisplay(ob_display, ob_screen)); + PROP_SET32A(ob_root, net_desktop_geometry, cardinal, geometry, 2); + screen_physical_size.width = geometry[0]; + screen_physical_size.height = geometry[1]; + + if (ob_state == State_Starting) + return; + + screen_update_struts(); + + /* XXX adjust more stuff ? */ +} + +void screen_set_num_desktops(guint num) +{ + unsigned long *viewport; + + g_assert(num > 0); + + /* move windows on desktops that will no longer exist! */ + /* XXX + std::list::iterator it, end = clients.end(); + for (it = clients.begin(); it != end; ++it) { + unsigned int d = (*it)->desktop(); + if (d >= num && d != 0xffffffff) { + XEvent ce; + ce.xclient.type = ClientMessage; + ce.xclient.message_type = otk::Property::atoms.net_wm_desktop; + ce.xclient.display = **otk::display; + ce.xclient.window = (*it)->window(); + ce.xclient.format = 32; + ce.xclient.data.l[0] = num - 1; + XSendEvent(**otk::display, _info->rootWindow(), false, + SubstructureNotifyMask | SubstructureRedirectMask, &ce); + } + } + */ + + screen_num_desktops = num; + PROP_SET32(ob_root, net_number_of_desktops, cardinal, num); + + /* set the viewport hint */ + viewport = g_new0(unsigned long, num * 2); + PROP_SET32A(ob_root, net_desktop_viewport, cardinal, viewport, num * 2); + g_free(viewport); + + /* change our struts/area to match */ + screen_update_struts(); + + /* the number of rows/columns will differ */ + screen_update_layout(); + + /* may be some unnamed desktops that we need to fill in with names */ + screen_update_desktop_names(); + + /* change our desktop if we're on one that no longer exists! */ + if (screen_desktop >= screen_num_desktops) + screen_set_desktop(num - 1); +} + +void screen_set_desktop(guint num) +{ + GList *it; + + guint old = screen_desktop; + + g_assert(num < screen_num_desktops); + + g_message("Moving to desktop %u", num); + + screen_desktop = num; + PROP_SET32(ob_root, net_current_desktop, cardinal, num); + + if (old == num) return; + + for (it = stacking_list; it != NULL; it = it->next) + client_showhide(it->data, FALSE); + + /* force the callbacks to fire */ + if (focus_client == NULL) + focus_set_client(NULL); +} + +void screen_update_layout() +{ + unsigned long *data = NULL; + + /* defaults */ + screen_desktop_layout.orientation = prop_atoms.net_wm_orientation_horz; + screen_desktop_layout.start_corner = prop_atoms.net_wm_topleft; + screen_desktop_layout.rows = 1; + screen_desktop_layout.columns = screen_num_desktops; + + if (PROP_GET32A(ob_root, net_desktop_layout, cardinal, data, 4)) { + if (data[0] == prop_atoms.net_wm_orientation_vert) + screen_desktop_layout.orientation = data[0]; + if (data[3] == prop_atoms.net_wm_topright) + screen_desktop_layout.start_corner = data[3]; + else if (data[3] == prop_atoms.net_wm_bottomright) + screen_desktop_layout.start_corner = data[3]; + else if (data[3] == prop_atoms.net_wm_bottomleft) + screen_desktop_layout.start_corner = data[3]; + + /* fill in a zero rows/columns */ + if (!(data[1] == 0 && data[2] == 0)) { /* both 0's is bad data.. */ + if (data[1] == 0) { + data[1] = (screen_num_desktops + + screen_num_desktops % data[2]) / data[2]; + } else if (data[2] == 0) { + data[2] = (screen_num_desktops + + screen_num_desktops % data[1]) / data[1]; + } + screen_desktop_layout.columns = data[1]; + screen_desktop_layout.rows = data[2]; + } + + /* bounds checking */ + if (screen_desktop_layout.orientation == + prop_atoms.net_wm_orientation_horz) { + if (screen_desktop_layout.rows > screen_num_desktops) + screen_desktop_layout.rows = screen_num_desktops; + if (screen_desktop_layout.columns > ((screen_num_desktops + + screen_num_desktops % + screen_desktop_layout.rows) / + screen_desktop_layout.rows)) + screen_desktop_layout.columns = + (screen_num_desktops + screen_num_desktops % + screen_desktop_layout.rows) / + screen_desktop_layout.rows; + } else { + if (screen_desktop_layout.columns > screen_num_desktops) + screen_desktop_layout.columns = screen_num_desktops; + if (screen_desktop_layout.rows > ((screen_num_desktops + + screen_num_desktops % + screen_desktop_layout.columns) / + screen_desktop_layout.columns)) + screen_desktop_layout.rows = + (screen_num_desktops + screen_num_desktops % + screen_desktop_layout.columns) / + screen_desktop_layout.columns; + } + g_free(data); + } +} + +void screen_update_desktop_names() +{ + guint i; + + /* empty the array */ + for (i = 0; i < screen_desktop_names->len; ++i) + g_free(g_ptr_array_index(screen_desktop_names, i)); + g_ptr_array_set_size(screen_desktop_names, 0); + + PROP_GETSA(ob_root, net_desktop_names, utf8, screen_desktop_names); + + while (screen_desktop_names->len < screen_num_desktops) + g_ptr_array_add(screen_desktop_names, g_strdup("Unnamed Desktop")); +} + +void screen_show_desktop(gboolean show) +{ + GList *it; + static Window saved_focus = 0; + + if (show == screen_showing_desktop) return; /* no change */ + + /* save the window focus, and restore it when leaving the show-desktop + mode */ + if (show && focus_client) + saved_focus = focus_client->window; + + screen_showing_desktop = show; + + if (show) { + /* bottom to top */ + for (it = g_list_last(stacking_list); it != NULL; it = it->prev) { + Client *client = it->data; + if (client->type == Type_Desktop) + client_focus(client); + else + client_showhide(client, FALSE); + } + } else { + /* top to bottom */ + for (it = stacking_list; it != NULL; it = it->next) { + Client *client = it->data; + if (client->type != Type_Desktop) + client_showhide(client, FALSE); + } + } + + if (!show) { + Client *f = focus_client; + if (!f || f->type == Type_Desktop) { + Client *c = g_hash_table_lookup(client_map, + (gpointer)saved_focus); + if (c) client_focus(c); + } + } + + show = show ? 1 : 0; /* make it boolean */ + PROP_SET32(ob_root, net_showing_desktop, cardinal, show); +} + +void screen_install_colormap(Client *client, gboolean install) +{ + if (client == NULL) { + /* XXX DONT USE THE DEFAULT SHIT HERE */ + if (install) + XInstallColormap(ob_display, + DefaultColormap(ob_display, ob_screen)); + else + XUninstallColormap(ob_display, + DefaultColormap(ob_display, ob_screen)); + } else { + XWindowAttributes wa; + if (XGetWindowAttributes(ob_display, client->window, &wa)) { + if (install) + XInstallColormap(ob_display, wa.colormap); + else + XUninstallColormap(ob_display, wa.colormap); + } + } +} + +void screen_update_struts() +{ + GSList *it; + guint i; + + if (strut != NULL) + g_free(strut); + strut = g_new0(Strut, screen_num_desktops + 1); + + for (it = client_list; it; it = it->next) { + Client *c = it->data; + if (c->iconic) continue; /* these dont count in the strut */ + + if (c->desktop == 0xffffffff) { + for (i = 0; i < screen_num_desktops; ++i) + STRUT_ADD(strut[i], c->strut); + } else { + g_assert(c->desktop < screen_num_desktops); + STRUT_ADD(strut[c->desktop], c->strut); + } + /* apply to the 'all desktops' strut */ + STRUT_ADD(strut[screen_num_desktops], c->strut); + } + screen_update_area(); +} + +static void screen_update_area() +{ + guint i; + gulong *dims; + + if (area != NULL) + g_free(area); + area = g_new0(Rect, screen_num_desktops + 1); + + dims = g_new(unsigned long, 4 * screen_num_desktops); + for (i = 0; i < screen_num_desktops + 1; ++i) { + Rect old_area = area[i]; +/* + #ifdef XINERAMA + // reset to the full areas + if (isXineramaActive()) + xineramaUsableArea = getXineramaAreas(); + #endif // XINERAMA +*/ + + RECT_SET(area[i], strut[i].left, strut[i].top, + screen_physical_size.width - (strut[i].left + + strut[i].right), + screen_physical_size.height - (strut[i].top + + strut[i].bottom)); + +/* + #ifdef XINERAMA + if (isXineramaActive()) { + // keep each of the ximerama-defined areas inside the strut + RectList::iterator xit, xend = xineramaUsableArea.end(); + for (xit = xineramaUsableArea.begin(); xit != xend; ++xit) { + if (xit->x() < usableArea.x()) { + xit->setX(usableArea.x()); + xit->setWidth(xit->width() - usableArea.x()); + } + if (xit->y() < usableArea.y()) { + xit->setY(usableArea.y()); + xit->setHeight(xit->height() - usableArea.y()); + } + if (xit->x() + xit->width() > usableArea.width()) + xit->setWidth(usableArea.width() - xit->x()); + if (xit->y() + xit->height() > usableArea.height()) + xit->setHeight(usableArea.height() - xit->y()); + } + } + #endif // XINERAMA +*/ + if (!RECT_EQUAL(old_area, area[i])) { + /* the area has changed, adjust all the maximized windows */ + GSList *it; + for (it = client_list; it; it = it->next) { + Client *c = it->data; + if (i < screen_num_desktops) { + if (c->desktop == i) + client_remaximize(c); + } else { + /* the 'all desktops' size */ + if (c->desktop == DESKTOP_ALL) + client_remaximize(c); + } + } + } + + /* don't set these for the 'all desktops' area */ + if (i < screen_num_desktops) { + dims[(i * 4) + 0] = area[i].x; + dims[(i * 4) + 1] = area[i].y; + dims[(i * 4) + 2] = area[i].width; + dims[(i * 4) + 3] = area[i].height; + } + } + PROP_SET32A(ob_root, net_workarea, cardinal, + dims, 4 * screen_num_desktops); + g_free(dims); +} + +Rect *screen_area(guint desktop) +{ + if (desktop >= screen_num_desktops) { + if (desktop == DESKTOP_ALL) + return &area[screen_num_desktops]; + return NULL; + } + return &area[desktop]; +} + +Strut *screen_strut(guint desktop) +{ + if (desktop >= screen_num_desktops) { + if (desktop == DESKTOP_ALL) + return &strut[screen_num_desktops]; + return NULL; + } + return &strut[desktop]; +} diff --git a/openbox/screen.h b/openbox/screen.h new file mode 100644 index 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 + +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 + +struct Client; + +/*! The possible stacking layers a client window can be a part of */ +typedef enum { + Layer_Icon, /*!< 0 - iconified windows, in any order at all */ + Layer_Desktop, /*!< 1 - desktop windows */ + Layer_Below, /*!< 2 - normal windows w/ below */ + Layer_Normal, /*!< 3 - normal windows */ + Layer_Above, /*!< 4 - normal windows w/ above */ + Layer_Top, /*!< 5 - always-on-top-windows (docks?) */ + Layer_Fullscreen, /*!< 6 - fullscreeen windows */ + Layer_Internal /*!< 7 - openbox windows/menus */ +} StackLayer; + +/* list of Client*s in stacking order from highest to lowest */ +extern GList *stacking_list; + +/*! Sets the client stacking list on the root window from the + stacking_clientlist */ +void stacking_set_list(); + +/*! Raises a client window above all others in its stacking layer + raiseWindow has a couple of constraints that lowerWindow does not.
+ 1) raiseWindow can be called after changing a Client's stack layer, and + the list will be reorganized properly.
+ 2) raiseWindow guarantees that XRestackWindows() will always be + called for the specified client. +*/ +void stacking_raise(struct Client *client); + +/*! Lowers a client window below all others in its stacking layer */ +void stacking_lower(struct Client *client); + +#endif diff --git a/openbox/themerc.c b/openbox/themerc.c new file mode 100644 index 00000000..7f587afe --- /dev/null +++ b/openbox/themerc.c @@ -0,0 +1,156 @@ +#include +#ifdef HAVE_STRING_H +# include +#endif + +char *themerc_engine; +char *themerc_theme; +char *themerc_font; +char *themerc_titlebar_layout; + +GError *error; + +static void parse(char *path, int fd) +{ + GScanner *scanner; + + scanner = g_scanner_new(NULL); + g_scanner_input_file(scanner, fd); + + while (g_scanner_get_next_token(scanner) != G_TOKEN_EOF) { + char *name, *val; + + if (scanner->token != G_TOKEN_IDENTIFIER) { + g_scanner_unexp_token(scanner, scanner->token, NULL, NULL, NULL, + NULL, TRUE); + return; + } + name = g_strdup(scanner->value.v_identifier); + + g_scanner_get_next_token(scanner); + if (scanner->token != G_TOKEN_EQUAL_SIGN) { + g_scanner_unexp_token(scanner, scanner->token, NULL, NULL, NULL, + NULL, TRUE); + g_free(name); + return; + } + + g_scanner_get_next_token(scanner); + if (scanner->token == G_TOKEN_STRING) { + val = g_strdup(scanner->value.v_identifier); + + if (!g_ascii_strcasecmp(name, "engine")) { + if (themerc_engine != NULL) { + g_warning("%s:%d: '%s' already defined", path, + scanner->line, name); + g_free(name); + g_free(val); + } else + themerc_engine = val; + } else if (!g_ascii_strcasecmp(name, "theme")) { + if (themerc_theme != NULL) { + g_warning("%s:%d: '%s' already defined", path, + scanner->line, name); + g_free(name); + g_free(val); + } else + themerc_theme = val; + } else if (!g_ascii_strcasecmp(name, "font")) { + if (themerc_font != NULL) { + g_warning("%s:%d: '%s' already defined", path, + scanner->line, name); + g_free(name); + g_free(val); + } else + themerc_font = val; + } else if (!g_ascii_strcasecmp(name, "titlebarlayout")) { + if (themerc_titlebar_layout != NULL) { + g_warning("%s:%d: '%s' already defined", path, + scanner->line, name); + g_free(name); + g_free(val); + } else { + char *lowval = g_ascii_strup(val, -1); + int i, len = strlen(lowval); + g_free(val); + for (i = 0; i < len; ++i) { + gboolean valid = FALSE; + switch(lowval[i]) { + case 'I': + case 'L': + case 'M': + case 'C': + case 'N': + case 'D': + valid = TRUE; + } + if (!valid) { + g_warning("%s:%d: invalid titlebarlayout element " + "'%c'", path, scanner->line, lowval[i]); + break; + } + } + if (i == len) + themerc_titlebar_layout = lowval; + else { + g_free(name); + g_free(val); + } + } + } else { + g_warning("%s:%d: invalid option '%s'", path, + scanner->line, name); + g_free(name); + g_free(val); + } + } else { + g_scanner_unexp_token(scanner, scanner->token, NULL, NULL, NULL, + NULL, TRUE); + g_free(name); + return; + } + } +} + +void themerc_startup() +{ + GIOChannel *chan = NULL; + char *path = NULL; + + /* defaults */ + themerc_engine = NULL; + themerc_theme = NULL; + themerc_font = NULL; + themerc_titlebar_layout = NULL; + + path = g_build_filename(g_get_home_dir(), ".openbox", "themerc", NULL); + error = NULL; + chan = g_io_channel_new_file(path, "r", &error); + + if (chan == NULL) { + g_free(path); + path = g_build_filename(THEMERCDIR, "themerc", NULL); + error = NULL; + chan = g_io_channel_new_file(path, "r", &error); + } + + if (chan != NULL) { + parse(path, g_io_channel_unix_get_fd(chan)); + g_free(path); + g_io_channel_close(chan); + } + + /* non-NULL defaults */ + if (themerc_titlebar_layout == NULL) + themerc_titlebar_layout = g_strdup("NDLIMC"); + if (themerc_font == NULL) + themerc_font = g_strdup("sans-8"); +} + +void themerc_shutdown() +{ + if (themerc_engine != NULL) g_free(themerc_engine); + if (themerc_theme != NULL) g_free(themerc_theme); + if (themerc_font != NULL) g_free(themerc_font); + if (themerc_titlebar_layout != NULL) g_free(themerc_titlebar_layout); +} diff --git a/openbox/themerc.h b/openbox/themerc.h new file mode 100644 index 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 +#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 + +/*! 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 +#include + +static gboolean xerror_ignore = FALSE; + +int xerror_handler(Display *d, XErrorEvent *e) +{ +#ifdef DEBUG + if (!xerror_ignore) { + char errtxt[128]; + + /*if (e->error_code != BadWindow) */ + { + XGetErrorText(d, e->error_code, errtxt, 127); + if (e->error_code == BadWindow) + g_warning("X Error: %s", errtxt); + else + g_error("X Error: %s", errtxt); + } + } +#else + (void)d; (void)e; +#endif + return 0; +} + +void xerror_set_ignore(gboolean ignore) +{ + XSync(ob_display, FALSE); + xerror_ignore = ignore; +} diff --git a/openbox/xerror.h b/openbox/xerror.h new file mode 100644 index 00000000..74b236fb --- /dev/null +++ b/openbox/xerror.h @@ -0,0 +1,11 @@ +#ifndef __xerror_h +#define __xerror_h + +#include +#include + +int xerror_handler(Display *, XErrorEvent *); + +void xerror_set_ignore(gboolean ignore); + +#endif -- cgit v1.2.3