From: Charles McGarvey Date: Fri, 26 Oct 2012 19:03:24 +0000 (-0600) Subject: Merge branch 'master' into chaz X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fopenbox;a=commitdiff_plain;h=9325c92056ff4268db2263cdf8c07bb529e158aa;hp=32c93f09f03bd2d7c7bf3408febbe9fa7f8dd1cc Merge branch 'master' into chaz Conflicts: openbox/config.c --- diff --git a/Makefile.am b/Makefile.am index 1f7427cd..f32591f5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -114,6 +114,7 @@ obrender_libobrender_la_SOURCES = \ ## obt ## obt_libobt_la_CPPFLAGS = \ + $(X_CFLAGS) \ $(XINERAMA_CFLAGS) \ $(XKB_CFLAGS) \ $(XRANDR_CFLAGS) \ @@ -128,6 +129,7 @@ obt_libobt_la_CPPFLAGS = \ obt_libobt_la_LDFLAGS = \ -version-info $(OBT_CURRENT):$(OBT_REVISION):$(OBT_AGE) obt_libobt_la_LIBADD = \ + $(X_LIBS) \ $(XINERAMA_LIBS) \ $(XKB_LIBS) \ $(XRANDR_LIBS) \ @@ -284,6 +286,8 @@ openbox_openbox_SOURCES = \ openbox/ping.h \ openbox/place.c \ openbox/place.h \ + openbox/place_overlap.c \ + openbox/place_overlap.h \ openbox/prompt.c \ openbox/prompt.h \ openbox/popup.c \ diff --git a/README b/README index 9c668640..ff52ea93 100644 --- a/README +++ b/README @@ -1,6 +1,6 @@ Openbox -Copyright (C) 2004-2007 Mikael Magnusson -Copyright (C) 2002-2007 Dana Jansens +Copyright (C) 2004 Mikael Magnusson +Copyright (C) 2002 Dana Jansens ---- diff --git a/bootstrap b/bootstrap index 6a5b10fb..076005cc 100755 --- a/bootstrap +++ b/bootstrap @@ -4,7 +4,7 @@ sh() { /bin/sh -c "set -x; $*" } -export WANT_AUTOMAKE=1.9 +export WANT_AUTOMAKE=1.11 sh autopoint --force || exit 1 # for GNU gettext sh libtoolize --copy --force --automake || exit 1 diff --git a/data/autostart/openbox-autostart.in b/data/autostart/openbox-autostart.in index 063c635f..5c727774 100755 --- a/data/autostart/openbox-autostart.in +++ b/data/autostart/openbox-autostart.in @@ -2,11 +2,11 @@ # Set a background color BG="" -if which hsetroot >/dev/null; then +if which hsetroot >/dev/null 2>/dev/null; then BG=hsetroot -elif which esetroot >/dev/null; then +elif which esetroot >/dev/null 2>/dev/null; then BG=esetroot -elif which xsetroot >/dev/null; then +elif which xsetroot >/dev/null 2>/dev/null; then BG=xsetroot fi test -z $BG || $BG -solid "#303030" diff --git a/data/autostart/openbox-xdg-autostart b/data/autostart/openbox-xdg-autostart index ea760281..04a17a19 100755 --- a/data/autostart/openbox-xdg-autostart +++ b/data/autostart/openbox-xdg-autostart @@ -29,7 +29,7 @@ try: from xdg.Exceptions import ParsingError except ImportError: print - print "ERROR:", ME, "requires PyXDG to be installed" + print >>sys.stderr, "ERROR:", ME, "requires PyXDG to be installed" print sys.exit(1) diff --git a/data/openbox.desktop b/data/openbox.desktop index d49ae227..631778bf 100644 --- a/data/openbox.desktop +++ b/data/openbox.desktop @@ -1,6 +1,5 @@ [Desktop Entry] Type=Application -Encoding=UTF-8 Name=Openbox Exec=openbox Icon=openbox diff --git a/data/rc.xml b/data/rc.xml index 70ad9bf9..ce1e8fc1 100644 --- a/data/rc.xml +++ b/data/rc.xml @@ -33,9 +33,6 @@ Smart -
yes
- Primary 400 + entry in parent menu if this is a negative value, then the delay is infinite and the submenu will not be hidden until a different submenu is opened --> - yes + yes yes @@ -654,6 +651,8 @@ - - + @@ -236,11 +235,18 @@ + + + + + + + @@ -396,7 +402,8 @@ - + + @@ -421,7 +428,12 @@ - + + + + + + @@ -528,7 +540,7 @@ - + diff --git a/data/xsession/openbox-gnome.desktop.in b/data/xsession/openbox-gnome.desktop.in index 19ae82e0..24931b50 100644 --- a/data/xsession/openbox-gnome.desktop.in +++ b/data/xsession/openbox-gnome.desktop.in @@ -1,8 +1,7 @@ [Desktop Entry] -Encoding=UTF-8 Name=GNOME/Openbox Comment=Use the Openbox window manager inside of the GNOME desktop environment Exec=@bindir@/openbox-gnome-session TryExec=gnome-session -Icon=openbox.png -Type=XSession +Icon=openbox +Type=Application diff --git a/data/xsession/openbox-kde.desktop.in b/data/xsession/openbox-kde.desktop.in index ddfc72d7..bc78a97c 100644 --- a/data/xsession/openbox-kde.desktop.in +++ b/data/xsession/openbox-kde.desktop.in @@ -1,8 +1,7 @@ [Desktop Entry] -Encoding=UTF-8 Name=KDE/Openbox Comment=Use the Openbox window manager inside of the K Desktop Environment Exec=@bindir@/openbox-kde-session TryExec=startkde -Icon=openbox.png -Type=XSession +Icon=openbox +Type=Application diff --git a/data/xsession/openbox.desktop.in b/data/xsession/openbox.desktop.in index 0914e5b4..21669cf8 100644 --- a/data/xsession/openbox.desktop.in +++ b/data/xsession/openbox.desktop.in @@ -1,8 +1,7 @@ [Desktop Entry] -Encoding=UTF-8 Name=Openbox Comment=Log in using the Openbox window manager (without a session manager) Exec=@bindir@/openbox-session TryExec=@bindir@/openbox-session -Icon=openbox.png -Type=XSession +Icon=openbox +Type=Application diff --git a/doc/openbox.1.in b/doc/openbox.1.in index d508e8c6..bd3747fb 100644 --- a/doc/openbox.1.in +++ b/doc/openbox.1.in @@ -58,11 +58,14 @@ to ~/.config/openbox and edit it to your liking. .PP These are the possible options that \fBopenbox\fR accepts: .IP "\fB\-\-help\fP" 10 -Show a summary of the options available. +Show a summary of the command line options available +and exit. .IP "\fB\-\-version\fP" 10 -Show the version of the program. +Show the version of the program and exit. .IP "\fB\-\-replace\fP" 10 Replace the currently running window manager. +.IP "\fB\-\-config-file FILE\fP" 10 +Specify the path to the config file to use. .IP "\fB\-\-reconfigure\fP" 10 If Openbox is already running on the display, tell it to reload its configuration. @@ -70,14 +73,20 @@ reload its configuration. If Openbox is already running on the display, tell it to restart. This is useful if you have upgraded Openbox and don't want to restart X. +.IP "\fB\-\-exit\fP" 10 +Exit Openbox. .IP "\fB\-\-sm-disable\fP" 10 Do not connect to the session manager. .IP "\fB\-\-sync\fP" 10 Run in synchronous mode (for debugging). +.IP "\fB\-\-startup CMD\fP" 10 +Run CMD after starting. .IP "\fB\-\-debug\fP" 10 Display debugging output. .IP "\fB\-\-debug-focus\fP" 10 Display debugging output for focus handling. +.IP "\fB\-\-debug-session\fP" 10 +Display debugging output for session management. .IP "\fB\-\-debug-xinerama\fP" 10 Split the display into two fake xinerama regions, if xinerama is not already enabled. This is for debugging @@ -92,4 +101,4 @@ The program's full documentation is available on the website: .PP Please report bugs to: \fBhttp://bugzilla.icculus.org/ \fP -.\" created by instant / docbook-to-man, Wed 06 Jan 2010, 13:40 +.\" created by instant / docbook-to-man, Sun 30 Sep 2012, 17:58 diff --git a/doc/openbox.1.sgml b/doc/openbox.1.sgml index 275c3246..281f2a01 100644 --- a/doc/openbox.1.sgml +++ b/doc/openbox.1.sgml @@ -106,13 +106,14 @@ manpage.1: manpage.sgml - Show a summary of the options available. + Show a summary of the command line options available + and exit. - Show the version of the program. + Show the version of the program and exit. @@ -121,6 +122,12 @@ manpage.1: manpage.sgml Replace the currently running window manager. + + + + Specify the path to the config file to use. + + @@ -136,6 +143,12 @@ manpage.1: manpage.sgml want to restart X. + + + + Exit Openbox. + + @@ -148,6 +161,12 @@ manpage.1: manpage.sgml Run in synchronous mode (for debugging). + + + + Run CMD after starting. + + @@ -160,6 +179,12 @@ manpage.1: manpage.sgml Display debugging output for focus handling. + + + + Display debugging output for session management. + + diff --git a/obrender/theme.c b/obrender/theme.c index b20182ad..d5ce8659 100644 --- a/obrender/theme.c +++ b/obrender/theme.c @@ -324,8 +324,9 @@ RrTheme* RrThemeNew(const RrInstance *inst, const gchar *name, "osd.label.text.color", theme->osd_text_active_color, RrColorCopy(theme->title_focused_color)); - READ_COLOR("osd.inactive.label.text.color", theme->osd_text_inactive_color, - RrColorNew(inst, 0xff, 0xff, 0xff)); + READ_COLOR_("osd.inactive.label.text.color", + "osd.label.text.color", + theme->osd_text_inactive_color, RrColorCopy(theme->title_unfocused_color)); READ_COLOR("window.active.button.unpressed.image.color", theme->titlebut_focused_unpressed_color, @@ -698,36 +699,36 @@ RrTheme* RrThemeNew(const RrInstance *inst, const gchar *name, /* hover */ READ_APPEARANCE_COPY("window.active.button.max.hover.bg", theme->btn_max->a_hover_focused, TRUE, - theme->btn_max->a_focused_unpressed); + a_hover_focused_tmp); READ_APPEARANCE_COPY("window.inactive.button.max.hover.bg", theme->btn_max->a_hover_unfocused, TRUE, - theme->btn_max->a_unfocused_unpressed); + a_hover_unfocused_tmp); /* toggled unpressed */ READ_APPEARANCE_("window.active.button.max.toggled.unpressed.bg", "window.active.button.max.toggled.bg", theme->btn_max->a_toggled_focused_unpressed, TRUE, - theme->btn_max->a_focused_pressed); + a_toggled_focused_unpressed_tmp); READ_APPEARANCE_("window.inactive.button.max.toggled.unpressed.bg", "window.inactive.button.max.toggled.bg", theme->btn_max->a_toggled_unfocused_unpressed, TRUE, - theme->btn_max->a_unfocused_pressed); + a_toggled_unfocused_unpressed_tmp); /* toggled pressed */ READ_APPEARANCE_COPY("window.active.button.max.toggled.pressed.bg", theme->btn_max->a_toggled_focused_pressed, TRUE, - theme->btn_max->a_focused_pressed); + a_toggled_focused_pressed_tmp); READ_APPEARANCE_COPY("window.inactive.button.max.toggled.pressed.bg", theme->btn_max->a_toggled_unfocused_pressed, TRUE, - theme->btn_max->a_unfocused_pressed); + a_toggled_unfocused_pressed_tmp); /* toggled hover */ READ_APPEARANCE_COPY("window.active.button.max.toggled.hover.bg", theme->btn_max->a_toggled_hover_focused, TRUE, - theme->btn_max->a_toggled_focused_unpressed); + a_toggled_hover_focused_tmp); READ_APPEARANCE_COPY("window.inactive.button.max.toggled.hover.bg", theme->btn_max->a_toggled_hover_unfocused, TRUE, - theme->btn_max->a_toggled_unfocused_unpressed); + a_toggled_hover_unfocused_tmp); /* close button */ read_button_colors(db, inst, theme, theme->btn_close, "close"); @@ -752,10 +753,10 @@ RrTheme* RrThemeNew(const RrInstance *inst, const gchar *name, a_disabled_unfocused_tmp); READ_APPEARANCE_COPY("window.active.button.close.hover.bg", theme->btn_close->a_hover_focused, TRUE, - theme->btn_close->a_focused_unpressed); + a_hover_focused_tmp); READ_APPEARANCE_COPY("window.inactive.button.close.hover.bg", theme->btn_close->a_hover_unfocused, TRUE, - theme->btn_close->a_unfocused_unpressed); + a_hover_unfocused_tmp); /* desk button */ read_button_colors(db, inst, theme, theme->btn_desk, "desk"); @@ -783,36 +784,36 @@ RrTheme* RrThemeNew(const RrInstance *inst, const gchar *name, /* hover */ READ_APPEARANCE_COPY("window.active.button.desk.hover.bg", theme->btn_desk->a_hover_focused, TRUE, - theme->btn_desk->a_focused_unpressed); + a_hover_focused_tmp); READ_APPEARANCE_COPY("window.inactive.button.desk.hover.bg", theme->btn_desk->a_hover_unfocused, TRUE, - theme->btn_desk->a_unfocused_unpressed); + a_hover_unfocused_tmp); /* toggled unpressed */ READ_APPEARANCE_("window.active.button.desk.toggled.unpressed.bg", "window.active.button.desk.toggled.bg", theme->btn_desk->a_toggled_focused_unpressed, TRUE, - theme->btn_desk->a_focused_pressed); + a_toggled_focused_unpressed_tmp); READ_APPEARANCE_("window.inactive.button.desk.toggled.unpressed.bg", "window.inactive.button.desk.toggled.bg", theme->btn_desk->a_toggled_unfocused_unpressed, TRUE, - theme->btn_desk->a_unfocused_pressed); + a_toggled_unfocused_unpressed_tmp); /* toggled pressed */ READ_APPEARANCE_COPY("window.active.button.desk.toggled.pressed.bg", theme->btn_desk->a_toggled_focused_pressed, TRUE, - theme->btn_desk->a_focused_pressed); + a_toggled_focused_pressed_tmp); READ_APPEARANCE_COPY("window.inactive.button.desk.toggled.pressed.bg", theme->btn_desk->a_toggled_unfocused_pressed, TRUE, - theme->btn_desk->a_unfocused_pressed); + a_toggled_unfocused_pressed_tmp); /* toggled hover */ READ_APPEARANCE_COPY("window.active.button.desk.toggled.hover.bg", theme->btn_desk->a_toggled_hover_focused, TRUE, - theme->btn_desk->a_toggled_focused_unpressed); + a_toggled_hover_focused_tmp); READ_APPEARANCE_COPY("window.inactive.button.desk.toggled.hover.bg", theme->btn_desk->a_toggled_hover_unfocused, TRUE, - theme->btn_desk->a_toggled_unfocused_unpressed); + a_toggled_hover_unfocused_tmp); /* shade button */ read_button_colors(db, inst, theme, theme->btn_shade, "shade"); @@ -840,36 +841,36 @@ RrTheme* RrThemeNew(const RrInstance *inst, const gchar *name, /* hover */ READ_APPEARANCE_COPY("window.active.button.shade.hover.bg", theme->btn_shade->a_hover_focused, TRUE, - theme->btn_shade->a_focused_unpressed); + a_hover_focused_tmp); READ_APPEARANCE_COPY("window.inactive.button.shade.hover.bg", theme->btn_shade->a_hover_unfocused, TRUE, - theme->btn_shade->a_unfocused_unpressed); + a_hover_unfocused_tmp); /* toggled unpressed */ READ_APPEARANCE_("window.active.button.shade.toggled.unpressed.bg", "window.active.button.shade.toggled.bg", theme->btn_shade->a_toggled_focused_unpressed, TRUE, - theme->btn_shade->a_focused_pressed); + a_toggled_focused_unpressed_tmp); READ_APPEARANCE_("window.inactive.button.shade.toggled.unpressed.bg", "window.inactive.button.shade.toggled.bg", theme->btn_shade->a_toggled_unfocused_unpressed, TRUE, - theme->btn_shade->a_unfocused_pressed); + a_toggled_unfocused_unpressed_tmp); /* toggled pressed */ READ_APPEARANCE_COPY("window.active.button.shade.toggled.pressed.bg", theme->btn_shade->a_toggled_focused_pressed, TRUE, - theme->btn_shade->a_focused_pressed); + a_toggled_focused_pressed_tmp); READ_APPEARANCE_COPY("window.inactive.button.shade.toggled.pressed.bg", theme->btn_shade->a_toggled_unfocused_pressed, TRUE, - theme->btn_shade->a_unfocused_pressed); + a_toggled_unfocused_pressed_tmp); /* toggled hover */ READ_APPEARANCE_COPY("window.active.button.shade.toggled.hover.bg", theme->btn_shade->a_toggled_hover_focused, TRUE, - theme->btn_shade->a_toggled_focused_unpressed); + a_toggled_hover_focused_tmp); READ_APPEARANCE_COPY("window.inactive.button.shade.toggled.hover.bg", theme->btn_shade->a_toggled_hover_unfocused, TRUE, - theme->btn_shade->a_toggled_unfocused_unpressed); + a_toggled_hover_unfocused_tmp); /* iconify button */ read_button_colors(db, inst, theme, theme->btn_iconify, "iconify"); @@ -894,10 +895,10 @@ RrTheme* RrThemeNew(const RrInstance *inst, const gchar *name, a_disabled_unfocused_tmp); READ_APPEARANCE_COPY("window.active.button.iconify.hover.bg", theme->btn_iconify->a_hover_focused, TRUE, - theme->btn_iconify->a_focused_unpressed); + a_hover_focused_tmp); READ_APPEARANCE_COPY("window.inactive.button.iconify.hover.bg", theme->btn_iconify->a_hover_unfocused, TRUE, - theme->btn_iconify->a_unfocused_unpressed); + a_hover_unfocused_tmp); /* osd buttons */ READ_APPEARANCE_COPY("osd.button.unpressed.bg", theme->osd_unpressed_button, TRUE, a_focused_unpressed_tmp); diff --git a/obt/bsearch.h b/obt/bsearch.h index 9613c51b..60da51d3 100644 --- a/obt/bsearch.h +++ b/obt/bsearch.h @@ -23,13 +23,22 @@ G_BEGIN_DECLS -/*! Setup to do a binary search on an array holding elements of type @t */ -#define BSEARCH_SETUP(t) \ - register t l_BSEARCH, r_BSEARCH, out_BSEARCH; +/*! Setup to do a binary search on an array. */ +#define BSEARCH_SETUP() \ + register guint l_BSEARCH, r_BSEARCH, out_BSEARCH; + +/*! Helper macro that just returns the input */ +#define BSEARCH_IS_T(t) (t) /*! Search an array @ar holding elements of type @t, starting at index @start, with @size elements, looking for value @val. */ -#define BSEARCH(t, ar, start, size, val) \ +#define BSEARCH(t, ar, start, size, val) \ + BSEARCH_CMP(t, ar, start, size, val, BSEARCH_IS_T) + +/*! Search an array @ar, starting at index @start, + with @size elements, looking for value @val of type @t, + using the macro @to_t to convert values in the arrau @ar to type @t.*/ +#define BSEARCH_CMP(t, ar, start, size, val, to_t) \ { \ l_BSEARCH = (start); \ r_BSEARCH = (start)+(size)-1; \ @@ -37,10 +46,10 @@ G_BEGIN_DECLS /* m is in the middle, but to the left if there's an even number \ of elements */ \ out_BSEARCH = l_BSEARCH + (r_BSEARCH - l_BSEARCH)/2; \ - if ((val) == (ar)[out_BSEARCH]) { \ + if ((val) == to_t((ar)[out_BSEARCH])) { \ break; \ } \ - else if ((val) < (ar)[out_BSEARCH] && out_BSEARCH > 0) { \ + else if ((val) < to_t((ar)[out_BSEARCH]) && out_BSEARCH > 0) { \ r_BSEARCH = out_BSEARCH-1; /* search to the left side */ \ } \ else \ diff --git a/obt/keyboard.c b/obt/keyboard.c index ef2678b5..4e84f481 100644 --- a/obt/keyboard.c +++ b/obt/keyboard.c @@ -49,7 +49,7 @@ void obt_keyboard_context_renew(ObtIC *ic); static XModifierKeymap *modmap; static KeySym *keymap; static gint min_keycode, max_keycode, keysyms_per_keycode; -/* This is a bitmask of the different masks for each modifier key */ +/*! This is a bitmask of the different masks for each modifier key */ static guchar modkeys_keys[OBT_KEYBOARD_NUM_MODKEYS]; static gboolean alt_l = FALSE; @@ -190,40 +190,20 @@ void xim_init(void) g_free(aname); } -ObtModkeysKey obt_keyboard_keyevent_to_modkey(XEvent *e) +guint obt_keyboard_keyevent_to_modmask(XEvent *e) { - KeySym sym; + gint i, masknum; g_return_val_if_fail(e->type == KeyPress || e->type == KeyRelease, OBT_KEYBOARD_MODKEY_NONE); - XLookupString(&e->xkey, NULL, 0, &sym, NULL); - - switch (sym) { - case XK_Num_Lock: return OBT_KEYBOARD_MODKEY_NUMLOCK; - case XK_Scroll_Lock: return OBT_KEYBOARD_MODKEY_SCROLLLOCK; - case XK_Caps_Lock: return OBT_KEYBOARD_MODKEY_SHIFT; - case XK_Alt_L: - case XK_Alt_R: return OBT_KEYBOARD_MODKEY_ALT; - case XK_Super_L: - case XK_Super_R: return OBT_KEYBOARD_MODKEY_SUPER; - case XK_Hyper_L: - case XK_Hyper_R: return OBT_KEYBOARD_MODKEY_HYPER; - case XK_Meta_L: - case XK_Meta_R: return OBT_KEYBOARD_MODKEY_META; - case XK_Control_L: - case XK_Control_R: return OBT_KEYBOARD_MODKEY_CONTROL; - case XK_Shift_L: - case XK_Shift_R: return OBT_KEYBOARD_MODKEY_SHIFT; - default: return OBT_KEYBOARD_MODKEY_NONE; - } -} - -guint obt_keyboard_keyevent_to_modmask(XEvent *e) -{ - g_return_val_if_fail(e->type == KeyPress || e->type == KeyRelease, 0); - - return obt_keyboard_modkey_to_modmask(obt_keyboard_keyevent_to_modkey(e)); + for (masknum = 0; masknum < NUM_MASKS; ++masknum) + for (i = 0; i < modmap->max_keypermod; ++i) { + KeyCode c = modmap->modifiermap[masknum*modmap->max_keypermod + i]; + if (c == e->xkey.keycode) + return 1<exec_dirs; it; it = g_slist_next(it)) { - gchar *f = g_strdup_printf(it->data, G_DIR_SEPARATOR_S, path); + gchar *f = g_build_filename(it->data, path, NULL); gboolean e = try_exec(p, f); g_free(f); if (e) return TRUE; diff --git a/obt/prop.c b/obt/prop.c index 638373fd..f7919d6c 100644 --- a/obt/prop.c +++ b/obt/prop.c @@ -197,6 +197,8 @@ void obt_prop_startup(void) CREATE_(OB_APP_TITLE); CREATE_(OB_APP_NAME); CREATE_(OB_APP_CLASS); + CREATE_(OB_APP_GROUP_NAME); + CREATE_(OB_APP_GROUP_CLASS); CREATE_(OB_APP_TYPE); } diff --git a/obt/prop.h b/obt/prop.h index b30232e9..acb5c956 100644 --- a/obt/prop.h +++ b/obt/prop.h @@ -219,6 +219,8 @@ typedef enum { OBT_PROP_OB_APP_TITLE, OBT_PROP_OB_APP_NAME, OBT_PROP_OB_APP_CLASS, + OBT_PROP_OB_APP_GROUP_NAME, + OBT_PROP_OB_APP_GROUP_CLASS, OBT_PROP_OB_APP_TYPE, OBT_PROP_NUM_ATOMS diff --git a/obt/xml.c b/obt/xml.c index c8729129..5b7e77b5 100644 --- a/obt/xml.c +++ b/obt/xml.c @@ -112,6 +112,11 @@ void obt_xml_register(ObtXmlInst *i, const gchar *tag, g_hash_table_insert(i->callbacks, c->tag, c); } +void obt_xml_unregister(ObtXmlInst *i, const gchar *tag) +{ + g_hash_table_remove(i->callbacks, tag); +} + static gboolean load_file(ObtXmlInst *i, const gchar *domain, const gchar *filename, @@ -316,16 +321,22 @@ void obt_xml_tree_from_root(ObtXmlInst *i) obt_xml_tree(i, i->root->children); } -gchar *obt_xml_node_string(xmlNodePtr node) +gchar *obt_xml_node_string_unstripped(xmlNodePtr node) { xmlChar *c = xmlNodeGetContent(node); gchar *s; - if (c) g_strstrip((char*)c); /* strip leading/trailing whitespace */ s = g_strdup(c ? (gchar*)c : ""); xmlFree(c); return s; } +gchar *obt_xml_node_string(xmlNodePtr node) +{ + gchar* result = obt_xml_node_string_unstripped(node); + g_strstrip(result); /* strip leading/trailing whitespace */ + return result; +} + gint obt_xml_node_int(xmlNodePtr node) { xmlChar *c = xmlNodeGetContent(node); @@ -408,13 +419,12 @@ gboolean obt_xml_attr_int(xmlNodePtr node, const gchar *name, gint *value) return r; } -gboolean obt_xml_attr_string(xmlNodePtr node, const gchar *name, - gchar **value) +gboolean obt_xml_attr_string_unstripped(xmlNodePtr node, const gchar *name, + gchar **value) { xmlChar *c = xmlGetProp(node, (const xmlChar*) name); gboolean r = FALSE; if (c) { - g_strstrip((char*)c); /* strip leading/trailing whitespace */ *value = g_strdup((gchar*)c); r = TRUE; } @@ -422,6 +432,15 @@ gboolean obt_xml_attr_string(xmlNodePtr node, const gchar *name, return r; } +gboolean obt_xml_attr_string(xmlNodePtr node, const gchar *name, + gchar **value) +{ + gboolean result = obt_xml_attr_string_unstripped(node, name, value); + if (result) + g_strstrip(*value); /* strip leading/trailing whitespace */ + return result; +} + gboolean obt_xml_attr_contains(xmlNodePtr node, const gchar *name, const gchar *val) { diff --git a/obt/xml.h b/obt/xml.h index ac2dc57f..831aba63 100644 --- a/obt/xml.h +++ b/obt/xml.h @@ -62,6 +62,7 @@ void obt_xml_close(ObtXmlInst *inst); void obt_xml_register(ObtXmlInst *inst, const gchar *tag, ObtXmlCallback func, gpointer data); +void obt_xml_unregister(ObtXmlInst *inst, const gchar *tag); void obt_xml_tree(ObtXmlInst *i, xmlNodePtr node); void obt_xml_tree_from_root(ObtXmlInst *i); @@ -71,12 +72,15 @@ void obt_xml_tree_from_root(ObtXmlInst *i); xmlNodePtr obt_xml_find_node (xmlNodePtr node, const gchar *name); gboolean obt_xml_node_contains (xmlNodePtr node, const gchar *val); +gchar *obt_xml_node_string_unstripped(xmlNodePtr node); gchar *obt_xml_node_string (xmlNodePtr node); gint obt_xml_node_int (xmlNodePtr node); gboolean obt_xml_node_bool (xmlNodePtr node); gboolean obt_xml_attr_contains (xmlNodePtr node, const gchar *name, const gchar *val); +gboolean obt_xml_attr_string_unstripped(xmlNodePtr node, const gchar *name, + gchar **value); gboolean obt_xml_attr_string (xmlNodePtr node, const gchar *name, gchar **value); gboolean obt_xml_attr_int (xmlNodePtr node, const gchar *name, diff --git a/openbox/actions.c b/openbox/actions.c index ee9d55f1..ac849a97 100644 --- a/openbox/actions.c +++ b/openbox/actions.c @@ -51,6 +51,7 @@ struct _ObActionsDefinition { ObActionsDataFreeFunc free; ObActionsRunFunc run; ObActionsShutdownFunc shutdown; + gboolean modifies_focused_window; }; struct _ObActionsAct { @@ -103,12 +104,13 @@ ObActionsDefinition* do_register(const gchar *name, return NULL; } - def = g_slice_new(ObActionsDefinition); + def = g_slice_new0(ObActionsDefinition); def->ref = 1; def->name = g_strdup(name); def->free = free; def->run = run; def->shutdown = NULL; + def->modifies_focused_window = TRUE; registered = g_slist_prepend(registered, def); return def; @@ -156,6 +158,22 @@ gboolean actions_set_shutdown(const gchar *name, return FALSE; } +gboolean actions_set_modifies_focused_window(const gchar *name, + gboolean modifies) +{ + GSList *it; + ObActionsDefinition *def; + + for (it = registered; it; it = g_slist_next(it)) { + def = it->data; + if (!g_ascii_strcasecmp(name, def->name)) { + def->modifies_focused_window = modifies; + return TRUE; + } + } + return FALSE; +} + static void actions_definition_ref(ObActionsDefinition *def) { ++def->ref; @@ -340,8 +358,11 @@ void actions_run_acts(GSList *acts, if (!act->def->run(&data, act->options)) { if (actions_act_is_interactive(act)) actions_interactive_end_act(); - if (client && client == focus_client) + if (client && client == focus_client && + act->def->modifies_focused_window) + { update_user_time = TRUE; + } } else { /* make sure its interactive if it returned TRUE */ g_assert(act->i_input); @@ -375,7 +396,7 @@ static gboolean actions_interactive_begin_act(ObActionsAct *act, guint state) interactive_act = act; actions_act_ref(interactive_act); - interactive_initial_state = obt_keyboard_only_modmasks(state); + interactive_initial_state = state; /* if using focus_delay, stop the timer now so that focus doesn't go moving on us, which would kill the action */ @@ -453,7 +474,7 @@ void actions_client_move(ObActionsData *data, gboolean start) ob_debug_type(OB_DEBUG_FOCUS, "Generating fake leave because we did a " "mouse-event action"); - event_enter_client(data->client); + event_leave_client(data->client); } } } diff --git a/openbox/actions.h b/openbox/actions.h index e03bc577..f413ad82 100644 --- a/openbox/actions.h +++ b/openbox/actions.h @@ -82,6 +82,8 @@ gboolean actions_register(const gchar *name, gboolean actions_set_shutdown(const gchar *name, ObActionsShutdownFunc shutdown); +gboolean actions_set_modifies_focused_window(const gchar *name, + gboolean modifies); ObActionsAct* actions_parse(xmlNodePtr node); ObActionsAct* actions_parse_string(const gchar *name); diff --git a/openbox/actions/cyclewindows.c b/openbox/actions/cyclewindows.c index 5f0db27c..f8349515 100644 --- a/openbox/actions/cyclewindows.c +++ b/openbox/actions/cyclewindows.c @@ -16,6 +16,7 @@ typedef struct { gboolean forward; gboolean bar; gboolean raise; + gboolean interactive; ObFocusCyclePopupMode dialog_mode; GSList *actions; @@ -69,6 +70,7 @@ static gpointer setup_func(xmlNodePtr node, o = g_slice_new0(Options); o->bar = TRUE; o->dialog_mode = OB_FOCUS_CYCLE_POPUP_MODE_LIST; + o->interactive = TRUE; if ((n = obt_xml_find_node(node, "linear"))) o->linear = obt_xml_node_bool(n); @@ -80,6 +82,8 @@ static gpointer setup_func(xmlNodePtr node, else if (obt_xml_node_contains(n, "icons")) o->dialog_mode = OB_FOCUS_CYCLE_POPUP_MODE_ICONS; } + if ((n = obt_xml_find_node(node, "interactive"))) + o->interactive = obt_xml_node_bool(n); if ((n = obt_xml_find_node(node, "bar"))) o->bar = obt_xml_node_bool(n); if ((n = obt_xml_find_node(node, "raise"))) @@ -157,21 +161,24 @@ static gboolean run_func(ObActionsData *data, gpointer options) Options *o = options; struct _ObClient *ft; - ft = focus_cycle(o->forward, - o->all_desktops, - !o->only_hilite_windows, - o->dock_windows, - o->desktop_windows, - o->linear, - TRUE, - o->bar, - o->dialog_mode, - FALSE, FALSE); + gboolean done = FALSE; + gboolean cancel = FALSE; + + ft = focus_cycle( + o->forward, + o->all_desktops, + !o->only_hilite_windows, + o->dock_windows, + o->desktop_windows, + o->linear, + (o->interactive ? o->bar : FALSE), + (o->interactive ? o->dialog_mode : OB_FOCUS_CYCLE_POPUP_MODE_NONE), + done, cancel); stacking_restore(); if (o->raise && ft) stacking_temp_raise(CLIENT_AS_WINDOW(ft)); - return TRUE; + return o->interactive; } static gboolean i_input_func(guint initial_state, @@ -181,8 +188,9 @@ static gboolean i_input_func(guint initial_state, gboolean *used) { Options *o = options; - guint mods; + guint mods, initial_mods; + initial_mods = obt_keyboard_only_modmasks(initial_state); mods = obt_keyboard_only_modmasks(e->xkey.state); if (e->type == KeyRelease) { /* remove from the state the mask of the modifier key being @@ -201,14 +209,14 @@ static gboolean i_input_func(guint initial_state, } /* There were no modifiers and they pressed enter */ - else if ((sym == XK_Return || sym == XK_KP_Enter) && !initial_state) { + else if ((sym == XK_Return || sym == XK_KP_Enter) && !initial_mods) { o->cancel = FALSE; o->state = e->xkey.state; return FALSE; } } /* They released the modifiers */ - else if (e->type == KeyRelease && initial_state && !(mods & initial_state)) + else if (e->type == KeyRelease && initial_mods && !(mods & initial_mods)) { o->cancel = FALSE; o->state = e->xkey.state; @@ -230,16 +238,17 @@ static void i_post_func(gpointer options) Options *o = options; struct _ObClient *ft; + gboolean done = TRUE; + ft = focus_cycle(o->forward, o->all_desktops, !o->only_hilite_windows, o->dock_windows, o->desktop_windows, o->linear, - TRUE, o->bar, o->dialog_mode, - TRUE, o->cancel); + done, o->cancel); if (ft) actions_run_acts(o->actions, OB_USER_ACTION_KEYBOARD_KEY, diff --git a/openbox/actions/desktop.c b/openbox/actions/desktop.c index a3a1f6b9..8dadf550 100644 --- a/openbox/actions/desktop.c +++ b/openbox/actions/desktop.c @@ -319,8 +319,9 @@ static gboolean i_input_func(guint initial_state, gpointer options, gboolean *used) { - guint mods; + guint mods, initial_mods; + initial_mods = obt_keyboard_only_modmasks(initial_state); mods = obt_keyboard_only_modmasks(e->xkey.state); if (e->type == KeyRelease) { /* remove from the state the mask of the modifier key being @@ -336,11 +337,11 @@ static gboolean i_input_func(guint initial_state, return FALSE; /* There were no modifiers and they pressed enter */ - else if ((sym == XK_Return || sym == XK_KP_Enter) && !initial_state) + else if ((sym == XK_Return || sym == XK_KP_Enter) && !initial_mods) return FALSE; } /* They released the modifiers */ - else if (e->type == KeyRelease && initial_state && !(mods & initial_state)) + else if (e->type == KeyRelease && initial_mods && !(mods & initial_mods)) { return FALSE; } @@ -350,7 +351,8 @@ static gboolean i_input_func(guint initial_state, static gboolean i_pre_func(guint initial_state, gpointer options) { - if (!initial_state) { + guint initial_mods = obt_keyboard_only_modmasks(initial_state); + if (!initial_mods) { Options *o = options; o->interactive = FALSE; return FALSE; diff --git a/openbox/actions/directionalwindows.c b/openbox/actions/directionalwindows.c index 602e7edc..0ad464b7 100644 --- a/openbox/actions/directionalwindows.c +++ b/openbox/actions/directionalwindows.c @@ -259,8 +259,9 @@ static gboolean i_input_func(guint initial_state, gpointer options, gboolean *used) { - guint mods; + guint mods, initial_mods; + initial_mods = obt_keyboard_only_modmasks(initial_state); mods = obt_keyboard_only_modmasks(e->xkey.state); if (e->type == KeyRelease) { /* remove from the state the mask of the modifier key being @@ -278,14 +279,13 @@ static gboolean i_input_func(guint initial_state, } /* There were no modifiers and they pressed enter */ - else if ((sym == XK_Return || sym == XK_KP_Enter) && !initial_state) { + else if ((sym == XK_Return || sym == XK_KP_Enter) && !initial_mods) { end_cycle(FALSE, e->xkey.state, options); return FALSE; } } /* They released the modifiers */ - else if (e->type == KeyRelease && initial_state && !(mods & initial_state)) - { + else if (e->type == KeyRelease && initial_mods && !(mods & initial_mods)) { end_cycle(FALSE, e->xkey.state, options); return FALSE; } diff --git a/openbox/actions/execute.c b/openbox/actions/execute.c index 380ffa00..2f76c45d 100644 --- a/openbox/actions/execute.c +++ b/openbox/actions/execute.c @@ -33,6 +33,7 @@ void action_execute_startup(void) { actions_register("Execute", setup_func, free_func, run_func); actions_set_shutdown("Execute", shutdown_func); + actions_set_modifies_focused_window("Execute", FALSE); client_add_destroy_notify(client_dest, NULL); } @@ -130,6 +131,103 @@ static void prompt_cleanup(ObPrompt *p, gpointer options) free_func(options); } +/* Replace occurrences of $variables */ +static gchar* expand_variables(gchar* cmd, ObActionsData* data) +{ + gchar *c, *before, *expand; + + expand = NULL; + before = cmd; + + while ((c = strchr(before, '$'))) { + if ((c[1] == 'p' || c[1] == 'P') && + (c[2] == 'i' || c[2] == 'I') && + (c[3] == 'd' || c[3] == 'D') && + !g_ascii_isalnum(c[4])) + { + /* found $pid */ + gchar *tmp; + + *c = '\0'; + tmp = expand; + expand = g_strdup_printf("%s%s%u", + (expand ? expand : ""), + before, + data->client ? data->client->pid : 0); + g_free(tmp); + + before = c + 4; /* 4 = strlen("$pid") */ + } + else if ((c[1] == 'w' || c[1] == 'W') && + (c[2] == 'i' || c[2] == 'I') && + (c[3] == 'd' || c[3] == 'D') && + !g_ascii_isalnum(c[4])) + { + /* found $wid */ + gchar *tmp; + + *c = '\0'; + tmp = expand; + expand = g_strdup_printf("%s%s%lu", + (expand ? expand : ""), + before, + data->client ? data->client->window : 0); + g_free(tmp); + + before = c + 4; /* 4 = strlen("$wid") */ + } + else if ((c[1] == 'p' || c[1] == 'P') && + (c[2] == 'o' || c[2] == 'O') && + (c[3] == 'i' || c[3] == 'I') && + (c[4] == 'n' || c[4] == 'N') && + (c[5] == 't' || c[5] == 'T') && + (c[6] == 'e' || c[6] == 'E') && + (c[7] == 'r' || c[7] == 'R') && + !g_ascii_isalnum(c[8])) + { + /* found $pointer */ + gchar *tmp; + + *c = '\0'; + tmp = expand; + expand = g_strdup_printf("%s%s%u %u", + (expand ? expand : ""), + before, + data->x, data->y); + g_free(tmp); + + before = c + 8; /* 4 = strlen("$pointer") */ + } + else { + /* found something unknown, copy the $ and continue */ + gchar *tmp; + + *c = '\0'; + tmp = expand; + expand = g_strdup_printf("%s%s$", + (expand ? expand : ""), + before); + g_free(tmp); + + before = c + 1; /* 1 = strlen("$") */ + } + } + + if (expand) { + gchar *tmp; + + /* add on the end of the string after the last replacement */ + tmp = expand; + expand = g_strconcat(expand, before, NULL); + g_free(tmp); + + /* replace the command with the expanded one */ + g_free(cmd); + cmd = expand; + } + return cmd; +} + /* Always return FALSE because its not interactive */ static gboolean run_func(ObActionsData *data, gpointer options) { @@ -162,68 +260,7 @@ static gboolean run_func(ObActionsData *data, gpointer options) return FALSE; } - if (data->client) { - gchar *c, *before, *expand; - - /* replace occurrences of $pid and $wid */ - - expand = NULL; - before = cmd; - - while ((c = strchr(before, '$'))) { - if ((c[1] == 'p' || c[1] == 'P') && - (c[2] == 'i' || c[2] == 'I') && - (c[3] == 'd' || c[3] == 'D') && - !g_ascii_isalnum(c[4])) - { - /* found $pid */ - gchar *tmp; - - *c = '\0'; - tmp = expand; - expand = g_strdup_printf("%s%s%u", - (expand ? expand : ""), - before, - data->client->pid); - g_free(tmp); - - before = c + 4; /* 4 = strlen("$pid") */ - } - else if ((c[1] == 'w' || c[1] == 'W') && - (c[2] == 'i' || c[2] == 'I') && - (c[3] == 'd' || c[3] == 'D') && - !g_ascii_isalnum(c[4])) - { - /* found $wid */ - gchar *tmp; - - *c = '\0'; - tmp = expand; - expand = g_strdup_printf("%s%s%lu", - (expand ? expand : ""), - before, - data->client->window); - g_free(tmp); - - before = c + 4; /* 4 = strlen("$wid") */ - } - else - before = c + 1; /* no infinite loops plz */ - } - - if (expand) { - gchar *tmp; - - /* add on the end of the string after the last replacement */ - tmp = expand; - expand = g_strconcat(expand, before, NULL); - g_free(tmp); - - /* replace the command with the expanded one */ - g_free(cmd); - cmd = expand; - } - } + cmd = expand_variables(cmd, data); /* If there is a keyboard grab going on then we need to cancel it so the application can grab things */ diff --git a/openbox/actions/moveresizeto.c b/openbox/actions/moveresizeto.c index c23661cd..4b3f2699 100644 --- a/openbox/actions/moveresizeto.c +++ b/openbox/actions/moveresizeto.c @@ -19,6 +19,8 @@ typedef struct { gint h; gint h_denom; gint monitor; + gboolean w_sets_client_size; + gboolean h_sets_client_size; } Options; static gpointer setup_func(xmlNodePtr node); @@ -57,12 +59,16 @@ static gpointer setup_func(xmlNodePtr node) if (g_ascii_strcasecmp(s, "current") != 0) config_parse_relative_number(s, &o->w, &o->w_denom); g_free(s); + + obt_xml_attr_bool(n, "client", &o->w_sets_client_size); } if ((n = obt_xml_find_node(node, "height"))) { gchar *s = obt_xml_node_string(n); if (g_ascii_strcasecmp(s, "current") != 0) config_parse_relative_number(s, &o->h, &o->h_denom); g_free(s); + + obt_xml_attr_bool(n, "client", &o->h_sets_client_size); } if ((n = obt_xml_find_node(node, "monitor"))) { @@ -118,14 +124,31 @@ static gboolean run_func(ObActionsData *data, gpointer options) area = screen_area(c->desktop, mon, NULL); carea = screen_area(c->desktop, cmon, NULL); + /* find a target size for the client/frame. */ w = o->w; - if (w == G_MININT) w = c->area.width; + if (w == G_MININT) { + if (o->w_sets_client_size) + w = c->area.width; + else + w = c->frame->area.width; + } else if (o->w_denom) w = (w * area->width) / o->w_denom; h = o->h; - if (h == G_MININT) h = c->area.height; + if (h == G_MININT) { + if (o->w_sets_client_size) + h = c->area.height; + else + h = c->frame->area.height; + } else if (o->h_denom) h = (h * area->height) / o->h_denom; + /* get back to the client's size. */ + if (!o->w_sets_client_size) + w -= c->frame->size.left + c->frame->size.right; + if (!o->h_sets_client_size) + h -= c->frame->size.top + c->frame->size.bottom; + /* it might not be able to resize how they requested, so find out what it will actually be resized to */ x = c->area.x; diff --git a/openbox/actions/resizerelative.c b/openbox/actions/resizerelative.c index e32aff32..a4739104 100644 --- a/openbox/actions/resizerelative.c +++ b/openbox/actions/resizerelative.c @@ -70,24 +70,34 @@ static gboolean run_func(ObActionsData *data, gpointer options) gint left = o->left, right = o->right, top = o->top, bottom = o->bottom; if (o->left_denom) - left = (left * c->area.width / c->size_inc.width) / o->left_denom; + left = left * c->area.width / o->left_denom; if (o->right_denom) - right = (right * c->area.width / c->size_inc.width) / o->right_denom; + right = right * c->area.width / o->right_denom; if (o->top_denom) - top = (top * c->area.height / c->size_inc.height) / o->top_denom; + top = top * c->area.height / o->top_denom; if (o->bottom_denom) - bottom = (bottom * c->area.height / c->size_inc.height) / o->bottom_denom; - + bottom = bottom * c->area.height / o->bottom_denom; + + if (left && ABS(left) < c->size_inc.width) + left = left < 0 ? -c->size_inc.width : c->size_inc.width; + if (right && ABS(right) < c->size_inc.width) + right = right < 0 ? -c->size_inc.width : c->size_inc.width; + if (top && ABS(top) < c->size_inc.height) + top = top < 0 ? -c->size_inc.height : c->size_inc.height; + if (bottom && ABS(bottom) < c->size_inc.height) + bottom = bottom < 0 ? -c->size_inc.height : c->size_inc.height; + + // When resizing, if the resize has a non-zero value then make sure it + // is at least as big as the size increment so the window does actually + // resize. x = c->area.x; y = c->area.y; ow = c->area.width; - xoff = -left * c->size_inc.width; - nw = ow + right * c->size_inc.width - + left * c->size_inc.width; + xoff = -left; + nw = ow + right + left; oh = c->area.height; - yoff = -top * c->size_inc.height; - nh = oh + bottom * c->size_inc.height - + top * c->size_inc.height; + yoff = -top; + nh = oh + bottom + top; client_try_configure(c, &x, &y, &nw, &nh, &lw, &lh, TRUE); xoff = xoff == 0 ? 0 : diff --git a/openbox/client.c b/openbox/client.c index 46162c31..1478d840 100644 --- a/openbox/client.c +++ b/openbox/client.c @@ -167,6 +167,21 @@ void client_remove_destroy_notify(ObClientCallback func) } } +void client_remove_destroy_notify_data(ObClientCallback func, gpointer data) +{ + GSList *it; + + for (it = client_destroy_notifies; it; it = g_slist_next(it)) { + ClientCallback *d = it->data; + if (d->func == func && d->data == data) { + g_slice_free(ClientCallback, d); + client_destroy_notifies = + g_slist_delete_link(client_destroy_notifies, it); + break; + } + } +} + void client_set_list(void) { Window *windows, *win_it; @@ -203,6 +218,7 @@ void client_manage(Window window, ObPrompt *prompt) Time launch_time; guint32 user_time; gboolean obplaced; + gulong ignore_start; ob_debug("Managing window: 0x%lx", window); @@ -234,6 +250,8 @@ void client_manage(Window window, ObPrompt *prompt) ob_debug("Window group: 0x%x", self->group?self->group->leader:0); ob_debug("Window name: %s class: %s role: %s title: %s", self->name, self->class, self->role, self->title); + ob_debug("Window group name: %s group class: %s", + self->group_name, self->group_class); /* per-app settings override stuff from client_get_all, and return the settings for other uses too. the returned settings is a shallow copy, @@ -352,8 +370,7 @@ void client_manage(Window window, ObPrompt *prompt) "program + user specified" : "BADNESS !?")))), place.width, place.height); - obplaced = place_client(self, do_activate, &place.x, &place.y, - settings); + obplaced = place_client(self, do_activate, &place, settings); /* watch for buggy apps that ask to be placed at (0,0) when there is a strut there */ @@ -468,19 +485,13 @@ void client_manage(Window window, ObPrompt *prompt) /* grab mouse bindings before showing the window */ mouse_grab_for_client(self, TRUE); + if (!config_focus_under_mouse) + ignore_start = event_start_ignore_all_enters(); + /* this has to happen before we try focus the window, but we want it to happen after the client's stacking has been determined or it looks bad */ - { - gulong ignore_start; - if (!config_focus_under_mouse) - ignore_start = event_start_ignore_all_enters(); - - client_show(self); - - if (!config_focus_under_mouse) - event_end_ignore_all_enters(ignore_start); - } + client_show(self); /* activate/hilight/raise the window */ if (try_activate) { @@ -508,6 +519,9 @@ void client_manage(Window window, ObPrompt *prompt) stacking_raise(CLIENT_AS_WINDOW(self)); } + if (!config_focus_under_mouse) + event_end_ignore_all_enters(ignore_start); + /* add to client list/map */ client_list = g_list_append(client_list, self); window_add(&self->window, CLIENT_AS_WINDOW(self)); @@ -710,6 +724,8 @@ void client_unmanage(ObClient *self) g_free(self->name); g_free(self->class); g_free(self->role); + g_free(self->group_name); + g_free(self->group_class); g_free(self->client_machine); g_free(self->sm_client_id); g_slice_free(ObClient, self); @@ -902,15 +918,25 @@ static ObAppSettings *client_get_settings_state(ObClient *self) g_assert(app->name != NULL || app->class != NULL || app->role != NULL || app->title != NULL || + app->group_name != NULL || app->group_class != NULL || (signed)app->type >= 0); if (app->name && !g_pattern_match(app->name, strlen(self->name), self->name, NULL)) match = FALSE; + else if (app->group_name && + !g_pattern_match(app->group_name, + strlen(self->group_name), self->group_name, NULL)) + match = FALSE; else if (app->class && !g_pattern_match(app->class, strlen(self->class), self->class, NULL)) match = FALSE; + else if (app->group_class && + !g_pattern_match(app->group_class, + strlen(self->group_class), self->group_class, + NULL)) + match = FALSE; else if (app->role && !g_pattern_match(app->role, strlen(self->role), self->role, NULL)) @@ -1202,13 +1228,15 @@ static void client_get_all(ObClient *self, gboolean real) from per-app settings */ client_get_session_ids(self); - /* now we got everything that can affect the decorations */ + /* get this early so we have it for debugging, also this can be used + by app rule matching */ + client_update_title(self); + + /* now we got everything that can affect the decorations or app rule + matching */ if (!real) return; - /* get this early so we have it for debugging */ - client_update_title(self); - /* save the values of the variables used for app rule matching */ client_save_app_rule_values(self); @@ -2357,6 +2385,25 @@ static void client_get_session_ids(ObClient *self) if (self->name == NULL) self->name = g_strdup(""); if (self->class == NULL) self->class = g_strdup(""); + /* get the WM_CLASS (name and class) from the group leader. make them "" if + they are not provided */ + if (leader) + got = OBT_PROP_GETSS_TYPE(leader, WM_CLASS, STRING_NO_CC, &ss); + else + got = FALSE; + + if (got) { + if (ss[0]) { + self->group_name = g_strdup(ss[0]); + if (ss[1]) + self->group_class = g_strdup(ss[1]); + } + g_strfreev(ss); + } + + if (self->group_name == NULL) self->group_name = g_strdup(""); + if (self->group_class == NULL) self->group_class = g_strdup(""); + /* get the WM_WINDOW_ROLE. make it "" if it is not provided */ got = OBT_PROP_GETS_XPCS(self->window, WM_WINDOW_ROLE, &s); @@ -2426,6 +2473,8 @@ static void client_save_app_rule_values(ObClient *self) OBT_PROP_SETS(self->window, OB_APP_ROLE, self->role); OBT_PROP_SETS(self->window, OB_APP_NAME, self->name); OBT_PROP_SETS(self->window, OB_APP_CLASS, self->class); + OBT_PROP_SETS(self->window, OB_APP_GROUP_NAME, self->group_name); + OBT_PROP_SETS(self->window, OB_APP_GROUP_CLASS, self->group_class); OBT_PROP_SETS(self->window, OB_APP_TITLE, self->original_title); switch (self->type) { @@ -2766,6 +2815,12 @@ gboolean client_helper(ObClient *self) self->type == OB_CLIENT_TYPE_TOOLBAR); } +gboolean client_occupies_space(ObClient *self) +{ + return !(self->type == OB_CLIENT_TYPE_DESKTOP || + self->type == OB_CLIENT_TYPE_SPLASH); +} + gboolean client_mouse_focusable(ObClient *self) { return !(self->type == OB_CLIENT_TYPE_MENU || diff --git a/openbox/client.h b/openbox/client.h index 99fdfd05..a946f274 100644 --- a/openbox/client.h +++ b/openbox/client.h @@ -127,6 +127,10 @@ struct _ObClient gchar *class; /*! The specified role of the window, used for identification */ gchar *role; + /*! The application that created the window's group. */ + gchar *group_name; + /*! The class of the window's group, can used for grouping */ + gchar *group_class; /*! The session client id for the window. *This can be NULL!* */ gchar *sm_client_id; @@ -328,6 +332,7 @@ typedef void (*ObClientCallback)(ObClient *client, gpointer data); /*! Get notified when the client is unmanaged */ void client_add_destroy_notify(ObClientCallback func, gpointer data); void client_remove_destroy_notify(ObClientCallback func); +void client_remove_destroy_notify_data(ObClientCallback func, gpointer data); /*! Manages a given window @param prompt This specifies an ObPrompt which is being managed. It is @@ -364,6 +369,10 @@ gboolean client_normal(ObClient *self); (utilty, menu, etc) */ gboolean client_helper(ObClient *self); +/*! Returns true if the window occupies space in the monitor conceptually, or + false if it does not and its presence should be ignored when possible. */ +gboolean client_occupies_space(ObClient *self); + /*! Return if the client is a type which should be given focus from mouse presses on the *client* window. This doesn't affect clicking on the decorations. This doesn't count for focus cycling, different rules apply to @@ -495,7 +504,7 @@ void client_fullscreen(ObClient *self, gboolean fs); /*! Determine if the window, using the given client-area, would be considered as an "oldschool fullscreen" window, that is, if it is filling a whole monitor. */ -gboolean client_is_oldfullscreen(const ObClient const *self, const Rect *area); +gboolean client_is_oldfullscreen(const ObClient *self, const Rect *area); /*! Iconifies or uniconifies the client window @param iconic true if the window should be iconified; false if it should be diff --git a/openbox/config.c b/openbox/config.c index f0f6c7b3..c21b47d8 100644 --- a/openbox/config.c +++ b/openbox/config.c @@ -37,7 +37,6 @@ gboolean config_focus_under_mouse; gboolean config_unfocus_leave; ObPlacePolicy config_place_policy; -gboolean config_place_center; ObPlaceMonitor config_place_monitor; guint config_primary_monitor_index; @@ -158,6 +157,14 @@ void config_app_settings_copy_non_defaults(const ObAppSettings *src, dst->position = src->position; /* monitor is copied above */ } + + if (src->size_given) { + dst->size_given = TRUE; + dst->width_num = src->width_num; + dst->width_denom = src->width_denom; + dst->height_num = src->height_num; + dst->height_denom = src->height_denom; + } } void config_parse_relative_number(gchar *s, gint *num, gint *denom) @@ -204,6 +211,148 @@ void config_parse_gravity_coord(xmlNodePtr node, GravityCoord *c) */ +static void parse_single_per_app_settings(xmlNodePtr app, + ObAppSettings *settings) +{ + xmlNodePtr n, c; + gboolean x_pos_given = FALSE; + gboolean width_given = FALSE; + + if ((n = obt_xml_find_node(app->children, "decor"))) + if (!obt_xml_node_contains(n, "default")) + settings->decor = obt_xml_node_bool(n); + + if ((n = obt_xml_find_node(app->children, "shade"))) + if (!obt_xml_node_contains(n, "default")) + settings->shade = obt_xml_node_bool(n); + + if ((n = obt_xml_find_node(app->children, "position"))) { + if ((c = obt_xml_find_node(n->children, "x"))) { + if (!obt_xml_node_contains(c, "default")) { + config_parse_gravity_coord(c, &settings->position.x); + x_pos_given = TRUE; + } + } + + if (x_pos_given && (c = obt_xml_find_node(n->children, "y"))) { + if (!obt_xml_node_contains(c, "default")) { + config_parse_gravity_coord(c, &settings->position.y); + settings->pos_given = TRUE; + } + } + + /* monitor can be set without setting x or y */ + if ((c = obt_xml_find_node(n->children, "monitor"))) { + if (!obt_xml_node_contains(c, "default")) { + gchar *s = obt_xml_node_string(c); + if (!g_ascii_strcasecmp(s, "mouse")) + settings->monitor_type = OB_PLACE_MONITOR_MOUSE; + else if (!g_ascii_strcasecmp(s, "active")) + settings->monitor_type = OB_PLACE_MONITOR_ACTIVE; + else if (!g_ascii_strcasecmp(s, "primary")) + settings->monitor_type = OB_PLACE_MONITOR_PRIMARY; + else + settings->monitor = obt_xml_node_int(c); + g_free(s); + } + } + + obt_xml_attr_bool(n, "force", &settings->pos_force); + } + + if ((n = obt_xml_find_node(app->children, "size"))) { + if ((c = obt_xml_find_node(n->children, "width"))) { + if (!obt_xml_node_contains(c, "default")) { + gchar *s = obt_xml_node_string(c); + config_parse_relative_number(s, + &settings->width_num, + &settings->width_denom); + if (settings->width_num > 0 && settings->width_denom >= 0) + width_given = TRUE; + g_free(s); + } + } + + if (width_given && (c = obt_xml_find_node(n->children, "height"))) { + gchar *s = obt_xml_node_string(c); + config_parse_relative_number(s, + &settings->height_num, + &settings->height_denom); + if (settings->height_num > 0 && settings->height_denom >= 0) + settings->size_given = TRUE; + g_free(s); + } + } + + if ((n = obt_xml_find_node(app->children, "focus"))) { + if (!obt_xml_node_contains(n, "default")) + settings->focus = obt_xml_node_bool(n); + } + + if ((n = obt_xml_find_node(app->children, "desktop"))) { + if (!obt_xml_node_contains(n, "default")) { + gchar *s = obt_xml_node_string(n); + if (!g_ascii_strcasecmp(s, "all")) + settings->desktop = DESKTOP_ALL; + else { + gint i = obt_xml_node_int(n); + if (i > 0) + settings->desktop = i; + } + g_free(s); + } + } + + if ((n = obt_xml_find_node(app->children, "layer"))) { + if (!obt_xml_node_contains(n, "default")) { + gchar *s = obt_xml_node_string(n); + if (!g_ascii_strcasecmp(s, "above")) + settings->layer = 1; + else if (!g_ascii_strcasecmp(s, "below")) + settings->layer = -1; + else + settings->layer = 0; + g_free(s); + } + } + + if ((n = obt_xml_find_node(app->children, "iconic"))) + if (!obt_xml_node_contains(n, "default")) + settings->iconic = obt_xml_node_bool(n); + + if ((n = obt_xml_find_node(app->children, "skip_pager"))) + if (!obt_xml_node_contains(n, "default")) + settings->skip_pager = obt_xml_node_bool(n); + + if ((n = obt_xml_find_node(app->children, "skip_taskbar"))) + if (!obt_xml_node_contains(n, "default")) + settings->skip_taskbar = obt_xml_node_bool(n); + + if ((n = obt_xml_find_node(app->children, "fullscreen"))) + if (!obt_xml_node_contains(n, "default")) + settings->fullscreen = obt_xml_node_bool(n); + + if ((n = obt_xml_find_node(app->children, "maximized"))) { + if (!obt_xml_node_contains(n, "default")) { + gchar *s = obt_xml_node_string(n); + if (!g_ascii_strcasecmp(s, "horizontal")) { + settings->max_horz = TRUE; + settings->max_vert = FALSE; + } else if (!g_ascii_strcasecmp(s, "vertical")) { + settings->max_horz = FALSE; + settings->max_vert = TRUE; + } else + settings->max_horz = settings->max_vert = + obt_xml_node_bool(n); + g_free(s); + } + } + + if ((n = obt_xml_find_node(app->children, "opacity"))) + if (!obt_xml_node_contains(n, "default")) + settings->opacity = obt_xml_node_int(n); +} + /* Manages settings for individual applications. Some notes: monitor is the screen number in a multi monitor (Xinerama) setup (starting from 0), or mouse: the monitor the pointer @@ -218,17 +367,19 @@ void config_parse_gravity_coord(xmlNodePtr node, GravityCoord *c) static void parse_per_app_settings(xmlNodePtr node, gpointer d) { xmlNodePtr app = obt_xml_find_node(node->children, "application"); - gchar *name = NULL, *class = NULL, *role = NULL, *title = NULL, - *type_str = NULL; - gboolean name_set, class_set, type_set, role_set, title_set; - ObClientType type; - gboolean x_pos_given; + for (; app; app = obt_xml_find_node(app->next, "application")) { + ObAppSettings *settings; - while (app) { - x_pos_given = FALSE; + gboolean name_set, class_set, role_set, title_set, + type_set, group_name_set, group_class_set; + gchar *name = NULL, *class = NULL, *role = NULL, *title = NULL, + *type_str = NULL, *group_name = NULL, *group_class = NULL; + ObClientType type; class_set = obt_xml_attr_string(app, "class", &class); name_set = obt_xml_attr_string(app, "name", &name); + group_class_set = obt_xml_attr_string(app, "groupclass", &group_class); + group_name_set = obt_xml_attr_string(app, "groupname", &group_name); type_set = obt_xml_attr_string(app, "type", &type_str); role_set = obt_xml_attr_string(app, "role", &role); title_set = obt_xml_attr_string(app, "title", &title); @@ -255,143 +406,38 @@ static void parse_per_app_settings(xmlNodePtr node, gpointer d) type_set = FALSE; /* not valid! */ } - if (class_set || name_set || role_set || title_set || type_set) { - xmlNodePtr n, c; - ObAppSettings *settings = config_create_app_settings(); - - if (name_set) - settings->name = g_pattern_spec_new(name); - - if (class_set) - settings->class = g_pattern_spec_new(class); - - if (role_set) - settings->role = g_pattern_spec_new(role); - - if (title_set) - settings->title = g_pattern_spec_new(title); - - if (type_set) - settings->type = type; - - if ((n = obt_xml_find_node(app->children, "decor"))) - if (!obt_xml_node_contains(n, "default")) - settings->decor = obt_xml_node_bool(n); - - if ((n = obt_xml_find_node(app->children, "shade"))) - if (!obt_xml_node_contains(n, "default")) - settings->shade = obt_xml_node_bool(n); - - if ((n = obt_xml_find_node(app->children, "position"))) { - if ((c = obt_xml_find_node(n->children, "x"))) - if (!obt_xml_node_contains(c, "default")) { - config_parse_gravity_coord(c, &settings->position.x); - x_pos_given = TRUE; - } - - if (x_pos_given && (c = obt_xml_find_node(n->children, "y"))) - if (!obt_xml_node_contains(c, "default")) { - config_parse_gravity_coord(c, &settings->position.y); - settings->pos_given = TRUE; - } - - /* monitor can be set without setting x or y */ - if ((c = obt_xml_find_node(n->children, "monitor"))) - if (!obt_xml_node_contains(c, "default")) { - gchar *s = obt_xml_node_string(c); - if (!g_ascii_strcasecmp(s, "mouse")) - settings->monitor_type = - OB_PLACE_MONITOR_MOUSE; - else if (!g_ascii_strcasecmp(s, "active")) - settings->monitor_type = - OB_PLACE_MONITOR_ACTIVE; - else if (!g_ascii_strcasecmp(s, "primary")) - settings->monitor_type = - OB_PLACE_MONITOR_PRIMARY; - else - settings->monitor = obt_xml_node_int(c); - g_free(s); - } - - obt_xml_attr_bool(n, "force", &settings->pos_force); - } - - if ((n = obt_xml_find_node(app->children, "focus"))) - if (!obt_xml_node_contains(n, "default")) - settings->focus = obt_xml_node_bool(n); - - if ((n = obt_xml_find_node(app->children, "desktop"))) { - if (!obt_xml_node_contains(n, "default")) { - gchar *s = obt_xml_node_string(n); - if (!g_ascii_strcasecmp(s, "all")) - settings->desktop = DESKTOP_ALL; - else { - gint i = obt_xml_node_int(n); - if (i > 0) - settings->desktop = i; - } - g_free(s); - } - } + if (!(class_set || name_set || role_set || title_set || + type_set || group_class_set || group_name_set)) + continue; + + settings = config_create_app_settings(); + + if (name_set) + settings->name = g_pattern_spec_new(name); + if (class_set) + settings->class = g_pattern_spec_new(class); + if (group_name_set) + settings->group_name = g_pattern_spec_new(group_name); + if (group_class_set) + settings->group_class = g_pattern_spec_new(group_class); + if (role_set) + settings->role = g_pattern_spec_new(role); + if (title_set) + settings->title = g_pattern_spec_new(title); + if (type_set) + settings->type = type; - if ((n = obt_xml_find_node(app->children, "layer"))) - if (!obt_xml_node_contains(n, "default")) { - gchar *s = obt_xml_node_string(n); - if (!g_ascii_strcasecmp(s, "above")) - settings->layer = 1; - else if (!g_ascii_strcasecmp(s, "below")) - settings->layer = -1; - else - settings->layer = 0; - g_free(s); - } - - if ((n = obt_xml_find_node(app->children, "iconic"))) - if (!obt_xml_node_contains(n, "default")) - settings->iconic = obt_xml_node_bool(n); - - if ((n = obt_xml_find_node(app->children, "skip_pager"))) - if (!obt_xml_node_contains(n, "default")) - settings->skip_pager = obt_xml_node_bool(n); - - if ((n = obt_xml_find_node(app->children, "skip_taskbar"))) - if (!obt_xml_node_contains(n, "default")) - settings->skip_taskbar = obt_xml_node_bool(n); - - if ((n = obt_xml_find_node(app->children, "fullscreen"))) - if (!obt_xml_node_contains(n, "default")) - settings->fullscreen = obt_xml_node_bool(n); - - if ((n = obt_xml_find_node(app->children, "maximized"))) - if (!obt_xml_node_contains(n, "default")) { - gchar *s = obt_xml_node_string(n); - if (!g_ascii_strcasecmp(s, "horizontal")) { - settings->max_horz = TRUE; - settings->max_vert = FALSE; - } else if (!g_ascii_strcasecmp(s, "vertical")) { - settings->max_horz = FALSE; - settings->max_vert = TRUE; - } else - settings->max_horz = settings->max_vert = - obt_xml_node_bool(n); - g_free(s); - } - - if ((n = obt_xml_find_node(app->children, "opacity"))) - if (!obt_xml_node_contains(n, "default")) - settings->opacity = obt_xml_node_int(n); - - config_per_app_settings = g_slist_append(config_per_app_settings, - (gpointer) settings); - g_free(name); - g_free(class); - g_free(role); - g_free(title); - g_free(type_str); - name = class = role = title = type_str = NULL; - } - - app = obt_xml_find_node(app->next, "application"); + g_free(name); + g_free(class); + g_free(group_name); + g_free(group_class); + g_free(role); + g_free(title); + g_free(type_str); + + parse_single_per_app_settings(app, settings); + config_per_app_settings = g_slist_append(config_per_app_settings, + (gpointer)settings); } } @@ -592,8 +638,6 @@ static void parse_placement(xmlNodePtr node, gpointer d) if ((n = obt_xml_find_node(node, "policy"))) if (obt_xml_node_contains(n, "UnderMouse")) config_place_policy = OB_PLACE_POLICY_MOUSE; - if ((n = obt_xml_find_node(node, "center"))) - config_place_center = obt_xml_node_bool(n); if ((n = obt_xml_find_node(node, "monitor"))) { if (obt_xml_node_contains(n, "active")) config_place_monitor = OB_PLACE_MONITOR_ACTIVE; @@ -1025,7 +1069,6 @@ void config_startup(ObtXmlInst *i) obt_xml_register(i, "focus", parse_focus, NULL); config_place_policy = OB_PLACE_POLICY_SMART; - config_place_center = TRUE; config_place_monitor = OB_PLACE_MONITOR_PRIMARY; config_primary_monitor_index = 1; @@ -1146,10 +1189,12 @@ void config_shutdown(void) for (it = config_per_app_settings; it; it = g_slist_next(it)) { ObAppSettings *itd = (ObAppSettings *)it->data; - if (itd->name) g_pattern_spec_free(itd->name); - if (itd->role) g_pattern_spec_free(itd->role); + if (itd->name) g_pattern_spec_free(itd->name); + if (itd->role) g_pattern_spec_free(itd->role); if (itd->title) g_pattern_spec_free(itd->title); if (itd->class) g_pattern_spec_free(itd->class); + if (itd->group_name) g_pattern_spec_free(itd->group_name); + if (itd->group_class) g_pattern_spec_free(itd->group_class); g_slice_free(ObAppSettings, it->data); } g_slist_free(config_per_app_settings); diff --git a/openbox/config.h b/openbox/config.h index beb54be4..c09a1f4f 100644 --- a/openbox/config.h +++ b/openbox/config.h @@ -38,6 +38,8 @@ struct _ObAppSettings GPatternSpec *class; GPatternSpec *name; GPatternSpec *role; + GPatternSpec *group_class; + GPatternSpec *group_name; GPatternSpec *title; ObClientType type; @@ -45,6 +47,12 @@ struct _ObAppSettings gboolean pos_given; gboolean pos_force; + gint width_num; + gint width_denom; + gint height_num; + gint height_denom; + gboolean size_given; + guint desktop; gint shade; gint decor; @@ -82,8 +90,6 @@ extern gboolean config_unfocus_leave; /*! The algorithm to use for placing new windows */ extern ObPlacePolicy config_place_policy; -/*! Place windows in the center of the free area */ -extern gboolean config_place_center; /*! Place windows on the active monitor (unless they are part of an application already on another monitor) */ extern ObPlaceMonitor config_place_monitor; diff --git a/openbox/event.c b/openbox/event.c index cf089b64..ccbb56e6 100644 --- a/openbox/event.c +++ b/openbox/event.c @@ -698,7 +698,6 @@ static void event_process(const XEvent *ec, gpointer data) if (e->type == ButtonPress || e->type == ButtonRelease) { ObWindow *w; static guint pressed = 0; - static Window pressed_win = None; event_sourcetime = event_curtime; @@ -719,10 +718,8 @@ static void event_process(const XEvent *ec, gpointer data) if (prompt && !used) used = event_handle_prompt(prompt, e); - if (e->type == ButtonPress) { + if (e->type == ButtonPress) pressed = e->xbutton.button; - pressed_win = e->xbutton.subwindow; - } } } else if (e->type == KeyPress || e->type == KeyRelease || @@ -813,11 +810,14 @@ void event_enter_client(ObClient *client) g_assert(config_focus_follow); if (is_enter_focus_event_ignored(event_curserial)) { - ob_debug_type(OB_DEBUG_FOCUS, "Ignoring enter event with serial %lu\n" + ob_debug_type(OB_DEBUG_FOCUS, "Ignoring enter event with serial %lu " "on client 0x%x", event_curserial, client->window); return; } + ob_debug_type(OB_DEBUG_FOCUS, "using enter event with serial %lu " + "on client 0x%x", event_curserial, client->window); + if (client_enter_focusable(client) && client_can_focus(client)) { if (config_focus_delay) { ObFocusDelayData *data; @@ -1495,7 +1495,8 @@ static void event_handle_client(ObClient *client, XEvent *e) } else if ((Atom)e->xclient.data.l[2] == OBT_PROP_ATOM(NET_WM_MOVERESIZE_CANCEL)) - moveresize_end(TRUE); + if (moveresize_client) + moveresize_end(TRUE); } else if (msgtype == OBT_PROP_ATOM(NET_MOVERESIZE_WINDOW)) { gint ograv, x, y, w, h; @@ -2002,6 +2003,20 @@ static void event_handle_menu(ObMenuFrame *frame, XEvent *ev) ObMenuEntryFrame *e; switch (ev->type) { + case MotionNotify: + // We need to catch MotionNotify in addition to EnterNotify because + // it is possible for the menu to be opened under the mouse cursor, and + // moving the mouse should select the item. + if ((e = g_hash_table_lookup(menu_frame_map, &ev->xmotion.window))) { + if (e->ignore_enters) + --e->ignore_enters; + else if (!(f = find_active_menu()) || + f == e->frame || + f->parent == e->frame || + f->child == e->frame) + menu_frame_select(e->frame, e, FALSE); + } + break; case EnterNotify: if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) { if (e->ignore_enters) diff --git a/openbox/event.h b/openbox/event.h index 4d9984e1..cc441405 100644 --- a/openbox/event.h +++ b/openbox/event.h @@ -26,7 +26,7 @@ struct _ObClient; /*! The amount of time before a window appears that is checked for user input to determine if the user is working in another window */ -#define OB_EVENT_USER_TIME_DELAY (1000) /* 1.0 seconds */ +#define OB_EVENT_USER_TIME_DELAY (1000) /* milliseconds */ /*! The last user-interaction time, as given by the clients */ extern Time event_last_user_time; diff --git a/openbox/focus_cycle.c b/openbox/focus_cycle.c index 6d4cc2a5..de176501 100644 --- a/openbox/focus_cycle.c +++ b/openbox/focus_cycle.c @@ -74,7 +74,7 @@ void focus_cycle_addremove(ObClient *c, gboolean redraw) gboolean v, s; v = focus_cycle_valid(c); - s = focus_cycle_popup_is_showing(c); + s = focus_cycle_popup_is_showing(c) || c == focus_cycle_target; if (v != s) focus_cycle_reorder(); @@ -93,7 +93,7 @@ void focus_cycle_reorder() focus_cycle_update_indicator(focus_cycle_target); if (!focus_cycle_target) focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, - TRUE, TRUE, OB_FOCUS_CYCLE_POPUP_MODE_NONE, + TRUE, OB_FOCUS_CYCLE_POPUP_MODE_NONE, TRUE, TRUE); } } @@ -101,8 +101,8 @@ void focus_cycle_reorder() ObClient* focus_cycle(gboolean forward, gboolean all_desktops, gboolean nonhilite_windows, gboolean dock_windows, gboolean desktop_windows, - gboolean linear, gboolean interactive, - gboolean showbar, ObFocusCyclePopupMode mode, + gboolean linear, gboolean showbar, + ObFocusCyclePopupMode mode, gboolean done, gboolean cancel) { static GList *order = NULL; @@ -110,23 +110,17 @@ ObClient* focus_cycle(gboolean forward, gboolean all_desktops, ObClient *ft = NULL; ObClient *ret = NULL; - if (interactive) { - if (cancel) { - focus_cycle_target = NULL; - goto done_cycle; - } else if (done) - goto done_cycle; + if (cancel) { + focus_cycle_target = NULL; + goto done_cycle; + } else if (done) + goto done_cycle; - if (!focus_order) - goto done_cycle; + if (!focus_order) + goto done_cycle; - if (linear) list = client_list; - else list = focus_order; - } else { - if (!focus_order) - goto done_cycle; - list = client_list; - } + if (linear) list = client_list; + else list = focus_order; if (focus_cycle_target == NULL) { focus_cycle_linear = linear; @@ -153,21 +147,14 @@ ObClient* focus_cycle(gboolean forward, gboolean all_desktops, } ft = it->data; if (focus_cycle_valid(ft)) { - if (interactive) { - if (ft != focus_cycle_target) { /* prevents flicker */ - focus_cycle_target = ft; - focus_cycle_type = OB_CYCLE_NORMAL; - focus_cycle_draw_indicator(showbar ? ft : NULL); - } - /* same arguments as focus_target_valid */ - focus_cycle_popup_show(ft, mode, focus_cycle_linear); - return focus_cycle_target; - } else if (ft != focus_cycle_target) { + if (ft != focus_cycle_target) { /* prevents flicker */ focus_cycle_target = ft; focus_cycle_type = OB_CYCLE_NORMAL; - done = TRUE; - break; + focus_cycle_draw_indicator(showbar ? ft : NULL); } + /* same arguments as focus_target_valid */ + focus_cycle_popup_show(ft, mode, focus_cycle_linear); + return focus_cycle_target; } } while (it != start); @@ -179,10 +166,8 @@ done_cycle: g_list_free(order); order = NULL; - if (interactive) { - focus_cycle_draw_indicator(NULL); - focus_cycle_popup_hide(); - } + focus_cycle_draw_indicator(NULL); + focus_cycle_popup_hide(); return ret; } diff --git a/openbox/focus_cycle.h b/openbox/focus_cycle.h index 9394b3df..8acb53d9 100644 --- a/openbox/focus_cycle.h +++ b/openbox/focus_cycle.h @@ -38,8 +38,8 @@ void focus_cycle_shutdown(gboolean reconfig); struct _ObClient* focus_cycle(gboolean forward, gboolean all_desktops, gboolean nonhilite_windows, gboolean dock_windows, gboolean desktop_windows, - gboolean linear, gboolean interactive, - gboolean showbar, ObFocusCyclePopupMode mode, + gboolean linear, gboolean showbar, + ObFocusCyclePopupMode mode, gboolean done, gboolean cancel); struct _ObClient* focus_directional_cycle(ObDirection dir, gboolean dock_windows, diff --git a/openbox/frame.c b/openbox/frame.c index 4468e191..0f2d56eb 100644 --- a/openbox/frame.c +++ b/openbox/frame.c @@ -378,13 +378,22 @@ void frame_adjust_area(ObFrame *self, gboolean moved, STRUT_SET(self->size, self->cbwidth_l + (!self->max_horz ? self->bwidth : 0), - self->cbwidth_t + self->bwidth, + self->cbwidth_t + + (!self->max_horz || !self->max_vert ? self->bwidth : 0), self->cbwidth_r + (!self->max_horz ? self->bwidth : 0), self->cbwidth_b + (!self->max_horz || !self->max_vert ? self->bwidth : 0)); if (self->decorations & OB_FRAME_DECOR_TITLEBAR) self->size.top += ob_rr_theme->title_height + self->bwidth; + else if (self->max_horz && self->max_vert) { + /* A maximized and undecorated window needs a small border on the + top of the window to let the user still undecorate/unmaximize the + window via the client menu. */ + /* XXX This size should probably be a theme option. */ + self->size.top += 1; + } + if (self->decorations & OB_FRAME_DECOR_HANDLE && ob_rr_theme->handle_height > 0) { diff --git a/openbox/geom.h b/openbox/geom.h index 003b0081..8e50834b 100644 --- a/openbox/geom.h +++ b/openbox/geom.h @@ -65,6 +65,8 @@ typedef struct _Rect { #define RECT_RIGHT(r) ((r).x + (r).width - 1) #define RECT_BOTTOM(r) ((r).y + (r).height - 1) +#define RECT_AREA(r) ((r).width * (r).height) + #define RECT_SET_POINT(r, nx, ny) \ (r).x = (nx), (r).y = (ny) #define RECT_SET_SIZE(r, w, h) \ @@ -102,6 +104,25 @@ typedef struct _Rect { (r).height = MIN((a).y + (a).height - 1, \ (b).y + (b).height - 1) - (r).y + 1) +/* Returns the shortest manhatten distance between two rects, or 0 if they + intersect. */ +static inline gint rect_manhatten_distance(Rect r, Rect o) +{ + if (RECT_INTERSECTS_RECT(r, o)) + return 0; + + gint min_distance = G_MAXINT; + if (RECT_RIGHT(o) < RECT_LEFT(r)) + min_distance = MIN(min_distance, RECT_LEFT(r) - RECT_RIGHT(o)); + if (RECT_LEFT(o) > RECT_RIGHT(r)) + min_distance = MIN(min_distance, RECT_LEFT(o) - RECT_RIGHT(r)); + if (RECT_BOTTOM(o) < RECT_TOP(r)) + min_distance = MIN(min_distance, RECT_TOP(r) - RECT_BOTTOM(o)); + if (RECT_TOP(o) > RECT_BOTTOM(r)) + min_distance = MIN(min_distance, RECT_TOP(o) - RECT_BOTTOM(r)); + return min_distance; +} + typedef struct _Strut { int left; int top; diff --git a/openbox/menu.c b/openbox/menu.c index 1294c4c9..7c49cedb 100644 --- a/openbox/menu.c +++ b/openbox/menu.c @@ -90,7 +90,16 @@ void menu_startup(gboolean reconfig) loaded = TRUE; obt_xml_tree_from_root(menu_parse_inst); obt_xml_close(menu_parse_inst); - } else + } + else if (obt_xml_load_file(menu_parse_inst, + it->data, + "openbox_menu")) + { + loaded = TRUE; + obt_xml_tree_from_root(menu_parse_inst); + obt_xml_close(menu_parse_inst); + } + else g_message(_("Unable to find a valid menu file \"%s\""), (const gchar*)it->data); } @@ -115,10 +124,11 @@ void menu_shutdown(gboolean reconfig) obt_xml_instance_unref(menu_parse_inst); menu_parse_inst = NULL; - client_list_menu_shutdown(reconfig); + menu_frame_hide_all(); + client_list_combined_menu_shutdown(reconfig); + client_list_menu_shutdown(reconfig); - menu_frame_hide_all(); g_hash_table_destroy(menu_hash); menu_hash = NULL; } @@ -278,7 +288,7 @@ static void parse_menu_item(xmlNodePtr node, gpointer data) /* Don't try to extract "icon" attribute if icons in user-defined menus are not enabled. */ - if (obt_xml_attr_string(node, "label", &label)) { + if (obt_xml_attr_string_unstripped(node, "label", &label)) { xmlNodePtr c; GSList *acts = NULL; @@ -313,7 +323,7 @@ static void parse_menu_separator(xmlNodePtr node, gpointer data) if (state->parent) { gchar *label; - if (!obt_xml_attr_string(node, "label", &label)) + if (!obt_xml_attr_string_unstripped(node, "label", &label)) label = NULL; menu_add_separator(state->parent, -1, label); @@ -333,7 +343,7 @@ static void parse_menu(xmlNodePtr node, gpointer data) goto parse_menu_fail; if (!g_hash_table_lookup(menu_hash, name)) { - if (!obt_xml_attr_string(node, "label", &title)) + if (!obt_xml_attr_string_unstripped(node, "label", &title)) goto parse_menu_fail; if ((menu = menu_new(name, title, TRUE, NULL))) { @@ -384,6 +394,7 @@ ObMenu* menu_new(const gchar *name, const gchar *title, self->shortcut = parse_shortcut(title, allow_shortcut_selection, &self->title, &self->shortcut_position, &self->shortcut_always_show); + self->collate_key = g_utf8_collate_key(self->title, -1); g_hash_table_replace(menu_hash, self->name, self); @@ -399,6 +410,7 @@ ObMenu* menu_new(const gchar *name, const gchar *title, self->more_menu = g_slice_new0(ObMenu); self->more_menu->name = _("More..."); self->more_menu->title = _("More..."); + self->more_menu->collate_key = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; self->more_menu->data = data; self->more_menu->shortcut = g_unichar_tolower(g_utf8_get_char("M")); @@ -425,6 +437,7 @@ static void menu_destroy_hash_value(ObMenu *self) menu_clear_entries(self); g_free(self->name); g_free(self->title); + g_free(self->collate_key); g_free(self->execute); g_slice_free(ObMenu, self->more_menu); @@ -540,6 +553,7 @@ void menu_entry_unref(ObMenuEntry *self) case OB_MENU_ENTRY_TYPE_NORMAL: RrImageUnref(self->data.normal.icon); g_free(self->data.normal.label); + g_free(self->data.normal.collate_key); while (self->data.normal.actions) { actions_act_unref(self->data.normal.actions->data); self->data.normal.actions = @@ -713,10 +727,13 @@ void menu_entry_set_label(ObMenuEntry *self, const gchar *label, break; case OB_MENU_ENTRY_TYPE_NORMAL: g_free(self->data.normal.label); + g_free(self->data.normal.collate_key); self->data.normal.shortcut = parse_shortcut(label, allow_shortcut, &self->data.normal.label, &self->data.normal.shortcut_position, &self->data.normal.shortcut_always_show); + self->data.normal.collate_key = + g_utf8_collate_key(self->data.normal.label, -1); break; default: g_assert_not_reached(); @@ -727,3 +744,69 @@ void menu_show_all_shortcuts(ObMenu *self, gboolean show) { self->show_all_shortcuts = show; } + +static int sort_func(const void *a, const void *b) { + const ObMenuEntry *e[2] = {*(ObMenuEntry**)a, *(ObMenuEntry**)b}; + const gchar *k[2]; + gint i; + + for (i = 0; i < 2; ++i) { + if (e[i]->type == OB_MENU_ENTRY_TYPE_NORMAL) + k[i] = e[i]->data.normal.collate_key; + else { + g_assert(e[i]->type == OB_MENU_ENTRY_TYPE_SUBMENU); + if (e[i]->data.submenu.submenu) + k[i] = e[i]->data.submenu.submenu->collate_key; + else + return -1; /* arbitrary really.. the submenu doesn't exist. */ + } + } + return strcmp(k[0], k[1]); +} + +/*! + @param start The first entry in the range to sort. + @param end The last entry in the range to sort. +*/ +static void sort_range(ObMenu *self, GList *start, GList *end, guint len) +{ + ObMenuEntry **ar; + GList *it; + guint i; + if (!len) return; + + ar = g_slice_alloc(sizeof(ObMenuEntry*) * len); + for (i = 0, it = start; it != g_list_next(end); ++i, it = g_list_next(it)) + ar[i] = it->data; + qsort(ar, len, sizeof(ObMenuEntry*), sort_func); + for (i = 0, it = start; it != g_list_next(end); ++i, it = g_list_next(it)) + it->data = ar[i]; + g_slice_free1(sizeof(ObMenuEntry*) * len, ar); +} + +void menu_sort_entries(ObMenu *self) +{ + GList *it, *start, *end, *last; + guint len; + + /* need the submenus to know their labels for sorting */ + menu_find_submenus(self); + + start = self->entries; + len = 0; + for (it = self->entries; it; it = g_list_next(it)) { + ObMenuEntry *e = it->data; + if (e->type == OB_MENU_ENTRY_TYPE_SEPARATOR) { + end = g_list_previous(it); + sort_range(self, start, end, len); + + it = g_list_next(it); /* skip over the separator */ + start = it; + len = 0; + } + else + len += 1; + last = it; + } + sort_range(self, start, last, len); +} diff --git a/openbox/menu.h b/openbox/menu.h index 76cc238f..7d719729 100644 --- a/openbox/menu.h +++ b/openbox/menu.h @@ -59,6 +59,7 @@ struct _ObMenu gchar *name; /* Displayed title */ gchar *title; + gchar *collate_key; /*! The shortcut key that would be used to activate this menu if it was displayed as a submenu */ gunichar shortcut; @@ -108,6 +109,7 @@ struct _ObNormalMenuEntry { gint icon_alpha; gchar *label; + gchar *collate_key; /*! The shortcut key that would be used to activate this menu entry */ gunichar shortcut; /*! The shortcut's position in the string */ @@ -211,7 +213,10 @@ ObMenuEntry* menu_add_normal(ObMenu *menu, gint id, const gchar *label, ObMenuEntry* menu_add_submenu(ObMenu *menu, gint id, const gchar *submenu); ObMenuEntry* menu_add_separator(ObMenu *menu, gint id, const gchar *label); -void menu_clear_entries(ObMenu *menu); +/*! This sorts groups of menu entries between consecutive separators */ +void menu_sort_entries(ObMenu *self); + +void menu_clear_entries(ObMenu *self); void menu_entry_remove(ObMenuEntry *self); void menu_entry_set_label(ObMenuEntry *self, const gchar *label, diff --git a/openbox/menuframe.c b/openbox/menuframe.c index 6110045c..b49a221b 100644 --- a/openbox/menuframe.c +++ b/openbox/menuframe.c @@ -38,7 +38,8 @@ #define FRAME_EVENTMASK (ButtonPressMask |ButtonMotionMask | EnterWindowMask |\ LeaveWindowMask) #define ENTRY_EVENTMASK (EnterWindowMask | LeaveWindowMask | \ - ButtonPressMask | ButtonReleaseMask) + ButtonPressMask | ButtonReleaseMask | \ + PointerMotionMask) GList *menu_frame_visible; GHashTable *menu_frame_map; @@ -1046,8 +1047,11 @@ gboolean menu_frame_show_submenu(ObMenuFrame *self, ObMenuFrame *parent, parent->child_entry = parent_entry; } - if (!menu_frame_show(self)) + if (!menu_frame_show(self)) { + parent->child = NULL; + parent->child_entry = NULL; return FALSE; + } menu_frame_place_submenu(self, &x, &y); menu_frame_move_on_screen(self, x, y, &dx, &dy); @@ -1272,7 +1276,8 @@ void menu_entry_frame_show_submenu(ObMenuEntryFrame *self) /* pass our direction on to our child */ f->direction_right = self->frame->direction_right; - menu_frame_show_submenu(f, self->frame, self); + if (!menu_frame_show_submenu(f, self->frame, self)) + menu_frame_free(f); } void menu_entry_frame_execute(ObMenuEntryFrame *self, guint state) diff --git a/openbox/menuframe.h b/openbox/menuframe.h index 44c02562..2d7a2ae0 100644 --- a/openbox/menuframe.h +++ b/openbox/menuframe.h @@ -121,7 +121,7 @@ void menu_frame_move_on_screen(ObMenuFrame *self, gint x, gint y, gint *dx, gint *dy); gboolean menu_frame_show_topmenu(ObMenuFrame *self, gint x, gint y, - gint button); + gboolean mouse); gboolean menu_frame_show_submenu(ObMenuFrame *self, ObMenuFrame *parent, ObMenuEntryFrame *parent_entry); diff --git a/openbox/moveresize.c b/openbox/moveresize.c index d700da9e..ffed9093 100644 --- a/openbox/moveresize.c +++ b/openbox/moveresize.c @@ -630,6 +630,10 @@ static void do_edge_warp(gint x, gint y) for (i = 0; i < screen_num_monitors; ++i) { const Rect *a = screen_physical_area_monitor(i); + + if (!RECT_CONTAINS(*a, x, y)) + continue; + if (x == RECT_LEFT(*a)) dir = OB_DIRECTION_WEST; if (x == RECT_RIGHT(*a)) dir = OB_DIRECTION_EAST; if (y == RECT_TOP(*a)) dir = OB_DIRECTION_NORTH; diff --git a/openbox/openbox.c b/openbox/openbox.c index fbc01fdd..4ac09cd6 100644 --- a/openbox/openbox.c +++ b/openbox/openbox.c @@ -515,9 +515,9 @@ static void print_version(void) { g_print("Openbox %s\n", PACKAGE_VERSION); g_print(_("Copyright (c)")); - g_print(" 2008-2011 Mikael Magnusson\n"); + g_print(" 2004 Mikael Magnusson\n"); g_print(_("Copyright (c)")); - g_print(" 2003-2011 Dana Jansens\n\n"); + g_print(" 2002 Dana Jansens\n\n"); g_print("This program comes with ABSOLUTELY NO WARRANTY.\n"); g_print("This is free software, and you are welcome to redistribute it\n"); g_print("under certain conditions. See the file COPYING for details.\n\n"); diff --git a/openbox/place.c b/openbox/place.c index 2cd21bb0..48e4fb16 100644 --- a/openbox/place.c +++ b/openbox/place.c @@ -25,15 +25,16 @@ #include "config.h" #include "dock.h" #include "debug.h" +#include "place_overlap.h" extern ObDock *dock; -static Rect *pick_pointer_head(ObClient *c) +static Rect *choose_pointer_monitor(ObClient *c) { return screen_area(c->desktop, screen_monitor_pointer(), NULL); } -/* use the following priority lists for pick_head() +/* use the following priority lists for choose_monitor() When a window is being placed in the FOREGROUND, use a monitor chosen in the following order: @@ -155,8 +156,8 @@ gint cmp_background(const void *a, const void *b) } /*! Pick a monitor to place a window on. */ -static Rect *pick_head(ObClient *c, gboolean foreground, - ObAppSettings *settings) +static Rect* choose_monitor(ObClient *c, gboolean client_to_be_foregrounded, + ObAppSettings *settings) { Rect *area; ObPlaceHead *choice; @@ -229,7 +230,7 @@ static Rect *pick_head(ObClient *c, gboolean foreground, } qsort(choice, screen_num_monitors, sizeof(ObPlaceHead), - foreground ? cmp_foreground : cmp_background); + client_to_be_foregrounded ? cmp_foreground : cmp_background); /* save the areas of the monitors in order of their being chosen */ for (i = 0; i < screen_num_monitors; ++i) @@ -255,205 +256,12 @@ static Rect *pick_head(ObClient *c, gboolean foreground, return area; } -static gboolean place_random(ObClient *client, Rect *area, gint *x, gint *y) +static gboolean place_under_mouse(ObClient *client, gint *x, gint *y, + Size frame_size) { - gint l, r, t, b; - - ob_debug("placing randomly"); - - l = area->x; - t = area->y; - r = area->x + area->width - client->frame->area.width; - b = area->y + area->height - client->frame->area.height; - - if (r > l) *x = g_random_int_range(l, r + 1); - else *x = area->x; - if (b > t) *y = g_random_int_range(t, b + 1); - else *y = area->y; - - return TRUE; -} - -static GSList* area_add(GSList *list, Rect *a) -{ - Rect *r = g_slice_new(Rect); - *r = *a; - return g_slist_prepend(list, r); -} - -static GSList* area_remove(GSList *list, Rect *a) -{ - GSList *sit; - GSList *result = NULL; - - for (sit = list; sit; sit = g_slist_next(sit)) { - Rect *r = sit->data; - - if (!RECT_INTERSECTS_RECT(*r, *a)) { - result = g_slist_prepend(result, r); - /* dont free r, it's moved to the result list */ - } else { - Rect isect, extra; - - /* Use an intersection of a and r to determine the space - around r that we can use. - - NOTE: the spaces calculated can overlap. - */ - - RECT_SET_INTERSECTION(isect, *r, *a); - - if (RECT_LEFT(isect) > RECT_LEFT(*r)) { - RECT_SET(extra, r->x, r->y, - RECT_LEFT(isect) - r->x, r->height); - result = area_add(result, &extra); - } - - if (RECT_TOP(isect) > RECT_TOP(*r)) { - RECT_SET(extra, r->x, r->y, - r->width, RECT_TOP(isect) - r->y + 1); - result = area_add(result, &extra); - } - - if (RECT_RIGHT(isect) < RECT_RIGHT(*r)) { - RECT_SET(extra, RECT_RIGHT(isect) + 1, r->y, - RECT_RIGHT(*r) - RECT_RIGHT(isect), r->height); - result = area_add(result, &extra); - } - - if (RECT_BOTTOM(isect) < RECT_BOTTOM(*r)) { - RECT_SET(extra, r->x, RECT_BOTTOM(isect) + 1, - r->width, RECT_BOTTOM(*r) - RECT_BOTTOM(isect)); - result = area_add(result, &extra); - } - - /* 'r' is not being added to the result list, so free it */ - g_slice_free(Rect, r); - } - } - g_slist_free(list); - return result; -} - -enum { - IGNORE_FULLSCREEN = 1, - IGNORE_MAXIMIZED = 2, - IGNORE_MENUTOOL = 3, - /*IGNORE_SHADED = 3,*/ - IGNORE_NONGROUP = 4, - IGNORE_BELOW = 5, - /*IGNORE_NONFOCUS = 1 << 5,*/ - IGNORE_DOCK = 6, - IGNORE_END = 7 -}; - -static gboolean place_nooverlap(ObClient *c, Rect *area, gint *x, gint *y) -{ - gint ignore; - gboolean ret; - gint maxsize; - GSList *spaces = NULL, *sit, *maxit; - - ob_debug("placing nonoverlap"); - - ret = FALSE; - maxsize = 0; - maxit = NULL; - - /* try ignoring different things to find empty space */ - for (ignore = 0; ignore < IGNORE_END && !ret; ignore++) { - GList *it; - - /* add the whole monitor */ - spaces = area_add(spaces, area); - - /* go thru all the windows */ - for (it = client_list; it; it = g_list_next(it)) { - ObClient *test = it->data; - - /* should we ignore this client? */ - if (screen_showing_desktop) continue; - if (c == test) continue; - if (test->iconic) continue; - if (c->desktop != DESKTOP_ALL) { - if (test->desktop != c->desktop && - test->desktop != DESKTOP_ALL) continue; - } else { - if (test->desktop != screen_desktop && - test->desktop != DESKTOP_ALL) continue; - } - if (test->type == OB_CLIENT_TYPE_SPLASH || - test->type == OB_CLIENT_TYPE_DESKTOP) continue; - - - if ((ignore >= IGNORE_FULLSCREEN) && - test->fullscreen) continue; - if ((ignore >= IGNORE_MAXIMIZED) && - test->max_horz && test->max_vert) continue; - if ((ignore >= IGNORE_MENUTOOL) && - (test->type == OB_CLIENT_TYPE_MENU || - test->type == OB_CLIENT_TYPE_TOOLBAR) && - client_has_parent(c)) continue; - /* - if ((ignore >= IGNORE_SHADED) && - test->shaded) continue; - */ - if ((ignore >= IGNORE_NONGROUP) && - client_has_group_siblings(c) && - test->group != c->group) continue; - if ((ignore >= IGNORE_BELOW) && - test->layer < c->layer) continue; - /* - if ((ignore >= IGNORE_NONFOCUS) && - focus_client != test) continue; - */ - /* don't ignore this window, so remove it from the available - area */ - spaces = area_remove(spaces, &test->frame->area); - } - - if (ignore < IGNORE_DOCK) { - Rect a; - dock_get_area(&a); - spaces = area_remove(spaces, &a); - } - - for (sit = spaces; sit; sit = g_slist_next(sit)) { - Rect *r = sit->data; - - if (r->width >= c->frame->area.width && - r->height >= c->frame->area.height && - r->width * r->height > maxsize) - { - maxsize = r->width * r->height; - maxit = sit; - } - } - - if (maxit) { - Rect *r = maxit->data; - - /* center it in the area */ - *x = r->x; - *y = r->y; - if (config_place_center) { - *x += (r->width - c->frame->area.width) / 2; - *y += (r->height - c->frame->area.height) / 2; - } - ret = TRUE; - } - - while (spaces) { - g_slice_free(Rect, spaces->data); - spaces = g_slist_delete_link(spaces, spaces); - } - } - - return ret; -} + if (config_place_policy != OB_PLACE_POLICY_MOUSE) + return FALSE; -static gboolean place_under_mouse(ObClient *client, gint *x, gint *y) -{ gint l, r, t, b; gint px, py; Rect *area; @@ -462,16 +270,16 @@ static gboolean place_under_mouse(ObClient *client, gint *x, gint *y) if (!screen_pointer_pos(&px, &py)) return FALSE; - area = pick_pointer_head(client); + area = choose_pointer_monitor(client); l = area->x; t = area->y; - r = area->x + area->width - client->frame->area.width; - b = area->y + area->height - client->frame->area.height; + r = area->x + area->width - frame_size.width; + b = area->y + area->height - frame_size.height; - *x = px - client->area.width / 2 - client->frame->size.left; + *x = px - frame_size.width / 2; *x = MIN(MAX(*x, l), r); - *y = py - client->area.height / 2 - client->frame->size.top; + *y = py - frame_size.height / 2; *y = MIN(MAX(*y, t), b); g_slice_free(Rect, area); @@ -479,11 +287,12 @@ static gboolean place_under_mouse(ObClient *client, gint *x, gint *y) return TRUE; } -static gboolean place_per_app_setting(ObClient *client, Rect *screen, - gint *x, gint *y, - ObAppSettings *settings) +static gboolean place_per_app_setting_position(ObClient *client, Rect *screen, + gint *x, gint *y, + ObAppSettings *settings, + Size frame_size) { - if (!settings || (settings && !settings->pos_given)) + if (!settings || !settings->pos_given) return FALSE; ob_debug("placing by per-app settings"); @@ -491,7 +300,7 @@ static gboolean place_per_app_setting(ObClient *client, Rect *screen, if (settings->position.x.center) *x = screen->x + screen->width / 2 - client->area.width / 2; else if (settings->position.x.opposite) - *x = screen->x + screen->width - client->frame->area.width - + *x = screen->x + screen->width - frame_size.width - settings->position.x.pos; else *x = screen->x + settings->position.x.pos; @@ -501,7 +310,7 @@ static gboolean place_per_app_setting(ObClient *client, Rect *screen, if (settings->position.y.center) *y = screen->y + screen->height / 2 - client->area.height / 2; else if (settings->position.y.opposite) - *y = screen->y + screen->height - client->frame->area.height - + *y = screen->y + screen->height - frame_size.height - settings->position.y.pos; else *y = screen->y + settings->position.y.pos; @@ -511,8 +320,37 @@ static gboolean place_per_app_setting(ObClient *client, Rect *screen, return TRUE; } +static void place_per_app_setting_size(ObClient *client, Rect *screen, + gint *w, gint *h, + ObAppSettings *settings) +{ + if (!settings || !settings->size_given) + return; + + ob_debug("sizing by per-app settings"); + + g_assert(settings->width_num > 0); + g_assert(settings->width_denom >= 0); + g_assert(settings->height_num > 0); + g_assert(settings->height_denom >= 0); + + if (!settings->width_denom) + *w = settings->width_num; + else { + *w = screen->width * settings->width_num / settings->width_denom; + *w = MIN(*w, screen->width); + } + + if (!settings->height_denom) + *h = settings->height_num; + else { + *h = screen->height * settings->height_num / settings->height_denom; + *h = MIN(*h, screen->height); + } +} + static gboolean place_transient_splash(ObClient *client, Rect *area, - gint *x, gint *y) + gint *x, gint *y, Size frame_size) { if (client->type == OB_CLIENT_TYPE_DIALOG) { GSList *it; @@ -538,8 +376,8 @@ static gboolean place_transient_splash(ObClient *client, Rect *area, } } if (!first) { - *x = ((r + 1 - l) - client->frame->area.width) / 2 + l; - *y = ((b + 1 - t) - client->frame->area.height) / 2 + t; + *x = ((r + 1 - l) - frame_size.width) / 2 + l; + *y = ((b + 1 - t) - frame_size.height) / 2 + t; return TRUE; } } @@ -550,42 +388,118 @@ static gboolean place_transient_splash(ObClient *client, Rect *area, { ob_debug("placing dialog or splash"); - *x = (area->width - client->frame->area.width) / 2 + area->x; - *y = (area->height - client->frame->area.height) / 2 + area->y; + *x = (area->width - frame_size.width) / 2 + area->x; + *y = (area->height - frame_size.height) / 2 + area->y; return TRUE; } return FALSE; } -/*! Return TRUE if openbox chose the position for the window, and FALSE if - the application chose it */ -gboolean place_client(ObClient *client, gboolean foreground, gint *x, gint *y, - ObAppSettings *settings) +static gboolean place_least_overlap(ObClient *c, Rect *head, int *x, int *y, + Size frame_size) +{ + /* Assemble the list of windows that could overlap with @c in the user's + current view. */ + GSList* potential_overlap_clients = NULL; + gint n_client_rects = 0; + + /* if we're "showing desktop", ignore all existing windows */ + if (!screen_showing_desktop) { + GList* it; + for (it = client_list; it != NULL; it = g_list_next(it)) { + ObClient* maybe_client = (ObClient*)it->data; + if (maybe_client == c) + continue; + if (maybe_client->iconic) + continue; + if (!client_occupies_space(maybe_client)) + continue; + if (c->desktop != DESKTOP_ALL) { + if (maybe_client->desktop != c->desktop && + maybe_client->desktop != DESKTOP_ALL) + continue; + } else { + if (maybe_client->desktop != screen_desktop && + maybe_client->desktop != DESKTOP_ALL) + continue; + } + + potential_overlap_clients = g_slist_prepend( + potential_overlap_clients, maybe_client); + n_client_rects += 1; + } + } + Rect client_rects[n_client_rects]; + + GSList* it; + guint i = 0; + for (it = potential_overlap_clients; it != NULL; it = g_slist_next(it)) { + ObClient* potential_overlap_client = (ObClient*)it->data; + client_rects[i] = potential_overlap_client->frame->area; + i += 1; + } + g_slist_free(potential_overlap_clients); + + Point result; + place_overlap_find_least_placement(client_rects, n_client_rects, head, + &frame_size, &result); + *x = result.x; + *y = result.y; + + return TRUE; +} + +static gboolean should_set_client_position(ObClient *client, + ObAppSettings *settings) +{ + gboolean has_position = settings && settings->pos_given; + gboolean has_forced_position = has_position && settings->pos_force; + + gboolean user_positioned = client->positioned & USPosition; + if (user_positioned && !has_forced_position) + return FALSE; + + gboolean program_positioned = client->positioned & PPosition; + if (program_positioned && !has_position) + return FALSE; + + return TRUE; +} + +gboolean place_client(ObClient *client, gboolean client_to_be_foregrounded, + Rect* client_area, ObAppSettings *settings) { - Rect *area; gboolean ret; + Rect *monitor_area; + int *x, *y, *w, *h; + Size frame_size; + + monitor_area = choose_monitor(client, client_to_be_foregrounded, settings); - /* per-app settings override program specified position - * but not user specified, unless pos_force is enabled */ - if (((client->positioned & USPosition) && - !(settings && settings->pos_given && settings->pos_force)) || - ((client->positioned & PPosition) && - !(settings && settings->pos_given))) + w = &client_area->width; + h = &client_area->height; + place_per_app_setting_size(client, monitor_area, w, h, settings); + + if (!should_set_client_position(client, settings)) return FALSE; - area = pick_head(client, foreground, settings); + x = &client_area->x; + y = &client_area->y; + + SIZE_SET(frame_size, + *w + client->frame->size.left + client->frame->size.right, + *h + client->frame->size.top + client->frame->size.bottom); - /* try a number of methods */ - ret = place_per_app_setting(client, area, x, y, settings) || - place_transient_splash(client, area, x, y) || - (config_place_policy == OB_PLACE_POLICY_MOUSE && - place_under_mouse(client, x, y)) || - place_nooverlap(client, area, x, y) || - place_random(client, area, x, y); + ret = + place_per_app_setting_position(client, monitor_area, x, y, settings, + frame_size) || + place_transient_splash(client, monitor_area, x, y, frame_size) || + place_under_mouse(client, x, y, frame_size) || + place_least_overlap(client, monitor_area, x, y, frame_size); g_assert(ret); - g_slice_free(Rect, area); + g_slice_free(Rect, monitor_area); /* get where the client should be */ frame_frame_gravity(client->frame, x, y); diff --git a/openbox/place.h b/openbox/place.h index 94e2dc0f..3bc679e0 100644 --- a/openbox/place.h +++ b/openbox/place.h @@ -20,6 +20,8 @@ #ifndef ob__place_h #define ob__place_h +#include "geom.h" + #include struct _ObClient; @@ -39,7 +41,10 @@ typedef enum OB_PLACE_MONITOR_PRIMARY } ObPlaceMonitor; -gboolean place_client(struct _ObClient *client, gboolean foreground, - gint *x, gint *y, struct _ObAppSettings *settings); +/*! Return TRUE if openbox chose the position for the window, and FALSE if + the application chose it */ +gboolean place_client(struct _ObClient *client, + gboolean client_to_be_foregrounded, + Rect* client_area, struct _ObAppSettings *settings); #endif diff --git a/openbox/place_overlap.c b/openbox/place_overlap.c new file mode 100644 index 00000000..ef73bd8d --- /dev/null +++ b/openbox/place_overlap.c @@ -0,0 +1,175 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + overlap.c for the Openbox window manager + Copyright (c) 2011 Ian Zimmerman + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, 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 General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#include "config.h" +#include "geom.h" +#include "place_overlap.h" + +#include + +static void make_grid(const Rect* client_rects, int n_client_rects, + const Rect* bound, int* x_edges, int* y_edges, + int max_edges); + +static int best_direction(const Point* grid_point, + const Rect* client_rects, int n_client_rects, + const Rect* bound, const Size* req_size, + Point* best_top_left); + +/* Choose the placement on a grid with least overlap */ + +void place_overlap_find_least_placement(const Rect* client_rects, + int n_client_rects, + Rect *const bound, + const Size* req_size, + Point* result) +{ + POINT_SET(*result, 0, 0); + int overlap = G_MAXINT; + int max_edges = 2 * (n_client_rects + 1); + + int x_edges[max_edges]; + int y_edges[max_edges]; + make_grid(client_rects, n_client_rects, bound, + x_edges, y_edges, max_edges); + int i; + for (i = 0; i < max_edges; ++i) { + if (x_edges[i] == G_MAXINT) + break; + int j; + for (j = 0; j < max_edges; ++j) { + if (y_edges[j] == G_MAXINT) + break; + Point grid_point = {.x = x_edges[i], .y = y_edges[j]}; + Point best_top_left; + int this_overlap = + best_direction(&grid_point, client_rects, n_client_rects, + bound, req_size, &best_top_left); + if (this_overlap < overlap) { + overlap = this_overlap; + *result = best_top_left; + } + if (overlap == 0) + break; + } + if (overlap == 0) + break; + } +} + +static int compare_ints(const void* a, const void* b) +{ + const int* ia = (const int*)a; + const int* ib = (const int*)b; + return *ia - *ib; +} + +static void uniquify(int* edges, int n_edges) +{ + int i = 0; + int j = 0; + + while (j < n_edges) { + int last = edges[j++]; + edges[i++] = last; + while (j < n_edges && edges[j] == last) + ++j; + } + /* fill the rest with nonsense */ + for (; i < n_edges ; ++i) + edges[i] = G_MAXINT; +} + +static void make_grid(const Rect* client_rects, int n_client_rects, + const Rect* bound, int* x_edges, int* y_edges, + int max_edges) +{ + int i; + int n_edges = 0; + for (i = 0; i < n_client_rects; ++i) { + if (!RECT_INTERSECTS_RECT(client_rects[i], *bound)) + continue; + x_edges[n_edges] = client_rects[i].x; + y_edges[n_edges++] = client_rects[i].y; + x_edges[n_edges] = client_rects[i].x + client_rects[i].width; + y_edges[n_edges++] = client_rects[i].y + client_rects[i].height; + } + x_edges[n_edges] = bound->x; + y_edges[n_edges++] = bound->y; + x_edges[n_edges] = bound->x + bound->width; + y_edges[n_edges++] = bound->y + bound->height; + for (i = n_edges; i < max_edges; ++i) + x_edges[i] = y_edges[i] = G_MAXINT; + qsort(x_edges, n_edges, sizeof(int), compare_ints); + uniquify(x_edges, n_edges); + qsort(y_edges, n_edges, sizeof(int), compare_ints); + uniquify(y_edges, n_edges); +} + +static int total_overlap(const Rect* client_rects, int n_client_rects, + const Rect* proposed_rect) +{ + int overlap = 0; + int i; + for (i = 0; i < n_client_rects; ++i) { + if (!RECT_INTERSECTS_RECT(*proposed_rect, client_rects[i])) + continue; + Rect rtemp; + RECT_SET_INTERSECTION(rtemp, *proposed_rect, client_rects[i]); + overlap += RECT_AREA(rtemp); + } + return overlap; +} + +/* Given a list of Rect RECTS, a Point PT and a Size size, determine the + direction from PT which results in the least total overlap with RECTS + if a rectangle is placed in that direction. Return the top/left + Point of such rectangle and the resulting overlap amount. Only + consider placements within BOUNDS. */ + +#define NUM_DIRECTIONS 4 + +static int best_direction(const Point* grid_point, + const Rect* client_rects, int n_client_rects, + const Rect* bound, const Size* req_size, + Point* best_top_left) +{ + static const Size directions[NUM_DIRECTIONS] = { + {0, 0}, {0, -1}, {-1, 0}, {-1, -1} + }; + int overlap = G_MAXINT; + int i; + for (i = 0; i < NUM_DIRECTIONS; ++i) { + Point pt = { + .x = grid_point->x + (req_size->width * directions[i].width), + .y = grid_point->y + (req_size->height * directions[i].height) + }; + Rect r; + RECT_SET(r, pt.x, pt.y, req_size->width, req_size->height); + if (!RECT_CONTAINS_RECT(*bound, r)) + continue; + int this_overlap = total_overlap(client_rects, n_client_rects, &r); + if (this_overlap < overlap) { + overlap = this_overlap; + *best_top_left = pt; + } + if (overlap == 0) + break; + } + return overlap; +} diff --git a/openbox/place_overlap.h b/openbox/place_overlap.h new file mode 100644 index 00000000..9ceed34e --- /dev/null +++ b/openbox/place_overlap.h @@ -0,0 +1,25 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + overlap.h for the Openbox window manager + Copyright (c) 2011 Ian Zimmerman + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, 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 General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#include "geom.h" + +void place_overlap_find_least_placement(const Rect* client_rects, + int n_client_rects, + Rect *const bounds, + const Size* req_size, + Point* result); diff --git a/openbox/prompt.c b/openbox/prompt.c index 1aa79d2a..88c74b68 100644 --- a/openbox/prompt.c +++ b/openbox/prompt.c @@ -75,11 +75,12 @@ void prompt_startup(gboolean reconfig) void prompt_shutdown(gboolean reconfig) { - GList *it; + GList *it, *next; if (!reconfig) { - for (it = prompt_list; it; it = g_list_next(it)) { + for (it = prompt_list; it; it = next) { ObPrompt *p = it->data; + next = it->next; if (p->cleanup) p->cleanup(p, p->data); } @@ -594,7 +595,7 @@ static void prompt_run_callback(ObPrompt *self, gint result) { prompt_ref(self); if (self->func) { - gboolean clean = self->func(self, self->focus->result, self->data); + gboolean clean = self->func(self, result, self->data); if (clean && self->cleanup) self->cleanup(self, self->data); } diff --git a/openbox/screen.c b/openbox/screen.c index ffe74a07..33acb4a1 100644 --- a/openbox/screen.c +++ b/openbox/screen.c @@ -305,6 +305,8 @@ gboolean screen_annex(void) supported[i++] = OBT_PROP_ATOM(OB_APP_TITLE); supported[i++] = OBT_PROP_ATOM(OB_APP_NAME); supported[i++] = OBT_PROP_ATOM(OB_APP_CLASS); + supported[i++] = OBT_PROP_ATOM(OB_APP_GROUP_NAME); + supported[i++] = OBT_PROP_ATOM(OB_APP_GROUP_CLASS); supported[i++] = OBT_PROP_ATOM(OB_APP_TYPE); g_assert(i == num_support); @@ -1369,6 +1371,14 @@ static void get_xinerama_screens(Rect **xin_areas, guint *nxin) b = MAX(b, (*xin_areas)[i].y + (*xin_areas)[i].height - 1); } RECT_SET((*xin_areas)[*nxin], l, t, r - l + 1, b - t + 1); + + for (i = 0; i < *nxin; ++i) + ob_debug("Monitor %d @ %d,%d %dx%d\n", i, + (*xin_areas)[i].x, (*xin_areas)[i].y, + (*xin_areas)[i].width, (*xin_areas)[i].height); + ob_debug("Full desktop @ %d,%d %dx%d\n", + (*xin_areas)[i].x, (*xin_areas)[i].y, + (*xin_areas)[i].width, (*xin_areas)[i].height); } void screen_update_areas(void) @@ -1628,28 +1638,153 @@ Rect* screen_area(guint desktop, guint head, Rect *search) return a; } +typedef struct { + Rect r; + gboolean subtract; +} RectArithmetic; + guint screen_find_monitor(const Rect *search) { guint i; - guint most = screen_num_monitors; - guint mostv = 0; + guint mostpx_index = screen_num_monitors; + guint mostpx = 0; + guint closest_distance_index = screen_num_monitors; + guint closest_distance = G_MAXUINT; + GSList *counted = NULL; + + /* we want to count the number of pixels search has on each monitor, but not + double count. so if a pixel is counted on monitor A then we should not + count it again on monitor B. in the end we want to return the monitor + that had the most pixels counted under this scheme. + + this assumes that monitors earlier in the list are more desirable to be + considered the search area's monitor. we try the configured primary + monitor first, so it gets the highest preference. + + if we have counted an area A, then we want to subtract the intersection + of A with the area on future monitors. + but now consider if we count an area B that intersects A. we want to + subtract the area B from that counted on future monitors, but not + subtract the intersection of A and B twice! so we would add the + intersection of A and B back, to account for it being subtracted both + for A and B. + + this is the idea behind the algorithm. we always subtract the full area + for monitor M intersected with the search area. we'll call that AREA. + but then we go through the list |counted| and for each rectangle in + the list that is being subtracted from future monitors, we insert a + request to add back the intersection of the subtracted rect with AREA. + vice versa for a rect in |counted| that is getting added back. + */ + + if (config_primary_monitor_index < screen_num_monitors) { + const Rect *monitor; + Rect on_current_monitor; + glong area; + + monitor = screen_physical_area_monitor(config_primary_monitor_index); + + if (RECT_INTERSECTS_RECT(*monitor, *search)) { + RECT_SET_INTERSECTION(on_current_monitor, *monitor, *search); + area = RECT_AREA(on_current_monitor); + + if (area > mostpx) { + mostpx = area; + mostpx_index = config_primary_monitor_index; + } + + /* add the intersection rect on the current monitor to the + counted list. that's easy for the first one, we just mark it for + subtraction */ + { + RectArithmetic *ra = g_slice_new(RectArithmetic); + ra->r = on_current_monitor; + ra->subtract = TRUE; + counted = g_slist_prepend(counted, ra); + } + } + } for (i = 0; i < screen_num_monitors; ++i) { - const Rect *area = screen_physical_area_monitor(i); - if (RECT_INTERSECTS_RECT(*area, *search)) { - Rect r; - guint v; + const Rect *monitor; + Rect on_current_monitor; + glong area; + GSList *it; - RECT_SET_INTERSECTION(r, *area, *search); - v = r.width * r.height; + monitor = screen_physical_area_monitor(i); - if (v > mostv) { - mostv = v; - most = i; + if (!RECT_INTERSECTS_RECT(*monitor, *search)) { + /* If we don't intersect then find the distance between the search + rect and the monitor. We'll use the closest monitor from this + metric if none of the monitors intersect. */ + guint distance = rect_manhatten_distance(*monitor, *search); + + if (distance < closest_distance) { + closest_distance = distance; + closest_distance_index = i; } + continue; + } + + if (i == config_primary_monitor_index) + continue; /* already did this one */ + + RECT_SET_INTERSECTION(on_current_monitor, *monitor, *search); + area = RECT_AREA(on_current_monitor); + + /* remove pixels we already counted on any previous monitors. */ + for (it = counted; it; it = g_slist_next(it)) { + RectArithmetic *ra = it->data; + Rect intersection; + + RECT_SET_INTERSECTION(intersection, ra->r, *search); + if (ra->subtract) area -= RECT_AREA(intersection); + else area += RECT_AREA(intersection); + } + + if (area > mostpx) { + mostpx = area; + mostpx_index = i; + } + + /* add the intersection rect on the current monitor I to the counted + list. + but now we need to compensate for every rectangle R already in the + counted list, and add a new rect R' that is the intersection of + R and I, but with the reverse subtraction/addition operation. + */ + for (it = counted; it; it = g_slist_next(it)) { + RectArithmetic *saved = it->data; + + if (!RECT_INTERSECTS_RECT(saved->r, on_current_monitor)) + continue; + /* we are going to subtract our rect from future monitors, but + part of it may already be being subtracted/added, so compensate + to not double add/subtract. */ + RectArithmetic *reverse = g_slice_new(RectArithmetic); + RECT_SET_INTERSECTION(reverse->r, saved->r, on_current_monitor); + reverse->subtract = !saved->subtract; + /* prepend so we can continue thru the list uninterupted */ + counted = g_slist_prepend(counted, reverse); + } + { + RectArithmetic *ra = g_slice_new(RectArithmetic); + ra->r = on_current_monitor; + ra->subtract = TRUE; + counted = g_slist_prepend(counted, ra); } } - return most < screen_num_monitors ? most : screen_monitor_primary(FALSE); + + while (counted) { + g_slice_free(RectArithmetic, counted->data); + counted = g_slist_delete_link(counted, counted); + } + + if (mostpx_index < screen_num_monitors) + return mostpx_index; + + g_assert(closest_distance_index < screen_num_monitors); + return closest_distance_index; } const Rect* screen_physical_area_all_monitors(void) diff --git a/po/de.po b/po/de.po index 8d12a1f4..c4dec13e 100644 --- a/po/de.po +++ b/po/de.po @@ -6,15 +6,16 @@ # Peter Schwindt # Finn Zirngibl , 2008 # Florian Walch , 2008 +# Mario Blättermann , 2011. # msgid "" msgstr "" -"Project-Id-Version: Openbox 3.4.7\n" +"Project-Id-Version: Openbox 3.5.0\n" "Report-Msgid-Bugs-To: http://bugzilla.icculus.org\n" -"POT-Creation-Date: 2011-08-01 18:20+0200\n" -"PO-Revision-Date: 2008-03-13 13:38+0100\n" -"Last-Translator: Florian Walch \n" -"Language-Team: \n" +"POT-Creation-Date: 2011-11-17 22:54+0100\n" +"PO-Revision-Date: 2011-11-17 22:54+0100\n" +"Last-Translator: Mario Blättermann \n" +"Language-Team: \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -24,7 +25,7 @@ msgstr "" #: openbox/actions.c:198 #, c-format msgid "Invalid action \"%s\" requested. No such action exists." -msgstr "Unzulässige Aktion \"%s\" angefordert. Diese Aktion existiert nicht." +msgstr "Unzulässige Aktion »%s« angefordert. Diese Aktion existiert nicht." #: openbox/actions/execute.c:147 msgid "No" @@ -41,9 +42,9 @@ msgstr "Ausführen" #: openbox/actions/execute.c:161 #, c-format msgid "Failed to convert the path \"%s\" from utf8" -msgstr "Konnte Pfad \"%s\" nicht von UTF-8 konvertieren" +msgstr "Pfad »%s« konnte nicht aus UTF-8 konvertiert werden" -#: openbox/actions/exit.c:69 openbox/client.c:3550 +#: openbox/actions/exit.c:69 openbox/client.c:3567 msgid "Cancel" msgstr "Abbrechen" @@ -65,61 +66,61 @@ msgstr "Sind Sie sicher, dass Openbox beendet werden soll?" #: openbox/actions/exit.c:79 msgid "Exit Openbox" -msgstr "Beende Openbox" +msgstr "Openbox beenden" -#: openbox/client.c:2037 +#: openbox/client.c:2054 msgid "Unnamed Window" msgstr "Unbenanntes Fenster" -#: openbox/client.c:2051 openbox/client.c:2082 +#: openbox/client.c:2068 openbox/client.c:2099 msgid "Killing..." -msgstr "Wird beendet..." +msgstr "Wird beendet …" -#: openbox/client.c:2053 openbox/client.c:2084 +#: openbox/client.c:2070 openbox/client.c:2101 msgid "Not Responding" msgstr "Reagiert nicht" -#: openbox/client.c:3539 +#: openbox/client.c:3556 #, c-format msgid "" "The window \"%s\" does not seem to be responding. Do you want to force it " "to exit by sending the %s signal?" msgstr "" -"Das Fenster \"%s\" scheint nicht zu reagieren. Wollen Sie die Beendigung " -"durch das Senden des %s-Signals erzwingen?" +"Das Fenster »%s« scheint nicht zu reagieren. Wollen Sie die Beendigung durch " +"das Senden des %s-Signals erzwingen?" -#: openbox/client.c:3541 +#: openbox/client.c:3558 msgid "End Process" -msgstr "Beende Prozess" +msgstr "Prozess beenden" -#: openbox/client.c:3545 +#: openbox/client.c:3562 #, c-format msgid "" "The window \"%s\" does not seem to be responding. Do you want to disconnect " "it from the X server?" msgstr "" -"Das Fenster \"%s\" scheint nicht zu reagieren. Soll es vom X-Server getrennt " +"Das Fenster »%s« scheint nicht zu reagieren. Soll es vom X-Server getrennt " "werden?" -#: openbox/client.c:3547 +#: openbox/client.c:3564 msgid "Disconnect" msgstr "Trennen" #: openbox/client_list_combined_menu.c:93 openbox/client_list_menu.c:90 msgid "Go there..." -msgstr "Hierher wechseln..." +msgstr "Hierher wechseln …" #: openbox/client_list_combined_menu.c:100 msgid "Manage desktops" -msgstr "Desktops verwalten" +msgstr "Arbeitsflächen verwalten" #: openbox/client_list_combined_menu.c:101 openbox/client_list_menu.c:166 msgid "_Add new desktop" -msgstr "_Neuen Desktop hinzufügen" +msgstr "_Neue Arbeitsfläche hinzufügen" #: openbox/client_list_combined_menu.c:102 openbox/client_list_menu.c:167 msgid "_Remove last desktop" -msgstr "_Letzten Desktop entfernen" +msgstr "_Letzte Arbeitsfläche entfernen" #: openbox/client_list_combined_menu.c:157 msgid "Windows" @@ -127,15 +128,15 @@ msgstr "Fenster" #: openbox/client_list_menu.c:214 msgid "Desktops" -msgstr "Desktops" +msgstr "Arbeitsflächen" #: openbox/client_menu.c:259 msgid "All desktops" -msgstr "Alle Desktops" +msgstr "Alle Arbeitsflächen" #: openbox/client_menu.c:371 msgid "_Layer" -msgstr "_Layer" +msgstr "_Ebene" #: openbox/client_menu.c:376 msgid "Always on _top" @@ -151,15 +152,15 @@ msgstr "Immer im _Hintergrund" #: openbox/client_menu.c:380 msgid "_Send to desktop" -msgstr "_An Desktop senden" +msgstr "_An Arbeitsfläche senden" #: openbox/client_menu.c:384 msgid "Client menu" -msgstr "Client menu" +msgstr "Client-Menü" #: openbox/client_menu.c:394 msgid "R_estore" -msgstr "Wi_ederherstellen" +msgstr "_Wiederherstellen" #: openbox/client_menu.c:398 msgid "_Move" @@ -192,25 +193,27 @@ msgstr "_Schließen" #: openbox/config.c:503 #, c-format msgid "Invalid context \"%s\" in mouse binding" -msgstr "Maus-Binding enthält ungültigen Kontext \"%s\"" +msgstr "Maus-Zuordnung enthält ungültigen Kontext »%s«" #: openbox/config.c:857 #, c-format msgid "Invalid button \"%s\" specified in config file" -msgstr "Unzulässiger Button \"%s\" in der Konfigurationsdatei angegeben" +msgstr "Unzulässige Taste »%s« in der Konfigurationsdatei angegeben" #: openbox/config.c:882 msgid "" "Openbox was compiled without Imlib2 image loading support. Icons in menus " "will not be loaded." msgstr "" +"Openbox wurde ohne die Unterstützung der Imlib2 zum Laden von Grafiken " +"kompiliert. Symbole in Menüs werden nicht geladen." -#: openbox/debug.c:55 +#: openbox/debug.c:57 #, c-format msgid "Unable to make directory '%s': %s" -msgstr "Das Verzeichnis '%s' konnte nicht angelegt werden: %s" +msgstr "Der Ordner »%s« konnte nicht angelegt werden: %s" -#: openbox/debug.c:138 openbox/openbox.c:372 +#: openbox/debug.c:195 openbox/openbox.c:372 msgid "Close" msgstr "Schließen" @@ -221,45 +224,46 @@ msgstr "Konflikt mit Tastenkombination in der Konfigurationsdatei" #: openbox/menu.c:94 openbox/menu.c:106 #, c-format msgid "Unable to find a valid menu file \"%s\"" -msgstr "Konnte keine gültige Menü-Datei \"%s\" finden" +msgstr "Es konnte keine gültige Menü-Datei »%s« gefunden werden" #: openbox/menu.c:158 #, c-format msgid "Failed to execute command for pipe-menu \"%s\": %s" -msgstr "Konnte Befehl \"%s\" für pipe-menu nicht ausführen: %s" +msgstr "Befehl »%s« für Pipe-Menü konnte nicht ausgeführt werden: %s" #: openbox/menu.c:172 #, c-format msgid "Invalid output from pipe-menu \"%s\"" -msgstr "Ungültige Ausgabe vom pipe-menu \"%s\"" +msgstr "Ungültige Ausgabe vom Pipe-Menü »%s«" #: openbox/menu.c:185 #, c-format msgid "Attempted to access menu \"%s\" but it does not exist" msgstr "" -"Auf das Menü \"%s\" konnte nicht zugegriffen werden, da es nicht existiert" +"Auf das Menü »%s« konnte nicht zugegriffen werden, da es nicht existiert" #: openbox/menu.c:400 openbox/menu.c:401 msgid "More..." -msgstr "Mehr..." +msgstr "Mehr …" -#: openbox/mouse.c:376 +#: openbox/mouse.c:382 #, c-format msgid "Invalid button \"%s\" in mouse binding" -msgstr "Maus-Binding enthält ungültigen Button \"%s\"" +msgstr "Maus-Zuordnung enthält ungültige Taste »%s«" #: openbox/openbox.c:137 #, c-format msgid "Unable to change to home directory \"%s\": %s" -msgstr "Konnte nicht in das Heimatverzeichnis \"%s\" wechseln: %s" +msgstr "Wechsel in den persönlichen Ordner »%s« ist gescheitert: %s" #: openbox/openbox.c:152 msgid "Failed to open the display from the DISPLAY environment variable." -msgstr "Konnte das Display aus der Umgebungsvariable DISPLAY nicht öffnen." +msgstr "" +"Die Anzeige aus der Umgebungsvariable DISPLAY konnte nicht geöffnet werden." #: openbox/openbox.c:182 msgid "Failed to initialize the obrender library." -msgstr "Konnte die obrender-Bibliothek nicht initialisieren." +msgstr "Die obrender-Bibliothek konnte nicht initialisiert werden." #: openbox/openbox.c:193 msgid "X server does not support locale." @@ -273,12 +277,12 @@ msgstr "" #: openbox/openbox.c:253 msgid "Unable to find a valid config file, using some simple defaults" msgstr "" -"Es wurde keine gültige Konfigurationsdatei gefunden, benutze einfache " -"Standardwerte" +"Es wurde keine gültige Konfigurationsdatei gefunden, es werden einfache " +"Standardwerte verwendet" #: openbox/openbox.c:286 msgid "Unable to load a theme." -msgstr "Konnte kein Thema laden." +msgstr "Es konnte kein Thema geladen werden." #: openbox/openbox.c:370 #, c-format @@ -287,9 +291,10 @@ msgid "" "configuration files. See stdout for more information. The last error seen " "was in file \"%s\" line %d, with message: %s" msgstr "" -"Beim Parsen der Openbox-Konfigurationsdateien wurden ein oder mehrere XML-" -"Syntaxfehler gefunden. Die Standardausgabe enthält weitere Informationen. " -"Der letzte Fehler wurde in der Datei \"%s\" in Zeile %d festgestellt: %s" +"Beim Verarbeiten der Openbox-Konfigurationsdateien wurden ein oder mehrere " +"XML-Syntaxfehler gefunden. Die Standardausgabe enthält weitere " +"Informationen. Der letzte Fehler wurde in der Datei »%s« in Zeile %d " +"festgestellt: %s" #: openbox/openbox.c:372 msgid "Openbox Syntax Error" @@ -298,8 +303,7 @@ msgstr "Openbox Syntax-Fehler" #: openbox/openbox.c:438 #, c-format msgid "Restart failed to execute new executable \"%s\": %s" -msgstr "" -"Neustart fehlgeschlagen, um die ausführbare Datei \"%s\" zu starten: %s" +msgstr "Neustart fehlgeschlagen, um die ausführbare Datei »%s« zu starten: %s" #: openbox/openbox.c:517 openbox/openbox.c:519 msgid "Copyright (c)" @@ -338,7 +342,8 @@ msgstr " --config-file DATEI Pfad zur Konfigurationsdatei\n" #: openbox/openbox.c:537 msgid " --sm-disable Disable connection to the session manager\n" -msgstr " --sm-disable Keine Verbindung zum Sitzungsmanager aufbauen\n" +msgstr "" +" --sm-disable Keine Verbindung zur Sitzungsverwaltung aufbauen\n" #: openbox/openbox.c:538 msgid "" @@ -350,7 +355,7 @@ msgstr "" #: openbox/openbox.c:539 msgid " --reconfigure Reload Openbox's configuration\n" -msgstr " --reconfigure Openbox's Konfiguration neu laden\n" +msgstr " --reconfigure Openbox-Konfiguration neu laden\n" #: openbox/openbox.c:540 msgid " --restart Restart Openbox\n" @@ -358,7 +363,7 @@ msgstr " --restart Openbox neu starten\n" #: openbox/openbox.c:541 msgid " --exit Exit Openbox\n" -msgstr " --exit Beende Openbox\n" +msgstr " --exit Openbox beenden\n" #: openbox/openbox.c:542 msgid "" @@ -366,7 +371,7 @@ msgid "" "Debugging options:\n" msgstr "" "\n" -"Debugging Optionen:\n" +"Debugging-Optionen:\n" #: openbox/openbox.c:543 msgid " --sync Run in synchronous mode\n" @@ -374,7 +379,7 @@ msgstr " --sync im Synchronisierungsmodus starten\n" #: openbox/openbox.c:544 msgid " --startup CMD Run CMD after starting\n" -msgstr "" +msgstr " --startup BEFEHL Befehl nach dem Start ausführen\n" #: openbox/openbox.c:545 msgid " --debug Display debugging output\n" @@ -383,13 +388,12 @@ msgstr " --debug Debugging-Informationen anzeigen\n" #: openbox/openbox.c:546 msgid " --debug-focus Display debugging output for focus handling\n" msgstr "" -" --debug-focus Debugging-Informationen für's Fokus-Handling anzeigen\n" +" --debug-focus Debugging-Informationen für Fokus-Handling anzeigen\n" #: openbox/openbox.c:547 -#, fuzzy msgid " --debug-session Display debugging output for session management\n" msgstr "" -" --debug-session Debugging-Informationen für's Session-Handling " +" --debug-session Debugging-Informationen für die Sitzungsverwaltung " "anzeigen\n" #: openbox/openbox.c:548 @@ -404,7 +408,7 @@ msgid "" "Please report bugs at %s\n" msgstr "" "\n" -"Bitte melden Sie Bugreports an: %s\n" +"Bitte melden Sie Fehler an: %s\n" #: openbox/openbox.c:632 openbox/openbox.c:666 #, c-format @@ -414,7 +418,7 @@ msgstr "%s erfordert einen Parameter\n" #: openbox/openbox.c:709 #, c-format msgid "Invalid command line argument \"%s\"\n" -msgstr "Ungültiges Kommandozeilen Argument \"%s\"\n" +msgstr "Ungültiges Befehlszeilenargument »%s«\n" #: openbox/screen.c:106 openbox/screen.c:191 #, c-format @@ -424,7 +428,8 @@ msgstr "Ein Fenstermanager läuft bereits auf Bildschirm %d" #: openbox/screen.c:127 #, c-format msgid "Could not acquire window manager selection on screen %d" -msgstr "Konnte die Fenstermanagerauswahl auf Bildschirm %d nicht reservieren" +msgstr "" +"Die Fenstermanagerauswahl auf Bildschirm %d konnte nicht reserviert werden" #: openbox/screen.c:150 #, c-format @@ -444,74 +449,42 @@ msgid_plural "" "Openbox is configured for %d desktops, but the current session has %d. " "Overriding the Openbox configuration." msgstr[0] "" -"Openbox wurde für %d Desktop konfiguriert, aber die aktuelle Sitzung hat %d. " -"Überschreibe die Openbox-Konfiguration." +"Openbox wurde für %d Arbeitsfläche konfiguriert, aber die aktuelle Sitzung " +"hat %d. Die Openbox-Konfiguration wird überschrieben." msgstr[1] "" -"Openbox wurde für %d Desktops konfiguriert, aber die aktuelle Sitzung hat " -"%d. Überschreibe die Openbox-Konfiguration." +"Openbox wurde für %d Arbeitsflächen konfiguriert, aber die aktuelle Sitzung " +"hat %d. Die Openbox-Konfiguration wird überschrieben." #: openbox/screen.c:1205 #, c-format msgid "desktop %i" -msgstr "Desktop %i" +msgstr "Arbeitsfläche %i" #: openbox/startupnotify.c:241 #, c-format msgid "Running %s" -msgstr "Starte %s" +msgstr "%s starten" #: openbox/translate.c:59 #, c-format msgid "Invalid modifier key \"%s\" in key/mouse binding" -msgstr "Ungültige Modifier-Taste \"%s\" in Tastenbelegung/Maus-Binding" +msgstr "Ungültige Modifier-Taste »%s« in Tastenbelegung/Maus-Zuordnung" #: openbox/translate.c:138 #, c-format msgid "Invalid key code \"%s\" in key binding" -msgstr "Ungültiger Tastencode \"%s\" in Tastenkombination" +msgstr "Ungültiger Tastencode »%s« in Tastenkombination" #: openbox/translate.c:145 #, c-format msgid "Invalid key name \"%s\" in key binding" -msgstr "Ungültiger Tastenname \"%s\" in Tastenkombination" +msgstr "Ungültiger Tastenname »%s« in Tastenkombination" #: openbox/translate.c:151 #, c-format msgid "Requested key \"%s\" does not exist on the display" -msgstr "Angeforderte Taste \"%s\" existiert nicht auf dem Display" +msgstr "Angeforderte Taste »%s« existiert nicht in der Anzeige" #: openbox/prompt.c:153 msgid "OK" msgstr "OK" - -#, fuzzy -#~ msgid "Openbox" -#~ msgstr "Beende Openbox" - -#~ msgid "--config-file requires an argument\n" -#~ msgstr "--config-file erfordert einen Parameter\n" - -#~ msgid "" -#~ "The SessionLogout action is not available since Openbox was built without " -#~ "session management support" -#~ msgstr "" -#~ "Die SessionLogout-Aktion ist nicht verfügbar, da Openbox ohne " -#~ "Unterstützung für Sitzungsmanagement kompiliert wurde" - -#~ msgid "Unable to save the session to \"%s\": %s" -#~ msgstr "Konnte die Sitzung \"%s\" nicht sichern: %s" - -#~ msgid "Error while saving the session to \"%s\": %s" -#~ msgstr "Fehler beim Speichern der Sitzung nach \"%s\": %s" - -#~ msgid "Not connected to a session manager" -#~ msgstr "Nicht mit einem Sitzungsmanager verbunden" - -#~ msgid "X Error: %s" -#~ msgstr "X-Fehler: %s" - -#~ msgid "Failed to execute \"%s\": %s" -#~ msgstr "Konnte \"%s\" nicht ausführen: %s" - -#~ msgid "Invalid use of action \"%s\". Action will be ignored." -#~ msgstr "Unzulässiger Einsatz der Aktion \"%s\". Aktion wird ignoriert." diff --git a/release/bugs b/release/bugs new file mode 100755 index 00000000..f36a7aa9 --- /dev/null +++ b/release/bugs @@ -0,0 +1,38 @@ +#!/bin/sh + +help() { + echo "Usage: $0 [lastrelease]" + echo + echo " The revision which should be used for release." + echo " [lastrelease] The revision of the most recent release made." + echo " By default it uses the most recent release-tag." + exit 1 +} + +error() { + echo "error: $1" + exit 1 +} + +test -e "./openbox/openbox.c" || \ + error "must be run from the project's top level directory" + +REV="$1" +test -z "$REV" && help +RELEASE_SHA=$(git rev-parse "$REV") +test $? = 0 || error "revision $REV not found" + +LAST="$2" +if test -z "$LAST"; then + LAST=$(git describe --match 'release-*' $REV) + test $? = 0 || \ + error "unable to find last release" +fi + +#### CHANGELOG ##### +git log --no-merges $LAST..$REV --oneline|egrep '[bB][uU][Gg] #?[0-9]+' +git log --no-merges $LAST..$REV --oneline | \ + perl -n -e'/[bB][uU][Gg] #?([0-9]+)/ && print "#$1, "' +perl -e'print "\b\b \n"' + +exit 0 diff --git a/release/common b/release/common new file mode 100644 index 00000000..56093c0a --- /dev/null +++ b/release/common @@ -0,0 +1,56 @@ +#!/bin/sh + +NOW=$(date +%s) +SRCDIR="$PWD" +WORKDIR="/tmp/openbox.$NOW" +TESTDIR="$WORKDIR/.test" + +clean() { + cd "$SRCDIR" + if test -d "$WORKDIR"; then + chmod -R +w "$WORKDIR" + rm -r -f "$WORKDIR" + fi +} + +error() { + echo "error: $1" + test -n "$2" && echo "$2" + clean + exit 1 +} + +RELEASE_SHA=$(git rev-parse "$REV") +test $? = 0 || error "revision $REV not found" + +#NON_MASTER=$(git rev-list $RELEASE_SHA ^master) +#test $? = 0 || error "git rev-list failed" +#test -z $NON_MASTER || error "REVISION contains commits not on master" + +git new-workdir "$SRCDIR" "$WORKDIR" +test $? = 0 || error "git-new-workdir failed" + +echo "working in $WORKDIR" + +cd "$WORKDIR" +test $? = 0 || error "cd to $WORKDIR failed" + +git checkout -q $RELEASE_SHA +test $? = 0 || error "git-checkout failed" + +# get last release +if test -z "$LAST"; then + LAST=$(git describe --match 'release-*' --abbrev=0 $REV) + test $? = 0 || \ + error "unable to find last release" + echo "Using previous release as $LAST" +fi + +H="$(head -1 CHANGELOG|cut -d: -f 1)" +test $H = $VERSION || + error "CHANGELOG does not contain version $VERSION" + +CL="$(cat CHANGELOG|sed -n /^$VERSION:\$/,/^\$/p|tail -n +2|head -n -1)" +CLNOWRAP=$(echo "$CL" \ + |sed -e "s/^ \*/*/"|sed -n '1h;1!H;${;g;s/\n / /g;p;}') +SHORTLOG="$(git shortlog --no-merges $LAST..$REV)" diff --git a/release/email b/release/email new file mode 100755 index 00000000..7fa9e575 --- /dev/null +++ b/release/email @@ -0,0 +1,63 @@ +#!/bin/sh + +help() { + echo "Usage: $0 [lastrelease]" + echo + echo " The revision which should be used for release." + echo " The version of the release." + echo " [lastrelease] The revision of the most recent release made." + echo " By default it uses the most recent release-tag." + exit 1 +} + +REV="$1" +test -z "$REV" && help +VERSION="$2" +test -z "$VERSION" && help +LAST="$3" + +. release/common + +SUBJECT="[RELEASE] Openbox $VERSION" +MAILINGLIST=openbox@icculus.org +MIKACHU=mikachu@icculus.org + +cat < $WORKDIR/.email +Hello, + +Openbox $VERSION is now available! + +Some noteworthy changes are: +$CLNOWRAP + +======== Download ======== + +Download links are here: http://openbox.org/wiki/Openbox:Download + +======== Commits ======== + +The following is a full list of commits appearing in this release. +You can see the full commits here: http://git.openbox.org/?p=dana/openbox.git;a=shortlog;h=refs/tags/release-$VERSION + + +$SHORTLOG +EOF + +if test -z $EDITOR; then + nano -w $WORKDIR/.email || error "failed to edit email, set \$EDITOR" +else + $EDITOR $WORKDIR/.email || error "failed to edit email with \$EDTIOR" +fi + +test -e $WORKDIR/.email || error "email file disappeared" +cat $WORKDIR/.email | mail -s "$SUBJECT" "$MAILINGLIST" || \ + error "mail to $MAILINGLIST failed" + +echo "Hi Mikachu, + +Please update the freshmeat.net Openbox stuff for $VERSION. Thanks!" | \ +mail -s "Freshmeat.net for Openbox $VERSION" "$MIKACHU" || \ + error "mail to $MIKACHU failed" + +clean +exit 0 diff --git a/release/go b/release/go new file mode 100755 index 00000000..3158ac21 --- /dev/null +++ b/release/go @@ -0,0 +1,158 @@ +#!/bin/sh + +help() { + echo "Usage: $0 [lastrelease]" + echo + echo " The revision which should be used for release." + echo " The version of the release." + echo " [lastrelease] The revision of the most recent release made." + echo " By default it uses the most recent release-tag." + exit 1 +} + +REV="$1" +test -z "$REV" && help +VERSION="$2" +test -z "$VERSION" && help +LAST="$3" + +. release/common + +#### CONFIRM SHORTLOG ##### + +echo Shortlog from previous release: +echo "$SHORTLOG" +echo +echo Shortlog from $LAST contains $(echo "$SHORTLOG"|wc -l) lines +echo -n "ok? (y/n) " +read a +test "$a" = "y" || error "aborted" + +#### TEST english po VERSIONS #### + +BAD_PO="$(grep Project-Id-Version po/en*.po|grep -v "openbox $VERSION\\\\n")" +test -z "$BAD_PO" || error "wrong version in po files" "$BAD_PO" + +#### TEST COMPILATION #### + +# check that it builds +./bootstrap >/dev/null || "bootstrap failed" +#CFLAGS="-Werror -isystem /usr/lib/glib-2.0" \ +./configure -C --enable-debug >/dev/null || \ + error "configure (with debug) failed" +make || error "make (with debug and Werror) failed" +git clean -f -x -d -q + +# check that it builds with each optional featureset +./bootstrap >/dev/null || "bootstrap failed" + +echo Check compile with all options enabled +./configure -C >/dev/null || \ + error "configure failed" +make >/dev/null 2>/dev/null || \ + error "make failed" +grep "XKB 1" config.log >/dev/null || error "missing xkb extension" +grep "XRANDR 1" config.log >/dev/null || error "missing xrandr extension" +grep "XINERAMA 1" config.log >/dev/null || error "missing xinerama extension" +grep "SYNC 1" config.log >/dev/null || error "missing sync extension" +make clean >/dev/null || error "make clean failed" + +echo Check compile with startup notification disabled +./configure -C --disable-startup-notification >/dev/null || \ + error "configure failed" +make >/dev/null 2>/dev/null || \ + error "make (with --disable-startup-notification) failed" +make clean >/dev/null || error "make clean failed" + +echo Check compile with xcursor disabled +./configure -C --disable-xcursor >/dev/null || \ + error "configure failed" +make >/dev/null 2>/dev/null || \ + error "make (with --disable-xcursor) failed" +make clean >/dev/null || error "make clean failed" + +echo Check compile with imlib2 disabled +./configure -C --disable-imlib2 >/dev/null || \ + error "configure failed" +make >/dev/null 2>/dev/null || \ + error "make (with --disable-imlib2) failed" +make clean >/dev/null || error "make clean failed" + +echo Check compile with session management disabled +./configure -C --disable-session-management >/dev/null || \ + error "configure failed" +make >/dev/null 2>/dev/null || \ + error "make (with --disable-session-management) failed" +make clean >/dev/null || error "make clean failed" + +echo Check compile with xkb disabled +./configure -C --disable-xkb >/dev/null || error "configure failed" +make >/dev/null 2>/dev/null || error "make (with --disable-xkb) failed" +make clean >/dev/null || error "make clean failed" + +echo Check compile with xrandr disabled +./configure -C --disable-xrandr >/dev/null || error "configure failed" +make >/dev/null 2>/dev/null || error "make (with --disable-xrandr) failed" +make clean >/dev/null || error "make clean failed" + +echo Check compile with xinerama disabled +./configure -C --disable-xinerama >/dev/null || error "configure failed" +make >/dev/null 2>/dev/null || error "make (with --disable-xinerama) failed" +make clean >/dev/null || error "make clean failed" + +echo Check compile with xsync disabled +./configure -C --disable-xsync >/dev/null || error "configure failed" +make >/dev/null 2>/dev/null || error "make (with --disable-xsync) failed" +make clean >/dev/null || error "make clean failed" + +# check that it installs sanely +echo Check installation correctness +./configure -C >/dev/null || \ + error "configure failed" +make distcheck >/dev/null || \ + error "make distcheck failed" + +# VERIFY TARBALL + +TAR="openbox-$VERSION.tar.gz" +ASC="openbox-$VERSION.tar.gz.asc" + +echo Found Openbox release tarball: +ls -d openbox-*.tar.gz +test -e "$TAR" || \ + error "Specified version does not match configure.am" + +# SIGN THE TARBALL + +echo Signing the release tarball: +gpg --sign --detach-sign --armor "$TAR" +test $? = 0 || \ + error "Failed to sign release tarball" + +echo Tagging the release: +git tag -s -m "tagging the $VERSION release" "release-$VERSION" $REV || \ + error "Failed to tag the release" + +mv "$TAR" "$ASC" "$SRCDIR" + +echo "=$VERSION=" +echo "$CLNOWRAP" +echo +echo +echo Edit download page: +echo " http://openbox.org/oldwiki/index.php?title=Openbox:Download&action=edit§ion=1" +echo +echo Edit changelog: +echo " http://openbox.org/oldwiki/index.php?title=Openbox:Changelog&action=edit§ion=1" +echo +echo Push the tag: +echo " git push origin tag release-$VERSION" +echo +echo Email: +echo " ./release/email $*" +echo +cd "$SRCDIR" +ls -l "$TAR" "$ASC" + +clean +exit 0