diff options
| author | Dana Jansens <danakj@orodu.net> | 2003-03-16 21:11:39 +0000 |
|---|---|---|
| committer | Dana Jansens <danakj@orodu.net> | 2003-03-16 21:11:39 +0000 |
| commit | f8a47de5ec444c452093371e3db16857eb39a490 (patch) | |
| tree | 31db2567842d98232775f9980f7a8d2586c0ac71 /c | |
| parent | 8ba0586bcbdc7fe9648f1063812126d71a041670 (diff) | |
merge the C branch into HEAD
Diffstat (limited to 'c')
| -rw-r--r-- | c/.cvsignore | 5 | ||||
| -rw-r--r-- | c/Makefile.am | 34 | ||||
| -rw-r--r-- | c/client.c | 1869 | ||||
| -rw-r--r-- | c/client.h | 401 | ||||
| -rw-r--r-- | c/clientwrap.c | 792 | ||||
| -rw-r--r-- | c/clientwrap.h | 19 | ||||
| -rw-r--r-- | c/event.c | 593 | ||||
| -rw-r--r-- | c/event.h | 12 | ||||
| -rw-r--r-- | c/eventdata.c | 433 | ||||
| -rw-r--r-- | c/eventdata.h | 74 | ||||
| -rw-r--r-- | c/extensions.c | 34 | ||||
| -rw-r--r-- | c/extensions.h | 33 | ||||
| -rw-r--r-- | c/focus.c | 56 | ||||
| -rw-r--r-- | c/focus.h | 20 | ||||
| -rw-r--r-- | c/frame.c | 533 | ||||
| -rw-r--r-- | c/frame.h | 101 | ||||
| -rw-r--r-- | c/geom.h | 53 | ||||
| -rw-r--r-- | c/gettext.h | 73 | ||||
| -rw-r--r-- | c/hooks.c | 285 | ||||
| -rw-r--r-- | c/hooks.h | 23 | ||||
| -rw-r--r-- | c/kbind.c | 354 | ||||
| -rw-r--r-- | c/kbind.h | 23 | ||||
| -rw-r--r-- | c/mbind.c | 220 | ||||
| -rw-r--r-- | c/mbind.h | 21 | ||||
| -rw-r--r-- | c/obexport.c | 116 | ||||
| -rw-r--r-- | c/obexport.h | 74 | ||||
| -rw-r--r-- | c/openbox.c | 203 | ||||
| -rw-r--r-- | c/openbox.h | 35 | ||||
| -rw-r--r-- | c/prop.c | 271 | ||||
| -rw-r--r-- | c/prop.h | 202 | ||||
| -rw-r--r-- | c/python.c | 55 | ||||
| -rw-r--r-- | c/python.h | 10 | ||||
| -rw-r--r-- | c/screen.c | 516 | ||||
| -rw-r--r-- | c/screen.h | 66 | ||||
| -rw-r--r-- | c/screenwrap.c | 433 | ||||
| -rw-r--r-- | c/screenwrap.h | 14 | ||||
| -rw-r--r-- | c/stacking.c | 116 | ||||
| -rw-r--r-- | c/stacking.h | 26 | ||||
| -rw-r--r-- | c/xerror.c | 32 | ||||
| -rw-r--r-- | c/xerror.h | 11 |
40 files changed, 8241 insertions, 0 deletions
diff --git a/c/.cvsignore b/c/.cvsignore new file mode 100644 index 00000000..250363e4 --- /dev/null +++ b/c/.cvsignore @@ -0,0 +1,5 @@ +ob3 +Makefile.in +.libs +.deps +Makefile diff --git a/c/Makefile.am b/c/Makefile.am new file mode 100644 index 00000000..cfde7b63 --- /dev/null +++ b/c/Makefile.am @@ -0,0 +1,34 @@ +localedir=$(datadir)/locale +scriptdir = $(libdir)/openbox/python + +CPPFLAGS=$(PYTHON_CFLAGS) $(GLIB_CFLAGS) @CPPFLAGS@ \ +-DLOCALEDIR=\"$(localedir)\" \ +-DSCRIPTDIR=\"$(scriptdir)\" \ +-DG_LOG_DOMAIN=\"Openbox\" + +LIBS=$(PYTHON_LIBS) $(GLIB_LIBS) @LIBS@ + +bin_PROGRAMS= ob3 + +ob3_LDADD=@LIBINTL@ +ob3_LDFLAGS=-export-dynamic +ob3_SOURCES=client.c event.c extensions.c focus.c frame.c openbox.c prop.c \ + python.c screen.c stacking.c xerror.c hooks.c eventdata.c obexport.c \ + clientwrap.c screenwrap.c kbind.c mbind.c + +noinst_HEADERS=client.h event.h extensions.h focus.h frame.h geom.h gettext.h \ + openbox.h prop.h python.h screen.h stacking.h xerror.h hooks.h \ + eventdata.h clientwrap.h obexport.h screenwrap.h kbind.h mbind.h + +MAINTAINERCLEANFILES= Makefile.in + +#if CVS +#ob3.i: $(wildcard *.h) +# @touch $@ + +#ob3_wrap.c: ob3.i +# $(SWIG) $(SWIG_PYTHON_OPT) $(filter -I%,$(CPPFLAGS)) -o $@ $< +#endif + +distclean-local: + $(RM) *\~ *.orig *.rej .\#* diff --git a/c/client.c b/c/client.c new file mode 100644 index 00000000..995cba92 --- /dev/null +++ b/c/client.c @@ -0,0 +1,1869 @@ +#include "client.h" +#include "screen.h" +#include "prop.h" +#include "extensions.h" +#include "frame.h" +#include "event.h" +#include "focus.h" +#include "clientwrap.h" +#include "stacking.h" +#include "hooks.h" +#include "mbind.h" +#include <X11/Xutil.h> + +/*! The event mask to grab on client windows */ +#define CLIENT_EVENTMASK (PropertyChangeMask | FocusChangeMask | \ + StructureNotifyMask) + +#define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \ + ButtonMotionMask) + +GSList *client_list = NULL; +GHashTable *client_map = NULL; + +static void client_get_all(Client *self); +static void client_toggle_border(Client *self, gboolean show); +static void client_get_area(Client *self); +static void client_get_desktop(Client *self); +static void client_get_state(Client *self); +static void client_get_shaped(Client *self); +static void client_get_mwm_hints(Client *self); +static void client_get_gravity(Client *self); +static void client_change_allowed_actions(Client *self); +static void client_change_state(Client *self); +static Client *search_focus_tree(Client *node, Client *skip); +static void client_apply_startup_state(Client *self); +static Client *search_modal_tree(Client *node, Client *skip); + +static guint map_hash(Window w) { return w; } +static gboolean map_key_comp(Window w1, Window w2) { return w1 == w2; } + +void client_startup() +{ + client_map = g_hash_table_new((GHashFunc)map_hash, + (GEqualFunc)map_key_comp); + client_set_list(); +} + +void client_shutdown() +{ + g_hash_table_destroy(client_map); +} + +void client_set_list() +{ + Window *windows, *win_it; + GSList *it; + guint size = g_slist_length(client_list); + + /* create an array of the window ids */ + if (size > 0) { + windows = g_new(Window, size); + win_it = windows; + for (it = client_list; it != NULL; it = it->next, ++win_it) + *win_it = ((Client*)it->data)->window; + } else + windows = NULL; + + PROP_SET32A(ob_root, net_client_list, window, windows, size); + + if (windows) + g_free(windows); + + stacking_set_list(); +} + +void client_manage_all() +{ + unsigned int i, j, nchild; + Window w, *children; + XWMHints *wmhints; + XWindowAttributes attrib; + + XQueryTree(ob_display, ob_root, &w, &w, &children, &nchild); + + /* remove all icon windows from the list */ + for (i = 0; i < nchild; i++) { + if (children[i] == None) continue; + wmhints = XGetWMHints(ob_display, children[i]); + if (wmhints) { + if ((wmhints->flags & IconWindowHint) && + (wmhints->icon_window != children[i])) + for (j = 0; j < nchild; j++) + if (children[j] == wmhints->icon_window) { + children[j] = None; + break; + } + XFree(wmhints); + } + } + + for (i = 0; i < nchild; ++i) { + if (children[i] == None) + continue; + if (XGetWindowAttributes(ob_display, children[i], &attrib)) { + if (attrib.override_redirect) continue; + + if (attrib.map_state != IsUnmapped) + client_manage(children[i]); + } + } + XFree(children); +} + +void client_manage(Window window) +{ + Client *client; + XEvent e; + XWindowAttributes attrib; + XSetWindowAttributes attrib_set; + XWMHints *wmhint; + + XGrabServer(ob_display); + XSync(ob_display, FALSE); + + /* check if it has already been unmapped by the time we started mapping + the grab does a sync so we don't have to here */ + if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) || + XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e)) { + XPutBackEvent(ob_display, &e); + + XUngrabServer(ob_display); + XFlush(ob_display); + return; /* don't manage it */ + } + + /* make sure it isn't an override-redirect window */ + if (!XGetWindowAttributes(ob_display, window, &attrib) || + attrib.override_redirect) { + XUngrabServer(ob_display); + XFlush(ob_display); + return; /* don't manage it */ + } + + /* is the window a docking app */ + if ((wmhint = XGetWMHints(ob_display, window))) { + if ((wmhint->flags & StateHint) && + wmhint->initial_state == WithdrawnState) { + /* XXX: make dock apps work! */ + XUngrabServer(ob_display); + XFlush(ob_display); + XFree(wmhint); + return; + } + XFree(wmhint); + } + + /* choose the events we want to receive on the CLIENT window */ + attrib_set.event_mask = CLIENT_EVENTMASK; + attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK; + XChangeWindowAttributes(ob_display, window, + CWEventMask|CWDontPropagate, &attrib_set); + + + /* create the Client struct, and populate it from the hints on the + window */ + client = g_new(Client, 1); + client->window = window; + client_get_all(client); + + /* remove the client's border (and adjust re gravity) */ + client_toggle_border(client, FALSE); + + /* specify that if we exit, the window should not be destroyed and should + be reparented back to root automatically */ + XChangeSaveSet(ob_display, window, SetModeInsert); + + /* create the decoration frame for the client window */ + client->frame = frame_new(client); + + frame_grab_client(client->frame); + + client_apply_startup_state(client); + + XUngrabServer(ob_display); + XFlush(ob_display); + + client_list = g_slist_append(client_list, client); + stacking_list = g_list_append(stacking_list, client); + g_hash_table_insert(client_map, (gpointer)window, client); + + stacking_raise(client); + + screen_update_struts(); + + LOGICALHOOK(NewWindow, g_quark_try_string("client"), client); + + client_showhide(client); + + /* grab all mouse bindings */ + mbind_grab_all(client, TRUE); + + /* update the list hints */ + client_set_list(); + + g_message("Managed window 0x%lx frame 0x%lx", window, + client->frame->window); +} + +void client_unmanage_all() +{ + while (client_list != NULL) { + client_unmanage(client_list->data); + } +} + +void client_unmanage(Client *client) +{ + int j; + GSList *it; + + g_message("Unmanaging window: %lx", client->window); + + LOGICALHOOK(CloseWindow, g_quark_try_string("client"), client); + + /* remove the window from our save set */ + XChangeSaveSet(ob_display, client->window, SetModeDelete); + + /* we dont want events no more */ + XSelectInput(ob_display, client->window, NoEventMask); + + /* ungrab any mouse bindings */ + mbind_grab_all(client, FALSE); + + frame_hide(client->frame); + + /* give the client its border back */ + client_toggle_border(client, TRUE); + + /* reparent the window out of the frame */ + frame_release_client(client->frame); + + client_list = g_slist_remove(client_list, client); + stacking_list = g_list_remove(stacking_list, client); + g_hash_table_remove(client_map, (gpointer)client->window); + + /* once the client is out of the list, update the struts to remove it's + influence */ + screen_update_struts(); + + frame_free(client->frame); + + /* notify the wrapper that its useless now */ + if (client->wrap != NULL) + client->wrap->client = NULL; + + /* tell our parent that we're gone */ + if (client->transient_for != NULL) + client->transient_for->transients = + g_slist_remove(client->transient_for->transients, client); + + /* tell our transients that we're gone */ + for (it = client->transients; it != NULL; it = it->next) { + Client *t = ((Client*)it->data)->transient_for; + ((Client*)it->data)->transient_for = NULL; + if (t != NULL) + client_calc_layer(it->data); + } + + /* unfocus the client (calls the focus callbacks) (we're out of the + transient lists already, so being modal doesn't matter) */ + if (client->focused) + client_unfocus(client); + + if (ob_state != State_Exiting) { + /* these values should not be persisted across a window + unmapping/mapping */ + prop_erase(client->window, prop_atoms.net_wm_desktop); + prop_erase(client->window, prop_atoms.net_wm_state); + } else { + /* if we're left in an iconic state, the client wont be mapped. this is + bad, since we will no longer be managing the window on restart */ + if (client->iconic) + XMapWindow(ob_display, client->window); + } + + /* free all data allocated in the client struct */ + g_slist_free(client->transients); + for (j = 0; j < client->nicons; ++j) + g_free(client->icons[j].data); + if (client->nicons > 0) + g_free(client->icons); + g_free(client->title); + g_free(client->icon_title); + g_free(client->res_name); + g_free(client->res_class); + g_free(client->role); + g_free(client); + + /* update the list hints */ + client_set_list(); +} + +static void client_toggle_border(Client *self, gboolean show) +{ + /* adjust our idea of where the client is, based on its border. When the + border is removed, the client should now be considered to be in a + different position. + when re-adding the border to the client, the same operation needs to be + reversed. */ + int oldx = self->area.x, oldy = self->area.y; + int x = oldx, y = oldy; + switch(self->gravity) { + default: + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + if (show) x -= self->border_width * 2; + else x += self->border_width * 2; + break; + case NorthGravity: + case SouthGravity: + case CenterGravity: + case ForgetGravity: + case StaticGravity: + if (show) x -= self->border_width; + else x += self->border_width; + break; + } + switch(self->gravity) { + default: + case NorthWestGravity: + case NorthGravity: + case NorthEastGravity: + break; + case SouthWestGravity: + case SouthGravity: + case SouthEastGravity: + if (show) y -= self->border_width * 2; + else y += self->border_width * 2; + break; + case WestGravity: + case EastGravity: + case CenterGravity: + case ForgetGravity: + case StaticGravity: + if (show) y -= self->border_width; + else y += self->border_width; + break; + } + self->area.x = x; + self->area.y = y; + + if (show) { + XSetWindowBorderWidth(ob_display, self->window, self->border_width); + + /* move the client so it is back it the right spot _with_ its + border! */ + if (x != oldx || y != oldy) + XMoveWindow(ob_display, self->window, x, y); + } else + XSetWindowBorderWidth(ob_display, self->window, 0); +} + + +static void client_get_all(Client *self) +{ + /* update EVERYTHING!! */ + + self->ignore_unmaps = 0; + + /* defaults */ + self->frame = NULL; + self->title = self->icon_title = NULL; + self->res_name = self->res_class = self->role = NULL; + self->wmstate = NormalState; + self->focused = FALSE; + self->transient = FALSE; + self->transients = NULL; + self->transient_for = NULL; + self->layer = -1; + self->urgent = FALSE; + self->positioned = FALSE; + self->disabled_decorations = 0; + self->group = None; + self->nicons = 0; + self->wrap = NULL; + + client_get_area(self); + client_get_desktop(self); + client_get_state(self); + client_get_shaped(self); + + client_update_transient_for(self); + client_get_mwm_hints(self); + client_get_type(self);/* this can change the mwmhints for special cases */ + + client_update_protocols(self); + + client_get_gravity(self); /* get the attribute gravity */ + client_update_normal_hints(self); /* this may override the attribute + gravity */ + + /* got the type, the mwmhints, the protocols, and the normal hints + (min/max sizes), so we're ready to set up the decorations/functions */ + client_setup_decor_and_functions(self); + + client_update_wmhints(self); + client_update_title(self); + client_update_icon_title(self); + client_update_class(self); + client_update_strut(self); + client_update_icons(self); + client_update_kwm_icon(self); + + /* this makes sure that these windows appear on all desktops */ + if (self->type == Type_Desktop) + self->desktop = DESKTOP_ALL; + + /* set the desktop hint, to make sure that it always exists, and to + reflect any changes we've made here */ + PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop); + + client_change_state(self); +} + +static void client_get_area(Client *self) +{ + XWindowAttributes wattrib; + Status ret; + + ret = XGetWindowAttributes(ob_display, self->window, &wattrib); + g_assert(ret != BadWindow); + + RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height); + self->border_width = wattrib.border_width; +} + +static void client_get_desktop(Client *self) +{ + unsigned int d; + + if (PROP_GET32(self->window, net_wm_desktop, cardinal, d)) { + if (d >= screen_desktop && d != DESKTOP_ALL) + d = screen_desktop - 1; + self->desktop = d; + } else { + /* defaults to the current desktop */ + self->desktop = screen_desktop; + } +} + +static void client_get_state(Client *self) +{ + gulong *state; + gulong num; + + self->modal = self->shaded = self->max_horz = self->max_vert = + self->fullscreen = self->above = self->below = self->iconic = + self->skip_taskbar = self->skip_pager = FALSE; + + if (PROP_GET32U(self->window, net_wm_state, atom, state, num)) { + gulong i; + for (i = 0; i < num; ++i) { + if (state[i] == prop_atoms.net_wm_state_modal) + self->modal = TRUE; + else if (state[i] == prop_atoms.net_wm_state_shaded) + self->shaded = TRUE; + else if (state[i] == prop_atoms.net_wm_state_hidden) + self->iconic = TRUE; + else if (state[i] == prop_atoms.net_wm_state_skip_taskbar) + self->skip_taskbar = TRUE; + else if (state[i] == prop_atoms.net_wm_state_skip_pager) + self->skip_pager = TRUE; + else if (state[i] == prop_atoms.net_wm_state_fullscreen) + self->fullscreen = TRUE; + else if (state[i] == prop_atoms.net_wm_state_maximized_vert) + self->max_vert = TRUE; + else if (state[i] == prop_atoms.net_wm_state_maximized_horz) + self->max_horz = TRUE; + else if (state[i] == prop_atoms.net_wm_state_above) + self->above = TRUE; + else if (state[i] == prop_atoms.net_wm_state_below) + self->below = TRUE; + } + + g_free(state); + } +} + +static void client_get_shaped(Client *self) +{ + self->shaped = FALSE; +#ifdef SHAPE + if (extensions_shape) { + int foo; + guint ufoo; + int s; + + XShapeSelectInput(ob_display, self->window, ShapeNotifyMask); + + XShapeQueryExtents(ob_display, self->window, &s, &foo, + &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, + &ufoo); + self->shaped = (s != 0); + } +#endif +} + +void client_update_transient_for(Client *self) +{ + Window t = None; + Client *c = NULL; + + if (XGetTransientForHint(ob_display, self->window, &t) && + t != self->window) { /* cant be transient to itself! */ + self->transient = TRUE; + c = g_hash_table_lookup(client_map, (gpointer)t); + g_assert(c != self);/* if this happens then we need to check for it*/ + + if (!c /*XXX: && _group*/) { + /* not transient to a client, see if it is transient for a + group */ + if (/*t == _group->leader() || */ + t == None || + t == ob_root) { + /* window is a transient for its group! */ + /* XXX: for now this is treated as non-transient. + this needs to be fixed! */ + } + } + } else + self->transient = FALSE; + + /* if anything has changed... */ + if (c != self->transient_for) { + if (self->transient_for) + /* remove from old parent */ + g_slist_remove(self->transient_for->transients, self); + self->transient_for = c; + if (self->transient_for) + /* add to new parent */ + g_slist_append(self->transient_for->transients, self); + } +} + +static void client_get_mwm_hints(Client *self) +{ + unsigned long num; + unsigned long *hints; + + self->mwmhints.flags = 0; /* default to none */ + + if (PROP_GET32U(self->window, motif_wm_hints, motif_wm_hints, hints, num)) { + if (num >= MWM_ELEMENTS) { + self->mwmhints.flags = hints[0]; + self->mwmhints.functions = hints[1]; + self->mwmhints.decorations = hints[2]; + } + g_free(hints); + } +} + +void client_get_type(Client *self) +{ + gulong *val, num, i; + + self->type = -1; + + if (PROP_GET32U(self->window, net_wm_window_type, atom, val, num)) { + /* use the first value that we know about in the array */ + for (i = 0; i < num; ++i) { + if (val[i] == prop_atoms.net_wm_window_type_desktop) + self->type = Type_Desktop; + else if (val[i] == prop_atoms.net_wm_window_type_dock) + self->type = Type_Dock; + else if (val[i] == prop_atoms.net_wm_window_type_toolbar) + self->type = Type_Toolbar; + else if (val[i] == prop_atoms.net_wm_window_type_menu) + self->type = Type_Menu; + else if (val[i] == prop_atoms.net_wm_window_type_utility) + self->type = Type_Utility; + else if (val[i] == prop_atoms.net_wm_window_type_splash) + self->type = Type_Splash; + else if (val[i] == prop_atoms.net_wm_window_type_dialog) + self->type = Type_Dialog; + else if (val[i] == prop_atoms.net_wm_window_type_normal) + self->type = Type_Normal; + else if (val[i] == prop_atoms.kde_net_wm_window_type_override) { + /* prevent this window from getting any decor or + functionality */ + self->mwmhints.flags &= (MwmFlag_Functions | + MwmFlag_Decorations); + self->mwmhints.decorations = 0; + self->mwmhints.functions = 0; + } + if (self->type != (WindowType) -1) + break; /* grab the first legit type */ + } + g_free(val); + } + + if (self->type == (WindowType) -1) { + /*the window type hint was not set, which means we either classify + ourself as a normal window or a dialog, depending on if we are a + transient. */ + if (self->transient) + self->type = Type_Dialog; + else + self->type = Type_Normal; + } +} + +void client_update_protocols(Client *self) +{ + Atom *proto; + int num_return = 0, i; + + self->focus_notify = FALSE; + self->delete_window = FALSE; + + if (XGetWMProtocols(ob_display, self->window, &proto, &num_return)) { + for (i = 0; i < num_return; ++i) { + if (proto[i] == prop_atoms.wm_delete_window) { + /* this means we can request the window to close */ + self->delete_window = TRUE; + } else if (proto[i] == prop_atoms.wm_take_focus) + /* if this protocol is requested, then the window will be + notified whenever we want it to receive focus */ + self->focus_notify = TRUE; + } + XFree(proto); + } +} + +static void client_get_gravity(Client *self) +{ + XWindowAttributes wattrib; + Status ret; + + ret = XGetWindowAttributes(ob_display, self->window, &wattrib); + g_assert(ret != BadWindow); + self->gravity = wattrib.win_gravity; +} + +void client_update_normal_hints(Client *self) +{ + XSizeHints size; + long ret; + int oldgravity = self->gravity; + + /* defaults */ + self->min_ratio = 0.0f; + self->max_ratio = 0.0f; + SIZE_SET(self->size_inc, 1, 1); + SIZE_SET(self->base_size, 0, 0); + SIZE_SET(self->min_size, 0, 0); + SIZE_SET(self->max_size, G_MAXINT, G_MAXINT); + + /* get the hints from the window */ + if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) { + self->positioned = (size.flags & (PPosition|USPosition)); + + if (size.flags & PWinGravity) { + self->gravity = size.win_gravity; + + /* if the client has a frame, i.e. has already been mapped and + is changing its gravity */ + if (self->frame && self->gravity != oldgravity) { + /* move our idea of the client's position based on its new + gravity */ + self->area.x = self->frame->area.x; + self->area.y = self->frame->area.y; + frame_frame_gravity(self->frame, + &self->area.x, &self->area.y); + } + } + + if (size.flags & PAspect) { + if (size.min_aspect.y) + self->min_ratio = size.min_aspect.x / size.min_aspect.y; + if (size.max_aspect.y) + self->max_ratio = size.max_aspect.x / size.max_aspect.y; + } + + if (size.flags & PMinSize) + SIZE_SET(self->min_size, size.min_width, size.min_height); + + if (size.flags & PMaxSize) + SIZE_SET(self->max_size, size.max_width, size.max_height); + + if (size.flags & PBaseSize) + SIZE_SET(self->base_size, size.base_width, size.base_height); + + if (size.flags & PResizeInc) + SIZE_SET(self->size_inc, size.width_inc, size.height_inc); + } +} + +void client_setup_decor_and_functions(Client *self) +{ + /* start with everything (cept fullscreen) */ + self->decorations = Decor_Titlebar | Decor_Handle | Decor_Border | + Decor_Icon | Decor_AllDesktops | Decor_Iconify | Decor_Maximize; + self->functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize | + Func_Shade; + if (self->delete_window) { + self->decorations |= Decor_Close; + self->functions |= Func_Close; + } + + if (!(self->min_size.width < self->max_size.width || + self->min_size.height < self->max_size.height)) { + self->decorations &= ~(Decor_Maximize | Decor_Handle); + self->functions &= ~(Func_Resize | Func_Maximize); + } + + switch (self->type) { + case Type_Normal: + /* normal windows retain all of the possible decorations and + functionality, and are the only windows that you can fullscreen */ + self->functions |= Func_Fullscreen; + break; + + case Type_Dialog: + /* dialogs cannot be maximized */ + self->decorations &= ~Decor_Maximize; + self->functions &= ~Func_Maximize; + break; + + case Type_Menu: + case Type_Toolbar: + case Type_Utility: + /* these windows get less functionality */ + self->decorations &= ~(Decor_Iconify | Decor_Handle); + self->functions &= ~(Func_Iconify | Func_Resize); + break; + + case Type_Desktop: + case Type_Dock: + case Type_Splash: + /* none of these windows are manipulated by the window manager */ + self->decorations = 0; + self->functions = 0; + break; + } + + /* Mwm Hints are applied subtractively to what has already been chosen for + decor and functionality */ + if (self->mwmhints.flags & MwmFlag_Decorations) { + if (! (self->mwmhints.decorations & MwmDecor_All)) { + if (! (self->mwmhints.decorations & MwmDecor_Border)) + self->decorations &= ~Decor_Border; + if (! (self->mwmhints.decorations & MwmDecor_Handle)) + self->decorations &= ~Decor_Handle; + if (! (self->mwmhints.decorations & MwmDecor_Title)) + self->decorations &= ~Decor_Titlebar; + if (! (self->mwmhints.decorations & MwmDecor_Iconify)) + self->decorations &= ~Decor_Iconify; + if (! (self->mwmhints.decorations & MwmDecor_Maximize)) + self->decorations &= ~Decor_Maximize; + } + } + + if (self->mwmhints.flags & MwmFlag_Functions) { + if (! (self->mwmhints.functions & MwmFunc_All)) { + if (! (self->mwmhints.functions & MwmFunc_Resize)) + self->functions &= ~Func_Resize; + if (! (self->mwmhints.functions & MwmFunc_Move)) + self->functions &= ~Func_Move; + if (! (self->mwmhints.functions & MwmFunc_Iconify)) + self->functions &= ~Func_Iconify; + if (! (self->mwmhints.functions & MwmFunc_Maximize)) + self->functions &= ~Func_Maximize; + /* dont let mwm hints kill the close button + if (! (self->mwmhints.functions & MwmFunc_Close)) + self->functions &= ~Func_Close; */ + } + } + + /* can't maximize without moving/resizing */ + if (!((self->functions & Func_Move) && (self->functions & Func_Resize))) + self->functions &= ~Func_Maximize; + + /* finally, user specified disabled decorations are applied to subtract + decorations */ + if (self->disabled_decorations & Decor_Titlebar) + self->decorations &= ~Decor_Titlebar; + if (self->disabled_decorations & Decor_Handle) + self->decorations &= ~Decor_Handle; + if (self->disabled_decorations & Decor_Border) + self->decorations &= ~Decor_Border; + if (self->disabled_decorations & Decor_Iconify) + self->decorations &= ~Decor_Iconify; + if (self->disabled_decorations & Decor_Maximize) + self->decorations &= ~Decor_Maximize; + if (self->disabled_decorations & Decor_AllDesktops) + self->decorations &= ~Decor_AllDesktops; + if (self->disabled_decorations & Decor_Close) + self->decorations &= ~Decor_Close; + + /* if we don't have a titlebar, then we cannot shade! */ + if (!(self->decorations & Decor_Titlebar)) + self->functions &= ~Func_Shade; + + client_change_allowed_actions(self); + + if (self->frame) { + /* change the decors on the frame */ + frame_adjust_size(self->frame); + /* with more/less decorations, we may need to be repositioned */ + frame_adjust_position(self->frame); + /* with new decor, the window's maximized size may change */ + client_remaximize(self); + } +} + +static void client_change_allowed_actions(Client *self) +{ + Atom actions[9]; + int num = 0; + + actions[num++] = prop_atoms.net_wm_action_change_desktop; + + if (self->functions & Func_Shade) + actions[num++] = prop_atoms.net_wm_action_shade; + if (self->functions & Func_Close) + actions[num++] = prop_atoms.net_wm_action_close; + if (self->functions & Func_Move) + actions[num++] = prop_atoms.net_wm_action_move; + if (self->functions & Func_Iconify) + actions[num++] = prop_atoms.net_wm_action_minimize; + if (self->functions & Func_Resize) + actions[num++] = prop_atoms.net_wm_action_resize; + if (self->functions & Func_Fullscreen) + actions[num++] = prop_atoms.net_wm_action_fullscreen; + if (self->functions & Func_Maximize) { + actions[num++] = prop_atoms.net_wm_action_maximize_horz; + actions[num++] = prop_atoms.net_wm_action_maximize_vert; + } + + PROP_SET32A(self->window, net_wm_allowed_actions, atom, actions, num); + + /* make sure the window isn't breaking any rules now */ + + if (!(self->functions & Func_Shade) && self->shaded) { + if (self->frame) client_shade(self, FALSE); + else self->shaded = FALSE; + } + if (!(self->functions & Func_Iconify) && self->iconic) { + if (self->frame) client_iconify(self, FALSE, TRUE); + else self->iconic = FALSE; + } + if (!(self->functions & Func_Fullscreen) && self->fullscreen) { + if (self->frame) client_fullscreen(self, FALSE, TRUE); + else self->fullscreen = FALSE; + } + if (!(self->functions & Func_Maximize) && (self->max_horz || + self->max_vert)) { + if (self->frame) client_maximize(self, FALSE, 0, TRUE); + else self->max_vert = self->max_horz = FALSE; + } +} + +void client_remaximize(Client *self) +{ + int dir; + if (self->max_horz && self->max_vert) + dir = 0; + else if (self->max_horz) + dir = 1; + else if (self->max_vert) + dir = 2; + else + return; /* not maximized */ + self->max_horz = self->max_vert = FALSE; + client_maximize(self, TRUE, dir, FALSE); +} + +void client_update_wmhints(Client *self) +{ + XWMHints *hints; + gboolean ur = FALSE; + + /* assume a window takes input if it doesnt specify */ + self->can_focus = TRUE; + + if ((hints = XGetWMHints(ob_display, self->window)) != NULL) { + if (hints->flags & InputHint) + self->can_focus = hints->input; + + /* only do this when starting! */ + if (ob_state == State_Starting && (hints->flags & StateHint)) + self->iconic = hints->initial_state == IconicState; + + if (hints->flags & XUrgencyHint) + ur = TRUE; + + if (hints->flags & WindowGroupHint) { + if (hints->window_group != self->group) { + /* XXX: remove from the old group if there was one */ + self->group = hints->window_group; + /* XXX: do stuff with the group */ + } + } else /* no group! */ + self->group = None; + + if (hints->flags & IconPixmapHint) { + client_update_kwm_icon(self); + /* try get the kwm icon first, this is a fallback only */ + if (self->pixmap_icon == None) { + self->pixmap_icon = hints->icon_pixmap; + if (hints->flags & IconMaskHint) + self->pixmap_icon_mask = hints->icon_mask; + else + self->pixmap_icon_mask = None; + + if (self->frame) + frame_adjust_icon(self->frame); + } + } + + XFree(hints); + } + + if (ur != self->urgent) { + self->urgent = ur; + g_message("Urgent Hint for 0x%lx: %s\n", self->window, + ur ? "ON" : "OFF"); + /* fire the urgent callback if we're mapped, otherwise, wait until + after we're mapped */ + if (self->frame) + LOGICALHOOK(UrgentWindow, g_quark_try_string("client"), self); + } +} + +void client_update_title(Client *self) +{ + gchar *data = NULL; + + if (self->title != NULL) + g_free(self->title); + + /* try netwm */ + if (!PROP_GETS(self->window, net_wm_name, utf8, data)) { + /* try old x stuff */ + if (PROP_GETS(self->window, wm_name, string, data)) { + /* convert it to UTF-8 */ + gsize r, w; + gchar *u; + + u = g_locale_to_utf8(data, -1, &r, &w, NULL); + if (u == NULL) { + g_warning("Unable to convert string to UTF-8"); + } else { + g_free(data); + data = u; + } + } + } + + if (data == NULL) + data = g_strdup("Unnamed Window"); + + self->title = data; + + if (self->frame) + frame_adjust_title(self->frame); +} + +void client_update_icon_title(Client *self) +{ + gchar *data = NULL; + + if (self->icon_title != NULL) + g_free(self->icon_title); + + /* try netwm */ + if (!PROP_GETS(self->window, net_wm_icon_name, utf8, data)) { + /* try old x stuff */ + if (PROP_GETS(self->window, wm_icon_name, string, data)) { + /* convert it to UTF-8 */ + gsize r, w; + gchar *u; + + u = g_locale_to_utf8(data, -1, &r, &w, NULL); + if (u == NULL) { + g_warning("Unable to convert string to UTF-8"); + } else { + g_free(data); + data = u; + } + } + } + + if (data == NULL) + data = g_strdup(self->title); + + self->icon_title = data; +} + +void client_update_class(Client *self) +{ + GPtrArray *data; + gchar *s; + guint i; + + if (self->res_name) g_free(self->res_name); + if (self->res_class) g_free(self->res_class); + if (self->role) g_free(self->role); + + self->res_name = self->res_class = self->role = NULL; + + data = g_ptr_array_new(); + + if (PROP_GETSA(self->window, wm_class, string, data)) { + if (data->len > 0) + self->res_name = g_strdup(g_ptr_array_index(data, 0)); + if (data->len > 1) + self->res_class = g_strdup(g_ptr_array_index(data, 1)); + } + + for (i = 0; i < data->len; ++i) + g_free(g_ptr_array_index(data, i)); + g_ptr_array_free(data, TRUE); + + if (PROP_GETS(self->window, wm_window_role, string, s)) + self->role = g_strdup(s); + + if (self->res_name == NULL) self->res_name = g_strdup(""); + if (self->res_class == NULL) self->res_class = g_strdup(""); + if (self->role == NULL) self->role = g_strdup(""); +} + +void client_update_strut(Client *self) +{ + gulong num = 4; + gulong *data; + + if (PROP_GET32A(self->window, net_wm_strut, cardinal, data, num)) { + STRUT_SET(self->strut, data[0], data[1], data[2], data[3]); + g_free(data); + } else + STRUT_SET(self->strut, 0, 0, 0, 0); + + /* updating here is pointless while we're being mapped cuz we're not in + the client list yet */ + if (self->frame) + screen_update_struts(); +} + +void client_update_icons(Client *self) +{ + unsigned long num; + unsigned long *data; + unsigned long w, h, i; + int j; + + for (j = 0; j < self->nicons; ++j) + g_free(self->icons[j].data); + if (self->nicons > 0) + g_free(self->icons); + self->nicons = 0; + + if (PROP_GET32U(self->window, net_wm_icon, cardinal, data, num)) { + /* figure out how many valid icons are in here */ + i = 0; + while (num - i > 2) { + w = data[i++]; + h = data[i++]; + i += w * h; + if (i > num) break; + ++self->nicons; + } + + self->icons = g_new(Icon, self->nicons); + + /* store the icons */ + i = 0; + for (j = 0; j < self->nicons; ++j) { + w = self->icons[j].w = data[i++]; + h = self->icons[j].h = data[i++]; + self->icons[j].data = + g_memdup(&data[i], w * h * sizeof(gulong)); + i += w * h; + g_assert(i <= num); + } + + g_free(data); + } + + if (self->nicons <= 0) { + self->nicons = 1; + self->icons = g_new0(Icon, 1); + } + + if (self->frame) + frame_adjust_icon(self->frame); +} + +void client_update_kwm_icon(Client *self) +{ + Pixmap *data; + + if (PROP_GET32A(self->window, kwm_win_icon, kwm_win_icon, data, 2)) { + self->pixmap_icon = data[0]; + self->pixmap_icon_mask = data[1]; + g_free(data); + } else { + self->pixmap_icon = self->pixmap_icon_mask = None; + } + if (self->frame) + frame_adjust_icon(self->frame); +} + +static void client_change_state(Client *self) +{ + unsigned long state[2]; + Atom netstate[10]; + int num; + + state[0] = self->wmstate; + state[1] = None; + PROP_SET32A(self->window, wm_state, wm_state, state, 2); + + num = 0; + if (self->modal) + netstate[num++] = prop_atoms.net_wm_state_modal; + if (self->shaded) + netstate[num++] = prop_atoms.net_wm_state_shaded; + if (self->iconic) + netstate[num++] = prop_atoms.net_wm_state_hidden; + if (self->skip_taskbar) + netstate[num++] = prop_atoms.net_wm_state_skip_taskbar; + if (self->skip_pager) + netstate[num++] = prop_atoms.net_wm_state_skip_pager; + if (self->fullscreen) + netstate[num++] = prop_atoms.net_wm_state_fullscreen; + if (self->max_vert) + netstate[num++] = prop_atoms.net_wm_state_maximized_vert; + if (self->max_horz) + netstate[num++] = prop_atoms.net_wm_state_maximized_horz; + if (self->above) + netstate[num++] = prop_atoms.net_wm_state_above; + if (self->below) + netstate[num++] = prop_atoms.net_wm_state_below; + PROP_SET32A(self->window, net_wm_state, atom, netstate, num); + + client_calc_layer(self); + + if (self->frame) + frame_adjust_state(self->frame); +} + +static Client *search_focus_tree(Client *node, Client *skip) +{ + GSList *it; + Client *ret; + + for (it = node->transients; it != NULL; it = g_slist_next(it)) { + Client *c = it->data; + if (c == skip) continue; /* circular? */ + if ((ret = search_focus_tree(c, skip))) return ret; + if (c->focused) return c; + } + return NULL; +} + +void client_calc_layer(Client *self) +{ + StackLayer l; + gboolean fs; + Client *c; + + /* are we fullscreen, or do we have a fullscreen transient parent? */ + c = self; + fs = FALSE; + while (c) { + if (c->fullscreen) { + fs = TRUE; + break; + } + c = c->transient_for; + } + if (!fs && self->fullscreen) { + /* is one of our transients focused? */ + c = search_focus_tree(self, self); + if (c != NULL) fs = TRUE; + } + + if (self->iconic) l = Layer_Icon; + else if (fs) l = Layer_Fullscreen; + else if (self->type == Type_Desktop) l = Layer_Desktop; + else if (self->type == Type_Dock) { + if (!self->below) l = Layer_Top; + else l = Layer_Normal; + } + else if (self->above) l = Layer_Above; + else if (self->below) l = Layer_Below; + else l = Layer_Normal; + + if (l != self->layer) { + self->layer = l; + if (self->frame) + stacking_raise(self); + } +} + +void client_showhide(Client *self) +{ + gboolean show; + + if (self->iconic) show = FALSE; + else if (!(self->desktop == screen_desktop || + self->desktop == DESKTOP_ALL)) show = FALSE; + else if (client_normal(self) && screen_showing_desktop) show = FALSE; + else show = TRUE; + + if (show) frame_show(self->frame); + else frame_hide(self->frame); +} + +gboolean client_normal(Client *self) { + return ! (self->type == Type_Desktop || self->type == Type_Dock || + self->type == Type_Splash); +} + +static void client_apply_startup_state(Client *self) +{ + /* these are in a carefully crafted order.. */ + + if (self->iconic) { + self->iconic = FALSE; + client_iconify(self, TRUE, FALSE); + } + if (self->fullscreen) { + self->fullscreen = FALSE; + client_fullscreen(self, TRUE, FALSE); + } + if (self->shaded) { + self->shaded = FALSE; + client_shade(self, TRUE); + } + if (self->urgent) + LOGICALHOOK(UrgentWindow, g_quark_try_string("client"), self); + + if (self->max_vert && self->max_horz) { + self->max_vert = self->max_horz = FALSE; + client_maximize(self, TRUE, 0, FALSE); + } else if (self->max_vert) { + self->max_vert = FALSE; + client_maximize(self, TRUE, 2, FALSE); + } else if (self->max_horz) { + self->max_horz = FALSE; + client_maximize(self, TRUE, 1, FALSE); + } + + /* nothing to do for the other states: + skip_taskbar + skip_pager + modal + above + below + */ +} + +void client_configure(Client *self, Corner anchor, int x, int y, int w, int h, + gboolean user, gboolean final) +{ + w -= self->base_size.width; + h -= self->base_size.height; + + if (user) { + /* for interactive resizing. have to move half an increment in each + direction. */ + + /* how far we are towards the next size inc */ + int mw = w % self->size_inc.width; + int mh = h % self->size_inc.height; + /* amount to add */ + int aw = self->size_inc.width / 2; + int ah = self->size_inc.height / 2; + /* don't let us move into a new size increment */ + if (mw + aw >= self->size_inc.width) + aw = self->size_inc.width - mw - 1; + if (mh + ah >= self->size_inc.height) + ah = self->size_inc.height - mh - 1; + w += aw; + h += ah; + + /* if this is a user-requested resize, then check against min/max + sizes and aspect ratios */ + + /* smaller than min size or bigger than max size? */ + if (w > self->max_size.width) w = self->max_size.width; + if (w < self->min_size.width) w = self->min_size.width; + if (h > self->max_size.height) h = self->max_size.height; + if (h < self->min_size.height) h = self->min_size.height; + + /* adjust the height ot match the width for the aspect ratios */ + if (self->min_ratio) + if (h * self->min_ratio > w) h = (int)(w / self->min_ratio); + if (self->max_ratio) + if (h * self->max_ratio < w) h = (int)(w / self->max_ratio); + } + + /* keep to the increments */ + w /= self->size_inc.width; + h /= self->size_inc.height; + + /* you cannot resize to nothing */ + if (w < 1) w = 1; + if (h < 1) h = 1; + + /* store the logical size */ + SIZE_SET(self->logical_size, w, h); + + w *= self->size_inc.width; + h *= self->size_inc.height; + + w += self->base_size.width; + h += self->base_size.height; + + switch (anchor) { + case Corner_TopLeft: + break; + case Corner_TopRight: + x -= w - self->area.width; + break; + case Corner_BottomLeft: + y -= h - self->area.height; + break; + case Corner_BottomRight: + x -= w - self->area.width; + y -= h - self->area.height; + break; + } + + RECT_SET(self->area, x, y, w, h); + + XResizeWindow(ob_display, self->window, w, h); + + /* move/resize the frame to match the request */ + if (self->frame) { + /* Adjust the size and then the position, as required by the EWMH */ + frame_adjust_size(self->frame); + frame_adjust_position(self->frame); + + if (!user || final) { + XEvent event; + event.type = ConfigureNotify; + event.xconfigure.display = ob_display; + event.xconfigure.event = self->window; + event.xconfigure.window = self->window; + + /* root window coords with border in mind */ + event.xconfigure.x = x - self->border_width + + self->frame->size.left; + event.xconfigure.y = y - self->border_width + + self->frame->size.top; + + event.xconfigure.width = self->area.width; + event.xconfigure.height = self->area.height; + event.xconfigure.border_width = self->border_width; + event.xconfigure.above = self->frame->plate; + event.xconfigure.override_redirect = FALSE; + XSendEvent(event.xconfigure.display, event.xconfigure.window, + FALSE, StructureNotifyMask, &event); + } + } +} + +void client_fullscreen(Client *self, gboolean fs, gboolean savearea) +{ + static int saved_func, saved_decor; + int x, y, w, h; + + if (!(self->functions & Func_Fullscreen) || /* can't */ + self->fullscreen == fs) return; /* already done */ + + self->fullscreen = fs; + client_change_state(self); /* change the state hints on the client */ + + if (fs) { + /* save the functions and remove them */ + saved_func = self->functions; + self->functions &= (Func_Close | Func_Fullscreen | + Func_Iconify); + /* save the decorations and remove them */ + saved_decor = self->decorations; + self->decorations = 0; + if (savearea) { + long dimensions[4]; + dimensions[0] = self->area.x; + dimensions[1] = self->area.y; + dimensions[2] = self->area.width; + dimensions[3] = self->area.height; + + PROP_SET32A(self->window, openbox_premax, cardinal, + dimensions, 4); + } + x = 0; + y = 0; + w = screen_physical_size.width; + h = screen_physical_size.height; + } else { + long *dimensions; + + self->functions = saved_func; + self->decorations = saved_decor; + + if (PROP_GET32A(self->window, openbox_premax, cardinal, + dimensions, 4)) { + x = dimensions[0]; + y = dimensions[1]; + w = dimensions[2]; + h = dimensions[3]; + g_free(dimensions); + } else { + /* pick some fallbacks... */ + x = screen_area(self->desktop)->x + + screen_area(self->desktop)->width / 4; + y = screen_area(self->desktop)->y + + screen_area(self->desktop)->height / 4; + w = screen_area(self->desktop)->width / 2; + h = screen_area(self->desktop)->height / 2; + } + } + + client_change_allowed_actions(self); /* based on the new _functions */ + + /* when fullscreening, don't obey things like increments, fill the + screen */ + client_configure(self, Corner_TopLeft, x, y, w, h, !fs, TRUE); + + /* raise (back) into our stacking layer */ + stacking_raise(self); + + /* try focus us when we go into fullscreen mode */ + client_focus(self); +} + +void client_iconify(Client *self, gboolean iconic, gboolean curdesk) +{ + if (self->iconic == iconic) return; /* nothing to do */ + + g_message("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"), + self->window); + + self->iconic = iconic; + + if (iconic) { + self->wmstate = IconicState; + self->ignore_unmaps++; + /* we unmap the client itself so that we can get MapRequest events, + and because the ICCCM tells us to! */ + XUnmapWindow(ob_display, self->window); + } else { + if (curdesk) + client_set_desktop(self, screen_desktop); + self->wmstate = self->shaded ? IconicState : NormalState; + XMapWindow(ob_display, self->window); + } + client_change_state(self); + client_showhide(self); + screen_update_struts(); +} + +void client_maximize(Client *self, gboolean max, int dir, gboolean savearea) +{ + int x, y, w, h; + + g_assert(dir == 0 || dir == 1 || dir == 2); + if (!(self->functions & Func_Maximize)) return; /* can't */ + + /* check if already done */ + if (max) { + if (dir == 0 && self->max_horz && self->max_vert) return; + if (dir == 1 && self->max_horz) return; + if (dir == 2 && self->max_vert) return; + } else { + if (dir == 0 && !self->max_horz && !self->max_vert) return; + if (dir == 1 && !self->max_horz) return; + if (dir == 2 && !self->max_vert) return; + } + + x = self->frame->area.x; + y = self->frame->area.y; + w = self->frame->area.width; + h = self->frame->area.height; + + if (max) { + if (savearea) { + long dimensions[4]; + long *readdim; + + dimensions[0] = x; + dimensions[1] = y; + dimensions[2] = w; + dimensions[3] = h; + + /* get the property off the window and use it for the dimensions + we are already maxed on */ + if (PROP_GET32A(self->window, openbox_premax, cardinal, + readdim, 4)) { + if (self->max_horz) { + dimensions[0] = readdim[0]; + dimensions[2] = readdim[2]; + } + if (self->max_vert) { + dimensions[1] = readdim[1]; + dimensions[3] = readdim[3]; + } + g_free(readdim); + } + + PROP_SET32A(self->window, openbox_premax, cardinal, + dimensions, 4); + } + if (dir == 0 || dir == 1) { /* horz */ + x = screen_area(self->desktop)->x; + w = screen_area(self->desktop)->x + + screen_area(self->desktop)->width; + } + if (dir == 0 || dir == 2) { /* vert */ + y = screen_area(self->desktop)->y; + h = screen_area(self->desktop)->y + + screen_area(self->desktop)->height - + self->frame->size.top - self->frame->size.bottom; + } + } else { + long *dimensions; + + if (PROP_GET32A(self->window, openbox_premax, cardinal, + dimensions, 4)) { + if (dir == 0 || dir == 1) { /* horz */ + x = dimensions[0]; + w = dimensions[2]; + } + if (dir == 0 || dir == 2) { /* vert */ + y = dimensions[1]; + h = dimensions[3]; + } + g_free(dimensions); + } else { + /* pick some fallbacks... */ + if (dir == 0 || dir == 1) { /* horz */ + x = screen_area(self->desktop)->x + + screen_area(self->desktop)->width / 4; + w = screen_area(self->desktop)->width / 2; + } + if (dir == 0 || dir == 2) { /* vert */ + y = screen_area(self->desktop)->y + + screen_area(self->desktop)->height / 4; + h = screen_area(self->desktop)->height / 2; + } + } + } + + if (dir == 0 || dir == 1) /* horz */ + self->max_horz = max; + if (dir == 0 || dir == 2) /* vert */ + self->max_vert = max; + + if (!self->max_horz && !self->max_vert) + PROP_ERASE(self->window, openbox_premax); + + client_change_state(self); /* change the state hints on the client */ + + /* figure out where the client should be going */ + frame_frame_gravity(self->frame, &x, &y); + client_configure(self, Corner_TopLeft, x, y, w, h, TRUE, TRUE); +} + +void client_shade(Client *self, gboolean shade) +{ + if (!(self->functions & Func_Shade) || /* can't */ + self->shaded == shade) return; /* already done */ + + /* when we're iconic, don't change the wmstate */ + if (!self->iconic) + self->wmstate = shade ? IconicState : NormalState; + self->shaded = shade; + client_change_state(self); + frame_adjust_size(self->frame); +} + +void client_close(Client *self) +{ + XEvent ce; + + if (!(self->functions & Func_Close)) return; + + /* + XXX: itd be cool to do timeouts and shit here for killing the client's + process off + like... if the window is around after 5 seconds, then the close button + turns a nice red, and if this function is called again, the client is + explicitly killed. + */ + + ce.xclient.type = ClientMessage; + ce.xclient.message_type = prop_atoms.wm_protocols; + ce.xclient.display = ob_display; + ce.xclient.window = self->window; + ce.xclient.format = 32; + ce.xclient.data.l[0] = prop_atoms.wm_delete_window; + ce.xclient.data.l[1] = CurrentTime; + ce.xclient.data.l[2] = 0l; + ce.xclient.data.l[3] = 0l; + ce.xclient.data.l[4] = 0l; + XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce); +} + +void client_set_desktop(Client *self, unsigned int target) +{ + if (target == self->desktop) return; + + g_message("Setting desktop %u\n", target); + + if (!(target < screen_num_desktops || + target == DESKTOP_ALL)) + return; + + self->desktop = target; + PROP_SET32(self->window, net_wm_desktop, cardinal, target); + /* the frame can display the current desktop state */ + frame_adjust_state(self->frame); + /* 'move' the window to the new desktop */ + client_showhide(self); + screen_update_struts(); +} + +static Client *search_modal_tree(Client *node, Client *skip) +{ + GSList *it; + Client *ret; + + for (it = node->transients; it != NULL; it = it->next) { + Client *c = it->data; + if (c == skip) continue; /* circular? */ + if ((ret = search_modal_tree(c, skip))) return ret; + if (c->modal) return c; + } + return NULL; +} + +Client *client_find_modal_child(Client *self) +{ + return search_modal_tree(self, self); +} + +gboolean client_validate(Client *self) +{ + XEvent e; + + XSync(ob_display, FALSE); /* get all events on the server */ + + if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) || + XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) { + XPutBackEvent(ob_display, &e); + return FALSE; + } + + return FALSE; +} + +void client_set_wm_state(Client *self, long state) +{ + if (state == self->wmstate) return; /* no change */ + + switch (state) { + case IconicState: + client_iconify(self, TRUE, TRUE); + break; + case NormalState: + client_iconify(self, FALSE, TRUE); + break; + } +} + +void client_set_state(Client *self, Atom action, long data1, long data2) +{ + gboolean shaded = self->shaded; + gboolean fullscreen = self->fullscreen; + gboolean max_horz = self->max_horz; + gboolean max_vert = self->max_vert; + int i; + + if (!(action == prop_atoms.net_wm_state_add || + action == prop_atoms.net_wm_state_remove || + action == prop_atoms.net_wm_state_toggle)) + /* an invalid action was passed to the client message, ignore it */ + return; + + for (i = 0; i < 2; ++i) { + Atom state = i == 0 ? data1 : data2; + + if (!state) continue; + + /* if toggling, then pick whether we're adding or removing */ + if (action == prop_atoms.net_wm_state_toggle) { + if (state == prop_atoms.net_wm_state_modal) + action = self->modal ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_maximized_vert) + action = self->max_vert ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_maximized_horz) + action = self->max_horz ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_shaded) + action = self->shaded ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_skip_taskbar) + action = self->skip_taskbar ? + prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_skip_pager) + action = self->skip_pager ? + prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_fullscreen) + action = self->fullscreen ? + prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_above) + action = self->above ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + else if (state == prop_atoms.net_wm_state_below) + action = self->below ? prop_atoms.net_wm_state_remove : + prop_atoms.net_wm_state_add; + } + + if (action == prop_atoms.net_wm_state_add) { + if (state == prop_atoms.net_wm_state_modal) { + /* XXX raise here or something? */ + self->modal = TRUE; + } else if (state == prop_atoms.net_wm_state_maximized_vert) { + max_vert = TRUE; + } else if (state == prop_atoms.net_wm_state_maximized_horz) { + max_horz = TRUE; + } else if (state == prop_atoms.net_wm_state_shaded) { + shaded = TRUE; + } else if (state == prop_atoms.net_wm_state_skip_taskbar) { + self->skip_taskbar = TRUE; + } else if (state == prop_atoms.net_wm_state_skip_pager) { + self->skip_pager = TRUE; + } else if (state == prop_atoms.net_wm_state_fullscreen) { + fullscreen = TRUE; + } else if (state == prop_atoms.net_wm_state_above) { + self->above = TRUE; + } else if (state == prop_atoms.net_wm_state_below) { + self->below = TRUE; + } + + } else { /* action == prop_atoms.net_wm_state_remove */ + if (state == prop_atoms.net_wm_state_modal) { + self->modal = FALSE; + } else if (state == prop_atoms.net_wm_state_maximized_vert) { + max_vert = FALSE; + } else if (state == prop_atoms.net_wm_state_maximized_horz) { + max_horz = FALSE; + } else if (state == prop_atoms.net_wm_state_shaded) { + shaded = FALSE; + } else if (state == prop_atoms.net_wm_state_skip_taskbar) { + self->skip_taskbar = FALSE; + } else if (state == prop_atoms.net_wm_state_skip_pager) { + self->skip_pager = FALSE; + } else if (state == prop_atoms.net_wm_state_fullscreen) { + fullscreen = FALSE; + } else if (state == prop_atoms.net_wm_state_above) { + self->above = FALSE; + } else if (state == prop_atoms.net_wm_state_below) { + self->below = FALSE; + } + } + } + if (max_horz != self->max_horz || max_vert != self->max_vert) { + if (max_horz != self->max_horz && max_vert != self->max_vert) { + /* toggling both */ + if (max_horz == max_vert) { /* both going the same way */ + client_maximize(self, max_horz, 0, TRUE); + } else { + client_maximize(self, max_horz, 1, TRUE); + client_maximize(self, max_vert, 2, TRUE); + } + } else { + /* toggling one */ + if (max_horz != self->max_horz) + client_maximize(self, max_horz, 1, TRUE); + else + client_maximize(self, max_vert, 2, TRUE); + } + } + /* change fullscreen state before shading, as it will affect if the window + can shade or not */ + if (fullscreen != self->fullscreen) + client_fullscreen(self, fullscreen, TRUE); + if (shaded != self->shaded) + client_shade(self, shaded); + client_calc_layer(self); + client_change_state(self); /* change the hint to relect these changes */ +} + +gboolean client_focus(Client *self) +{ + XEvent ev; + Client *child; + + /* if we have a modal child, then focus it, not us */ + child = client_find_modal_child(self); + if (child) + return client_focus(child); + + /* won't try focus if the client doesn't want it, or if the window isn't + visible on the screen */ + if (!(self->frame->visible && + (self->can_focus || self->focus_notify))) + return FALSE; + + /* do a check to see if the window has already been unmapped or destroyed + do this intelligently while watching out for unmaps we've generated + (ignore_unmaps > 0) */ + if (XCheckTypedWindowEvent(ob_display, self->window, + DestroyNotify, &ev)) { + XPutBackEvent(ob_display, &ev); + return FALSE; + } + while (XCheckTypedWindowEvent(ob_display, self->window, + UnmapNotify, &ev)) { + if (self->ignore_unmaps) { + self->ignore_unmaps--; + } else { + XPutBackEvent(ob_display, &ev); + return FALSE; + } + } + + if (self->can_focus) + XSetInputFocus(ob_display, self->window, RevertToNone, CurrentTime); + + if (self->focus_notify) { + XEvent ce; + ce.xclient.type = ClientMessage; + ce.xclient.message_type = prop_atoms.wm_protocols; + ce.xclient.display = ob_display; + ce.xclient.window = self->window; + ce.xclient.format = 32; + ce.xclient.data.l[0] = prop_atoms.wm_take_focus; + ce.xclient.data.l[1] = event_lasttime; + ce.xclient.data.l[2] = 0l; + ce.xclient.data.l[3] = 0l; + ce.xclient.data.l[4] = 0l; + XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce); + } + + /*XSync(ob_display, FALSE); XXX Why sync? */ + return TRUE; +} + +void client_unfocus(Client *self) +{ + g_assert(focus_client == self); + focus_set_client(NULL); +} diff --git a/c/client.h b/c/client.h new file mode 100644 index 00000000..d0f5266b --- /dev/null +++ b/c/client.h @@ -0,0 +1,401 @@ +#ifndef __client_h +#define __client_h + +#include "geom.h" +#include "obexport.h" +#include <glib.h> +#include <X11/Xlib.h> + +struct ClientWrap; +struct Frame; + + +/*! Holds an icon in ARGB format */ +typedef struct Icon { + unsigned long w, h; + unsigned long *data; +} Icon; + +/*! The MWM Hints as retrieved from the window property + This structure only contains 3 elements, even though the Motif 2.0 + structure contains 5. We only use the first 3, so that is all gets + defined. +*/ +typedef struct MwmHints { + /*! A bitmask of Client::MwmFlags values */ + unsigned long flags; + /*! A bitmask of Client::MwmFunctions values */ + unsigned long functions; + /*! A bitmask of Client::MwmDecorations values */ + unsigned long decorations; +} MwmHints; +/*! The number of elements in the Client::MwmHints struct */ +#define MWM_ELEMENTS 3 + +/*! Possible flags for MWM Hints (defined by Motif 2.0) */ +typedef enum { + MwmFlag_Functions = 1 << 0, /*!< The MMW Hints define funcs */ + MwmFlag_Decorations = 1 << 1 /*!< The MWM Hints define decor */ +} MwmFlags; + +/*! Possible functions for MWM Hints (defined by Motif 2.0) */ +typedef enum { + MwmFunc_All = 1 << 0, /*!< All functions */ + MwmFunc_Resize = 1 << 1, /*!< Allow resizing */ + MwmFunc_Move = 1 << 2, /*!< Allow moving */ + MwmFunc_Iconify = 1 << 3, /*!< Allow to be iconfied */ + MwmFunc_Maximize = 1 << 4 /*!< Allow to be maximized */ + /*MwmFunc_Close = 1 << 5 /!< Allow to be closed */ +} MwmFunctions; + +/*! Possible decorations for MWM Hints (defined by Motif 2.0) */ +typedef enum { + MwmDecor_All = 1 << 0, /*!< All decorations */ + MwmDecor_Border = 1 << 1, /*!< Show a border */ + MwmDecor_Handle = 1 << 2, /*!< Show a handle (bottom) */ + MwmDecor_Title = 1 << 3, /*!< Show a titlebar */ + /*MwmDecor_Menu = 1 << 4, /!< Show a menu */ + MwmDecor_Iconify = 1 << 5, /*!< Show an iconify button */ + MwmDecor_Maximize = 1 << 6 /*!< Show a maximize button */ +} MemDecorations; + + +typedef struct Client { + Window window; + + struct Frame *frame; + + /*! The number of unmap events to ignore on the window */ + int ignore_unmaps; + + /*! The id of the group the window belongs to */ + Window group; + /*! Whether or not the client is a transient window. This is guaranteed to + be TRUE if transient_for != NULL, but not guaranteed to be FALSE if + transient_for == NULL. */ + gboolean transient; + /*! The client which this client is a transient (child) for */ + struct Client *transient_for; + /*! The clients which are transients (children) of this client */ + GSList *transients; + /*! The desktop on which the window resides (0xffffffff for all + desktops) */ + unsigned int desktop; + + /*! Normal window title */ + gchar *title; + /*! Window title when iconified */ + gchar *icon_title; + + /*! The application that created the window */ + gchar *res_name; + /*! The class of the window, can used for grouping */ + gchar *res_class; + /*! The specified role of the window, used for identification */ + gchar *role; + + /*! The type of window (what its function is) */ + WindowType type; + + /*! Position and size of the window + This will not always be the actual position of the window on screen, it + is, rather, the position requested by the client, to which the window's + gravity is applied. + */ + Rect area; + + /*! The window's strut + The strut defines areas of the screen that are marked off-bounds for + window placement. In theory, where this window exists. + */ + Strut strut; + + /*! The logical size of the window + The "logical" size of the window is refers to the user's perception of + the size of the window, and is the value that should be displayed to the + user. For example, with xterms, this value it the number of characters + being displayed in the terminal, instead of the number of pixels. + */ + Size logical_size; + + /*! Width of the border on the window. + The window manager will set this to 0 while the window is being managed, + but needs to restore it afterwards, so it is saved here. + */ + guint border_width; + + /*! The minimum aspect ratio the client window can be sized to. + A value of 0 means this is ignored. + */ + float min_ratio; + /*! The maximum aspect ratio the client window can be sized to. + A value of 0 means this is ignored. + */ + float max_ratio; + + /*! The minimum size of the client window + If the min is > the max, then the window is not resizable + */ + Size min_size; + /*! The maximum size of the client window + If the min is > the max, then the window is not resizable + */ + Size max_size; + /*! The size of increments to resize the client window by */ + Size size_inc; + /*! The base size of the client window + This value should be subtracted from the window's actual size when + displaying its size to the user, or working with its min/max size + */ + Size base_size; + + /*! Window decoration and functionality hints */ + MwmHints mwmhints; + + /*! Where to place the decorated window in relation to the undecorated + window */ + int gravity; + + /*! The state of the window, one of WithdrawnState, IconicState, or + NormalState */ + long wmstate; + + /*! True if the client supports the delete_window protocol */ + gboolean delete_window; + + /*! Was the window's position requested by the application? if not, we + should place the window ourselves when it first appears */ + gboolean positioned; + + /*! Can the window receive input focus? */ + gboolean can_focus; + /*! Urgency flag */ + gboolean urgent; + /*! Notify the window when it receives focus? */ + gboolean focus_notify; + /*! Does the client window have the input focus? */ + gboolean focused; + + /*! The window uses shape extension to be non-rectangular? */ + gboolean shaped; + + /*! The window is modal, so it must be processed before any windows it is + related to can be focused */ + gboolean modal; + /*! Only the window's titlebar is displayed */ + gboolean shaded; + /*! The window is iconified */ + gboolean iconic; + /*! The window is maximized to fill the screen vertically */ + gboolean max_vert; + /*! The window is maximized to fill the screen horizontally */ + gboolean max_horz; + /*! The window should not be displayed by pagers */ + gboolean skip_pager; + /*! The window should not be displayed by taskbars */ + gboolean skip_taskbar; + /*! The window is a 'fullscreen' window, and should be on top of all + others */ + gboolean fullscreen; + /*! The window should be on top of other windows of the same type */ + gboolean above; + /*! The window should be underneath other windows of the same type */ + gboolean below; + + /*! The layer in which the window will be stacked, windows in lower layers + are always below windows in higher layers. */ + StackLayer layer; + + /*! A bitmask of values in the Decoration enum + The values in the variable are the decorations that the client wants to + be displayed around it. + */ + int decorations; + + /*! A bitmask of values in the Decoration enum. + Specifies the decorations that should NOT be displayed on the client. + */ + int disabled_decorations; + + /*! A bitmask of values in the Function enum + The values in the variable specify the ways in which the user is allowed + to modify this window. + */ + int functions; + + /*! Icons for the client as specified on the client window */ + Icon *icons; + /*! The number of icons in icons */ + int nicons; + + /*! The icon for the client specified in the WMHints or the KWM hints */ + Pixmap pixmap_icon; + /*! The mask for the pixmap_icon, or None if its not masked */ + Pixmap pixmap_icon_mask; + + /* The instance of the wrapper class if one exists */ + struct ClientWrap *wrap; +} Client; + +extern GSList *client_list; +extern GHashTable *client_map; + +void client_startup(); +void client_shutdown(); + +/*! Manages all existing windows */ +void client_manage_all(); +/*! Manages a given window */ +void client_manage(Window win); +/*! Unmanages all managed windows */ +void client_unmanage_all(); +/*! Unmanages a given client */ +void client_unmanage(Client *client); + +/*! Sets the client list on the root window from the client_list */ +void client_set_list(); + +/*! Reapplies the maximized state to the window + Use this to make the window readjust its maximized size to new + surroundings (struts, etc). */ +void client_remaximize(Client *self); + +/*! Shows the window if it should be shown, or hides it + Used when changing desktops, the window's state, etc. */ +void client_showhide(Client *self); + +/*! Returns if the window should be treated as a normal window. + Some windows (desktops, docks, splash screens) have special rules applied + to them in a number of places regarding focus or user interaction. */ +gboolean client_normal(Client *self); + +/*! Internal version of the Client::resize function + This also maintains things like the client's minsize, and size increments. + @param anchor The corner to keep in the same position when resizing. + @param x The x coordiante of the new position for the client. + @param y The y coordiante of the new position for the client. + @param w The width component of the new size for the client. + @param h The height component of the new size for the client. + @param user Specifies whether this is a user-requested change or a + program requested change. For program requested changes, the + constraints are not checked. + @param final If user is true, then this should specify if this is a final + configuration. e.g. Final should be FALSE if doing an + interactive move/resize, and then be TRUE for the last call + only. +*/ +void client_configure(Client *self, Corner anchor, int x, int y, int w, int h, + gboolean user, gboolean final); + +/*! Fullscreen's or unfullscreen's the client window + @param fs true if the window should be made fullscreen; false if it should + be returned to normal state. + @param savearea true to have the client's current size and position saved; + otherwise, they are not. You should not save when mapping a + new window that is set to fullscreen. This has no effect + when restoring a window from fullscreen. +*/ +void client_fullscreen(Client *self, gboolean fs, gboolean savearea); + +/*! Iconifies or uniconifies the client window + @param iconic true if the window should be iconified; false if it should be + restored. + @param curdesk If iconic is FALSE, then this determines if the window will + be uniconified to the current viewable desktop (true) or to + its previous desktop (false) +*/ +void client_iconify(Client *self, gboolean iconic, gboolean curdesk); + +/*! Maximize or unmaximize the client window + @param max true if the window should be maximized; false if it should be + returned to normal size. + @param dir 0 to set both horz and vert, 1 to set horz, 2 to set vert. + @param savearea true to have the client's current size and position saved; + otherwise, they are not. You should not save when mapping a + new window that is set to fullscreen. This has no effect + when unmaximizing a window. +*/ +void client_maximize(Client *self, gboolean max, int dir, + gboolean savearea); + +/*! Shades or unshades the client window + @param shade true if the window should be shaded; false if it should be + unshaded. +*/ +void client_shade(Client *self, gboolean shade); + +/*! Request the client to close its window. */ +void client_close(Client *self); + +/*! Sends the window to the specified desktop */ +void client_set_desktop(Client *self, unsigned int target); + +/*! Return a modal child of the client window + @return A modal child of the client window, or 0 if none was found. +*/ +Client *client_find_modal_child(Client *self); + +/*! Validate client, by making sure no Destroy or Unmap events exist in + the event queue for the window. + @return true if the client is valid; false if the client has already + been unmapped/destroyed, and so is invalid. +*/ +gboolean client_validate(Client *self); + +/*! Sets the wm_state to the specified value */ +void client_set_wm_state(Client *self, long state); + +/*! Adjusts the window's net_state + This should not be called as part of the window mapping process! It is for + use when updating the state post-mapping.<br> + client_apply_startup_state is used to do the same things during the mapping + process. +*/ +void client_set_state(Client *self, Atom action, long data1, long data2); + +/*! Attempt to focus the client window */ +gboolean client_focus(Client *self); + +/*! Remove focus from the client window */ +void client_unfocus(Client *self); + +/*! Calculates the stacking layer for the client window */ +void client_calc_layer(Client *self); + +/*! Updates the window's transient status, and any parents of it */ +void client_update_transient_for(Client *self); +/*! Update the protocols that the window supports and adjusts things if they + change */ +void client_update_protocols(Client *self); +/*! Updates the WMNormalHints and adjusts things if they change */ +void client_update_normal_hints(Client *self); + +/*! Updates the WMHints and adjusts things if they change + @param initstate Whether to read the initial_state property from the + WMHints. This should only be used during the mapping + process. +*/ +void client_update_wmhints(Client *self); +/*! Updates the window's title */ +void client_update_title(Client *self); +/*! Updates the window's icon title */ +void client_update_icon_title(Client *self); +/*! Updates the window's application name and class */ +void client_update_class(Client *self); +/*! Updates the strut for the client */ +void client_update_strut(Client *self); +/*! Updates the window's icons */ +void client_update_icons(Client *self); +/*! Updates the window's kwm icon */ +void client_update_kwm_icon(Client *self); + +/*! Set up what decor should be shown on the window and what functions should + be allowed (Client::_decorations and Client::_functions). + This also updates the NET_WM_ALLOWED_ACTIONS hint. +*/ +void client_setup_decor_and_functions(Client *self); + +/*! Retrieves the window's type and sets Client->type */ +void client_get_type(Client *self); + +#endif diff --git a/c/clientwrap.c b/c/clientwrap.c new file mode 100644 index 00000000..1ea5c9f0 --- /dev/null +++ b/c/clientwrap.c @@ -0,0 +1,792 @@ +#include "clientwrap.h" +#include "client.h" +#include "frame.h" +#include "stacking.h" +#include "focus.h" +#include <glib.h> + +/*************************************************************************** + + Define the type 'ClientWrap' + + ***************************************************************************/ + +#define IS_CWRAP(v) ((v)->ob_type == &ClientWrapType) +#define IS_VALID_CWRAP(v) ((v)->client != NULL) +#define CHECK_CWRAP(self, funcname) { \ + if (!IS_CWRAP(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires a 'Client' " \ + "object"); \ + return NULL; \ + } \ + if (!IS_VALID_CWRAP(self)) { \ + PyErr_SetString(PyExc_ValueError, \ + "This 'Client' is wrapping a client which no longer "\ + "exists."); \ + return NULL; \ + } \ +} + + +staticforward PyTypeObject ClientWrapType; + +/*************************************************************************** + + Attribute methods + + ***************************************************************************/ + +static PyObject *cwrap_window(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "window"); + if (!PyArg_ParseTuple(args, ":window")) + return NULL; + return PyInt_FromLong(self->client->window); +} + +static PyObject *cwrap_group(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "group"); + if (!PyArg_ParseTuple(args, ":group")) + return NULL; + return PyInt_FromLong(self->client->group); +} + +static PyObject *cwrap_parent(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "parent"); + if (!PyArg_ParseTuple(args, ":parent")) + return NULL; + if (self->client->transient_for != NULL) + return clientwrap_new(self->client->transient_for); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_children(ClientWrap *self, PyObject *args) +{ + PyObject *list; + GSList *it; + guint i, s; + + CHECK_CWRAP(self, "children"); + if (!PyArg_ParseTuple(args, ":children")) + return NULL; + s = g_slist_length(self->client->transients); + list = PyList_New(s); + for (i = 0, it = self->client->transients; i < s; ++i, it = it->next) + PyList_SET_ITEM(list, i, clientwrap_new(it->data)); + return list; +} + +static PyObject *cwrap_desktop(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "desktop"); + if (!PyArg_ParseTuple(args, ":desktop")) + return NULL; + return PyInt_FromLong(self->client->desktop); +} + +static PyObject *cwrap_title(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "title"); + if (!PyArg_ParseTuple(args, ":title")) + return NULL; + return PyString_FromString(self->client->title); +} + +static PyObject *cwrap_iconTitle(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "iconTitle"); + if (!PyArg_ParseTuple(args, ":iconTitle")) + return NULL; + return PyString_FromString(self->client->icon_title); +} + +static PyObject *cwrap_resName(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "resName"); + if (!PyArg_ParseTuple(args, ":resName")) + return NULL; + return PyString_FromString(self->client->res_name); +} + +static PyObject *cwrap_resClass(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "resClass"); + if (!PyArg_ParseTuple(args, ":resClass")) + return NULL; + return PyString_FromString(self->client->res_class); +} + +static PyObject *cwrap_role(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "role"); + if (!PyArg_ParseTuple(args, ":role")) + return NULL; + return PyString_FromString(self->client->role); +} + +static PyObject *cwrap_type(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "type"); + if (!PyArg_ParseTuple(args, ":type")) + return NULL; + return PyInt_FromLong(self->client->type); +} + +static PyObject *cwrap_area(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "area"); + if (!PyArg_ParseTuple(args, ":area")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->area.x)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->area.y)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(self->client->area.width)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(self->client->area.height)); + return tuple; +} + +static PyObject *cwrap_screenArea(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "screenArea"); + if (!PyArg_ParseTuple(args, ":screenArea")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->frame->area.x)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->frame->area.y)); + PyTuple_SET_ITEM(tuple, 2, + PyInt_FromLong(self->client->frame->area.width)); + PyTuple_SET_ITEM(tuple, 3, + PyInt_FromLong(self->client->frame->area.height)); + return tuple; +} + +static PyObject *cwrap_strut(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "strut"); + if (!PyArg_ParseTuple(args, ":strut")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->strut.left)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->strut.top)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(self->client->strut.right)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(self->client->strut.bottom)); + return tuple; +} + +static PyObject *cwrap_logicalSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "logicalSize"); + if (!PyArg_ParseTuple(args, ":logicalSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, + PyInt_FromLong(self->client->logical_size.width)); + PyTuple_SET_ITEM(tuple, 1, + PyInt_FromLong(self->client->logical_size.height)); + return tuple; +} + +static PyObject *cwrap_minRatio(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "minRatio"); + if (!PyArg_ParseTuple(args, ":minRatio")) + return NULL; + return PyFloat_FromDouble(self->client->min_ratio); +} + +static PyObject *cwrap_maxRatio(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maxRatio"); + if (!PyArg_ParseTuple(args, ":maxRatio")) + return NULL; + return PyFloat_FromDouble(self->client->max_ratio); +} + +static PyObject *cwrap_minSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "minSize"); + if (!PyArg_ParseTuple(args, ":minSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->min_size.width)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->min_size.height)); + return tuple; +} + +static PyObject *cwrap_maxSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "maxSize"); + if (!PyArg_ParseTuple(args, ":maxSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->max_size.width)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->max_size.height)); + return tuple; +} + +static PyObject *cwrap_sizeIncrement(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "sizeIncrement"); + if (!PyArg_ParseTuple(args, ":sizeIncrement")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->size_inc.width)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->size_inc.height)); + return tuple; +} + +static PyObject *cwrap_baseSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "baseSize"); + if (!PyArg_ParseTuple(args, ":baseSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->base_size.width)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->base_size.height)); + return tuple; +} + +static PyObject *cwrap_gravity(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "gravity"); + if (!PyArg_ParseTuple(args, ":gravity")) + return NULL; + return PyInt_FromLong(self->client->gravity); +} + +static PyObject *cwrap_canClose(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "canClose"); + if (!PyArg_ParseTuple(args, ":canClose")) + return NULL; + return PyInt_FromLong(self->client->delete_window ? 1 : 0); +} + +static PyObject *cwrap_positionRequested(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "positionRequested"); + if (!PyArg_ParseTuple(args, ":positionRequested")) + return NULL; + return PyInt_FromLong(self->client->positioned ? 1 : 0); +} + +static PyObject *cwrap_canFocus(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "canFocus"); + if (!PyArg_ParseTuple(args, ":canFocus")) + return NULL; + return PyInt_FromLong(self->client->can_focus || + self->client->focus_notify); +} + +static PyObject *cwrap_urgent(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "urgent"); + if (!PyArg_ParseTuple(args, ":urgent")) + return NULL; + return PyInt_FromLong(self->client->urgent ? 1 : 0); +} + +static PyObject *cwrap_focused(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "focused"); + if (!PyArg_ParseTuple(args, ":focused")) + return NULL; + return PyInt_FromLong(self->client->focused ? 1 : 0); +} + +static PyObject *cwrap_modal(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "modal"); + if (!PyArg_ParseTuple(args, ":modal")) + return NULL; + return PyInt_FromLong(self->client->modal ? 1 : 0); +} + +static PyObject *cwrap_shaded(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "shaded"); + if (!PyArg_ParseTuple(args, ":shaded")) + return NULL; + return PyInt_FromLong(self->client->shaded ? 1 : 0); +} + +static PyObject *cwrap_iconic(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "iconic"); + if (!PyArg_ParseTuple(args, ":iconc")) + return NULL; + return PyInt_FromLong(self->client->iconic ? 1 : 0); +} + +static PyObject *cwrap_maximizedVertical(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maximizedVertical"); + if (!PyArg_ParseTuple(args, ":maximizedVertical")) + return NULL; + return PyInt_FromLong(self->client->max_vert ? 1 : 0); +} + +static PyObject *cwrap_maximizedHorizontal(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "maximizedHorizontal"); + if (!PyArg_ParseTuple(args, ":maximizedHorizontal")) + return NULL; + return PyInt_FromLong(self->client->max_horz ? 1 : 0); +} + +static PyObject *cwrap_skipPager(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "skipPager"); + if (!PyArg_ParseTuple(args, ":skipPager")) + return NULL; + return PyInt_FromLong(self->client->skip_pager ? 1 : 0); +} + +static PyObject *cwrap_skipTaskbar(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "skipTaskbar"); + if (!PyArg_ParseTuple(args, ":skipTaskbar")) + return NULL; + return PyInt_FromLong(self->client->skip_taskbar ? 1 : 0); +} + +static PyObject *cwrap_fullscreen(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "fullscreen"); + if (!PyArg_ParseTuple(args, ":fullscreen")) + return NULL; + return PyInt_FromLong(self->client->fullscreen ? 1 : 0); +} + +static PyObject *cwrap_above(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "above"); + if (!PyArg_ParseTuple(args, ":above")) + return NULL; + return PyInt_FromLong(self->client->above ? 1 : 0); +} + +static PyObject *cwrap_below(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "below"); + if (!PyArg_ParseTuple(args, ":below")) + return NULL; + return PyInt_FromLong(self->client->below ? 1 : 0); +} + +static PyObject *cwrap_layer(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "layer"); + if (!PyArg_ParseTuple(args, ":layer")) + return NULL; + return PyInt_FromLong(self->client->layer); +} + +static PyObject *cwrap_decorations(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "decorations"); + if (!PyArg_ParseTuple(args, ":decorations")) + return NULL; + return PyInt_FromLong(self->client->decorations); +} + +static PyObject *cwrap_disabledDecorations(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "disabledDecorations"); + if (!PyArg_ParseTuple(args, ":disabledDecorations")) + return NULL; + return PyInt_FromLong(self->client->disabled_decorations); +} + +static PyObject *cwrap_functions(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "functions"); + if (!PyArg_ParseTuple(args, ":functions")) + return NULL; + return PyInt_FromLong(self->client->functions); +} + +static PyObject *cwrap_visible(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "visible"); + if (!PyArg_ParseTuple(args, ":visible")) + return NULL; + return PyInt_FromLong(self->client->frame->visible ? 1 : 0); +} + +static PyObject *cwrap_decorationSize(ClientWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_CWRAP(self, "decorationSize"); + if (!PyArg_ParseTuple(args, ":decorationSize")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->client->frame->size.left)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->client->frame->size.top)); + PyTuple_SET_ITEM(tuple, 2, + PyInt_FromLong(self->client->frame->size.right)); + PyTuple_SET_ITEM(tuple, 3, + PyInt_FromLong(self->client->frame->size.bottom)); + return tuple; +} + +static PyObject *cwrap_normal(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "normal"); + if (!PyArg_ParseTuple(args, ":normal")) + return NULL; + return PyInt_FromLong(client_normal(self->client) ? 1 : 0); +} + +static PyObject *cwrap_setVisible(ClientWrap *self, PyObject *args) +{ + int i; + + CHECK_CWRAP(self, "setVisible"); + if (!PyArg_ParseTuple(args, "i:setVisible", &i)) + return NULL; + if (i) + frame_show(self->client->frame); + else + frame_hide(self->client->frame); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_raiseWindow(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "raiseWindow"); + if (!PyArg_ParseTuple(args, ":raiseWindow")) + return NULL; + stacking_raise(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_lowerWindow(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "lowerWindow"); + if (!PyArg_ParseTuple(args, ":lowerWindow")) + return NULL; + stacking_lower(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_focus(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "focus"); + if (!PyArg_ParseTuple(args, ":focus")) + return NULL; + return PyInt_FromLong(client_focus(self->client) ? 1 : 0); +} + +static PyObject *cwrap_unfocus(ClientWrap *self, PyObject *args) +{ + CHECK_CWRAP(self, "unfocus"); + if (!PyArg_ParseTuple(args, ":unfocus")) + return NULL; + client_unfocus(self->client); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *cwrap_move(ClientWrap *self, PyObject *args) +{ + int x, y; + int final = TRUE; + CHECK_CWRAP(self, "move"); + if (!PyArg_ParseTuple(args, "ii|i:unfocus", &x, &y, &final)) + return NULL; + /* get the client's position based on x,y for the frame */ + frame_frame_gravity(self->client->frame, &x, &y); + + client_configure(self->client, Corner_TopLeft, x, y, + self->client->area.width, self->client->area.height, + TRUE, final); + Py_INCREF(Py_None); + return Py_None; +} + +#define ATTRMETH(n, d) {#n, (PyCFunction)cwrap_##n, METH_VARARGS, #d} + +static PyMethodDef ClientWrapMethods[] = { + ATTRMETH(window, + "c.window() -- Returns the window id for the Client."), + ATTRMETH(group, + "c.group() -- Returns the group id for the Client."), + ATTRMETH(parent, + "c.parent() -- Returns the parent Client for the Client, or the " + "Client for which this Client is a transient. Returns None if it " + "is not a transient."), + ATTRMETH(children, + "c.parent() -- Returns a list of child Clients for the Client, " + "or the Clients transients."), + ATTRMETH(desktop, + "c.desktop() -- Returns the desktop on which the Client resides, " + "or 0xffffffff for 'all desktops'."), + ATTRMETH(title, + "c.title() -- Returns the Client's title string. This is in " + "UTF-8 encoding."), + ATTRMETH(iconTitle, + "c.iconTitle() -- Returns the Client's icon title string. This " + "is in UTF-8 encoding."), + ATTRMETH(resName, + "c.resName() -- Returns the application's specified resource " + "name."), + ATTRMETH(resClass, + "c.resClass() -- Returns the application's specified resource " + "class."), + ATTRMETH(role, + "c.role() -- Returns the window's role, which should be unique " + "for every window of an application, if it is not empty."), + ATTRMETH(type, + "c.type() -- Returns the window's type, one of the ob.Type_ " + "constants. This is the logical type of the window."), + ATTRMETH(area, + "c.area() -- Returns the area rectangle for the Client in a " + "tuple. The tuple's format is (x, y, width, height). This is " + "not the area on-screen that the Client and frame occupies, but " + "rather, the position and size that the Client has requested, " + "before its gravity() and decorations have been applied. You " + "should use the c.screenArea() to get the actual on-screen area " + "for the Client."), + ATTRMETH(screenArea, + "c.screenArea() -- Returns the on-screen area rectangle for the " + "Client in a tuple. The tuple's format is (x, y, width, height). " + "This is the actual position and size of the Client plus its " + "decorations."), + ATTRMETH(strut, + "c.strut() -- Returns the strut requested by the Client in a " + "tuple. The format of the tuple is (left, top, right, bottom)."), + ATTRMETH(logicalSize, + "c.logicalSize() -- Returns the logical size of the Client. This " + "is the 'user friendly' size for the Client, such as for an " + "xterm, it will be the number of characters in the xterm " + "instead of the number of pixels it takes up."), + ATTRMETH(minRatio, + "c.minRatio() -- Returns the minimum width:height ratio for " + "the Client. A value of 0 implies no ratio enforcement."), + ATTRMETH(maxRatio, + "c.maxRatio() -- Returns the maximum width:height ratio for " + "the Client. A value of 0 implies no ratio enforcement."), + ATTRMETH(minSize, + "c.minSize() -- Returns the minimum size of the Client."), + ATTRMETH(maxSize, + "c.maxSize() -- Returns the maximum size of the Client."), + ATTRMETH(sizeIncrement, + "c.sizeIncrement() -- Returns the size increments in which the " + "Client must be resized."), + ATTRMETH(baseSize, + "c.baseSize() -- Returns the base size of the Client, which is " + "subtracted from the Client's size before comparing to its " + "various sizing constraints."), + ATTRMETH(gravity, + "c.gravity() -- Returns the gravity for the Client. One of the " + "ob.Gravity_ constants."), + ATTRMETH(canClose, + "c.canClose() -- Returns whether or not the Client provides a " + "means for Openbox to request that it close."), + ATTRMETH(positionRequested, + "c.positionRequested() -- Returns whether or not the Client has " + "requested a specified position on screen. When it has it should " + "probably not be placed using an algorithm when it is managed."), + ATTRMETH(canFocus, + "c.canFocus() -- Returns whether or not the Client can be " + "given input focus."), + ATTRMETH(urgent, + "c.urgent() -- Returns the urgent state of the window (on/off)."), + ATTRMETH(focused, + "c.focused() -- Returns whether or not the Client has the input " + "focus."), + ATTRMETH(modal, + "c.modal() -- Returns whether or not the Client is modal. A " + "modal Client implies that it needs to be closed before its " + "parent() can be used (focsed) again."), + ATTRMETH(shaded, + "c.shaded() -- Returns whether or not the Client is shaded. " + "Shaded clients are hidden except for their titlebar."), + ATTRMETH(iconic, + "c.iconic() -- Returns whether or not the Client is iconic. " + "Iconic windows are represented only by icons, or possibly " + "hidden entirely."), + ATTRMETH(maximizedVertical, + "c.maximizedVertical() -- Returns whether or not the Client is " + "maxized vertically. When a Client is maximized it will expand " + "to fill as much of the screen as it can in that direction."), + ATTRMETH(maximizedHorizontal, + "c.maximizedHorizontal() -- Returns whether or not the Client is " + "maxized horizontally. When a Client is maximized it will expand " + "to fill as much of the screen as it can in that direction."), + ATTRMETH(skipPager, + "c.skipPager() -- Returns whether the Client as requested to be " + "skipped by pagers."), + ATTRMETH(skipTaskbar, + "c.skipTaskbar() -- Returns whether the Client as requested to " + "be skipped by taskbars."), + ATTRMETH(fullscreen, + "c.fullscreen() -- Returns whether the Client is in fullscreen " + "mode."), + ATTRMETH(above, + "c.above() -- Returns whether the Client should be stacked above " + "other windows of the same type."), + ATTRMETH(below, + "c.below() -- Returns whether the Client should be stacked below " + "other windows of the same type."), + ATTRMETH(layer, + "c.layer() -- Returns the layer in which the window should be " + "stacked. This is one of the ob.Layer_ constants. Windows in " + "layers with higher values should be kept above windows in lower " + "valued layers."), + ATTRMETH(decorations, + "c.decorations() -- Returns a mask of decorations which the " + "Client will be given. It is made up of the ob.Decor_ constants. " + "These can be turned off with the " + " disabledDecorations()."), + ATTRMETH(disabledDecorations, + "c.disabledDecorations() -- returns a mask of decorations which " + "are disabled on the Client. This is made up of the ob.Decor_ " + "constants."), + ATTRMETH(functions, + "ob.functions() -- Returns the list of functionality for the " + "Client, in a mask made up of the ob.Func_ constants."), + ATTRMETH(visible, + "ob.visible() -- Returns if the client is currently visible " + "or hidden."), + ATTRMETH(decorationSize, + "c.decorationSize() -- Returns the size of the Client's " + "decorations around the Client window, in a tuple. The format of " + "the tuple is (left, top, right, bottom)."), + ATTRMETH(normal, + "c.normal() -- Returns if the window should be treated as a " + "normal window. Some windows (desktops, docks, splash screens) " + "should have special rules applied to them in a number of " + "places regarding focus or user interaction."), + ATTRMETH(setVisible, + "c.setVisible(show) -- Shows or hides the Client."), + ATTRMETH(raiseWindow, + "c.raiseWindow() -- Raises the Client to the top of its layer."), + ATTRMETH(lowerWindow, + "c.lowerWindow() -- Lowers the Client to the bottom of its " + "layer."), + ATTRMETH(focus, + "c.focus() -- Focuses the Client. Returns 1 if the Client will " + "be focused, or 0 if it will not."), + ATTRMETH(unfocus, + "c.unfocus() -- Unfocuses the Client, leaving nothing focused."), + ATTRMETH(move, + "c.move(x, y) -- Moves the Client to the specified position. The " + "top left corner of the Client's decorations is positioned at " + "the given x, y."), + { NULL, NULL, 0, NULL } +}; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +/*static PyObject *cwrap_getattr(ClientWrap *self, char *name) +{ + CHECK_CWRAP(self, "getattr"); + return Py_FindMethod(ClientWrapAttributeMethods, (PyObject*)self, name); +}*/ + +static void cwrap_dealloc(ClientWrap *self) +{ + if (self->client != NULL) + self->client->wrap = NULL; + PyObject_Del((PyObject*) self); +} + +static PyObject *cwrap_repr(ClientWrap *self) +{ + CHECK_CWRAP(self, "repr"); + return PyString_FromFormat("0x%x", (guint)self->client->window); +} + +static int cwrap_compare(ClientWrap *self, ClientWrap *other) +{ + Window w1, w2; + if (!IS_VALID_CWRAP(self)) { + PyErr_SetString(PyExc_ValueError, + "This 'Client' is wrapping a client which no longer " + "exists."); + } + + w1 = self->client->window; + w2 = self->client->window; + return w1 > w2 ? 1 : w1 < w2 ? -1 : 0; +} + +static PyTypeObject ClientWrapType = { + PyObject_HEAD_INIT(NULL) + 0, + "Client", + sizeof(ClientWrap), + 0, + (destructor) cwrap_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + (cmpfunc) cwrap_compare, /*tp_compare*/ + (reprfunc) cwrap_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/*************************************************************************** + + External methods + + ***************************************************************************/ + +void clientwrap_startup() +{ + ClientWrapType.ob_type = &PyType_Type; + ClientWrapType.tp_methods = ClientWrapMethods; + PyType_Ready(&ClientWrapType); +} + +void clientwrap_shutdown() +{ +} + +PyObject *clientwrap_new(Client *client) +{ + g_assert(client != NULL); + + if (client->wrap != NULL) { + /* already has a wrapper! */ + Py_INCREF((PyObject*) client->wrap); + } else { + client->wrap = PyObject_New(ClientWrap, &ClientWrapType); + client->wrap->client = client; + } + return (PyObject*) client->wrap; +} diff --git a/c/clientwrap.h b/c/clientwrap.h new file mode 100644 index 00000000..b20ca914 --- /dev/null +++ b/c/clientwrap.h @@ -0,0 +1,19 @@ +#ifndef __clientwrap_h +#define __clientwrap_h + +#include <Python.h> + +struct Client; + +/* ClientWrap is a PyObject */ +typedef struct ClientWrap { + PyObject_HEAD + struct Client *client; +} ClientWrap; + +void clientwrap_startup(); +void clientwrap_shutdown(); + +PyObject *clientwrap_new(struct Client *client); + +#endif diff --git a/c/event.c b/c/event.c new file mode 100644 index 00000000..32d87348 --- /dev/null +++ b/c/event.c @@ -0,0 +1,593 @@ +#include "openbox.h" +#include "client.h" +#include "xerror.h" +#include "prop.h" +#include "screen.h" +#include "frame.h" +#include "focus.h" +#include "hooks.h" +#include "stacking.h" +#include "kbind.h" +#include "mbind.h" + +#include <X11/Xlib.h> +#include <X11/keysym.h> +#include <X11/Xatom.h> + +static void event_process(XEvent *e); +static void event_handle_root(XEvent *e); +static void event_handle_client(Client *c, XEvent *e); + +Time event_lasttime = 0; + +/*! A list of all possible combinations of keyboard lock masks */ +static unsigned int mask_list[8]; +/*! The value of the mask for the NumLock modifier */ +static unsigned int NumLockMask; +/*! The value of the mask for the ScrollLock modifier */ +static unsigned int ScrollLockMask; +/*! The key codes for the modifier keys */ +static XModifierKeymap *modmap; +/*! Table of the constant modifier masks */ +static const int mask_table[] = { + ShiftMask, LockMask, ControlMask, Mod1Mask, + Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask +}; +static int mask_table_size; + +void event_startup() +{ + mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]); + + /* get lock masks that are defined by the display (not constant) */ + modmap = XGetModifierMapping(ob_display); + g_assert(modmap); + if (modmap && modmap->max_keypermod > 0) { + size_t cnt; + const size_t size = mask_table_size * modmap->max_keypermod; + /* get the values of the keyboard lock modifiers + Note: Caps lock is not retrieved the same way as Scroll and Num + lock since it doesn't need to be. */ + const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock); + const KeyCode scroll_lock = XKeysymToKeycode(ob_display, + XK_Scroll_Lock); + + for (cnt = 0; cnt < size; ++cnt) { + if (! modmap->modifiermap[cnt]) continue; + + if (num_lock == modmap->modifiermap[cnt]) + NumLockMask = mask_table[cnt / modmap->max_keypermod]; + if (scroll_lock == modmap->modifiermap[cnt]) + ScrollLockMask = mask_table[cnt / modmap->max_keypermod]; + } + } + + mask_list[0] = 0; + mask_list[1] = LockMask; + mask_list[2] = NumLockMask; + mask_list[3] = LockMask | NumLockMask; + mask_list[4] = ScrollLockMask; + mask_list[5] = ScrollLockMask | LockMask; + mask_list[6] = ScrollLockMask | NumLockMask; + mask_list[7] = ScrollLockMask | LockMask | NumLockMask; +} + +void event_shutdown() +{ + XFreeModifiermap(modmap); +} + +void event_loop() +{ + fd_set selset; + XEvent e; + int x_fd; + + while (TRUE) { + /* + There are slightly different event retrieval semantics here for + local (or high bandwidth) versus remote (or low bandwidth) + connections to the display/Xserver. + */ + if (ob_remote) { + if (!XPending(ob_display)) + break; + } else { + /* + This XSync allows for far more compression of events, which + makes things like Motion events perform far far better. Since + it also means network traffic for every event instead of every + X events (where X is the number retrieved at a time), it + probably should not be used for setups where Openbox is + running on a remote/low bandwidth display/Xserver. + */ + XSync(ob_display, FALSE); + if (!XEventsQueued(ob_display, QueuedAlready)) + break; + } + XNextEvent(ob_display, &e); + + event_process(&e); + } + + x_fd = ConnectionNumber(ob_display); + FD_ZERO(&selset); + FD_SET(x_fd, &selset); + select(x_fd + 1, &selset, NULL, NULL, NULL); +} + +void event_process(XEvent *e) +{ + XEvent ce; + KeyCode *kp; + Window window; + int i, k; + Client *client; + GQuark context; + static guint motion_button = 0; + + /* pick a window */ + switch (e->type) { + case UnmapNotify: + window = e->xunmap.window; + break; + case DestroyNotify: + window = e->xdestroywindow.window; + break; + case ConfigureRequest: + window = e->xconfigurerequest.window; + break; + default: + window = e->xany.window; + } + + /* grab the lasttime and hack up the state */ + switch (e->type) { + case ButtonPress: + case ButtonRelease: + event_lasttime = e->xbutton.time; + e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + break; + case KeyPress: + event_lasttime = e->xkey.time; + e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* add to the state the mask of the modifier being pressed, if it is + a modifier key being pressed (this is a little ugly..) */ +/* I'm commenting this out cuz i don't want "C-Control_L" being returned. */ +/* kp = modmap->modifiermap;*/ +/* for (i = 0; i < mask_table_size; ++i) {*/ +/* for (k = 0; k < modmap->max_keypermod; ++k) {*/ +/* if (*kp == e->xkey.keycode) {*/ /* found the keycode */ + /* add the mask for it */ +/* e->xkey.state |= mask_table[i];*/ + /* cause the first loop to break; */ +/* i = mask_table_size;*/ +/* break;*/ /* get outta here! */ +/* }*/ +/* ++kp;*/ +/* }*/ +/* }*/ + + break; + case KeyRelease: + event_lasttime = e->xkey.time; + e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* remove from the state the mask of the modifier being released, if + it is a modifier key being released (this is a little ugly..) */ + kp = modmap->modifiermap; + for (i = 0; i < mask_table_size; ++i) { + for (k = 0; k < modmap->max_keypermod; ++k) { + if (*kp == e->xkey.keycode) { /* found the keycode */ + /* remove the mask for it */ + e->xkey.state &= ~mask_table[i]; + /* cause the first loop to break; */ + i = mask_table_size; + break; /* get outta here! */ + } + ++kp; + } + } + break; + case MotionNotify: + event_lasttime = e->xmotion.time; + e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask); + /* kill off the Button1Mask etc, only want the modifiers */ + e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask | + Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); + /* compress events */ + while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) { + e->xmotion.x_root = ce.xmotion.x_root; + e->xmotion.y_root = ce.xmotion.y_root; + } + break; + case PropertyNotify: + event_lasttime = e->xproperty.time; + break; + case FocusIn: + case FocusOut: + if (e->xfocus.mode == NotifyGrab) + /*|| e.xfocus.mode == NotifyUngrab ||*/ + + /* From Metacity, from WindowMaker, ignore all funky pointer + root events. Its commented out cuz I don't think we need this + at all. If problems arise we can look into it */ + /*e.xfocus.detail > NotifyNonlinearVirtual) */ + return; /* skip me! */ + if (e->type == FocusOut) { + /* FocusOut events just make us look for FocusIn events. They + are mostly ignored otherwise. */ + XEvent fi; + if (XCheckTypedEvent(ob_display, FocusIn, &fi)) { + event_process(&fi); + /* dont unfocus the window we just focused! */ + if (fi.xfocus.window == e->xfocus.window) + return; + } + } + break; + case EnterNotify: + case LeaveNotify: + event_lasttime = e->xcrossing.time; + if (e->xcrossing.mode != NotifyNormal) + return; /* skip me! */ + break; + } + + client = g_hash_table_lookup(client_map, (gpointer)window); + if (client) { + event_handle_client(client, e); + } else if (window == ob_root) + event_handle_root(e); + else if (e->type == ConfigureRequest) { + /* unhandled configure requests must be used to configure the + window directly */ + XWindowChanges xwc; + + xwc.x = e->xconfigurerequest.x; + xwc.y = e->xconfigurerequest.y; + xwc.width = e->xconfigurerequest.width; + xwc.height = e->xconfigurerequest.height; + xwc.border_width = e->xconfigurerequest.border_width; + xwc.sibling = e->xconfigurerequest.above; + xwc.stack_mode = e->xconfigurerequest.detail; + + g_message("Proxying configure event for 0x%lx\n", window); + + /* we are not to be held responsible if someone sends us an + invalid request! */ + xerror_set_ignore(TRUE); + XConfigureWindow(ob_display, window, + e->xconfigurerequest.value_mask, &xwc); + xerror_set_ignore(FALSE); + } + + /* dispatch Crossing, Pointer and Key events to the hooks */ + switch(e->type) { + case EnterNotify: + context = frame_get_context(client, window); + LOGICALHOOK(EnterWindow, context, client); + break; + case LeaveNotify: + context = frame_get_context(client, window); + LOGICALHOOK(LeaveWindow, context, client); + break; + case ButtonPress: + if (!motion_button) motion_button = e->xbutton.button; + context = frame_get_context(client, window); + mbind_fire(e->xbutton.state, e->xbutton.button, context, + Pointer_Press, client, e->xbutton.x_root, + e->xbutton.y_root); + break; + case ButtonRelease: + if (motion_button == e->xbutton.button) motion_button = 0; + context = frame_get_context(client, window); + mbind_fire(e->xbutton.state, e->xbutton.button, context, + Pointer_Release, client, e->xbutton.x_root, + e->xbutton.y_root); + break; + case MotionNotify: + context = frame_get_context(client, window); + mbind_fire(e->xkey.state, motion_button, context, Pointer_Motion, + client, e->xmotion.x_root, e->xmotion.y_root); + break; + case KeyPress: + kbind_fire(e->xkey.state, e->xkey.keycode, TRUE); + break; + case KeyRelease: + kbind_fire(e->xkey.state, e->xkey.keycode, FALSE); + break; + } +} + +static void event_handle_root(XEvent *e) +{ + Atom msgtype; + + switch(e->type) { + case MapRequest: + g_message("MapRequest on root"); + client_manage(e->xmap.window); + break; + case ClientMessage: + if (e->xclient.format != 32) break; + + msgtype = e->xclient.message_type; + if (msgtype == prop_atoms.net_current_desktop) { + unsigned int d = e->xclient.data.l[0]; + if (d <= screen_num_desktops) + screen_set_desktop(d); + } else if (msgtype == prop_atoms.net_number_of_desktops) { + unsigned int d = e->xclient.data.l[0]; + if (d > 0) + screen_set_num_desktops(d); + } else if (msgtype == prop_atoms.net_showing_desktop) { + screen_show_desktop(e->xclient.data.l[0] != 0); + } + break; + case PropertyNotify: + if (e->xproperty.atom == prop_atoms.net_desktop_names) + screen_update_desktop_names(); + else if (e->xproperty.atom == prop_atoms.net_desktop_layout) + screen_update_layout(); + break; + } +} + +static void event_handle_client(Client *client, XEvent *e) +{ + XEvent ce; + Atom msgtype; + + switch (e->type) { + case FocusIn: + client->focused = TRUE; + frame_adjust_focus(client->frame); + + /* focus state can affect the stacking layer */ + client_calc_layer(client); + + focus_set_client(client); + break; + case FocusOut: + client->focused = FALSE; + frame_adjust_focus(client->frame); + + /* focus state can affect the stacking layer */ + client_calc_layer(client); + + if (focus_client == client) + focus_set_client(NULL); + break; + case ConfigureRequest: + g_message("ConfigureRequest for window %lx", client->window); + /* compress these */ + while (XCheckTypedWindowEvent(ob_display, client->window, + ConfigureRequest, &ce)) { + /* XXX if this causes bad things.. we can compress config req's + with the same mask. */ + e->xconfigurerequest.value_mask |= + ce.xconfigurerequest.value_mask; + if (ce.xconfigurerequest.value_mask & CWX) + e->xconfigurerequest.x = ce.xconfigurerequest.x; + if (ce.xconfigurerequest.value_mask & CWY) + e->xconfigurerequest.y = ce.xconfigurerequest.y; + if (ce.xconfigurerequest.value_mask & CWWidth) + e->xconfigurerequest.width = ce.xconfigurerequest.width; + if (ce.xconfigurerequest.value_mask & CWHeight) + e->xconfigurerequest.height = ce.xconfigurerequest.height; + if (ce.xconfigurerequest.value_mask & CWBorderWidth) + e->xconfigurerequest.border_width = + ce.xconfigurerequest.border_width; + if (ce.xconfigurerequest.value_mask & CWStackMode) + e->xconfigurerequest.detail = ce.xconfigurerequest.detail; + } + + /* if we are iconic (or shaded (fvwm does this)) ignore the event */ + if (client->iconic || client->shaded) return; + + if (e->xconfigurerequest.value_mask & CWBorderWidth) + client->border_width = e->xconfigurerequest.border_width; + + /* resize, then move, as specified in the EWMH section 7.7 */ + if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight | + CWX | CWY)) { + int x, y, w, h; + Corner corner; + + x = (e->xconfigurerequest.value_mask & CWX) ? + e->xconfigurerequest.x : client->area.x; + y = (e->xconfigurerequest.value_mask & CWY) ? + e->xconfigurerequest.y : client->area.y; + w = (e->xconfigurerequest.value_mask & CWWidth) ? + e->xconfigurerequest.width : client->area.width; + h = (e->xconfigurerequest.value_mask & CWHeight) ? + e->xconfigurerequest.height : client->area.height; + + switch (client->gravity) { + case NorthEastGravity: + case EastGravity: + corner = Corner_TopRight; + break; + case SouthWestGravity: + case SouthGravity: + corner = Corner_BottomLeft; + break; + case SouthEastGravity: + corner = Corner_BottomRight; + break; + default: /* NorthWest, Static, etc */ + corner = Corner_TopLeft; + } + + client_configure(client, corner, x, y, w, h, FALSE, FALSE); + } + + if (e->xconfigurerequest.value_mask & CWStackMode) { + switch (e->xconfigurerequest.detail) { + case Below: + case BottomIf: + stacking_lower(client); + break; + + case Above: + case TopIf: + default: + stacking_raise(client); + break; + } + } + break; + case UnmapNotify: + if (client->ignore_unmaps) { + client->ignore_unmaps--; + break; + } + g_message("UnmapNotify for %lx", client->window); + client_unmanage(client); + break; + case DestroyNotify: + g_message("DestroyNotify for %lx", client->window); + client_unmanage(client); + break; + case ReparentNotify: + /* this is when the client is first taken captive in the frame */ + if (e->xreparent.parent == client->frame->plate) break; + + /* + This event is quite rare and is usually handled in unmapHandler. + However, if the window is unmapped when the reparent event occurs, + the window manager never sees it because an unmap event is not sent + to an already unmapped window. + */ + + /* we don't want the reparent event, put it back on the stack for the + X server to deal with after we unmanage the window */ + XPutBackEvent(ob_display, e); + + client_unmanage(client); + break; + case MapRequest: + /* we shouldn't be able to get this unless we're iconic */ + g_assert(client->iconic); + + LOGICALHOOK(RequestActivate, g_quark_try_string("client"), client); + break; + case ClientMessage: + /* validate cuz we query stuff off the client here */ + if (!client_validate(client)) break; + + if (e->xclient.format != 32) return; + + msgtype = e->xclient.message_type; + if (msgtype == prop_atoms.wm_change_state) { + /* compress changes into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL messages of a + type, not just messages in a row without other + message types between. */ + if (ce.xclient.message_type != msgtype) { + XPutBackEvent(ob_display, &ce); + break; + } + e->xclient = ce.xclient; + } + client_set_wm_state(client, e->xclient.data.l[0]); + } else if (msgtype == prop_atoms.net_wm_desktop) { + /* compress changes into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL messages of a + type, not just messages in a row without other + message types between. */ + if (ce.xclient.message_type != msgtype) { + XPutBackEvent(ob_display, &ce); + break; + } + e->xclient = ce.xclient; + } + client_set_desktop(client, e->xclient.data.l[0]); + } else if (msgtype == prop_atoms.net_wm_state) { + /* can't compress these */ + g_message("net_wm_state %s %ld %ld for 0x%lx\n", + (e->xclient.data.l[0] == 0 ? "Remove" : + e->xclient.data.l[0] == 1 ? "Add" : + e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"), + e->xclient.data.l[1], e->xclient.data.l[2], + client->window); + client_set_state(client, e->xclient.data.l[0], + e->xclient.data.l[1], e->xclient.data.l[2]); + } else if (msgtype == prop_atoms.net_close_window) { + g_message("net_close_window for 0x%lx\n", client->window); + client_close(client); + } else if (msgtype == prop_atoms.net_active_window) { + g_message("net_active_window for 0x%lx\n", client->window); + if (screen_showing_desktop) + screen_show_desktop(FALSE); + if (client->iconic) + client_iconify(client, FALSE, TRUE); + else if (!client->frame->visible) + /* if its not visible for other reasons, then don't mess + with it */ + return; + LOGICALHOOK(RequestActivate, g_quark_try_string("client"), client); + } + break; + case PropertyNotify: + /* validate cuz we query stuff off the client here */ + if (!client_validate(client)) break; + + /* compress changes to a single property into a single change */ + while (XCheckTypedWindowEvent(ob_display, e->type, + client->window, &ce)) { + /* XXX: it would be nice to compress ALL changes to a property, + not just changes in a row without other props between. */ + if (ce.xproperty.atom != e->xproperty.atom) { + XPutBackEvent(ob_display, &ce); + break; + } + } + + msgtype = e->xproperty.atom; + if (msgtype == XA_WM_NORMAL_HINTS) { + client_update_normal_hints(client); + /* normal hints can make a window non-resizable */ + client_setup_decor_and_functions(client); + } else if (msgtype == XA_WM_HINTS) + client_update_wmhints(client); + else if (msgtype == XA_WM_TRANSIENT_FOR) { + client_update_transient_for(client); + client_get_type(client); + /* type may have changed, so update the layer */ + client_calc_layer(client); + client_setup_decor_and_functions(client); + } + else if (msgtype == prop_atoms.net_wm_name || + msgtype == prop_atoms.wm_name) + client_update_title(client); + else if (msgtype == prop_atoms.net_wm_icon_name || + msgtype == prop_atoms.wm_icon_name) + client_update_icon_title(client); + else if (msgtype == prop_atoms.wm_class) + client_update_class(client); + else if (msgtype == prop_atoms.wm_protocols) { + client_update_protocols(client); + client_setup_decor_and_functions(client); + } + else if (msgtype == prop_atoms.net_wm_strut) + client_update_strut(client); + else if (msgtype == prop_atoms.net_wm_icon) + client_update_icons(client); + else if (msgtype == prop_atoms.kwm_win_icon) + client_update_kwm_icon(client); + } +} diff --git a/c/event.h b/c/event.h new file mode 100644 index 00000000..9153116e --- /dev/null +++ b/c/event.h @@ -0,0 +1,12 @@ +#ifndef __events_h +#define __events_h + +/*! Time at which the last event with a timestamp occured. */ +extern Time event_lasttime; + +void event_startup(); +void event_shutdown(); + +void event_loop(); + +#endif diff --git a/c/eventdata.c b/c/eventdata.c new file mode 100644 index 00000000..e3bf15e1 --- /dev/null +++ b/c/eventdata.c @@ -0,0 +1,433 @@ +#include "eventdata.h" +#include "openbox.h" +#include "event.h" +#include "clientwrap.h" +#include <X11/Xlib.h> + +/* + * + * Define the type 'EventData' + * + */ + +#define IS_EVENTDATA(v) ((v)->ob_type == &EventDataType) +#define CHECK_EVENTDATA(self, funcname) { \ + if (!IS_EVENTDATA(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' requires an 'EventData' " \ + "object"); \ + return NULL; \ + } \ +} + +staticforward PyTypeObject EventDataType; + +static PyObject *eventdata_type(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "type"); + if (!PyArg_ParseTuple(args, ":type")) + return NULL; + return PyInt_FromLong(self->type); +} + +static PyObject *eventdata_time(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "time"); + if (!PyArg_ParseTuple(args, ":time")) + return NULL; + return PyInt_FromLong(event_lasttime); +} + +static PyObject *eventdata_context(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "context"); + if (!PyArg_ParseTuple(args, ":context")) + return NULL; + return PyString_FromString(self->context); +} + +static PyObject *eventdata_client(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "client"); + if (!PyArg_ParseTuple(args, ":client")) + return NULL; + if (self->client == NULL) { + Py_INCREF(Py_None); + return Py_None; + } else { + return clientwrap_new(self->client); + } +} + +static PyObject *eventdata_keycode(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "keycode"); + if (!PyArg_ParseTuple(args, ":keycode")) + return NULL; + switch (self->type) { + case Key_Press: + case Key_Release: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Key event"); + return NULL; + } + return PyInt_FromLong(self->details.key->keycode); +} + +static PyObject *eventdata_modifiers(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "key"); + if (!PyArg_ParseTuple(args, ":key")) + return NULL; + switch (self->type) { + case Key_Press: + case Key_Release: + case Pointer_Press: + case Pointer_Release: + case Pointer_Motion: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Key or Pointer event"); + return NULL; + } + return PyInt_FromLong(self->details.key->modifiers); +} + +static PyObject *eventdata_keyName(EventData *self, PyObject *args) +{ + GList *it; + PyObject *tuple; + int i; + + CHECK_EVENTDATA(self, "keyName"); + if (!PyArg_ParseTuple(args, ":keyName")) + return NULL; + switch (self->type) { + case Key_Press: + case Key_Release: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Key event"); + return NULL; + } + + if (self->details.key->keylist != NULL) { + tuple = PyTuple_New(g_list_length(self->details.key->keylist)); + for (i = 0, it = self->details.key->keylist; it != NULL; + it = it->next, ++i) + PyTuple_SET_ITEM(tuple, i, PyString_FromString(it->data)); + return tuple; + } else { + GString *str = g_string_sized_new(0); + KeySym sym; + + if (self->details.key->modifiers & ControlMask) + g_string_append(str, "C-"); + if (self->details.key->modifiers & ShiftMask) + g_string_append(str, "S-"); + if (self->details.key->modifiers & Mod1Mask) + g_string_append(str, "Mod1-"); + if (self->details.key->modifiers & Mod2Mask) + g_string_append(str, "Mod2-"); + if (self->details.key->modifiers & Mod3Mask) + g_string_append(str, "Mod3-"); + if (self->details.key->modifiers & Mod4Mask) + g_string_append(str, "Mod4-"); + if (self->details.key->modifiers & Mod5Mask) + g_string_append(str, "Mod5-"); + + sym = XKeycodeToKeysym(ob_display, self->details.key->keycode, 0); + if (sym == NoSymbol) + g_string_append(str, "NoSymbol"); + else { + char *name = XKeysymToString(sym); + if (name == NULL) + name = "Undefined"; + g_string_append(str, name); + } + + tuple = PyTuple_New(1); + PyTuple_SET_ITEM(tuple, 0, PyString_FromString(str->str)); + g_string_free(str, TRUE); + + return tuple; + } +} + +static PyObject *eventdata_button(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "button"); + if (!PyArg_ParseTuple(args, ":button")) + return NULL; + switch (self->type) { + case Pointer_Press: + case Pointer_Release: + case Pointer_Motion: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Pointer event"); + return NULL; + } + return PyInt_FromLong(self->details.pointer->button); +} + +static PyObject *eventdata_buttonName(EventData *self, PyObject *args) +{ + CHECK_EVENTDATA(self, "buttonName"); + if (!PyArg_ParseTuple(args, ":buttonName")) + return NULL; + switch (self->type) { + case Pointer_Press: + case Pointer_Release: + case Pointer_Motion: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Pointer event"); + return NULL; + } + + if (self->details.pointer->name != NULL) { + return PyString_FromString(self->details.pointer->name); + } else { + PyObject *pystr; + GString *str = g_string_sized_new(0); + + if (self->details.pointer->modifiers & ControlMask) + g_string_append(str, "C-"); + if (self->details.pointer->modifiers & ShiftMask) + g_string_append(str, "S-"); + if (self->details.pointer->modifiers & Mod1Mask) + g_string_append(str, "Mod1-"); + if (self->details.pointer->modifiers & Mod2Mask) + g_string_append(str, "Mod2-"); + if (self->details.pointer->modifiers & Mod3Mask) + g_string_append(str, "Mod3-"); + if (self->details.pointer->modifiers & Mod4Mask) + g_string_append(str, "Mod4-"); + if (self->details.pointer->modifiers & Mod5Mask) + g_string_append(str, "Mod5-"); + + g_string_append_printf(str, "%d", self->details.pointer->button); + + pystr = PyString_FromString(str->str); + + g_string_free(str, TRUE); + + return pystr; + } +} + +static PyObject *eventdata_position(EventData *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_EVENTDATA(self, "position"); + if (!PyArg_ParseTuple(args, ":position")) + return NULL; + switch (self->type) { + case Pointer_Press: + case Pointer_Release: + case Pointer_Motion: + break; + default: + PyErr_SetString(PyExc_TypeError, + "The EventData object is not a Pointer event"); + return NULL; + } + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(self->details.pointer->xroot)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(self->details.pointer->yroot)); + return tuple; +} + +static PyMethodDef EventDataAttributeMethods[] = { + {"type", (PyCFunction)eventdata_type, METH_VARARGS, + "data.type() -- Return the event type"}, + {"context", (PyCFunction)eventdata_context, METH_VARARGS, + "data.context() -- Return the context for the event. If it is " + "\"client\", then data.client() can be used to find out the " + "client."}, + {"client", (PyCFunction)eventdata_client, METH_VARARGS, + "data.client() -- Return the client for the event. This may be None if " + "there is no client, even if data.context() gives Context_Client."}, + {"time", (PyCFunction)eventdata_time, METH_VARARGS, + "data.time() -- Return the time at which the last X event occured with " + "a timestamp. Should be the time at which this event, or the event that " + "caused this event to occur happened."}, + {"modifiers", (PyCFunction)eventdata_modifiers, METH_VARARGS, + "data.modifiers() -- Return the modifier keymask that was pressed " + "when the event occured. A bitmask of ShiftMask, LockMask, ControlMask, " + "Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, and Mod5Mask. Cannot be used " + "when the data.type() is not a Key_* or Pointer_* event type."}, + {"keycode", (PyCFunction)eventdata_keycode, METH_VARARGS, + "data.keycode() -- Return the keycode for the key which generated the " + "event. Cannot be used when the data.type() is not a Key_* event type."}, + {"keyName", (PyCFunction)eventdata_keyName, METH_VARARGS, + "data.keyName() -- Return a tuple of the string names of the key which " + "generated the event. Cannot be used when the data.type() is not a Key_* " + "event " + "type."}, + {"button", (PyCFunction)eventdata_button, METH_VARARGS, + "data.button() -- Return the pointer button which generated the event. " + "Cannot be used when the data.type() is not a Pointer_* event type."}, + {"buttonName", (PyCFunction)eventdata_keyName, METH_VARARGS, + "data.buttonName() -- Return the name of the button which generated the " + "event. Cannot be used when the data.type() is not a Pointer_* event " + "type."}, + {"position", (PyCFunction)eventdata_position, METH_VARARGS, + "data.position() -- Returns the current position of the pointer on the " + "root window when the event was generated. Gives the position in a tuple " + "with a format of (x, y). Cannot be used when the data.type() is not a " + "Pointer_* event type."}, + { NULL, NULL, 0, NULL } +}; + +static void data_dealloc(EventData *self) +{ + GList *it; + + switch(self->type) { + case Logical_EnterWindow: + case Logical_LeaveWindow: + case Logical_NewWindow: + case Logical_CloseWindow: + case Logical_Startup: + case Logical_Shutdown: + case Logical_RequestActivate: + case Logical_WindowShow: + case Logical_WindowHide: + case Logical_Focus: + case Logical_Bell: + case Logical_UrgentWindow: + g_free(self->details.logical); + break; + case Pointer_Press: + case Pointer_Release: + case Pointer_Motion: + if (self->details.pointer->name != NULL) + g_free(self->details.pointer->name); + g_free(self->details.pointer); + break; + case Key_Press: + case Key_Release: + for (it = self->details.key->keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(self->details.key->keylist); + g_free(self->details.key); + break; + default: + g_assert_not_reached(); + } + PyObject_Del((PyObject*) self); +} + +static PyObject *eventdata_getattr(EventData *self, char *name) +{ + return Py_FindMethod(EventDataAttributeMethods, (PyObject*)self, name); +} + +static PyTypeObject EventDataType = { + PyObject_HEAD_INIT(NULL) + 0, + "EventData", + sizeof(EventData), + 0, + (destructor) data_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc) eventdata_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + + + +void eventdata_startup() +{ + EventDataType.ob_type = &PyType_Type; + PyType_Ready(&EventDataType); +} + +void eventdata_shutdown() +{ +} + +void eventdata_free(EventData *data) +{ + Py_DECREF(data); +} + +EventData *eventdata_new_logical(EventType type, GQuark context, + struct Client *client) +{ + EventData *data; + + g_assert(type < Pointer_Press); + + data = PyObject_New(EventData, &EventDataType); + data->type = type; + data->context = g_quark_to_string(context); + data->client = client; + data->details.logical = g_new(LogicalEvent, 1); + return data; +} + +EventData *eventdata_new_pointer(EventType type, GQuark context, + struct Client *client, guint modifiers, + guint button, char *name, + int xroot, int yroot) +{ + EventData *data; + + g_assert(type >= Pointer_Press && type < Key_Press); + + data = PyObject_New(EventData, &EventDataType); + data->type = type; + data->context = g_quark_to_string(context); + data->client = client; + data->details.pointer = g_new(PointerEvent, 1); + data->details.pointer->modifiers = modifiers; + data->details.pointer->button = button; + data->details.pointer->name = name == NULL ? name : g_strdup(name); + data->details.pointer->xroot = xroot; + data->details.pointer->yroot = yroot; + return data; +} + +EventData *eventdata_new_key(EventType type, GQuark context, + struct Client *client, guint modifiers, + guint keycode, GList *keylist) +{ + EventData *data; + GList *mykeylist, *it; + + g_assert(type >= Key_Press); + + data = PyObject_New(EventData, &EventDataType); + data->type = type; + data->context = g_quark_to_string(context); + data->client = client; + data->details.key = g_new(KeyEvent, 1); + + /* make a copy of the keylist. + If the user were to clear the key bindings, then the keylist given here + would no longer point at valid memory.*/ + mykeylist = g_list_copy(keylist); /* shallow copy */ + for (it = mykeylist; it != NULL; it = it->next) /* deep copy */ + it->data = g_strdup(it->data); + + data->details.key->keylist = mykeylist; + data->details.key->keycode = keycode; + data->details.key->modifiers = modifiers; + return data; +} diff --git a/c/eventdata.h b/c/eventdata.h new file mode 100644 index 00000000..ef6beabd --- /dev/null +++ b/c/eventdata.h @@ -0,0 +1,74 @@ +#ifndef __eventdata_h +#define __eventdata_h + +#include "obexport.h" +#include <Python.h> +#include <glib.h> + +struct Client; + +typedef struct { + int temp:1; /* just a placeholder to kill warnings for now.. */ +} LogicalEvent; + +typedef struct { + /*! The button which generated the event */ + guint button; + /*! The pointer's x position on the root window when the event occured */ + int xroot; + /*! The pointer's y position on the root window when the event occured */ + int yroot; + /*! The modifiers that were pressed when the event occured. A bitmask of: + ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask, + Mod4Mask, Mod5Mask */ + guint modifiers; + /*! The name of the button/modifier combination being pressed, + eg "Mod1-1" */ + char *name; +} PointerEvent; + +typedef struct { + /*! The keycode of the key which generated the event */ + guint keycode; + /*! The modifiers that were pressed when the event occured. A bitmask of: + ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask, + Mod4Mask, Mod5Mask */ + guint modifiers; + /* The list of strings which make up the chain that fired, + eg ("Mod1-a", "a") */ + GList *keylist; +} KeyEvent; + +/* EventData is a PyObject */ +typedef struct EventData { + PyObject_HEAD + /* The type of event which occured */ + EventType type; + /*! The context in which the event occured, the type of window it occured + for. */ + const char *context; + /* The Client on which the event occured, or NULL */ + struct Client *client; + + union EventDetails { + LogicalEvent *logical; + PointerEvent *pointer; + KeyEvent *key; + } details; +} EventData; + +void eventdata_startup(); +void eventdata_shutdown(); + +EventData *eventdata_new_logical(EventType type, GQuark context, + struct Client *client); +EventData *eventdata_new_pointer(EventType type, GQuark context, + struct Client *client, guint modifiers, + guint button, char *name, + int xroot, int yroot); +EventData *eventdata_new_key(EventType type, GQuark context, + struct Client *client, guint modifiers, + guint keycode, GList *keylist); +void eventdata_free(EventData *data); + +#endif diff --git a/c/extensions.c b/c/extensions.c new file mode 100644 index 00000000..3fe43194 --- /dev/null +++ b/c/extensions.c @@ -0,0 +1,34 @@ +#include "openbox.h" +#include "extensions.h" + +gboolean extensions_xkb = FALSE; +int extensions_xkb_event_basep; +gboolean extensions_shape = FALSE; +int extensions_shape_event_basep; +gboolean extensions_xinerama = FALSE; +int extensions_xinerama_event_basep; + + +void extensions_query_all() +{ + int junk; + (void)junk; + +#ifdef XKB + extensions_xkb = + XkbQueryExtension(ob_display, &junk, &extensions_xkb_event_basep, + &junk, NULL, NULL); +#endif + +#ifdef SHAPE + extensions_shape = + XShapeQueryExtension(ob_display, &extensions_shape_event_basep, + &junk); +#endif + +#ifdef XINERAMA + extensions_xinerama = + XineramaQueryExtension(ob_display, &extensions_xinerama_event_basep, + &junk); +#endif +} diff --git a/c/extensions.h b/c/extensions.h new file mode 100644 index 00000000..3c11076a --- /dev/null +++ b/c/extensions.h @@ -0,0 +1,33 @@ +#ifndef __extensions_h +#define __extensions_h + +#include <X11/Xlib.h> +#ifdef XKB +#include <X11/XKBlib.h> +#endif +#ifdef SHAPE +#include <X11/extensions/shape.h> +#endif +#ifdef XINERAMA +#include <X11/extensions/Xinerama.h> +#endif +#include <glib.h> + +/*! Does the display have the XKB extension? */ +extern gboolean extensions_xkb; +/*! Base for events for the XKB extension */ +extern int extensions_xkb_event_basep; + +/*! Does the display have the Shape extension? */ +extern gboolean extensions_shape; +/*! Base for events for the Shape extension */ +extern int extensions_shape_event_basep; + +/*! Does the display have the Xinerama extension? */ +extern gboolean extensions_xinerama; +/*! Base for events for the Xinerama extension */ +extern int extensions_xinerama_event_basep; + +void extensions_query_all(); + +#endif diff --git a/c/focus.c b/c/focus.c new file mode 100644 index 00000000..329a4a9d --- /dev/null +++ b/c/focus.c @@ -0,0 +1,56 @@ +#include "openbox.h" +#include "client.h" +#include "screen.h" +#include "prop.h" +#include "hooks.h" +#include <X11/Xlib.h> + +Client *focus_client = NULL; + +Window focus_backup = None; + +void focus_set_client(Client *client); + +void focus_startup() +{ + /* create the window which gets focus when no clients get it. Have to + make it override-redirect so we don't try manage it, since it is + mapped. */ + XSetWindowAttributes attrib; + + attrib.override_redirect = TRUE; + focus_backup = XCreateWindow(ob_display, ob_root, + -100, -100, 1, 1, 0, 0, InputOnly, + CopyFromParent, CWOverrideRedirect, &attrib); + XMapRaised(ob_display, focus_backup); + + /* start with nothing focused */ + focus_set_client(NULL); +} + +void focus_set_client(Client *client) +{ + Window active; + + /* sometimes this is called with the already-focused window, this is + important for the python scripts to work (eg, c = 0 twice). don't just + return if _focused_client == c */ + + /* uninstall the old colormap, and install the new one */ + screen_install_colormap(focus_client, FALSE); + screen_install_colormap(client, TRUE); + + + if (client == NULL) { + /* when nothing will be focused, send focus to the backup target */ + XSetInputFocus(ob_display, focus_backup, RevertToNone, CurrentTime); + } + + focus_client = client; + + /* set the NET_ACTIVE_WINDOW hint */ + active = client ? client->window : None; + PROP_SET32(ob_root, net_active_window, window, active); + + LOGICALHOOK(Focus, g_quark_try_string("client"), client); +} diff --git a/c/focus.h b/c/focus.h new file mode 100644 index 00000000..9db52026 --- /dev/null +++ b/c/focus.h @@ -0,0 +1,20 @@ +#ifndef __focus_h +#define __focus_h + +#include <X11/Xlib.h> + +struct Client; + +/*! The window which gets focus when nothing else will be focused */ +extern Window focus_backup; + +/*! The client which is currently focused */ +extern struct Client *focus_client; + +void focus_startup(); + +/*! Specify which client is currently focused, this doesn't actually + send focus anywhere, its called by the Focus event handlers */ +void focus_set_client(struct Client *client); + +#endif diff --git a/c/frame.c b/c/frame.c new file mode 100644 index 00000000..536b4cd6 --- /dev/null +++ b/c/frame.c @@ -0,0 +1,533 @@ +#include "openbox.h" +#include "frame.h" +#include "extensions.h" +#include "hooks.h" + +#define PLATE_EVENTMASK (SubstructureRedirectMask | ButtonPressMask) +#define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask) + +static Window createWindow(Window parent, unsigned long mask, + XSetWindowAttributes *attrib) +{ + /* XXX DONT USE THE DEFAULT SHIT */ + return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0, + DefaultDepth(ob_display, ob_screen), InputOutput, + DefaultVisual(ob_display, ob_screen), + mask, attrib); + +} + +Frame *frame_new(Client *client) +{ + XSetWindowAttributes attrib; + unsigned long mask; + Frame *self; + + self = g_new(Frame, 1); + + self->client = client; + self->visible = FALSE; + + /* create all of the decor windows */ + mask = CWOverrideRedirect | CWEventMask; + attrib.event_mask = FRAME_EVENTMASK; + attrib.override_redirect = TRUE; + self->window = createWindow(ob_root, mask, &attrib); + + mask = 0; + self->plate = createWindow(self->window, mask, &attrib); + mask = CWEventMask; + attrib.event_mask = (ButtonPressMask | ButtonReleaseMask | + ButtonMotionMask | ExposureMask); + self->title = createWindow(self->window, mask, &attrib); + self->label = createWindow(self->title, mask, &attrib); + self->max = createWindow(self->title, mask, &attrib); + self->close = createWindow(self->title, mask, &attrib); + self->desk = createWindow(self->title, mask, &attrib); + self->icon = createWindow(self->title, mask, &attrib); + self->iconify = createWindow(self->title, mask, &attrib); + self->handle = createWindow(self->window, mask, &attrib); + mask |= CWCursor; + attrib.cursor = ob_cursors.ll_angle; + self->lgrip = createWindow(self->handle, mask, &attrib); + attrib.cursor = ob_cursors.lr_angle; + self->rgrip = createWindow(self->handle, mask, &attrib); + + /* the other stuff is shown based on decor settings */ + XMapWindow(ob_display, self->plate); + XMapWindow(ob_display, self->lgrip); + XMapWindow(ob_display, self->rgrip); + XMapWindow(ob_display, self->label); + + + /* XXX TEMPORARY OF COURSE!@&*(@! */ + + XSetWindowBackground(ob_display, self->title, 0x3333aa); + XSetWindowBackground(ob_display, self->handle, 0x3333aa); + XSetWindowBackground(ob_display, self->lgrip, 0x2233aa); + XSetWindowBackground(ob_display, self->rgrip, 0x2233aa); + + XSetWindowBorder(ob_display, self->window, 0); + XSetWindowBorder(ob_display, self->label, 0); + XSetWindowBorder(ob_display, self->rgrip, 0); + XSetWindowBorder(ob_display, self->lgrip, 0); + XSetWindowBorder(ob_display, self->plate, 0x771122); + + /* XXX /TEMPORARY OF COURSE!@&*(@! */ + + /* set all the windows for the frame in the client_map */ + g_hash_table_insert(client_map, (gpointer)self->window, self->client); + g_hash_table_insert(client_map, (gpointer)self->plate, self->client); + g_hash_table_insert(client_map, (gpointer)self->title, self->client); + g_hash_table_insert(client_map, (gpointer)self->label, self->client); + g_hash_table_insert(client_map, (gpointer)self->max, self->client); + g_hash_table_insert(client_map, (gpointer)self->close, self->client); + g_hash_table_insert(client_map, (gpointer)self->desk, self->client); + g_hash_table_insert(client_map, (gpointer)self->icon, self->client); + g_hash_table_insert(client_map, (gpointer)self->iconify, self->client); + g_hash_table_insert(client_map, (gpointer)self->handle, self->client); + g_hash_table_insert(client_map, (gpointer)self->lgrip, self->client); + g_hash_table_insert(client_map, (gpointer)self->rgrip, self->client); + + return self; +} + +void frame_free(Frame *self) +{ + /* remove all the windows for the frame from the client_map */ + g_hash_table_remove(client_map, (gpointer)self->window); + g_hash_table_remove(client_map, (gpointer)self->plate); + g_hash_table_remove(client_map, (gpointer)self->title); + g_hash_table_remove(client_map, (gpointer)self->label); + g_hash_table_remove(client_map, (gpointer)self->max); + g_hash_table_remove(client_map, (gpointer)self->close); + g_hash_table_remove(client_map, (gpointer)self->desk); + g_hash_table_remove(client_map, (gpointer)self->icon); + g_hash_table_remove(client_map, (gpointer)self->iconify); + g_hash_table_remove(client_map, (gpointer)self->handle); + g_hash_table_remove(client_map, (gpointer)self->lgrip); + g_hash_table_remove(client_map, (gpointer)self->rgrip); + + XDestroyWindow(ob_display, self->window); + + g_free(self); +} + +void frame_grab_client(Frame *self) +{ + /* reparent the client to the frame */ + XReparentWindow(ob_display, self->client->window, self->plate, 0, 0); + /* + When reparenting the client window, it is usually not mapped yet, since + this occurs from a MapRequest. However, in the case where Openbox is + starting up, the window is already mapped, so we'll see unmap events for + it. There are 2 unmap events generated that we see, one with the 'event' + member set the root window, and one set to the client, but both get + handled and need to be ignored. + */ + if (ob_state == State_Starting) + self->client->ignore_unmaps += 2; + + /* select the event mask on the client's parent (to receive config/map + req's) the ButtonPress is to catch clicks on the client border */ + XSelectInput(ob_display, self->plate, PLATE_EVENTMASK); + + /* map the client so it maps when the frame does */ + XMapWindow(ob_display, self->client->window); + + frame_adjust_size(self); + frame_adjust_position(self); +} + +void frame_release_client(Frame *self) +{ + XEvent ev; + + /* check if the app has already reparented its window away */ + if (XCheckTypedWindowEvent(ob_display, self->client->window, + ReparentNotify, &ev)) { + XPutBackEvent(ob_display, &ev); + /* re-map the window since the unmanaging process unmaps it */ + XMapWindow(ob_display, self->client->window); + } else { + /* according to the ICCCM - if the client doesn't reparent itself, + then we will reparent the window to root for them */ + XReparentWindow(ob_display, self->client->window, ob_root, + self->client->area.x, self->client->area.y); + } +} + +void frame_show(Frame *self) +{ + if (!self->visible) { + self->visible = TRUE; + XMapWindow(ob_display, self->window); + LOGICALHOOK(WindowShow, g_quark_try_string("client"), self->client); + } +} + +void frame_hide(Frame *self) +{ + if (self->visible) { + self->visible = FALSE; + self->client->ignore_unmaps++; + XUnmapWindow(ob_display, self->window); + LOGICALHOOK(WindowHide, g_quark_try_string("client"), self->client); + } +} + +void frame_adjust_size(Frame *self) +{ + self->decorations = self->client->decorations; + + /* XXX set shit from the style */ + self->geom.font_height = 10; + self->geom.bevel = 1; + self->geom.button_size = self->geom.font_height - 2; + self->geom.handle_height = 2; + self->geom.grip_width = self->geom.button_size * 2; + XResizeWindow(ob_display, self->lgrip, self->geom.grip_width, + self->geom.handle_height); + XResizeWindow(ob_display, self->rgrip, self->geom.grip_width, + self->geom.handle_height); + + + + + if (self->decorations & Decor_Border) { + self->geom.bwidth = 1;/*XXX style->frameBorderWidth(); */ + self->geom.cbwidth = 1; /*XXX style->clientBorderWidth(); */ + } else { + self->geom.bwidth = self->geom.cbwidth = 0; + } + STRUT_SET(self->innersize, self->geom.cbwidth, self->geom.cbwidth, + self->geom.cbwidth, self->geom.cbwidth); + self->geom.width = self->client->area.width + self->geom.cbwidth * 2; + g_assert(self->geom.width > 0); + + /* set border widths */ + XSetWindowBorderWidth(ob_display, self->plate, self->geom.cbwidth); + XSetWindowBorderWidth(ob_display, self->window, self->geom.bwidth); + XSetWindowBorderWidth(ob_display, self->title, self->geom.bwidth); + XSetWindowBorderWidth(ob_display, self->handle, self->geom.bwidth); + XSetWindowBorderWidth(ob_display, self->lgrip, self->geom.bwidth); + XSetWindowBorderWidth(ob_display, self->rgrip, self->geom.bwidth); + + /* position/size and map/unmap all the windows */ + + if (self->decorations & Decor_Titlebar) { + self->geom.title_height = self->geom.font_height + + self->geom.bevel * 2; + XMoveResizeWindow(ob_display, self->title, + -self->geom.bwidth, -self->geom.bwidth, + self->geom.width, self->geom.title_height); + self->innersize.top += self->geom.title_height + self->geom.bwidth; + XMapWindow(ob_display, self->title); + + /* layout the title bar elements */ + /*XXX layoutTitle(); */ + } else { + XUnmapWindow(ob_display, self->title); + /* make all the titlebar stuff not render */ + self->decorations &= ~(Decor_Icon | Decor_Iconify | + Decor_Maximize | Decor_Close | + Decor_AllDesktops); + } + + if (self->decorations & Decor_Handle) { + self->geom.handle_y = self->innersize.top + + self->client->area.height + self->geom.cbwidth; + XMoveResizeWindow(ob_display, self->handle, + -self->geom.bwidth, self->geom.handle_y, + self->geom.width, self->geom.handle_height); + XMoveWindow(ob_display, self->lgrip, + -self->geom.bwidth, -self->geom.bwidth); + XMoveWindow(ob_display, self->rgrip, + -self->geom.bwidth + self->geom.width - + self->geom.grip_width, -self->geom.bwidth); + self->innersize.bottom += self->geom.handle_height + + self->geom.bwidth; + XMapWindow(ob_display, self->handle); + } else + XUnmapWindow(ob_display, self->handle); + + XResizeWindow(ob_display, self->window, self->geom.width, + (self->client->shaded ? self->geom.title_height : + self->innersize.top + self->innersize.bottom + + self->client->area.height)); + + /* do this in two steps because clients whose gravity is set to + 'Static' don't end up getting moved at all with an XMoveResizeWindow */ + XMoveWindow(ob_display, self->plate, + self->innersize.left - self->geom.cbwidth, + self->innersize.top - self->geom.cbwidth); + XResizeWindow(ob_display, self->plate, self->client->area.width, + self->client->area.height); + + STRUT_SET(self->size, + self->innersize.left + self->geom.bwidth, + self->innersize.right + self->geom.bwidth, + self->innersize.top + self->geom.bwidth, + self->innersize.bottom + self->geom.bwidth); + + RECT_SET_SIZE(self->area, + self->client->area.width + + self->size.left + self->size.right, + self->client->area.height + + self->size.top + self->size.bottom); + + /* + // render all the elements + int screen = _client->screen(); + bool focus = _client->focused(); + if (_decorations & Client::Decor_Titlebar) { + render(screen, otk::Size(geom.width, geom.title_height()), _title, + &_title_sur, *(focus ? style->titlebarFocusBackground() : + style->titlebarUnfocusBackground()), false); + + renderLabel(); + renderMax(); + renderDesk(); + renderIconify(); + renderIcon(); + renderClose(); + } + + if (_decorations & Client::Decor_Handle) { + render(screen, otk::Size(geom.width, geom.handle_height), _handle, + &_handle_sur, *(focus ? style->handleFocusBackground() : + style->handleUnfocusBackground())); + render(screen, otk::Size(geom.grip_width(), geom.handle_height), _lgrip, + &_grip_sur, *(focus ? style->gripFocusBackground() : + style->gripUnfocusBackground())); + if ((focus ? style->gripFocusBackground() : + style->gripUnfocusBackground())->parentRelative()) + XSetWindowBackgroundPixmap(**otk::display, _rgrip, ParentRelative); + else { + XSetWindowBackgroundPixmap(**otk::display, _rgrip, _grip_sur->pixmap()); + } + XClearWindow(**otk::display, _rgrip); + } + + XSetWindowBorder(**otk::display, _plate, + focus ? style->clientBorderFocusColor()->pixel() : + style->clientBorderUnfocusColor()->pixel()); + + */ + + frame_adjust_shape(self); +} + +void frame_adjust_position(Frame *self) +{ + self->area.x = self->client->area.x; + self->area.y = self->client->area.y; + frame_client_gravity(self, &self->area.x, &self->area.y); + XMoveWindow(ob_display, self->window, self->area.x, self->area.y); +} + +void frame_adjust_shape(Frame *self) +{ +#ifdef SHAPE + int num; + XRectangle xrect[2]; + + if (!self->client->shaped) { + /* clear the shape on the frame window */ + XShapeCombineMask(ob_display, self->window, ShapeBounding, + self->innersize.left, + self->innersize.top, + None, ShapeSet); + } else { + /* make the frame's shape match the clients */ + XShapeCombineShape(ob_display, self->window, ShapeBounding, + self->innersize.left, + self->innersize.top, + self->client->window, ShapeBounding, ShapeSet); + + num = 0; + if (self->decorations & Decor_Titlebar) { + xrect[0].x = -self->geom.bevel; + xrect[0].y = -self->geom.bevel; + xrect[0].width = self->geom.width + self->geom.bwidth * 2; + xrect[0].height = self->geom.title_height + + self->geom.bwidth * 2; + ++num; + } + + if (self->decorations & Decor_Handle) { + xrect[1].x = -self->geom.bevel; + xrect[1].y = self->geom.handle_y; + xrect[1].width = self->geom.width + self->geom.bwidth * 2; + xrect[1].height = self->geom.handle_height + + self->geom.bwidth * 2; + ++num; + } + + XShapeCombineRectangles(ob_display, self->window, + ShapeBounding, 0, 0, xrect, num, + ShapeUnion, Unsorted); + } +#endif +} + +void frame_client_gravity(Frame *self, int *x, int *y) +{ + /* horizontal */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case SouthWestGravity: + case WestGravity: + break; + + case NorthGravity: + case SouthGravity: + case CenterGravity: + *x -= (self->size.left + self->size.right) / 2; + break; + + case NorthEastGravity: + case SouthEastGravity: + case EastGravity: + *x -= self->size.left + self->size.right; + break; + + case ForgetGravity: + case StaticGravity: + *x -= self->size.left; + break; + } + + /* vertical */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case NorthEastGravity: + case NorthGravity: + break; + + case CenterGravity: + case EastGravity: + case WestGravity: + *y -= (self->size.top + self->size.bottom) / 2; + break; + + case SouthWestGravity: + case SouthEastGravity: + case SouthGravity: + *y -= self->size.top + self->size.bottom; + break; + + case ForgetGravity: + case StaticGravity: + *y -= self->size.top; + break; + } +} + +void frame_frame_gravity(Frame *self, int *x, int *y) +{ + /* horizontal */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + break; + case NorthGravity: + case CenterGravity: + case SouthGravity: + *x += (self->size.left + self->size.right) / 2; + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + *x += self->size.left + self->size.right; + break; + case StaticGravity: + case ForgetGravity: + x += self->size.left; + break; + } + + /* vertical */ + switch (self->client->gravity) { + default: + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + break; + case NorthGravity: + case CenterGravity: + case SouthGravity: + *y += (self->size.top + self->size.bottom) / 2; + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + *y += self->size.top + self->size.bottom; + break; + case StaticGravity: + case ForgetGravity: + *y += self->size.top; + break; + } +} + +void frame_adjust_state(Frame *self) +{ + /* XXX do shit.. buttons? */ +} + +void frame_adjust_focus(Frame *self) +{ + /* XXX optimizations later... */ + frame_adjust_size(self); +} + +void frame_adjust_title(Frame *self) +{ + /* XXX optimizations later... */ + frame_adjust_size(self); +} + +void frame_adjust_icon(Frame *self) +{ + /* XXX render icon */ +} + +GQuark frame_get_context(Client *client, Window win) +{ + Frame *self; + + if (win == ob_root) return g_quark_try_string("root"); + if (client == NULL) return g_quark_try_string("none"); + if (win == client->window) return g_quark_try_string("client"); + + self = client->frame; + if (win == self->window) return g_quark_try_string("frame"); + if (win == self->plate) return g_quark_try_string("frame"); + if (win == self->title) return g_quark_try_string("titlebar"); + if (win == self->label) return g_quark_try_string("titlebar"); + if (win == self->handle) return g_quark_try_string("handle"); + if (win == self->lgrip) return g_quark_try_string("blcorner"); + if (win == self->rgrip) return g_quark_try_string("brcorner"); + + return g_quark_try_string("none"); +} + +void frame_startup(void) +{ + g_quark_from_string("none"); + g_quark_from_string("root"); + g_quark_from_string("client"); + g_quark_from_string("titlebar"); + g_quark_from_string("handle"); + g_quark_from_string("frame"); + g_quark_from_string("blcorner"); + g_quark_from_string("brcorner"); + g_quark_from_string("tlcorner"); + g_quark_from_string("trcorner"); + g_quark_from_string("foo"); +} diff --git a/c/frame.h b/c/frame.h new file mode 100644 index 00000000..7d841673 --- /dev/null +++ b/c/frame.h @@ -0,0 +1,101 @@ +#ifndef __frame_h +#define __frame_h + +#include <X11/Xlib.h> +#include "geom.h" +#include "client.h" + +/*! Varius geometry settings in the frame decorations */ +typedef struct { + int width; /* title and handle */ + int font_height; +/* int title_height() { return font_height + bevel*2; } */ + int title_height; + int label_width; +/* int label_height() { return font_height; } */ + int handle_height; /* static, from the style */ + int icon_x; /* x-position of the window icon button */ + int title_x; /* x-position of the window title */ + int iconify_x; /* x-position of the window iconify button */ + int desktop_x; /* x-position of the window all-desktops button */ + int max_x; /* x-position of the window maximize button */ + int close_x; /* x-position of the window close button */ + int handle_y; + int button_size; /* static, from the style */ +/* int grip_width() { return button_size * 2; } */ + int grip_width; + int bevel; /* static, from the style */ + int bwidth; /* frame elements' border width */ + int cbwidth; /* client border width */ +} FrameGeometry; + +typedef struct Frame { + Window window; + Window plate; + Window title; + Window label; + Window max; + Window close; + Window desk; + Window icon; + Window iconify; + Window handle; + Window lgrip; + Window rgrip; + + Strut size; + Strut innersize; + Rect area; + FrameGeometry geom; + + Client *client; + int decorations; + + gboolean visible; +} Frame; + +Frame *frame_new(struct Client *client); +void frame_free(Frame *self); + +void frame_grab_client(Frame *self); +void frame_release_client(Frame *self); + +/*! Update the frame's size to match the client */ +void frame_adjust_size(Frame *self); +/*! Update the frame's position to match the client */ +void frame_adjust_position(Frame *self); +/*! Shape the frame window to the client window */ +void frame_adjust_shape(Frame *self); +/*! Update the frame to match the client's new state (for things like toggle + buttons, focus, and the title) XXX break this up */ +void frame_adjust_state(Frame *self); +/*! Update the frame to match the client's focused state */ +void frame_adjust_focus(Frame *self); +/*! Update the frame to display the client's current title */ +void frame_adjust_title(Frame *self); +/*! Update the frame to display the client's current icon */ +void frame_adjust_icon(Frame *self); + +/*! Applies gravity to the client's position to find where the frame should + be positioned. + @return The proper coordinates for the frame, based on the client. +*/ +void frame_client_gravity(Frame *self, int *x, int *y); + +/*! Reversly applies gravity to the frame's position to find where the client + should be positioned. + @return The proper coordinates for the client, based on the frame. +*/ +void frame_frame_gravity(Frame *self, int *x, int *y); + +/*! Shows the frame */ +void frame_show(Frame *self); +/*! Hides the frame */ +void frame_hide(Frame *self); + +/*! inits quarks - this will go in engines later */ +void frame_startup(void); + +GQuark frame_get_context(Client *client, Window win); + +#endif diff --git a/c/geom.h b/c/geom.h new file mode 100644 index 00000000..aa763dbc --- /dev/null +++ b/c/geom.h @@ -0,0 +1,53 @@ +#ifndef __geom_h +#define __geom_h + +#ifdef HAVE_ASSERT_H +# include <assert.h> +#endif + +typedef struct Point { + int x; + int y; +} Point; + +#define POINT_SET(pt, nx, ny) {pt.x = nx; pt.y = ny;} + +typedef struct Size { + int width; + int height; +} Size; + +#define SIZE_SET(sz, w, h) {sz.width = w; sz.height = h;} + +typedef struct Rect { + int x; + int y; + int width; + int height; +} Rect; + +#define RECT_SET_POINT(r, nx, ny) \ + {r.x = ny; r.y = ny;} +#define RECT_SET_SIZE(r, w, h) \ + {r.width = w; r.height = h;} +#define RECT_SET(r, nx, ny, w, h) \ + {r.x = nx; r.y = ny; r.width = w; r.height = h;} + +#define RECT_EQUAL(r1, r2) (r1.x == r2.x && r1.y == r2.y && \ + r1.width == r2.width && r1.height == r2.height) + +typedef struct Strut { + int left; + int top; + int right; + int bottom; +} Strut; + +#define STRUT_SET(s, l, t, r, b) \ + {s.left = l; s.top = t; s.right = r; s.bottom = b; } + +#define STRUT_ADD(s1, s2) \ + {s1.left = MAX(s1.left, s2.left); s1.right = MAX(s1.right, s2.right); \ + s1.top = MAX(s1.top, s2.top); s1.bottom = MAX(s1.bottom, s2.bottom); } + +#endif diff --git a/c/gettext.h b/c/gettext.h new file mode 100644 index 00000000..7bbc6a94 --- /dev/null +++ b/c/gettext.h @@ -0,0 +1,73 @@ +/* Convenience header for conditional use of GNU <libintl.h>. + Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published + by the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. */ + +#ifndef _LIBGETTEXT_H +#define _LIBGETTEXT_H 1 + + +/* NLS can be disabled through the configure --disable-nls option. */ +#if ENABLE_NLS + +/* Get declarations of GNU message catalog functions. */ +# include <libintl.h> + +#else + +/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which + chokes if dcgettext is defined as a macro. So include it now, to make + later inclusions of <locale.h> a NOP. We don't include <libintl.h> + as well because people using "gettext.h" will not include <libintl.h>, + and also including <libintl.h> would fail on SunOS 4, whereas <locale.h> + is OK. */ +#if defined(__sun) +# include <locale.h> +#endif + +/* Disabled NLS. + The casts to 'const char *' serve the purpose of producing warnings + for invalid uses of the value returned from these functions. + On pre-ANSI systems without 'const', the config.h file is supposed to + contain "#define const". */ +# define gettext(Msgid) ((const char *) (Msgid)) +# define dgettext(Domainname, Msgid) ((const char *) (Msgid)) +# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid)) +# define ngettext(Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dngettext(Domainname, Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define textdomain(Domainname) ((const char *) (Domainname)) +# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) +# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset)) + +#endif + +/* A pseudo function call that serves as a marker for the automated + extraction of messages, but does not call gettext(). The run-time + translation is done at a different place in the code. + The argument, String, should be a literal string. Concatenated strings + and other string expressions won't work. + The macro's expansion is not parenthesized, so that it is suitable as + initializer for static 'char[]' or 'const char[]' variables. */ +#define gettext_noop(String) String + +/* Custom macro to make life easier */ +#define _(str) gettext(str) + +#endif /* _LIBGETTEXT_H */ diff --git a/c/hooks.c b/c/hooks.c new file mode 100644 index 00000000..6bf15d20 --- /dev/null +++ b/c/hooks.c @@ -0,0 +1,285 @@ +#include "hooks.h" +#include <Python.h> +#include <glib.h> + +/* the 'hooks' module and its dictionary */ +static PyObject *hooks = NULL, *hooksdict = NULL; + +/* + * + * Define the type 'Hook' + * + */ +#define IS_HOOK(v) ((v)->ob_type == &HookType) + +staticforward PyTypeObject HookType; + +typedef struct { + PyObject_HEAD + GSList *funcs; +} HookObject; + +static PyObject *create_Hook(PyObject *self, PyObject *args) +{ + HookObject *hook; + char *name; + int ret; + + (void) self; + + if (!PyArg_ParseTuple(args, "s:Hook", &name)) + return NULL; + + hook = PyObject_New(HookObject, &HookType); + hook->funcs = NULL; + + /* add it to the hooks module */ + ret = PyDict_SetItemString(hooksdict, name, (PyObject*) hook); + Py_DECREF(hook); + + if (ret == -1) { + char *s = g_strdup_printf( + "Failed to add the hook '%s' to the 'hooks' module", name); + PyErr_SetString(PyExc_RuntimeError, s); + g_free(s); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static void hook_dealloc(HookObject *self) +{ + GSList *it; + + for (it = self->funcs; it != NULL; it = it->next) + Py_DECREF((PyObject*) it->data); + + PyObject_Del((PyObject*) self); +} + +static PyObject *hook_fire(HookObject *self, PyObject *args) +{ + GSList *it; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'fire' requires a 'Hook' object"); + return NULL; + } + + for (it = self->funcs; it != NULL; it = it->next) { + PyObject *ret = PyObject_CallObject(it->data, args); + if (ret == NULL) + return NULL; + Py_DECREF(ret); + } + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *hook_add(HookObject *self, PyObject *args) +{ + PyObject *func; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'add' requires a 'Hook' object"); + return NULL; + } + if (!PyArg_ParseTuple(args, "O:add", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'add' requires a callable argument"); + return NULL; + } + self->funcs = g_slist_append(self->funcs, func); + Py_INCREF(func); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *hook_remove(HookObject *self, PyObject *args) +{ + PyObject *func; + GSList *it; + + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'remove' requires a 'Hook' object"); + return NULL; + } + if (!PyArg_ParseTuple(args, "O:remove", &func)) + return NULL; + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'remove' requires a callable argument"); + return NULL; + } + it = g_slist_find(self->funcs, func); + if (it != NULL) { + self->funcs = g_slist_delete_link(self->funcs, it); + Py_DECREF(func); + + Py_INCREF(Py_None); + return Py_None; + } + PyErr_SetString(PyExc_TypeError, + "given callable object was not found in Hook"); + return NULL; +} + +static PyObject *hook_count(HookObject *self, PyObject *args) +{ + if (!IS_HOOK(self)) { + PyErr_SetString(PyExc_TypeError, + "descriptor 'fire' requires a 'Hook' object"); + return NULL; + } + if (!PyArg_ParseTuple(args, ":count")) + return NULL; + + return PyInt_FromLong(g_slist_length(self->funcs)); +} + +static PyTypeObject HookType = { + PyObject_HEAD_INIT(NULL) + 0, + "Hook", + sizeof(HookObject), + 0, + (destructor) hook_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +static PyMethodDef HookMethods[] = { + {"fire", (PyCFunction)hook_fire, METH_VARARGS, + "hook.fire() -- Fire the added hook functions for the Hook."}, + {"add", (PyCFunction)hook_add, METH_VARARGS, + "hook.add(func) -- Add a function to the hook." }, + {"remove", (PyCFunction)hook_remove, METH_VARARGS, + "hook.remove(func) -- Remove a function from the hook." }, + {"count", (PyCFunction)hook_count, METH_VARARGS, + "hook.count() -- Return the number of functions in the hook." }, + + { NULL, NULL, 0, NULL } +}; + + +/* + * + * Module initialization/finalization + * + */ + +/* the "events" hook */ +static HookObject *events_hook = NULL, *keyboard_hook = NULL, + *pointer_hook = NULL; + +static PyMethodDef HooksMethods[] = { + {"create", create_Hook, METH_VARARGS, + "hooks.create('name') -- Add a hook called 'name' to the hooks module."}, + + { NULL, NULL, 0, NULL } +}; + +void hooks_startup() +{ + int ret; + + HookType.ob_type = &PyType_Type; + HookType.tp_methods = HookMethods; + PyType_Ready(&HookType); + + Py_InitModule("hooks", HooksMethods); + + /* get the hooks module/dict */ + hooks = PyImport_ImportModule("hooks"); /* new */ + g_assert(hooks != NULL); + hooksdict = PyModule_GetDict(hooks); /* borrowed */ + g_assert(hooksdict != NULL); + + /* create the "events" hook */ + events_hook = PyObject_New(HookObject, &HookType); + events_hook->funcs = NULL; + + /* add it to the hooks module */ + ret = PyDict_SetItemString(hooksdict, "events", (PyObject*) events_hook); + g_assert(ret == 0); + + /* create the "keyboard" hook */ + keyboard_hook = PyObject_New(HookObject, &HookType); + keyboard_hook->funcs = NULL; + + /* add it to the hooks module */ + ret = PyDict_SetItemString(hooksdict, "keyboard", + (PyObject*) keyboard_hook); + g_assert(ret == 0); + + /* create the "pointer" hook */ + pointer_hook = PyObject_New(HookObject, &HookType); + pointer_hook->funcs = NULL; + + /* add it to the hooks module */ + ret = PyDict_SetItemString(hooksdict, "pointer", (PyObject*) pointer_hook); + g_assert(ret == 0); +} + +void hooks_shutdown() +{ + Py_DECREF(pointer_hook); + Py_DECREF(keyboard_hook); + Py_DECREF(events_hook); + Py_DECREF(hooks); +} + +void hooks_fire(EventData *data) +{ + PyObject *ret, *args; + + g_assert(events_hook != NULL); + + args = Py_BuildValue("(O)", data); + ret = hook_fire(events_hook, args); + Py_DECREF(args); + if (ret == NULL) + PyErr_Print(); +} + +void hooks_fire_keyboard(EventData *data) +{ + PyObject *ret, *args; + + g_assert(events_hook != NULL); + + args = Py_BuildValue("(O)", data); + ret = hook_fire(keyboard_hook, args); + Py_DECREF(args); + if (ret == NULL) + PyErr_Print(); +} + +void hooks_fire_pointer(EventData *data) +{ + PyObject *ret, *args; + + g_assert(events_hook != NULL); + + args = Py_BuildValue("(O)", data); + ret = hook_fire(pointer_hook, args); + Py_DECREF(args); + if (ret == NULL) + PyErr_Print(); +} diff --git a/c/hooks.h b/c/hooks.h new file mode 100644 index 00000000..477c8124 --- /dev/null +++ b/c/hooks.h @@ -0,0 +1,23 @@ +#ifndef __hooks_h +#define __hooks_h + +#include "eventdata.h" + +void hooks_startup(); +void hooks_shutdown(); + +void hooks_fire(EventData *data); + +void hooks_fire_keyboard(EventData *data); + +void hooks_fire_pointer(EventData *data); + +#define LOGICALHOOK(type, context, client) \ +{ EventData *data = eventdata_new_logical(Logical_##type, \ + context, client); \ + g_assert(data != NULL); \ + hooks_fire(data); \ + eventdata_free(data); \ +} + +#endif diff --git a/c/kbind.c b/c/kbind.c new file mode 100644 index 00000000..399ec830 --- /dev/null +++ b/c/kbind.c @@ -0,0 +1,354 @@ +#include "focus.h" +#include "openbox.h" +#include "hooks.h" +#include "kbind.h" + +#include <glib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif + +typedef struct KeyBindingTree { + guint state; + guint key; + GList *keylist; + + /* the next binding in the tree at the same level */ + struct KeyBindingTree *next_sibling; + /* the first child of this binding (next binding in a chained sequence).*/ + struct KeyBindingTree *first_child; +} KeyBindingTree; + + +static KeyBindingTree *firstnode, *curpos; +static guint reset_key, reset_state; +static gboolean grabbed, user_grabbed; + +guint kbind_translate_modifier(char *str) +{ + if (!strcmp("Mod1", str)) return Mod1Mask; + else if (!strcmp("Mod2", str)) return Mod2Mask; + else if (!strcmp("Mod3", str)) return Mod3Mask; + else if (!strcmp("Mod4", str)) return Mod4Mask; + else if (!strcmp("Mod5", str)) return Mod5Mask; + else if (!strcmp("C", str)) return ControlMask; + else if (!strcmp("S", str)) return ShiftMask; + g_warning("Invalid modifier '%s' in binding.", str); + return 0; +} + +static gboolean translate(char *str, guint *state, guint *keycode) +{ + char **parsed; + char *l; + int i; + gboolean ret = FALSE; + KeySym sym; + + parsed = g_strsplit(str, "-", -1); + + /* first, find the key (last token) */ + l = NULL; + for (i = 0; parsed[i] != NULL; ++i) + l = parsed[i]; + if (l == NULL) + goto translation_fail; + + /* figure out the mod mask */ + *state = 0; + for (i = 0; parsed[i] != l; ++i) { + guint m = kbind_translate_modifier(parsed[i]); + if (!m) goto translation_fail; + *state |= m; + } + + /* figure out the keycode */ + sym = XStringToKeysym(l); + if (sym == NoSymbol) { + g_warning("Invalid key name '%s' in key binding.", l); + goto translation_fail; + } + *keycode = XKeysymToKeycode(ob_display, sym); + if (!keycode) { + g_warning("Key '%s' does not exist on the display.", l); + goto translation_fail; + } + + ret = TRUE; + +translation_fail: + g_strfreev(parsed); + return ret; +} + +static void destroytree(KeyBindingTree *tree) +{ + KeyBindingTree *c; + + while (tree) { + destroytree(tree->next_sibling); + c = tree->first_child; + if (c == NULL) { + GList *it; + for (it = tree->keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(tree->keylist); + } + g_free(tree); + tree = c; + } +} + +static KeyBindingTree *buildtree(GList *keylist) +{ + GList *it; + KeyBindingTree *ret = NULL, *p; + + if (g_list_length(keylist) <= 0) + return NULL; /* nothing in the list.. */ + + for (it = g_list_last(keylist); it != NULL; it = it->prev) { + p = ret; + ret = g_new(KeyBindingTree, 1); + ret->next_sibling = NULL; + if (p == NULL) { + GList *it; + + /* this is the first built node, the bottom node of the tree */ + ret->keylist = g_list_copy(keylist); /* shallow copy */ + for (it = ret->keylist; it != NULL; it = it->next) /* deep copy */ + it->data = g_strdup(it->data); + } + ret->first_child = p; + if (!translate(it->data, &ret->state, &ret->key)) { + destroytree(ret); + return NULL; + } + } + return ret; +} + +static void assimilate(KeyBindingTree *node) +{ + KeyBindingTree *a, *b, *tmp, *last; + + if (firstnode == NULL) { + /* there are no nodes at this level yet */ + firstnode = node; + } else { + a = firstnode; + last = a; + b = node; + while (a) { + last = a; + if (!(a->state == b->state && a->key == b->key)) { + a = a->next_sibling; + } else { + tmp = b; + b = b->first_child; + g_free(tmp); + a = a->first_child; + } + } + if (!(last->state == b->state && last->key == a->key)) + last->next_sibling = b; + else { + last->first_child = b->first_child; + g_free(b); + } + } +} + +KeyBindingTree *find(KeyBindingTree *search, gboolean *conflict) +{ + KeyBindingTree *a, *b; + + *conflict = FALSE; + + a = firstnode; + b = search; + while (a && b) { + if (!(a->state == b->state && a->key == b->key)) { + a = a->next_sibling; + } else { + if ((a->first_child == NULL) == (b->first_child == NULL)) { + if (a->first_child == NULL) { + /* found it! (return the actual node, not the search's) */ + return a; + } + } else { + *conflict = TRUE; + return NULL; /* the chain status' don't match (conflict!) */ + } + b = b->first_child; + a = a->first_child; + } + } + return NULL; // it just isn't in here +} + +static void grab_keys(gboolean grab) +{ + if (!grab) { + XUngrabKey(ob_display, AnyKey, AnyModifier, ob_root); + } else { + KeyBindingTree *p = firstnode; + while (p) { + XGrabKey(ob_display, p->key, p->state, ob_root, FALSE, + GrabModeAsync, GrabModeSync); + p = p->next_sibling; + } + } +} + +void reset_chains() +{ + /* XXX kill timer */ + curpos = NULL; + if (grabbed) { + grabbed = FALSE; + g_message("reset chains. user: %d", user_grabbed); + if (!user_grabbed) + XUngrabKeyboard(ob_display, CurrentTime); + } +} + +void kbind_fire(guint state, guint key, gboolean press) +{ + EventData *data; + struct Client *c = focus_client; + GQuark context = c != NULL ? g_quark_try_string("client") + : g_quark_try_string("root"); + + if (user_grabbed) { + data = eventdata_new_key(press ? Key_Press : Key_Release, + context, c, state, key, NULL); + g_assert(data != NULL); + hooks_fire_keyboard(data); + eventdata_free(data); + } + + if (key == reset_key && state == reset_state) { + reset_chains(); + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + } else { + KeyBindingTree *p; + if (curpos == NULL) + p = firstnode; + else + p = curpos->first_child; + while (p) { + if (p->key == key && p->state == state) { + if (p->first_child != NULL) { /* part of a chain */ + /* XXX TIMER */ + if (!grabbed && !user_grabbed) { + /*grab should never fail because we should have a sync + grab at this point */ + XGrabKeyboard(ob_display, ob_root, 0, GrabModeAsync, + GrabModeSync, CurrentTime); + } + grabbed = TRUE; + curpos = p; + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + } else { + data = eventdata_new_key(press ? Key_Press : Key_Release, + context, c, state, key, + p->keylist); + g_assert(data != NULL); + hooks_fire(data); + eventdata_free(data); + + XAllowEvents(ob_display, AsyncKeyboard, CurrentTime); + reset_chains(); + } + break; + } + p = p->next_sibling; + } + } +} + +gboolean kbind_add(GList *keylist) +{ + KeyBindingTree *tree, *t; + gboolean conflict; + + if (!(tree = buildtree(keylist))) + return FALSE; /* invalid binding requested */ + + t = find(tree, &conflict); + if (conflict) { + /* conflicts with another binding */ + destroytree(tree); + return FALSE; + } + + if (t != NULL) { + /* already bound to something */ + destroytree(tree); + } else { + /* grab the server here to make sure no key pressed go missed */ + XGrabServer(ob_display); + XSync(ob_display, FALSE); + + grab_keys(FALSE); + + /* assimilate this built tree into the main tree */ + assimilate(tree); // assimilation destroys/uses the tree + + grab_keys(TRUE); + + XUngrabServer(ob_display); + XFlush(ob_display); + } + + return TRUE; +} + +void kbind_clearall() +{ + grab_keys(FALSE); + destroytree(firstnode); + firstnode = NULL; + grab_keys(TRUE); +} + +void kbind_startup() +{ + gboolean b; + + curpos = firstnode = NULL; + grabbed = user_grabbed = FALSE; + + b = translate("C-G", &reset_state, &reset_key); + g_assert(b); +} + +void kbind_shutdown() +{ + if (grabbed || user_grabbed) { + grabbed = FALSE; + kbind_grab_keyboard(FALSE); + } + grab_keys(FALSE); + destroytree(firstnode); + firstnode = NULL; +} + +gboolean kbind_grab_keyboard(gboolean grab) +{ + gboolean ret = TRUE; + + if (!grab) + g_message("grab_keyboard(false). grabbed: %d", grabbed); + + user_grabbed = grab; + if (!grabbed) { + if (grab) + ret = XGrabKeyboard(ob_display, ob_root, 0, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess; + else + XUngrabKeyboard(ob_display, CurrentTime); + } + return ret; +} diff --git a/c/kbind.h b/c/kbind.h new file mode 100644 index 00000000..3a584b79 --- /dev/null +++ b/c/kbind.h @@ -0,0 +1,23 @@ +#ifndef __kbind_h +#define __kbind_h + +#include <glib.h> + +void kbind_startup(); +void kbind_shutdown(); + +/*! Adds a new key binding + A binding will fail to be added if the binding already exists (as part of + a chain or not), or if any of the strings in the keylist are invalid. + @return TRUE if the binding could be added; FALSE if it could not. +*/ +gboolean kbind_add(GList *keylist); +void kbind_clearall(); + +guint kbind_translate_modifier(char *str); + +void kbind_fire(guint state, guint key, gboolean press); + +gboolean kbind_grab_keyboard(gboolean grab); + +#endif diff --git a/c/mbind.c b/c/mbind.c new file mode 100644 index 00000000..f363be68 --- /dev/null +++ b/c/mbind.c @@ -0,0 +1,220 @@ +#include "mbind.h" +#include "kbind.h" +#include "frame.h" +#include "openbox.h" +#include "eventdata.h" +#include "hooks.h" + +#include <glib.h> +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#endif + +/* GData of GSList*'s of PointerBinding*'s. */ +static GData *bound_contexts; + +static gboolean grabbed; + +struct mbind_foreach_grab_temp { + Client *client; + gboolean grab; +}; + +typedef struct { + guint state; + guint button; + char *name; +} PointerBinding; + +static gboolean translate(char *str, guint *state, guint *button) +{ + char **parsed; + char *l; + int i; + gboolean ret = FALSE; + + parsed = g_strsplit(str, "-", -1); + + /* first, find the button (last token) */ + l = NULL; + for (i = 0; parsed[i] != NULL; ++i) + l = parsed[i]; + if (l == NULL) + goto translation_fail; + + /* figure out the mod mask */ + *state = 0; + for (i = 0; parsed[i] != l; ++i) { + guint m = kbind_translate_modifier(parsed[i]); + if (!m) goto translation_fail; + *state |= m; + } + + /* figure out the button */ + *button = atoi(l); + if (!*button) { + g_warning("Invalid button '%s' in pointer binding.", l); + goto translation_fail; + } + + ret = TRUE; + +translation_fail: + g_strfreev(parsed); + return ret; +} + +void grab_button(Client *client, guint state, guint button, GQuark context, + gboolean grab) +{ + Window win; + int mode = GrabModeAsync; + unsigned int mask; + + if (context == g_quark_try_string("frame")) { + win = client->frame->window; + mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask; + } else if (context == g_quark_try_string("client")) { + win = client->frame->plate; + mode = GrabModeSync; /* this is handled in mbind_fire */ + mask = ButtonPressMask; /* can't catch more than this with Sync mode + the release event is manufactured in + mbind_fire */ + } else return; + + if (grab) + XGrabButton(ob_display, button, state, win, FALSE, mask, mode, + GrabModeAsync, None, None); + else + XUngrabButton(ob_display, button, state, win); +} + +static void mbind_foreach_grab(GQuark key, gpointer data, gpointer user_data) +{ + struct mbind_foreach_grab_temp *d = user_data; + PointerBinding *b = ((GSList *)data)->data; + if (b != NULL) + grab_button(d->client, b->state, b->button, key, d->grab); +} + +void mbind_grab_all(Client *client, gboolean grab) +{ + struct mbind_foreach_grab_temp bt; + bt.client = client; + bt.grab = grab; + g_datalist_foreach(&bound_contexts, mbind_foreach_grab, &bt); +} + +void grab_all_clients(gboolean grab) +{ + GSList *it; + + for (it = client_list; it != NULL; it = it->next) + mbind_grab_all(it->data, grab); +} + +void mbind_startup() +{ + grabbed = FALSE; + g_datalist_init(&bound_contexts); +} + +void mbind_shutdown() +{ + if (grabbed) + mbind_grab_pointer(FALSE); + mbind_clearall(); + g_datalist_clear(&bound_contexts); +} + +gboolean mbind_add(char *name, GQuark context) +{ + guint state, button; + PointerBinding *b; + GSList *it; + + if (!translate(name, &state, &button)) + return FALSE; + + for (it = g_datalist_id_get_data(&bound_contexts, context); + it != NULL; it = it->next){ + b = it->data; + if (b->state == state && b->button == button) + return TRUE; /* already bound */ + } + + grab_all_clients(FALSE); + + /* add the binding */ + b = g_new(PointerBinding, 1); + b->state = state; + b->button = button; + b->name = g_strdup(name); + g_datalist_id_set_data(&bound_contexts, context, + g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b)); + grab_all_clients(TRUE); + + return TRUE; +} + +static void mbind_foreach_clear(GQuark key, gpointer data, gpointer user_data) +{ + GSList *it; + user_data = user_data; + for (it = data; it != NULL; it = it->next) { + PointerBinding *b = it->data; + g_free(b->name); + g_free(b); + } + g_slist_free(data); +} +void mbind_clearall() +{ + grab_all_clients(FALSE); + g_datalist_foreach(&bound_contexts, mbind_foreach_clear, NULL); +} + +void mbind_fire(guint state, guint button, GQuark context, EventType type, + Client *client, int xroot, int yroot) +{ + GSList *it; + + if (grabbed) { + EventData *data; + data = eventdata_new_pointer(type, context, client, state, button, + NULL, xroot, yroot); + g_assert(data != NULL); + hooks_fire_pointer(data); + eventdata_free(data); + return; + } + + for (it = g_datalist_id_get_data(&bound_contexts, context); + it != NULL; it = it->next){ + PointerBinding *b = it->data; + if (b->state == state && b->button == button) { + EventData *data; + data = eventdata_new_pointer(type, context, client, state, button, + b->name, xroot, yroot); + g_assert(data != NULL); + hooks_fire(data); + eventdata_free(data); + break; + } + } +} + +gboolean mbind_grab_pointer(gboolean grab) +{ + gboolean ret = TRUE; + if (grab) + ret = XGrabPointer(ob_display, ob_root, FALSE, (ButtonPressMask | + ButtonReleaseMask | + ButtonMotionMask | + PointerMotionMask), + GrabModeAsync, GrabModeAsync, None, None, + CurrentTime) == GrabSuccess; + else + XUngrabPointer(ob_display, CurrentTime); + return ret; +} diff --git a/c/mbind.h b/c/mbind.h new file mode 100644 index 00000000..dfd8c911 --- /dev/null +++ b/c/mbind.h @@ -0,0 +1,21 @@ +#ifndef __mbind_h +#define __mbind_h + +#include "obexport.h" +#include "client.h" +#include <glib.h> + +void mbind_startup(); +void mbind_shutdown(); + +/*! Adds a new pointer binding */ +gboolean mbind_add(char *name, GQuark context); +void mbind_clearall(); + +void mbind_fire(guint state, guint button, GQuark context, EventType type, + Client *client, int xroot, int yroot); + +void mbind_grab_all(Client *client, gboolean grab); +gboolean mbind_grab_pointer(gboolean grab); + +#endif diff --git a/c/obexport.c b/c/obexport.c new file mode 100644 index 00000000..d92e3194 --- /dev/null +++ b/c/obexport.c @@ -0,0 +1,116 @@ +#include "obexport.h" +#include <Python.h> +#include <glib.h> + +static PyMethodDef obMethods[] = { + { NULL, NULL, 0, NULL } +}; + +#define ADD_INT_CONST(n) (PyModule_AddIntConstant(ob, #n, n)) + +void obexport_startup() +{ + PyObject *ob, *obdict; + + Py_InitModule("ob", obMethods); + + /* get the ob module/dict */ + ob = PyImport_ImportModule("ob"); /* new */ + g_assert(ob != NULL); + obdict = PyModule_GetDict(ob); /* borrowed */ + g_assert(obdict != NULL); + + /* define all the constants! */ + + /* State */ + ADD_INT_CONST(State_Starting); + ADD_INT_CONST(State_Exiting); + ADD_INT_CONST(State_Running); + + /* Corner */ + ADD_INT_CONST(Corner_TopLeft); + ADD_INT_CONST(Corner_TopRight); + ADD_INT_CONST(Corner_BottomLeft); + ADD_INT_CONST(Corner_BottomRight); + + /* Orientation */ + ADD_INT_CONST(Orientation_Horz); + ADD_INT_CONST(Orientation_Vert); + + /* Gravity */ + ADD_INT_CONST(Gravity_Forget); + ADD_INT_CONST(Gravity_NE); + ADD_INT_CONST(Gravity_N); + ADD_INT_CONST(Gravity_NW); + ADD_INT_CONST(Gravity_W); + ADD_INT_CONST(Gravity_SW); + ADD_INT_CONST(Gravity_S); + ADD_INT_CONST(Gravity_SE); + ADD_INT_CONST(Gravity_E); + ADD_INT_CONST(Gravity_Center); + ADD_INT_CONST(Gravity_Static); + + /* WindowType */ + ADD_INT_CONST(Type_Desktop); + ADD_INT_CONST(Type_Dock); + ADD_INT_CONST(Type_Toolbar); + ADD_INT_CONST(Type_Menu); + ADD_INT_CONST(Type_Utility); + ADD_INT_CONST(Type_Splash); + ADD_INT_CONST(Type_Dialog); + ADD_INT_CONST(Type_Normal); + + /* Function */ + ADD_INT_CONST(Func_Resize); + ADD_INT_CONST(Func_Move); + ADD_INT_CONST(Func_Iconify); + ADD_INT_CONST(Func_Maximize); + ADD_INT_CONST(Func_Shade); + ADD_INT_CONST(Func_Fullscreen); + ADD_INT_CONST(Func_Close); + + /* Decoration */ + ADD_INT_CONST(Decor_Titlebar); + ADD_INT_CONST(Decor_Handle); + ADD_INT_CONST(Decor_Border); + ADD_INT_CONST(Decor_Icon); + ADD_INT_CONST(Decor_Iconify); + ADD_INT_CONST(Decor_Maximize); + ADD_INT_CONST(Decor_AllDesktops); + ADD_INT_CONST(Decor_Close); + + /* StackLayer */ + ADD_INT_CONST(Layer_Icon); + ADD_INT_CONST(Layer_Desktop); + ADD_INT_CONST(Layer_Below); + ADD_INT_CONST(Layer_Normal); + ADD_INT_CONST(Layer_Above); + ADD_INT_CONST(Layer_Top); + ADD_INT_CONST(Layer_Fullscreen); + ADD_INT_CONST(Layer_Internal); + + /* EventType */ + ADD_INT_CONST(Logical_EnterWindow); + ADD_INT_CONST(Logical_LeaveWindow); + ADD_INT_CONST(Logical_NewWindow); + ADD_INT_CONST(Logical_CloseWindow); + ADD_INT_CONST(Logical_Startup); + ADD_INT_CONST(Logical_Shutdown); + ADD_INT_CONST(Logical_RequestActivate); + ADD_INT_CONST(Logical_Focus); + ADD_INT_CONST(Logical_Bell); + ADD_INT_CONST(Logical_UrgentWindow); + ADD_INT_CONST(Logical_WindowShow); + ADD_INT_CONST(Logical_WindowHide); + ADD_INT_CONST(Pointer_Press); + ADD_INT_CONST(Pointer_Release); + ADD_INT_CONST(Pointer_Motion); + ADD_INT_CONST(Key_Press); + ADD_INT_CONST(Key_Release); + + Py_DECREF(ob); +} + +void obexport_shutdown() +{ +} diff --git a/c/obexport.h b/c/obexport.h new file mode 100644 index 00000000..17f68f4a --- /dev/null +++ b/c/obexport.h @@ -0,0 +1,74 @@ +#ifndef __obexport_h +#define __obexport_h + +#include <X11/Xlib.h> + +/* Define values which will be exported in the 'ob' module. */ + +typedef enum { + /*! Occurs when the mouse enters a window */ + Logical_EnterWindow, + /*! Occurs when the mouse enters a window */ + Logical_LeaveWindow, + /*! Occurs when a window is finished being managed, just before it is + (possibly) displayed. + The python scripts are reponsible for showing the window when this is + called if they want it to be shown. + */ + Logical_NewWindow, + /*! Occurs when a window is being unmanaged */ + Logical_CloseWindow, + /*! Occurs when the window manager starts up */ + Logical_Startup, + /*! Occurs when the window manager is shutting down */ + Logical_Shutdown, + /*! Occurs when a client is requesting/requested to be activated (i.e. + focused, raised, unshaded) */ + Logical_RequestActivate, + /*! Occurs when the input focus target changes + The data.client will be NULL of no client is focused. */ + Logical_Focus, + /*! Occurs when the system is fired through X. + The data.client will hold the client associated with the bell if + one has been specified, or NULL. */ + Logical_Bell, + /*! Occurs when a client toggles its urgent status. + The client.urgent member can be used to get the status. */ + Logical_UrgentWindow, + /*! Occurs when a client becomes visible */ + Logical_WindowShow, + /*! Occurs when a client becomes non-visible */ + Logical_WindowHide, + /*! Occurs when a pointer button is pressed on a client or its + decorations. + Note: to get the event for the client's window or for the entire + window+decorations, you need to do an mgrab for the window. */ + Pointer_Press, + /*! Occurs when a pointer button is released on a client or its + decorations. + Note: to get the event for the client's window or for the entire + window+decorations, you need to do an mgrab for the window. */ + Pointer_Release, + /*! Occurs when a pointer button is held and the pointer is dragged on a + client or its decorations. + Note: to get the event for the client's window or for the entire + window+decorations, you need to do an mgrab for the window, or an + mgrab_pointer (in which case it may not be a drag). */ + Pointer_Motion, + /*! Occurs when a key is pressed. + Note: in order to recieve a key event, a kgrab must be done for the + key combination, or a kgrab_keyboard. + */ + Key_Press, + /*! Occurs when a key is released. + Note: in order to recieve a key event, a kgrab must be done for the + key combination, or a kgrab_keyboard. + */ + Key_Release +} EventType; + +/* create the 'ob' module */ +void obexport_startup(); +void obexport_shutdown(); + +#endif diff --git a/c/openbox.c b/c/openbox.c new file mode 100644 index 00000000..77e4123b --- /dev/null +++ b/c/openbox.c @@ -0,0 +1,203 @@ +#include "openbox.h" +#include "event.h" +#include "client.h" +#include "xerror.h" +#include "prop.h" +#include "screen.h" +#include "focus.h" +#include "extensions.h" +#include "python.h" +#include "hooks.h" +#include "eventdata.h" +#include "gettext.h" +#include "clientwrap.h" +#include "screenwrap.h" +#include "kbind.h" +#include "mbind.h" +#include "frame.h" + +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#ifdef HAVE_SIGNAL_H +# include <signal.h> +#endif +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#endif +#ifdef HAVE_SYS_WAIT_H +# include <sys/types.h> +# include <sys/wait.h> +#endif +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif + +#include <X11/cursorfont.h> + +Display *ob_display = NULL; +int ob_screen; +Window ob_root; +State ob_state; +gboolean ob_shutdown = FALSE; +gboolean ob_restart = FALSE; +gboolean ob_remote = FALSE; +gboolean ob_sync = TRUE; +Cursors ob_cursors; + +void signal_handler(int signal); + +int main(int argc, char **argv) +{ + struct sigaction action; + sigset_t sigset; + + ob_state = State_Starting; + + /* initialize the locale */ + if (!setlocale(LC_ALL, "")) + g_warning("Couldn't set locale from environment.\n"); + bindtextdomain(PACKAGE, LOCALEDIR); + bind_textdomain_codeset(PACKAGE, "UTF-8"); + textdomain(PACKAGE); + + /* set up signal handler */ + sigemptyset(&sigset); + action.sa_handler = signal_handler; + action.sa_mask = sigset; + action.sa_flags = SA_NOCLDSTOP | SA_NODEFER; + sigaction(SIGUSR1, &action, (struct sigaction *) NULL); + sigaction(SIGPIPE, &action, (struct sigaction *) NULL); + sigaction(SIGSEGV, &action, (struct sigaction *) NULL); + sigaction(SIGFPE, &action, (struct sigaction *) NULL); + sigaction(SIGTERM, &action, (struct sigaction *) NULL); + sigaction(SIGINT, &action, (struct sigaction *) NULL); + sigaction(SIGHUP, &action, (struct sigaction *) NULL); + sigaction(SIGCHLD, &action, (struct sigaction *) NULL); + + /* anything that died while we were restarting won't give us a SIGCHLD */ + while (waitpid(-1, NULL, WNOHANG) > 0); + + /* XXX parse out command line args */ + (void)argc;(void)argv; + + /* critical warnings will exit the program */ + g_log_set_always_fatal(G_LOG_LEVEL_CRITICAL); + + ob_display = XOpenDisplay(NULL); + if (ob_display == NULL) + /* print a message and exit */ + g_critical("Failed to open the display."); + if (fcntl(ConnectionNumber(ob_display), F_SETFD, 1) == -1) + /* print a message and exit */ + g_critical("Failed to set display as close-on-exec."); + + ob_screen = DefaultScreen(ob_display); + ob_root = RootWindow(ob_display, ob_screen); + + /* XXX fork self onto other screens */ + + XSynchronize(ob_display, ob_sync); + + /* check for locale support */ + if (!XSupportsLocale()) + g_warning("X server does not support locale."); + if (!XSetLocaleModifiers("")) + g_warning("Cannot set locale modifiers for the X server."); + + /* set our error handler */ + XSetErrorHandler(xerror_handler); + + /* set the DISPLAY environment variable for any lauched children, to the + display we're using, so they open in the right place. */ + putenv(g_strdup_printf("DISPLAY=%s", DisplayString(ob_display))); + + ob_cursors.left_ptr = XCreateFontCursor(ob_display, XC_left_ptr); + ob_cursors.ll_angle = XCreateFontCursor(ob_display, XC_ll_angle); + ob_cursors.lr_angle = XCreateFontCursor(ob_display, XC_lr_angle); + + prop_init(); /* get atoms values for the display */ + extensions_query_all(); /* find which extensions are present */ + + if (screen_annex()) { /* it will be ours! */ + frame_startup(); + python_startup(); + obexport_startup(); + hooks_startup(); + screenwrap_startup(); + clientwrap_startup(); + eventdata_startup(); + event_startup(); + screen_startup(); + focus_startup(); + client_startup(); + kbind_startup(); + mbind_startup(); + + /* load the user's settings */ + if (!python_import("rc")) + g_warning("ERROR LOADING RC FILE"); + + LOGICALHOOK(Startup, g_quark_try_string("none"), NULL); + + /* get all the existing windows */ + client_manage_all(); + + ob_state = State_Running; + while (!ob_shutdown) { + event_loop(); + } + ob_state = State_Exiting; + + client_unmanage_all(); + + LOGICALHOOK(Shutdown, g_quark_try_string("none"), NULL); + + mbind_shutdown(); + kbind_shutdown(); + client_shutdown(); + screen_shutdown(); + event_shutdown(); + eventdata_shutdown(); + clientwrap_shutdown(); + screenwrap_shutdown(); + hooks_shutdown(); + obexport_shutdown(); + python_shutdown(); + } + + XCloseDisplay(ob_display); + + /* XXX if (ob_restart) */ + + return 0; +} + +void signal_handler(int signal) +{ + switch (signal) { + case SIGUSR1: + g_message("Caught SIGUSR1 signal. Restarting."); + ob_shutdown = ob_restart = TRUE; + break; + + case SIGCHLD: + wait(NULL); + break; + + case SIGHUP: + case SIGINT: + case SIGTERM: + case SIGPIPE: + g_message("Caught signal %d. Exiting.", signal); + ob_shutdown = TRUE; + break; + + case SIGFPE: + case SIGSEGV: + g_error("Caught signal %d. Aborting and dumping core.", signal); + } +} diff --git a/c/openbox.h b/c/openbox.h new file mode 100644 index 00000000..34160c8e --- /dev/null +++ b/c/openbox.h @@ -0,0 +1,35 @@ +#ifndef __openbox_h +#define __openbox_h + +#include "obexport.h" +#include <glib.h> +#include <X11/Xlib.h> + +/*! The X display */ +extern Display *ob_display; +/*! The number of the screen on which we're running */ +extern int ob_screen; +/*! The root window */ +extern Window ob_root; + +/* The state of execution of the window manager */ +State ob_state; + +/*! When set to true, Openbox will exit */ +extern gboolean ob_shutdown; +/*! When set to true, Openbox will restart instead of shutting down */ +extern gboolean ob_restart; + +/*! Runtime option to specify running on a remote display */ +extern gboolean ob_remote; +/*! Runtime option to run in synchronous mode */ +extern gboolean ob_sync; + +typedef struct Cursors { + Cursor left_ptr; + Cursor ll_angle; + Cursor lr_angle; +} Cursors; +Cursors ob_cursors; + +#endif diff --git a/c/prop.c b/c/prop.c new file mode 100644 index 00000000..d2392766 --- /dev/null +++ b/c/prop.c @@ -0,0 +1,271 @@ +#include "prop.h" +#include "openbox.h" +#include <X11/Xatom.h> + +Atoms prop_atoms; + +#define CREATE(var, name) (prop_atoms.var = \ + XInternAtom(ob_display, name, FALSE)) + +void prop_init() +{ + g_assert(ob_display != NULL); + + CREATE(cardinal, "CARDINAL"); + CREATE(window, "WINDOW"); + CREATE(pixmap, "PIXMAP"); + CREATE(atom, "ATOM"); + CREATE(string, "STRING"); + CREATE(utf8, "UTF8_STRING"); + + CREATE(wm_colormap_windows, "WM_COLORMAP_WINDOWS"); + CREATE(wm_protocols, "WM_PROTOCOLS"); + CREATE(wm_state, "WM_STATE"); + CREATE(wm_change_state, "WM_CHANGE_STATE"); + CREATE(wm_delete_window, "WM_DELETE_WINDOW"); + CREATE(wm_take_focus, "WM_TAKE_FOCUS"); + CREATE(wm_name, "WM_NAME"); + CREATE(wm_icon_name, "WM_ICON_NAME"); + CREATE(wm_class, "WM_CLASS"); + CREATE(wm_window_role, "WM_WINDOW_ROLE"); + CREATE(motif_wm_hints, "_MOTIF_WM_HINTS"); + + CREATE(net_supported, "_NET_SUPPORTED"); + CREATE(net_client_list, "_NET_CLIENT_LIST"); + CREATE(net_client_list_stacking, "_NET_CLIENT_LIST_STACKING"); + CREATE(net_number_of_desktops, "_NET_NUMBER_OF_DESKTOPS"); + CREATE(net_desktop_geometry, "_NET_DESKTOP_GEOMETRY"); + CREATE(net_desktop_viewport, "_NET_DESKTOP_VIEWPORT"); + CREATE(net_current_desktop, "_NET_CURRENT_DESKTOP"); + CREATE(net_desktop_names, "_NET_DESKTOP_NAMES"); + CREATE(net_active_window, "_NET_ACTIVE_WINDOW"); + CREATE(net_workarea, "_NET_WORKAREA"); + CREATE(net_supporting_wm_check, "_NET_SUPPORTING_WM_CHECK"); +/* CREATE(net_virtual_roots, "_NET_VIRTUAL_ROOTS"); */ + CREATE(net_desktop_layout, "_NET_DESKTOP_LAYOUT"); + CREATE(net_showing_desktop, "_NET_SHOWING_DESKTOP"); + + CREATE(net_close_window, "_NET_CLOSE_WINDOW"); + CREATE(net_wm_moveresize, "_NET_WM_MOVERESIZE"); + +/* CREATE(net_properties, "_NET_PROPERTIES"); */ + CREATE(net_wm_name, "_NET_WM_NAME"); + CREATE(net_wm_visible_name, "_NET_WM_VISIBLE_NAME"); + CREATE(net_wm_icon_name, "_NET_WM_ICON_NAME"); + CREATE(net_wm_visible_icon_name, "_NET_WM_VISIBLE_ICON_NAME"); + CREATE(net_wm_desktop, "_NET_WM_DESKTOP"); + CREATE(net_wm_window_type, "_NET_WM_WINDOW_TYPE"); + CREATE(net_wm_state, "_NET_WM_STATE"); + CREATE(net_wm_strut, "_NET_WM_STRUT"); +/* CREATE(net_wm_icon_geometry, "_NET_WM_ICON_GEOMETRY"); */ + CREATE(net_wm_icon, "_NET_WM_ICON"); +/* CREATE(net_wm_pid, "_NET_WM_PID"); */ +/* CREATE(net_wm_handled_icons, "_NET_WM_HANDLED_ICONS"); */ + CREATE(net_wm_allowed_actions, "_NET_WM_ALLOWED_ACTIONS"); + +/* CREATE(net_wm_ping, "_NET_WM_PING"); */ + + CREATE(net_wm_window_type_desktop, "_NET_WM_WINDOW_TYPE_DESKTOP"); + CREATE(net_wm_window_type_dock, "_NET_WM_WINDOW_TYPE_DOCK"); + CREATE(net_wm_window_type_toolbar, "_NET_WM_WINDOW_TYPE_TOOLBAR"); + CREATE(net_wm_window_type_menu, "_NET_WM_WINDOW_TYPE_MENU"); + CREATE(net_wm_window_type_utility, "_NET_WM_WINDOW_TYPE_UTILITY"); + CREATE(net_wm_window_type_splash, "_NET_WM_WINDOW_TYPE_SPLASH"); + CREATE(net_wm_window_type_dialog, "_NET_WM_WINDOW_TYPE_DIALOG"); + CREATE(net_wm_window_type_normal, "_NET_WM_WINDOW_TYPE_NORMAL"); + + CREATE(net_wm_moveresize_size_topleft, "_NET_WM_MOVERESIZE_SIZE_TOPLEFT"); + CREATE(net_wm_moveresize_size_topright, + "_NET_WM_MOVERESIZE_SIZE_TOPRIGHT"); + CREATE(net_wm_moveresize_size_bottomleft, + "_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT"); + CREATE(net_wm_moveresize_size_bottomright, + "_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT"); + CREATE(net_wm_moveresize_move, "_NET_WM_MOVERESIZE_MOVE"); + + CREATE(net_wm_action_move, "_NET_WM_ACTION_MOVE"); + CREATE(net_wm_action_resize, "_NET_WM_ACTION_RESIZE"); + CREATE(net_wm_action_minimize, "_NET_WM_ACTION_MINIMIZE"); + CREATE(net_wm_action_shade, "_NET_WM_ACTION_SHADE"); + CREATE(net_wm_action_stick, "_NET_WM_ACTION_STICK"); + CREATE(net_wm_action_maximize_horz, "_NET_WM_ACTION_MAXIMIZE_HORZ"); + CREATE(net_wm_action_maximize_vert, "_NET_WM_ACTION_MAXIMIZE_VERT"); + CREATE(net_wm_action_fullscreen, "_NET_WM_ACTION_FULLSCREEN"); + CREATE(net_wm_action_change_desktop, "_NET_WM_ACTION_CHANGE_DESKTOP"); + CREATE(net_wm_action_close, "_NET_WM_ACTION_CLOSE"); + CREATE(net_wm_state_modal, "_NET_WM_STATE_MODAL"); + CREATE(net_wm_state_sticky, "_NET_WM_STATE_STICKY"); + CREATE(net_wm_state_maximized_vert, "_NET_WM_STATE_MAXIMIZED_VERT"); + CREATE(net_wm_state_maximized_horz, "_NET_WM_STATE_MAXIMIZED_HORZ"); + CREATE(net_wm_state_shaded, "_NET_WM_STATE_SHADED"); + CREATE(net_wm_state_skip_taskbar, "_NET_WM_STATE_SKIP_TASKBAR"); + CREATE(net_wm_state_skip_pager, "_NET_WM_STATE_SKIP_PAGER"); + CREATE(net_wm_state_hidden, "_NET_WM_STATE_HIDDEN"); + CREATE(net_wm_state_fullscreen, "_NET_WM_STATE_FULLSCREEN"); + CREATE(net_wm_state_above, "_NET_WM_STATE_ABOVE"); + CREATE(net_wm_state_below, "_NET_WM_STATE_BELOW"); + + prop_atoms.net_wm_state_add = 1; + prop_atoms.net_wm_state_remove = 0; + prop_atoms.net_wm_state_toggle = 2; + + prop_atoms.net_wm_orientation_horz = 0; + prop_atoms.net_wm_orientation_vert = 1; + prop_atoms.net_wm_topleft = 0; + prop_atoms.net_wm_topright = 1; + prop_atoms.net_wm_bottomright = 2; + prop_atoms.net_wm_bottomleft = 3; + + CREATE(kde_net_system_tray_windows, "_KDE_NET_SYSTEM_TRAY_WINDOWS"); + CREATE(kde_net_wm_system_tray_window_for, + "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR"); + CREATE(kde_net_wm_window_type_override, + "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"); + + CREATE(kwm_win_icon, "KWM_WIN_ICON"); + + CREATE(rootpmapid, "_XROOTPMAP_ID"); + CREATE(esetrootid, "ESETROOT_PMAP_ID"); + + CREATE(openbox_pid, "_OPENBOX_PID"); + CREATE(openbox_premax, "_OPENBOX_PREMAX"); +} + +gboolean prop_get(Window win, Atom prop, Atom type, int size, + guchar **data, gulong num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + long num32 = 32 / size * num; /* num in 32-bit elements */ + + res = XGetWindowProperty(ob_display, win, prop, 0l, num32, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success && ret_items && xdata) { + if (ret_size == size && ret_items >= num) { + *data = g_memdup(xdata, num * (size / 8)); + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_prealloc(Window win, Atom prop, Atom type, int size, + guchar *data, gulong num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + long num32 = 32 / size * num; /* num in 32-bit elements */ + + res = XGetWindowProperty(ob_display, win, prop, 0l, num32, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success && ret_items && xdata) { + if (ret_size == size && ret_items >= num) { + gulong i; + for (i = 0; i < num; ++i) + switch (size) { + case 8: + data[i] = xdata[i]; + break; + case 16: + ((guint16*)data)[i] = ((guint16*)xdata)[i]; + break; + case 32: + ((guint32*)data)[i] = ((guint32*)xdata)[i]; + break; + default: + g_assert_not_reached(); /* unhandled size */ + } + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_all(Window win, Atom prop, Atom type, int size, + guchar **data, gulong *num) +{ + gboolean ret = FALSE; + int res; + guchar *xdata = NULL; + Atom ret_type; + int ret_size; + gulong ret_items, bytes_left; + + res = XGetWindowProperty(ob_display, win, prop, 0l, G_MAXLONG, + FALSE, type, &ret_type, &ret_size, + &ret_items, &bytes_left, &xdata); + if (res == Success) { + if (ret_size == size && ret_items > 0) { + *data = g_memdup(xdata, ret_items * (size / 8)); + *num = ret_items; + ret = TRUE; + } + XFree(xdata); + } + return ret; +} + +gboolean prop_get_string(Window win, Atom prop, Atom type, guchar **data) +{ + guchar *raw; + gulong num; + GString *str; + + if (prop_get_all(win, prop, type, 8, &raw, &num)) { + str = g_string_new_len((char*)raw, num); + g_assert(str->str[num] == '\0'); + + g_free(raw); + + *data = (guchar*)g_string_free(str, FALSE); + return TRUE; + } + return FALSE; +} + +gboolean prop_get_strings(Window win, Atom prop, Atom type, + GPtrArray *data) +{ + guchar *raw; + gulong num; + GString *str, *str2; + guint i, start; + + if (prop_get_all(win, prop, type, 8, &raw, &num)) { + str = g_string_new_len((gchar*)raw, num); + g_assert(str->str[num] == '\0'); /* assuming this is always true.. */ + + g_free(raw); + + /* split it into the list */ + for (start = 0, i = 0; i < str->len; ++i) { + if (str->str[i] == '\0') { + str2 = g_string_new_len(&str->str[start], i - start); + g_ptr_array_add(data, g_string_free(str2, FALSE)); + start = i + 1; + } + } + g_string_free(str, TRUE); + + if (data->len > 0) + return TRUE; + } + return FALSE; +} + +void prop_erase(Window win, Atom prop) +{ + XDeleteProperty(ob_display, win, prop); +} diff --git a/c/prop.h b/c/prop.h new file mode 100644 index 00000000..2c081308 --- /dev/null +++ b/c/prop.h @@ -0,0 +1,202 @@ +#ifndef __atoms_h +#define __atoms_h + +#include <X11/Xlib.h> +#include <glib.h> +#ifdef HAVE_STRING_H +# include <string.h> +#endif + +#include "openbox.h" + +/*! The atoms on the X server which this class will cache */ +typedef struct Atoms { + /* types */ + Atom cardinal; /*!< The atom which represents the Cardinal data type */ + Atom window; /*!< The atom which represents window ids */ + Atom pixmap; /*!< The atom which represents pixmap ids */ + Atom atom; /*!< The atom which represents atom values */ + Atom string; /*!< The atom which represents ascii strings */ + Atom utf8; /*!< The atom which represents utf8-encoded strings */ + + /* window hints */ + Atom wm_colormap_windows; + Atom wm_protocols; + Atom wm_state; + Atom wm_delete_window; + Atom wm_take_focus; + Atom wm_change_state; + Atom wm_name; + Atom wm_icon_name; + Atom wm_class; + Atom wm_window_role; + Atom motif_wm_hints; + + /* NETWM atoms */ + + /* root window properties */ + Atom net_supported; + Atom net_client_list; + Atom net_client_list_stacking; + Atom net_number_of_desktops; + Atom net_desktop_geometry; + Atom net_desktop_viewport; + Atom net_current_desktop; + Atom net_desktop_names; + Atom net_active_window; + Atom net_workarea; + Atom net_supporting_wm_check; +/* Atom net_virtual_roots; */ + Atom net_desktop_layout; + Atom net_showing_desktop; + /* root window messages */ + Atom net_close_window; + Atom net_wm_moveresize; + /* application window properties */ +/* Atom net_properties; */ + Atom net_wm_name; + Atom net_wm_visible_name; + Atom net_wm_icon_name; + Atom net_wm_visible_icon_name; + Atom net_wm_desktop; + Atom net_wm_window_type; + Atom net_wm_state; + Atom net_wm_strut; +/* Atom net_wm_icon_geometry; */ + Atom net_wm_icon; +/* Atom net_wm_pid; */ +/* Atom net_wm_handled_icons; */ + Atom net_wm_allowed_actions; + /* application protocols */ +/* Atom Atom net_wm_ping; */ + + Atom net_wm_window_type_desktop; + Atom net_wm_window_type_dock; + Atom net_wm_window_type_toolbar; + Atom net_wm_window_type_menu; + Atom net_wm_window_type_utility; + Atom net_wm_window_type_splash; + Atom net_wm_window_type_dialog; + Atom net_wm_window_type_normal; + + Atom net_wm_moveresize_size_topleft; + Atom net_wm_moveresize_size_topright; + Atom net_wm_moveresize_size_bottomleft; + Atom net_wm_moveresize_size_bottomright; + Atom net_wm_moveresize_move; + + Atom net_wm_action_move; + Atom net_wm_action_resize; + Atom net_wm_action_minimize; + Atom net_wm_action_shade; + Atom net_wm_action_stick; + Atom net_wm_action_maximize_horz; + Atom net_wm_action_maximize_vert; + Atom net_wm_action_fullscreen; + Atom net_wm_action_change_desktop; + Atom net_wm_action_close; + + Atom net_wm_state_modal; + Atom net_wm_state_sticky; + Atom net_wm_state_maximized_vert; + Atom net_wm_state_maximized_horz; + Atom net_wm_state_shaded; + Atom net_wm_state_skip_taskbar; + Atom net_wm_state_skip_pager; + Atom net_wm_state_hidden; + Atom net_wm_state_fullscreen; + Atom net_wm_state_above; + Atom net_wm_state_below; + + Atom net_wm_state_add; + Atom net_wm_state_remove; + Atom net_wm_state_toggle; + + Atom net_wm_orientation_horz; + Atom net_wm_orientation_vert; + Atom net_wm_topleft; + Atom net_wm_topright; + Atom net_wm_bottomright; + Atom net_wm_bottomleft; + + /* Extra atoms */ + + Atom kde_net_system_tray_windows; + Atom kde_net_wm_system_tray_window_for; + Atom kde_net_wm_window_type_override; + + Atom kwm_win_icon; + + Atom rootpmapid; + Atom esetrootid; + + /* Openbox specific atoms */ + + Atom openbox_pid; + Atom openbox_premax; +} Atoms; +Atoms prop_atoms; + +void prop_init(); + +gboolean prop_get(Window win, Atom prop, Atom type, int size, + guchar **data, gulong num); + +gboolean prop_get_prealloc(Window win, Atom prop, Atom type, int size, + guchar *data, gulong num); + +gboolean prop_get_all(Window win, Atom prop, Atom type, int size, + guchar **data, gulong *num); + +gboolean prop_get_string(Window win, Atom prop, Atom type, guchar **data); +gboolean prop_get_strings(Window win, Atom prop, Atom type, + GPtrArray *data); + +void prop_erase(Window win, Atom prop); + +/* Set an 8-bit property from a string */ +#define PROP_SETS(win, prop, type, value) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 8, \ + PropModeReplace, (guchar*)value, strlen(value))) +/* Set a 32-bit property from a single value */ +#define PROP_SET32(win, prop, type, value) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 32, \ + PropModeReplace, (guchar*)&value, 1)) +/* Set a 32-bit property from an array */ +#define PROP_SET32A(win, prop, type, value, num) \ + (XChangeProperty(ob_display, win, prop_atoms.prop, prop_atoms.type, 32, \ + PropModeReplace, (guchar*)value, num)) + +/* Get an 8-bit property into a string */ +#define PROP_GETS(win, prop, type, value) \ + (prop_get_string(win, prop_atoms.prop, prop_atoms.type, \ + (guchar**)&value)) +/* Get an 8-bit property into a GPtrArray of strings + (The strings must be freed, the GPtrArray must already be created.) */ +#define PROP_GETSA(win, prop, type, value) \ + (prop_get_strings(win, prop_atoms.prop, prop_atoms.type, \ + value)) + +/* Get an entire 8-bit property into an array (which must be freed) */ +#define PROP_GET8U(win, prop, type, value, num) \ + (prop_get_all(win, prop_atoms.prop, prop_atoms.type, 8, \ + (guchar**)&value, &num)) + +/* Get 1 element of a 32-bit property into a given variable */ +#define PROP_GET32(win, prop, type, value) \ + (prop_get_prealloc(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar*)&value, 1)) + +/* Get an amount of a 32-bit property into an array (which must be freed) */ +#define PROP_GET32A(win, prop, type, value, num) \ + (prop_get(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar**)&value, num)) + +/* Get an entire 32-bit property into an array (which must be freed) */ +#define PROP_GET32U(win, prop, type, value, num) \ + (prop_get_all(win, prop_atoms.prop, prop_atoms.type, 32, \ + (guchar**)&value, &num)) + +#define PROP_ERASE(win, prop) (prop_erase(win, prop_atoms.prop)) + +#endif diff --git a/c/python.c b/c/python.c new file mode 100644 index 00000000..6622ed8a --- /dev/null +++ b/c/python.c @@ -0,0 +1,55 @@ +#include <Python.h> +#include <glib.h> + +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#endif + +void python_startup() +{ + PyObject *sys, *sysdict, *syspath, *path1, *path2; + char *home, *homescriptdir; + + Py_Initialize(); + + /* fix up the system path */ + + sys = PyImport_ImportModule((char*)"sys"); /* new */ + sysdict = PyModule_GetDict(sys); /* borrowed */ + syspath = PyDict_GetItemString(sysdict, (char*)"path"); /* borrowed */ + + path1 = PyString_FromString(SCRIPTDIR); /* new */ + PyList_Insert(syspath, 0, path1); + Py_DECREF(path1); + + home = getenv("HOME"); + if (home != NULL) { + homescriptdir = g_strdup_printf("%s/.openbox", home); + path2 = PyString_FromString(homescriptdir); /* new */ + g_free(homescriptdir); + + PyList_Insert(syspath, 0, path2); + Py_DECREF(path2); + } else + g_warning("Failed to read the $HOME environment variable"); + + Py_DECREF(sys); +} + +void python_shutdown() +{ + Py_Finalize(); +} + +gboolean python_import(char *module) +{ + PyObject *mod; + + mod = PyImport_ImportModule(module); /* new */ + if (mod == NULL) { + PyErr_Print(); + return FALSE; + } + Py_DECREF(mod); + return TRUE; +} diff --git a/c/python.h b/c/python.h new file mode 100644 index 00000000..90366315 --- /dev/null +++ b/c/python.h @@ -0,0 +1,10 @@ +#ifndef __python_h +#define __python_h + +void python_startup(); +void python_shutdown(); + +/*! Import a python module */ +gboolean python_import(char *module); + +#endif diff --git a/c/screen.c b/c/screen.c new file mode 100644 index 00000000..ab6765f3 --- /dev/null +++ b/c/screen.c @@ -0,0 +1,516 @@ +#include "openbox.h" +#include "prop.h" +#include "screen.h" +#include "client.h" +#include "focus.h" + +#include <X11/Xlib.h> +#ifdef HAVE_UNISTD_H +# include <sys/types.h> +# include <unistd.h> +#endif + +/*! The event mask to grab on the root window */ +#define ROOT_EVENTMASK (/*ColormapChangeMask |*/ PropertyChangeMask | \ + EnterWindowMask | LeaveWindowMask | \ + SubstructureNotifyMask | SubstructureRedirectMask | \ + ButtonPressMask | ButtonReleaseMask | ButtonMotionMask) + +guint screen_num_desktops = 1; +guint screen_desktop = 0; +Size screen_physical_size; +gboolean screen_showing_desktop; +DesktopLayout screen_desktop_layout; +GPtrArray *screen_desktop_names; + +static Rect *area = NULL; +static Strut *strut = NULL; + +static void screen_update_area(); + +static gboolean running; +static int another_running(Display *d, XErrorEvent *e) +{ + (void)d;(void)e; + g_message("A window manager is already running on screen %d", + ob_screen); + running = TRUE; + return -1; +} + +gboolean screen_annex() +{ + XErrorHandler old; + Window support; + pid_t pid; + int i, num_support; + Atom *supported; + + running = FALSE; + old = XSetErrorHandler(another_running); + XSelectInput(ob_display, ob_root, ROOT_EVENTMASK); + XSync(ob_display, FALSE); + XSetErrorHandler(old); + if (running) + return FALSE; + + g_message("Managing screen %d", ob_screen); + + /* set the mouse cursor for the root window (the default cursor) */ + XDefineCursor(ob_display, ob_root, ob_cursors.left_ptr); + + /* set the OPENBOX_PID hint */ + pid = getpid(); + PROP_SET32(ob_root, openbox_pid, cardinal, pid); + + /* create the netwm support window */ + support = XCreateSimpleWindow(ob_display, ob_root, 0, 0, 1, 1, 0, 0, 0); + + /* set supporting window */ + PROP_SET32(ob_root, net_supporting_wm_check, window, support); + + /* set properties on the supporting window */ + PROP_SETS(support, net_wm_name, utf8, "Openbox"); + PROP_SET32(support, net_supporting_wm_check, window, support); + + /* set the _NET_SUPPORTED_ATOMS hint */ + num_support = 48; + i = 0; + supported = g_new(Atom, num_support); + supported[i++] = prop_atoms.net_current_desktop; + supported[i++] = prop_atoms.net_number_of_desktops; + supported[i++] = prop_atoms.net_desktop_geometry; + supported[i++] = prop_atoms.net_desktop_viewport; + supported[i++] = prop_atoms.net_active_window; + supported[i++] = prop_atoms.net_workarea; + supported[i++] = prop_atoms.net_client_list; + supported[i++] = prop_atoms.net_client_list_stacking; + supported[i++] = prop_atoms.net_desktop_names; + supported[i++] = prop_atoms.net_close_window; + supported[i++] = prop_atoms.net_desktop_layout; + supported[i++] = prop_atoms.net_showing_desktop; + supported[i++] = prop_atoms.net_wm_name; + supported[i++] = prop_atoms.net_wm_visible_name; + supported[i++] = prop_atoms.net_wm_icon_name; + supported[i++] = prop_atoms.net_wm_visible_icon_name; + supported[i++] = prop_atoms.net_wm_desktop; + supported[i++] = prop_atoms.net_wm_strut; + supported[i++] = prop_atoms.net_wm_window_type; + supported[i++] = prop_atoms.net_wm_window_type_desktop; + supported[i++] = prop_atoms.net_wm_window_type_dock; + supported[i++] = prop_atoms.net_wm_window_type_toolbar; + supported[i++] = prop_atoms.net_wm_window_type_menu; + supported[i++] = prop_atoms.net_wm_window_type_utility; + supported[i++] = prop_atoms.net_wm_window_type_splash; + supported[i++] = prop_atoms.net_wm_window_type_dialog; + supported[i++] = prop_atoms.net_wm_window_type_normal; + supported[i++] = prop_atoms.net_wm_allowed_actions; + supported[i++] = prop_atoms.net_wm_action_move; + supported[i++] = prop_atoms.net_wm_action_resize; + supported[i++] = prop_atoms.net_wm_action_minimize; + supported[i++] = prop_atoms.net_wm_action_shade; + supported[i++] = prop_atoms.net_wm_action_maximize_horz; + supported[i++] = prop_atoms.net_wm_action_maximize_vert; + supported[i++] = prop_atoms.net_wm_action_fullscreen; + supported[i++] = prop_atoms.net_wm_action_change_desktop; + supported[i++] = prop_atoms.net_wm_action_close; + supported[i++] = prop_atoms.net_wm_state; + supported[i++] = prop_atoms.net_wm_state_modal; + supported[i++] = prop_atoms.net_wm_state_maximized_vert; + supported[i++] = prop_atoms.net_wm_state_maximized_horz; + supported[i++] = prop_atoms.net_wm_state_shaded; + supported[i++] = prop_atoms.net_wm_state_skip_taskbar; + supported[i++] = prop_atoms.net_wm_state_skip_pager; + supported[i++] = prop_atoms.net_wm_state_hidden; + supported[i++] = prop_atoms.net_wm_state_fullscreen; + supported[i++] = prop_atoms.net_wm_state_above; + supported[i++] = prop_atoms.net_wm_state_below; + g_assert(i == num_support); +/* + supported[] = prop_atoms.net_wm_moveresize; + supported[] = prop_atoms.net_wm_moveresize_size_topleft; + supported[] = prop_atoms.net_wm_moveresize_size_topright; + supported[] = prop_atoms.net_wm_moveresize_size_bottomleft; + supported[] = prop_atoms.net_wm_moveresize_size_bottomright; + supported[] = prop_atoms.net_wm_moveresize_move; + supported[] = prop_atoms.net_wm_action_stick; +*/ + + PROP_SET32A(ob_root, net_supported, atom, supported, num_support); + g_free(supported); + + return TRUE; +} + +void screen_startup() +{ + screen_desktop_names = g_ptr_array_new(); + + /* get the initial size */ + screen_resize(); + + screen_set_num_desktops(4); + screen_set_desktop(0); + + /* don't start in showing-desktop mode */ + screen_showing_desktop = FALSE; + PROP_SET32(ob_root, net_showing_desktop, cardinal, screen_showing_desktop); + + screen_update_layout(); +} + +void screen_shutdown() +{ + guint i; + for (i = 0; i < screen_desktop_names->len; ++i) + g_free(g_ptr_array_index(screen_desktop_names, i)); + g_ptr_array_free(screen_desktop_names, TRUE); + g_free(strut); + g_free(area); +} + +void screen_resize() +{ + /* Set the _NET_DESKTOP_GEOMETRY hint */ + /* XXX RandR support here? */ + int geometry[2]; + + geometry[0] = WidthOfScreen(ScreenOfDisplay(ob_display, ob_screen)); + geometry[1] = HeightOfScreen(ScreenOfDisplay(ob_display, ob_screen)); + PROP_SET32A(ob_root, net_desktop_geometry, cardinal, geometry, 2); + screen_physical_size.width = geometry[0]; + screen_physical_size.height = geometry[1]; + + if (ob_state == State_Starting) + return; + + screen_update_struts(); + + /* XXX adjust more stuff ? */ +} + +void screen_set_num_desktops(guint num) +{ + unsigned long *viewport; + + g_assert(num > 0); + + /* move windows on desktops that will no longer exist! */ + /* XXX + std::list<Client*>::iterator it, end = clients.end(); + for (it = clients.begin(); it != end; ++it) { + unsigned int d = (*it)->desktop(); + if (d >= num && d != 0xffffffff) { + XEvent ce; + ce.xclient.type = ClientMessage; + ce.xclient.message_type = otk::Property::atoms.net_wm_desktop; + ce.xclient.display = **otk::display; + ce.xclient.window = (*it)->window(); + ce.xclient.format = 32; + ce.xclient.data.l[0] = num - 1; + XSendEvent(**otk::display, _info->rootWindow(), false, + SubstructureNotifyMask | SubstructureRedirectMask, &ce); + } + } + */ + + screen_num_desktops = num; + PROP_SET32(ob_root, net_number_of_desktops, cardinal, num); + + /* set the viewport hint */ + viewport = g_new0(unsigned long, num * 2); + PROP_SET32A(ob_root, net_desktop_viewport, cardinal, viewport, num * 2); + g_free(viewport); + + /* change our struts/area to match */ + screen_update_struts(); + + /* the number of rows/columns will differ */ + screen_update_layout(); + + /* may be some unnamed desktops that we need to fill in with names */ + screen_update_desktop_names(); + + /* change our desktop if we're on one that no longer exists! */ + if (screen_desktop >= screen_num_desktops) + screen_set_desktop(num - 1); +} + +void screen_set_desktop(guint num) +{ + guint old = screen_desktop; + + g_assert(num < screen_num_desktops); + + g_message("Moving to desktop %u", num); + + screen_desktop = num; + PROP_SET32(ob_root, net_current_desktop, cardinal, num); + + if (old == num) return; + + /* XXX show/hide all the clients + std::list<Client*>::iterator it, end = clients.end(); + for (it = clients.begin(); it != end; ++it) + (*it)->showhide(); */ + + /* XXX force the callbacks to fire + if (!openbox->focusedClient()) + openbox->setFocusedClient(0); */ +} + +void screen_update_layout() +{ + unsigned long *data = NULL; + + /* defaults */ + screen_desktop_layout.orientation = prop_atoms.net_wm_orientation_horz; + screen_desktop_layout.start_corner = prop_atoms.net_wm_topleft; + screen_desktop_layout.rows = 1; + screen_desktop_layout.columns = screen_num_desktops; + + if (PROP_GET32A(ob_root, net_desktop_layout, cardinal, data, 4)) { + if (data[0] == prop_atoms.net_wm_orientation_vert) + screen_desktop_layout.orientation = data[0]; + if (data[3] == prop_atoms.net_wm_topright) + screen_desktop_layout.start_corner = data[3]; + else if (data[3] == prop_atoms.net_wm_bottomright) + screen_desktop_layout.start_corner = data[3]; + else if (data[3] == prop_atoms.net_wm_bottomleft) + screen_desktop_layout.start_corner = data[3]; + + /* fill in a zero rows/columns */ + if (!(data[1] == 0 && data[2] == 0)) { /* both 0's is bad data.. */ + if (data[1] == 0) { + data[1] = (screen_num_desktops + + screen_num_desktops % data[2]) / data[2]; + } else if (data[2] == 0) { + data[2] = (screen_num_desktops + + screen_num_desktops % data[1]) / data[1]; + } + screen_desktop_layout.columns = data[1]; + screen_desktop_layout.rows = data[2]; + } + + /* bounds checking */ + if (screen_desktop_layout.orientation == + prop_atoms.net_wm_orientation_horz) { + if (screen_desktop_layout.rows > screen_num_desktops) + screen_desktop_layout.rows = screen_num_desktops; + if (screen_desktop_layout.columns > ((screen_num_desktops + + screen_num_desktops % + screen_desktop_layout.rows) / + screen_desktop_layout.rows)) + screen_desktop_layout.columns = + (screen_num_desktops + screen_num_desktops % + screen_desktop_layout.rows) / + screen_desktop_layout.rows; + } else { + if (screen_desktop_layout.columns > screen_num_desktops) + screen_desktop_layout.columns = screen_num_desktops; + if (screen_desktop_layout.rows > ((screen_num_desktops + + screen_num_desktops % + screen_desktop_layout.columns) / + screen_desktop_layout.columns)) + screen_desktop_layout.rows = + (screen_num_desktops + screen_num_desktops % + screen_desktop_layout.columns) / + screen_desktop_layout.columns; + } + g_free(data); + } +} + +void screen_update_desktop_names() +{ + guint i; + + /* empty the array */ + for (i = 0; i < screen_desktop_names->len; ++i) + g_free(g_ptr_array_index(screen_desktop_names, i)); + g_ptr_array_set_size(screen_desktop_names, 0); + + PROP_GETSA(ob_root, net_desktop_names, utf8, screen_desktop_names); + + while (screen_desktop_names->len < screen_num_desktops) + g_ptr_array_add(screen_desktop_names, g_strdup("Unnamed Desktop")); +} + +void screen_show_desktop(gboolean show) +{ + GSList *it; + static Window saved_focus = 0; + + if (show == screen_showing_desktop) return; /* no change */ + + /* save the window focus, and restore it when leaving the show-desktop + mode */ + if (show && focus_client) + saved_focus = focus_client->window; + + screen_showing_desktop = show; + + for (it = client_list; it; it = g_slist_next(it)) { + Client *client = it->data; + if (client->type == Type_Desktop) { + if (show) client_focus(client); + } else + client_showhide(client); + } + + if (!show) { + Client *f = focus_client; + if (!f || f->type == Type_Desktop) { + Client *c = g_hash_table_lookup(client_map, + (gpointer)saved_focus); + if (c) client_focus(c); + } + } + + show = show ? 1 : 0; /* make it boolean */ + PROP_SET32(ob_root, net_showing_desktop, cardinal, show); +} + +void screen_install_colormap(Client *client, gboolean install) +{ + if (client == NULL) { + /* XXX DONT USE THE DEFAULT SHIT HERE */ + if (install) + XInstallColormap(ob_display, + DefaultColormap(ob_display, ob_screen)); + else + XUninstallColormap(ob_display, + DefaultColormap(ob_display, ob_screen)); + } else { + XWindowAttributes wa; + if (XGetWindowAttributes(ob_display, client->window, &wa)) { + if (install) + XInstallColormap(ob_display, wa.colormap); + else + XUninstallColormap(ob_display, wa.colormap); + } + } +} + +void screen_update_struts() +{ + GSList *it; + guint i; + + if (strut != NULL) + g_free(strut); + strut = g_new0(Strut, screen_num_desktops + 1); + + for (it = client_list; it; it = it->next) { + Client *c = it->data; + if (c->iconic) continue; /* these dont count in the strut */ + + if (c->desktop == 0xffffffff) { + for (i = 0; i < screen_num_desktops; ++i) + STRUT_ADD(strut[i], c->strut); + } else { + g_assert(c->desktop < screen_num_desktops); + STRUT_ADD(strut[c->desktop], c->strut); + } + /* apply to the 'all desktops' strut */ + STRUT_ADD(strut[screen_num_desktops], c->strut); + } + screen_update_area(); +} + +static void screen_update_area() +{ + guint i; + gulong *dims; + + if (area != NULL) + g_free(area); + area = g_new0(Rect, screen_num_desktops + 1); + + dims = g_new(unsigned long, 4 * screen_num_desktops); + for (i = 0; i < screen_num_desktops + 1; ++i) { + Rect old_area = area[i]; +/* + #ifdef XINERAMA + // reset to the full areas + if (isXineramaActive()) + xineramaUsableArea = getXineramaAreas(); + #endif // XINERAMA +*/ + + RECT_SET(area[i], strut[i].left, strut[i].top, + screen_physical_size.width - (strut[i].left + + strut[i].right), + screen_physical_size.height - (strut[i].top + + strut[i].bottom)); + +/* + #ifdef XINERAMA + if (isXineramaActive()) { + // keep each of the ximerama-defined areas inside the strut + RectList::iterator xit, xend = xineramaUsableArea.end(); + for (xit = xineramaUsableArea.begin(); xit != xend; ++xit) { + if (xit->x() < usableArea.x()) { + xit->setX(usableArea.x()); + xit->setWidth(xit->width() - usableArea.x()); + } + if (xit->y() < usableArea.y()) { + xit->setY(usableArea.y()); + xit->setHeight(xit->height() - usableArea.y()); + } + if (xit->x() + xit->width() > usableArea.width()) + xit->setWidth(usableArea.width() - xit->x()); + if (xit->y() + xit->height() > usableArea.height()) + xit->setHeight(usableArea.height() - xit->y()); + } + } + #endif // XINERAMA +*/ + if (!RECT_EQUAL(old_area, area[i])) { + /* the area has changed, adjust all the maximized windows */ + GSList *it; + for (it = client_list; it; it = it->next) { + Client *c = it->data; + if (i < screen_num_desktops) { + if (c->desktop == i) + client_remaximize(c); + } else { + /* the 'all desktops' size */ + if (c->desktop == DESKTOP_ALL) + client_remaximize(c); + } + } + } + + /* don't set these for the 'all desktops' area */ + if (i < screen_num_desktops) { + dims[(i * 4) + 0] = area[i].x; + dims[(i * 4) + 1] = area[i].y; + dims[(i * 4) + 2] = area[i].width; + dims[(i * 4) + 3] = area[i].height; + } + } + PROP_SET32A(ob_root, net_workarea, cardinal, + dims, 4 * screen_num_desktops); + g_free(dims); +} + +Rect *screen_area(guint desktop) +{ + if (desktop >= screen_num_desktops) { + if (desktop == DESKTOP_ALL) + return &area[screen_num_desktops]; + return NULL; + } + return &area[desktop]; +} + +Strut *screen_strut(guint desktop) +{ + if (desktop >= screen_num_desktops) { + if (desktop == DESKTOP_ALL) + return &strut[screen_num_desktops]; + return NULL; + } + return &strut[desktop]; +} diff --git a/c/screen.h b/c/screen.h new file mode 100644 index 00000000..a591a202 --- /dev/null +++ b/c/screen.h @@ -0,0 +1,66 @@ +#ifndef __screen_h +#define __screen_h + +#include "geom.h" + +struct Client; + +#define DESKTOP_ALL (0xffffffff) + +/*! The number of available desktops */ +extern guint screen_num_desktops; +/*! The current desktop */ +extern guint screen_desktop; +/*! The size of the screen */ +extern Size screen_physical_size; +/*! Are we in showing-desktop mode? */ +extern gboolean screen_showing_desktop; + +typedef struct DesktopLayout { + guint orientation; + guint start_corner; + guint rows; + guint columns; +} DesktopLayout; +extern DesktopLayout screen_desktop_layout; + +/*! An array of gchar*'s which are desktop names in UTF-8 format */ +extern GPtrArray *screen_desktop_names; + +/*! Take over the screen, set the basic hints on it claming it as ours */ +gboolean screen_annex(); + +/*! Once the screen is ours, set up its initial state */ +void screen_startup(); +/*! Free resources */ +void screen_shutdown(); + +/*! Figure out the new size of the screen and adjust stuff for it */ +void screen_resize(); + +/*! Change the number of available desktops */ +void screen_set_num_desktops(guint num); +/*! Change the current desktop */ +void screen_set_desktop(guint num); + +/*! Shows and focuses the desktop and hides all the client windows, or + returns to the normal state, showing client windows. */ +void screen_show_desktop(gboolean show); + +/*! Updates the desktop layout from the root property if available */ +void screen_update_layout(); + +/*! Get desktop names from the root window property */ +void screen_update_desktop_names(); + +/*! Installs or uninstalls a colormap for a client. If client is NULL, then + it handles the root colormap. */ +void screen_install_colormap(struct Client *client, gboolean install); + +void screen_update_struts(); + +Rect *screen_area(guint desktop); + +Strut *screen_strut(guint desktop); + +#endif diff --git a/c/screenwrap.c b/c/screenwrap.c new file mode 100644 index 00000000..9ef14d8d --- /dev/null +++ b/c/screenwrap.c @@ -0,0 +1,433 @@ +#include "screenwrap.h" +#include "openbox.h" +#include "screen.h" +#include "kbind.h" +#include "mbind.h" + +ScreenWrap *screenwrap_instance; + +/*************************************************************************** + + Define the type 'ScreenWrap' + + ***************************************************************************/ + +#define IS_SWRAP(v) ((v)->ob_type == &ScreenWrapType) +#define CHECK_SWRAP(self, funcname) { \ + if (!IS_SWRAP(self)) { \ + PyErr_SetString(PyExc_TypeError, \ + "descriptor '" funcname "' a 'Screen' object"); \ + return NULL; \ + } \ +} + +staticforward PyTypeObject ScreenWrapType; + +/*************************************************************************** + + Attribute methods + + ***************************************************************************/ + +static PyObject *swrap_number(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "number"); + if (!PyArg_ParseTuple(args, ":number")) + return NULL; + return PyInt_FromLong(ob_screen); +} + +static PyObject *swrap_rootWindow(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "rootWindow"); + if (!PyArg_ParseTuple(args, ":rootWindow")) + return NULL; + return PyInt_FromLong(ob_root); +} + +static PyObject *swrap_state(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "state"); + if (!PyArg_ParseTuple(args, ":state")) + return NULL; + return PyInt_FromLong(ob_state); +} + +static PyObject *swrap_numDesktops(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "numDesktops"); + if (!PyArg_ParseTuple(args, ":numDesktops")) + return NULL; + return PyInt_FromLong(screen_num_desktops); +} + +static PyObject *swrap_desktop(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "desktop"); + if (!PyArg_ParseTuple(args, ":desktop")) + return NULL; + return PyInt_FromLong(screen_desktop); +} + +static PyObject *swrap_physicalSize(ScreenWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_SWRAP(self, "physicalSize"); + if (!PyArg_ParseTuple(args, ":physicalSize")) + return NULL; + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(screen_physical_size.width)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(screen_physical_size.height)); + return tuple; +} + +static PyObject *swrap_showingDesktop(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "showingDesktop"); + if (!PyArg_ParseTuple(args, ":showingDesktop")) + return NULL; + return PyInt_FromLong(screen_showing_desktop ? 1 : 0); +} + +static PyObject *swrap_desktopLayout(ScreenWrap *self, PyObject *args) +{ + PyObject *tuple; + + CHECK_SWRAP(self, "desktopLayout"); + if (!PyArg_ParseTuple(args, ":desktopLayout")) + return NULL; + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, + PyInt_FromLong(screen_desktop_layout.orientation)); + PyTuple_SET_ITEM(tuple, 1, + PyInt_FromLong(screen_desktop_layout.start_corner)); + PyTuple_SET_ITEM(tuple, 2, + PyInt_FromLong(screen_desktop_layout.rows)); + PyTuple_SET_ITEM(tuple, 3, + PyInt_FromLong(screen_desktop_layout.columns)); + return tuple; +} + +static PyObject *swrap_desktopNames(ScreenWrap *self, PyObject *args) +{ + PyObject *list; + guint s, i; + + CHECK_SWRAP(self, "desktopNames"); + if (!PyArg_ParseTuple(args, ":desktopNames")) + return NULL; + s = screen_desktop_names->len; + list = PyList_New(s); + for (i = 0; i < s; ++i) + PyList_SET_ITEM(list, i, PyString_FromString + (g_ptr_array_index(screen_desktop_names, i))); + return list; +} + +static PyObject *swrap_area(ScreenWrap *self, PyObject *args) +{ + PyObject * tuple; + Rect *r; + guint i; + + CHECK_SWRAP(self, "area"); + if (!PyArg_ParseTuple(args, "i:area", &i)) + return NULL; + r = screen_area(i); + if (r == NULL) { + PyErr_SetString(PyExc_IndexError, + "the requested desktop was not valid"); + return NULL; + } + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(r->x)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(r->y)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(r->width)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(r->height)); + return tuple; +} + +static PyObject *swrap_strut(ScreenWrap *self, PyObject *args) +{ + PyObject *tuple; + Strut *s; + guint i; + + CHECK_SWRAP(self, "strut"); + if (!PyArg_ParseTuple(args, "i:strut", &i)) + return NULL; + s = screen_strut(i); + if (s == NULL) { + PyErr_SetString(PyExc_IndexError, + "the requested desktop was not valid"); + return NULL; + } + tuple = PyTuple_New(4); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(s->left)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(s->top)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(s->right)); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(s->bottom)); + return tuple; +} + +static PyObject *swrap_grabKey(ScreenWrap *self, PyObject *args) +{ + PyObject *item, *tuple; + GList *keylist = NULL, *it; + int i, s; + gboolean grab = FALSE; + + CHECK_SWRAP(self, "grabKey"); + if (!PyArg_ParseTuple(args, "O:grabKey", &tuple)) + return NULL; + + if (PyTuple_Check(tuple)) { + s = PyTuple_GET_SIZE(tuple); + if (s > 0) { + for (i = 0; i < s; ++i) { + item = PyTuple_GET_ITEM(tuple, i); + if (!PyString_Check(item)) + break; + keylist = g_list_append(keylist, + g_strdup(PyString_AsString(item))); + } + if (i == s) + grab = kbind_add(keylist); + + for (it = keylist; it != NULL; it = it->next) + g_free(it->data); + g_list_free(it); + + if (grab) { + Py_INCREF(Py_None); + return Py_None; + } else { + PyErr_SetString(PyExc_ValueError, + "the key could not be grabbed"); + return NULL; + } + } + } + + PyErr_SetString(PyExc_TypeError, "expected a tuple of strings"); + return NULL; +} + +static PyObject *swrap_clearKeyGrabs(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "clearKeyGrabs"); + if (!PyArg_ParseTuple(args, ":clearKeyGrabs")) + return NULL; + kbind_clearall(); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *swrap_grabKeyboard(ScreenWrap *self, PyObject *args) +{ + int grab; + + CHECK_SWRAP(self, "grabKeyboard"); + if (!PyArg_ParseTuple(args, "i:grabKeyboard", &grab)) + return NULL; + if (!kbind_grab_keyboard(grab)) { + PyErr_SetString(PyExc_RuntimeError, "failed to grab keyboard"); + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *swrap_grabButton(ScreenWrap *self, PyObject *args) +{ + char *name; + char *context_str; + GQuark context; + + CHECK_SWRAP(self, "grabButton"); + if (!PyArg_ParseTuple(args, "ss:grabKey", &name, &context_str)) + return NULL; + + context = g_quark_try_string(context_str); + + if (!context) { + PyErr_SetString(PyExc_ValueError, "invalid context"); + return NULL; + } + + if (mbind_add(name, context)) { + Py_INCREF(Py_None); + return Py_None; + } else { + PyErr_SetString(PyExc_ValueError, + "the button could not be grabbed"); + return NULL; + } +} + +static PyObject *swrap_clearButtonGrabs(ScreenWrap *self, PyObject *args) +{ + CHECK_SWRAP(self, "clearButtonGrabs"); + if (!PyArg_ParseTuple(args, ":clearButtonGrabs")) + return NULL; + mbind_clearall(); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *swrap_grabPointer(ScreenWrap *self, PyObject *args) +{ + int grab; + + CHECK_SWRAP(self, "grabPointer"); + if (!PyArg_ParseTuple(args, "i:grabPointer", &grab)) + return NULL; + if (!mbind_grab_pointer(grab)) { + PyErr_SetString(PyExc_RuntimeError, "failed to grab pointer"); + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef ScreenWrapAttributeMethods[] = { + {"number", (PyCFunction)swrap_number, METH_VARARGS, + "Screen.number() -- Returns the number of the screen on the X server on " + "which this Openbox instance is running."}, + {"rootWindow", (PyCFunction)swrap_rootWindow, METH_VARARGS, + "Screen.rootWindow() -- Returns the window id of the root window."}, + {"state", (PyCFunction)swrap_state, METH_VARARGS, + "Screen.state() -- Returns the running state of Openbox. One of the " + "ob.State_ constants."}, + {"numDesktops", (PyCFunction)swrap_numDesktops, METH_VARARGS, + "Screen.numDesktops() -- Returns the number of desktops available."}, + {"desktop", (PyCFunction)swrap_desktop, METH_VARARGS, + "Screen.desktop() -- Returns the currently visible desktop."}, + {"physicalSize", (PyCFunction)swrap_physicalSize, METH_VARARGS, + "Screen.physicalSize() -- Returns the physical size (in pixels) of the " + "display in a tuple. The tuple is formatted as (width, height)."}, + {"showingDesktop", (PyCFunction)swrap_showingDesktop, METH_VARARGS, + "Screen.showingDesktop() -- Returns if in showing-the-desktop mode or " + "not."}, + {"desktopNames", (PyCFunction)swrap_desktopNames, METH_VARARGS, + "Screen.desktopNames() -- Returns a list of the names of all the " + "desktops, and possibly for desktops beyond those. The names are encoded " + "as UTF-8."}, + {"desktopLayout", (PyCFunction)swrap_desktopLayout, METH_VARARGS, + "Screen.desktopLayout() -- Returns the layout of the desktops, as " + "specified by a compliant pager, in a tuple. The format of the tuple is " + "(orientation, corner, rows, columns). Where, orientation is one of the " + "ob.Orientation_ constants, corner is one of the ob.Corner_ constants, " + "and rows and columns specify the size of the layout. The rows and " + "columns will always include all the desktops."}, + {"area", (PyCFunction)swrap_area, METH_VARARGS, + "Screen.area(d) -- Returns the usuable area on the Screen for a desktop, " + "in the form of a tuple. The tuples format is (x, y, width, height). The " + "desktop must be in the range of desktops on the screen, or 0xffffffff " + "to get a combined area for 'all desktops'."}, + {"strut", (PyCFunction)swrap_area, METH_VARARGS, + "Screen.strut(d) -- Returns the combined strut of all clients on a " + "desktop, in the form of a tuple. The tuples format is " + "(left, top, right, bottom). The desktop must be in the range of " + "desktops on the screen, or 0xffffffff to get a combined strut for " + "'all desktops'."}, + {"grabKey", (PyCFunction)swrap_grabKey, METH_VARARGS, + "Screen.grabKey(('Mod1-C-a', 'd')) -- Grabs a key chain so that key " + "events for it will occur. The argument must be a tuple of one or " + "more elements. Each key element is made up of " + "Modifier-Modifier-...-Key, where Modifier is one of Mod1, Mod2, " + "Mod3, Mod4, Mod5, S (for Shift), or C (for Control)."}, + {"clearKeyGrabs", (PyCFunction)swrap_clearKeyGrabs, METH_VARARGS, + "Screen.clearKeyGrabs() -- Removes all key grabs that have been done " + "with grabKey()."}, + {"grabKeyboard", (PyCFunction)swrap_grabKeyboard, METH_VARARGS, + "Screen.grabKeyboard(grab) -- Grabs or ungrabs the entire keyboard. When " + "the keyboard is grabbed, all key presses will be sent to the " + "hooks.keyboard hook. (grabbed keys will go to the hooks.events hook " + "too. "}, + {"grabButton", (PyCFunction)swrap_grabButton, METH_VARARGS, + "Screen.grabButton('C-1', \"frame\") -- Grabs a pointer button " + "for the given context. The context must be one of the ob.Context_* " + "constants. The button definition is made up of " + "Modifier-Modifier-...-Button, where Modifier is one of Mod1, Mod2, " + "Mod3, Mod4, Mod5, S (for Shift), or C (for Control)."}, + {"clearButtonGrabs", (PyCFunction)swrap_clearButtonGrabs, METH_VARARGS, + "Screen.clearButtonGrabs() -- Removes all button grabs that have been " + "done with grabButton()."}, + {"grabPointer", (PyCFunction)swrap_grabPointer, METH_VARARGS, + "grabPointer(grab) -- Grabs or ungrabs the pointer device. When the " + "pointer is grabbed, all pointer events will be sent to the " + "hooks.pointer hook. (grabbed buttons will NOT go to the hooks.events " + "hook while the pointer is grabbed)."}, + {NULL, NULL, 0, NULL} +}; + +/*************************************************************************** + + Type methods/struct + + ***************************************************************************/ + +static PyObject *swrap_getattr(ScreenWrap *self, char *name) +{ + CHECK_SWRAP(self, "getattr"); + return Py_FindMethod(ScreenWrapAttributeMethods, (PyObject*)self, name); +} + +static void swrap_dealloc(ScreenWrap *self) +{ + PyObject_Del((PyObject*) self); +} + +static PyTypeObject ScreenWrapType = { + PyObject_HEAD_INIT(NULL) + 0, + "Screen", + sizeof(ScreenWrap), + 0, + (destructor) swrap_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc) swrap_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ +}; + +/*************************************************************************** + + External methods + + ***************************************************************************/ + +void screenwrap_startup() +{ + PyObject *ob, *obdict; + ScreenWrap *swrap; + + ScreenWrapType.ob_type = &PyType_Type; + ScreenWrapType.tp_doc = "Wraps information and functionality global to an " + "instance of Openbox."; + PyType_Ready(&ScreenWrapType); + swrap = PyObject_New(ScreenWrap, &ScreenWrapType); + + /* get the ob module/dict */ + ob = PyImport_ImportModule("ob"); /* new */ + g_assert(ob != NULL); + obdict = PyModule_GetDict(ob); /* borrowed */ + g_assert(obdict != NULL); + + PyDict_SetItemString(obdict, "Screen", (PyObject*)swrap); + Py_DECREF(swrap); + Py_DECREF(ob); +} + +void screenwrap_shutdown() +{ +} + + diff --git a/c/screenwrap.h b/c/screenwrap.h new file mode 100644 index 00000000..2ca278cf --- /dev/null +++ b/c/screenwrap.h @@ -0,0 +1,14 @@ +#ifndef __screenwrap_h +#define __screenwrap_h + +#include <Python.h> + +/* ScreenWrap is a PyObject */ +typedef struct ScreenWrap { + PyObject_HEAD +} ScreenWrap; + +void screenwrap_startup(); +void screenwrap_shutdown(); + +#endif diff --git a/c/stacking.c b/c/stacking.c new file mode 100644 index 00000000..081bde93 --- /dev/null +++ b/c/stacking.c @@ -0,0 +1,116 @@ +#include "openbox.h" +#include "prop.h" +#include "focus.h" +#include "client.h" +#include "frame.h" +#include <glib.h> + +GList *stacking_list = NULL; + +void stacking_set_list() +{ + Window *windows, *win_it; + GList *it; + guint size = g_list_length(stacking_list); + + /* create an array of the window ids (from bottom to top, + reverse order!) */ + if (size > 0) { + windows = g_new(Window, size); + win_it = windows; + for (it = g_list_last(stacking_list); it; it = it->prev, ++win_it) + *win_it = ((Client*)it->data)->window; + } else + windows = NULL; + + PROP_SET32A(ob_root, net_client_list_stacking, window, windows, size); + + if (windows) + g_free(windows); +} + +void stacking_raise(Client *client) +{ + Window wins[2]; /* only ever restack 2 windows. */ + GList *it; + Client *m; + + g_assert(stacking_list != NULL); /* this would be bad */ + + m = client_find_modal_child(client); + /* if we have a modal child, raise it instead, we'll go along tho later */ + if (m) stacking_raise(m); + + /* remove the client before looking so we can't run into ourselves */ + stacking_list = g_list_remove(stacking_list, client); + + /* the stacking list is from highest to lowest */ + it = stacking_list; + while (it) { + Client *c = it->data; + if (client->layer >= c->layer && m != c) + break; + it = it->next; + } + + /* + if our new position is the top, we want to stack under the focus_backup. + otherwise, we want to stack under the previous window in the stack. + */ + if (it == stacking_list) + wins[0] = focus_backup; + else if (it != NULL) + wins[0] = ((Client*)it->prev->data)->frame->window; + else + wins[0] = ((Client*)g_list_last(stacking_list)->data)->frame->window; + wins[1] = client->frame->window; + + stacking_list = g_list_insert_before(stacking_list, it, client); + + XRestackWindows(ob_display, wins, 2); + + stacking_set_list(); +} + +void stacking_lower(Client *client) +{ + Window wins[2]; /* only ever restack 2 windows. */ + GList *it; + + g_assert(stacking_list != NULL); /* this would be bad */ + + it = g_list_last(stacking_list); + + if (client->modal && client->transient_for) { + /* don't let a modal window lower below its transient_for */ + it = g_list_find(stacking_list, client->transient_for); + g_assert(it != NULL); + + wins[0] = (it == stacking_list ? focus_backup : + ((Client*)it->prev->data)->frame->window); + wins[1] = client->frame->window; + if (wins[0] == wins[1]) return; /* already right above the window */ + + stacking_list = g_list_remove(stacking_list, client); + stacking_list = g_list_insert_before(stacking_list, it, client); + } else { + while (it != stacking_list) { + Client *c = it->data; + if (client->layer >= c->layer) + break; + it = it->prev; + } + if (it->data == client) return; /* already the bottom, return */ + + wins[0] = ((Client*)it->data)->frame->window; + wins[1] = client->frame->window; + + stacking_list = g_list_remove(stacking_list, client); + stacking_list = g_list_insert_before(stacking_list, + it->next, client); + } + + XRestackWindows(ob_display, wins, 2); + stacking_set_list(); +} + diff --git a/c/stacking.h b/c/stacking.h new file mode 100644 index 00000000..fa6bcf87 --- /dev/null +++ b/c/stacking.h @@ -0,0 +1,26 @@ +#ifndef __stacking_h +#define __stacking_h + +#include <glib.h> + +struct Client; + +extern GList *stacking_list; + +/*! Sets the client stacking list on the root window from the + stacking_clientlist */ +void stacking_set_list(); + +/*! Raises a client window above all others in its stacking layer + raiseWindow has a couple of constraints that lowerWindow does not.<br> + 1) raiseWindow can be called after changing a Client's stack layer, and + the list will be reorganized properly.<br> + 2) raiseWindow guarantees that XRestackWindows() will <i>always</i> be + called for the specified client. +*/ +void stacking_raise(struct Client *client); + +/*! Lowers a client window below all others in its stacking layer */ +void stacking_lower(struct Client *client); + +#endif diff --git a/c/xerror.c b/c/xerror.c new file mode 100644 index 00000000..49a795f8 --- /dev/null +++ b/c/xerror.c @@ -0,0 +1,32 @@ +#include "openbox.h" +#include <glib.h> +#include <X11/Xlib.h> + +static gboolean xerror_ignore = FALSE; + +int xerror_handler(Display *d, XErrorEvent *e) +{ +#ifdef DEBUG + if (!xerror_ignore) { + char errtxt[128]; + + /*if (e->error_code != BadWindow) */ + { + XGetErrorText(d, e->error_code, errtxt, 127); + if (e->error_code == BadWindow) + g_warning("X Error: %s", errtxt); + else + g_error("X Error: %s", errtxt); + } + } +#else + (void)d; (void)e; +#endif + return 0; +} + +void xerror_set_ignore(gboolean ignore) +{ + XSync(ob_display, FALSE); + xerror_ignore = ignore; +} diff --git a/c/xerror.h b/c/xerror.h new file mode 100644 index 00000000..74b236fb --- /dev/null +++ b/c/xerror.h @@ -0,0 +1,11 @@ +#ifndef __xerror_h +#define __xerror_h + +#include <X11/Xlib.h> +#include <glib.h> + +int xerror_handler(Display *, XErrorEvent *); + +void xerror_set_ignore(gboolean ignore); + +#endif |
