diff options
Diffstat (limited to 'Add-nofocuswindows-preference-to-list-windows-that.patch')
-rw-r--r-- | Add-nofocuswindows-preference-to-list-windows-that.patch | 862 |
1 files changed, 862 insertions, 0 deletions
diff --git a/Add-nofocuswindows-preference-to-list-windows-that.patch b/Add-nofocuswindows-preference-to-list-windows-that.patch new file mode 100644 index 0000000..ddad5be --- /dev/null +++ b/Add-nofocuswindows-preference-to-list-windows-that.patch @@ -0,0 +1,862 @@ +From 88c66808ec5f2bfba425fc6d6f0b9ac43ed44696 Mon Sep 17 00:00:00 2001 +From: Owen W. Taylor <otaylor@fishsoup.net> +Date: Wed, 21 Oct 2009 18:07:12 -0400 +Subject: [PATCH] Add no_focus_windows preference to list windows that shouldn't be focused + +Notification windows from legacy software that don't set _NET_WM_USER_TIME +can be a huge annoyance for users, since they will pop up and steal focus. + +Add: + + /apps/metacity/general/no_focus_windows + +which is a list of expressions identifying new windows that shouldn't ever +be focused. For example: + + (and (eq class 'Mylegacyapp') (glob name 'New mail*')) + +https://bugzilla.gnome.org/show_bug.cgi?id=599248 +--- + src/Makefile.am | 2 + + src/core/prefs.c | 43 ++++ + src/core/window-matcher.c | 582 ++++++++++++++++++++++++++++++++++++++++++++ + src/core/window-matcher.h | 46 ++++ + src/core/window.c | 9 +- + src/include/prefs.h | 6 +- + src/metacity.schemas.in.in | 28 ++ + 7 files changed, 714 insertions(+), 2 deletions(-) + create mode 100644 src/core/window-matcher.c + create mode 100644 src/core/window-matcher.h + +diff --git a/src/Makefile.am b/src/Makefile.am +index bd3420f..3baf422 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -65,6 +65,8 @@ metacity_SOURCES= \ + core/stack.h \ + core/util.c \ + include/util.h \ ++ core/window-matcher.c \ ++ core/window-matcher.h \ + core/window-props.c \ + core/window-props.h \ + core/window.c \ +diff --git a/src/core/prefs.c b/src/core/prefs.c +index 6e41b3c..e03c816 100644 +--- a/src/core/prefs.c ++++ b/src/core/prefs.c +@@ -25,6 +25,7 @@ + + #include <config.h> + #include "prefs.h" ++#include "window-matcher.h" + #include "ui.h" + #include "util.h" + #ifdef HAVE_GCONF +@@ -76,6 +77,7 @@ static PangoFontDescription *titlebar_font = NULL; + static MetaVirtualModifier mouse_button_mods = Mod1Mask; + static MetaFocusMode focus_mode = META_FOCUS_MODE_CLICK; + static MetaFocusNewWindows focus_new_windows = META_FOCUS_NEW_WINDOWS_SMART; ++static GSList *no_focus_windows = NULL; + static gboolean raise_on_click = TRUE; + static char* current_theme = NULL; + static int num_workspaces = 4; +@@ -147,6 +149,7 @@ static void maybe_give_disable_workarounds_warning (void); + + static void titlebar_handler (MetaPreference, const gchar*, gboolean*); + static void theme_name_handler (MetaPreference, const gchar*, gboolean*); ++static void no_focus_windows_handler (MetaPreference, const gchar*, gboolean*); + static void mouse_button_mods_handler (MetaPreference, const gchar*, gboolean*); + static void button_layout_handler (MetaPreference, const gchar*, gboolean*); + +@@ -425,6 +428,11 @@ static MetaStringPreference preferences_string[] = + theme_name_handler, + NULL, + }, ++ { "/apps/metacity/general/no_focus_windows", ++ META_PREF_NO_FOCUS_WINDOWS, ++ no_focus_windows_handler, ++ NULL ++ }, + { KEY_TITLEBAR_FONT, + META_PREF_TITLEBAR_FONT, + titlebar_handler, +@@ -1344,6 +1352,30 @@ theme_name_handler (MetaPreference pref, + } + + static void ++no_focus_windows_handler (MetaPreference pref, ++ const gchar *string_value, ++ gboolean *inform_listeners) ++{ ++ if (no_focus_windows) ++ { ++ meta_window_matcher_list_free (no_focus_windows); ++ no_focus_windows = NULL; ++ } ++ ++ if (string_value) ++ { ++ GError *error = NULL; ++ no_focus_windows = meta_window_matcher_list_from_string (string_value, &error); ++ if (error != NULL) ++ { ++ meta_warning ("Error parsing no_focus_windows='%s': %s\n", ++ string_value, error->message); ++ g_error_free (error); ++ } ++ } ++} ++ ++static void + mouse_button_mods_handler (MetaPreference pref, + const gchar *string_value, + gboolean *inform_listeners) +@@ -1755,6 +1787,9 @@ meta_preference_to_string (MetaPreference pref) + + case META_PREF_FORCE_FULLSCREEN: + return "FORCE_FULLSCREEN"; ++ ++ case META_PREF_NO_FOCUS_WINDOWS: ++ return "NO_FOCUS_WINDOWS"; + } + + return "(unknown)"; +@@ -2633,6 +2668,14 @@ meta_prefs_get_action_right_click_titlebar (void) + } + + gboolean ++meta_prefs_window_is_no_focus (const char *window_name, ++ const char *window_class) ++{ ++ return meta_window_matcher_list_matches (no_focus_windows, ++ window_name, window_class); ++} ++ ++gboolean + meta_prefs_get_auto_raise (void) + { + return auto_raise; +diff --git a/src/core/window-matcher.c b/src/core/window-matcher.c +new file mode 100644 +index 0000000..e2fd293 +--- /dev/null ++++ b/src/core/window-matcher.c +@@ -0,0 +1,582 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++/* Tiny language for matching against windows */ ++ ++/* ++ * Copyright (C) 2009 Red Hat, Inc. ++ * ++ * 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. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ */ ++ ++#include <glib.h> ++#include <string.h> ++ ++#include "window-matcher.h" ++ ++typedef struct _MetaWindowMatcher MetaWindowMatcher; ++ ++typedef enum { ++ MATCHER_OPERAND_CLASS, ++ MATCHER_OPERAND_NAME ++} MatcherOperand; ++ ++typedef enum { ++ MATCHER_TOKEN_AND = G_TOKEN_LAST + 1, ++ MATCHER_TOKEN_OR, ++ MATCHER_TOKEN_NOT, ++ MATCHER_TOKEN_EQ, ++ MATCHER_TOKEN_GLOB, ++ MATCHER_TOKEN_NAME, ++ MATCHER_TOKEN_CLASS ++} MatcherToken; ++ ++struct _MetaWindowMatcher { ++ enum { ++ MATCHER_AND, ++ MATCHER_OR, ++ MATCHER_NOT, ++ MATCHER_EQ, ++ MATCHER_GLOB ++ } type; ++ ++ union { ++ struct { ++ MetaWindowMatcher *a; ++ MetaWindowMatcher *b; ++ } and; ++ struct { ++ MetaWindowMatcher *a; ++ MetaWindowMatcher *b; ++ } or; ++ struct { ++ MetaWindowMatcher *a; ++ } not; ++ struct { ++ MatcherOperand operand; ++ char *str; ++ } eq; ++ struct { ++ MatcherOperand operand; ++ char *str; ++ GPatternSpec *pattern; ++ } glob; ++ } u; ++}; ++ ++static void ++meta_window_matcher_free (MetaWindowMatcher *matcher) ++{ ++ switch (matcher->type) ++ { ++ case MATCHER_AND: ++ meta_window_matcher_free (matcher->u.and.a); ++ meta_window_matcher_free (matcher->u.and.b); ++ break; ++ case MATCHER_OR: ++ meta_window_matcher_free (matcher->u.or.a); ++ meta_window_matcher_free (matcher->u.or.b); ++ break; ++ case MATCHER_NOT: ++ meta_window_matcher_free (matcher->u.or.a); ++ break; ++ case MATCHER_EQ: ++ g_free (matcher->u.eq.str); ++ break; ++ case MATCHER_GLOB: ++ g_free (matcher->u.glob.str); ++ g_pattern_spec_free (matcher->u.glob.pattern); ++ break; ++ } ++ ++ g_slice_free (MetaWindowMatcher, matcher); ++} ++ ++void ++meta_window_matcher_list_free (GSList *list) ++{ ++ g_slist_foreach (list, (GFunc)meta_window_matcher_free, NULL); ++ g_slist_free (list); ++} ++ ++static gboolean ++meta_window_matcher_matches (MetaWindowMatcher *matcher, ++ const char *window_name, ++ const char *window_class) ++{ ++ switch (matcher->type) ++ { ++ case MATCHER_AND: ++ return (meta_window_matcher_matches (matcher->u.and.a, window_name, window_class) && ++ meta_window_matcher_matches (matcher->u.and.b, window_name, window_class)); ++ case MATCHER_OR: ++ return (meta_window_matcher_matches (matcher->u.or.a, window_name, window_class) || ++ meta_window_matcher_matches(matcher->u.or.b, window_name, window_class)); ++ case MATCHER_NOT: ++ return !meta_window_matcher_matches (matcher->u.not.a, window_name, window_class); ++ case MATCHER_EQ: ++ if (matcher->u.eq.operand == MATCHER_OPERAND_NAME) ++ return window_name && strcmp (matcher->u.eq.str, window_name) == 0; ++ else ++ return window_class && strcmp (matcher->u.eq.str, window_class) == 0; ++ case MATCHER_GLOB: ++ if (matcher->u.glob.operand == MATCHER_OPERAND_NAME) ++ return window_name && g_pattern_match_string (matcher->u.glob.pattern, window_name); ++ else ++ return window_class && g_pattern_match_string (matcher->u.glob.pattern, window_class); ++ } ++ ++ g_assert_not_reached(); ++ return FALSE; ++} ++ ++gboolean ++meta_window_matcher_list_matches (GSList *list, ++ const char *window_name, ++ const char *window_class) ++{ ++ GSList *l; ++ ++ for (l = list; l; l = l->next) ++ { ++ if (meta_window_matcher_matches (l->data, window_name, window_class)) ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ ++static const GScannerConfig scanner_config = ++{ ++ " \t\r\n" /* cset_skip_characters */, ++ ( ++ G_CSET_a_2_z ++ "_" ++ G_CSET_A_2_Z ++ ) /* cset_identifier_first */, ++ ( ++ G_CSET_a_2_z ++ "_" ++ G_CSET_A_2_Z ++ G_CSET_DIGITS ++ G_CSET_LATINS ++ G_CSET_LATINC ++ ) /* cset_identifier_nth */, ++ NULL /* cpair_comment_single */, ++ TRUE /* case_sensitive */, ++ TRUE /* skip_comment_multi */, ++ FALSE /* skip_comment_single */, ++ TRUE /* scan_comment_multi */, ++ TRUE /* scan_identifier */, ++ TRUE /* scan_identifier_1char */, ++ FALSE /* scan_identifier_NULL */, ++ TRUE /* scan_symbols */, ++ FALSE /* scan_binary */, ++ TRUE /* scan_octal */, ++ TRUE /* scan_float */, ++ TRUE /* scan_hex */, ++ FALSE /* scan_hex_dollar */, ++ TRUE /* scan_string_sq */, ++ TRUE /* scan_string_dq */, ++ TRUE /* numbers_2_int */, ++ FALSE /* int_2_float */, ++ FALSE /* identifier_2_string */, ++ TRUE /* char_2_token */, ++ TRUE /* symbol_2_token */, ++ FALSE /* scope_0_fallback */, ++ FALSE /* store_int64 */, ++}; ++ ++static void ++set_error (GScanner *scanner, ++ GError **error, ++ const char *message) ++{ ++ g_set_error (error, 0, 0, ++ "Parse error at %d:%d: %s", ++ g_scanner_cur_line (scanner), ++ g_scanner_cur_position (scanner), ++ message); ++} ++ ++static MetaWindowMatcher * ++meta_window_matcher_new_and (MetaWindowMatcher *a, ++ MetaWindowMatcher *b) ++{ ++ MetaWindowMatcher *matcher = g_slice_new0 (MetaWindowMatcher); ++ ++ matcher->type = MATCHER_AND; ++ matcher->u.and.a = a; ++ matcher->u.and.b = b; ++ ++ return matcher; ++} ++ ++static MetaWindowMatcher * ++meta_window_matcher_new_or (MetaWindowMatcher *a, ++ MetaWindowMatcher *b) ++{ ++ MetaWindowMatcher *matcher = g_slice_new0 (MetaWindowMatcher); ++ ++ matcher->type = MATCHER_OR; ++ matcher->u.or.a = a; ++ matcher->u.or.b = b; ++ ++ return matcher; ++} ++ ++static MetaWindowMatcher * ++meta_window_matcher_new_not (MetaWindowMatcher *a) ++{ ++ MetaWindowMatcher *matcher = g_slice_new0 (MetaWindowMatcher); ++ ++ matcher->type = MATCHER_NOT; ++ matcher->u.not.a = a; ++ ++ return matcher; ++} ++ ++static MetaWindowMatcher * ++meta_window_matcher_new_eq (MatcherOperand operand, ++ const char *str) ++{ ++ MetaWindowMatcher *matcher = g_slice_new0 (MetaWindowMatcher); ++ ++ matcher->type = MATCHER_EQ; ++ matcher->u.eq.operand = operand; ++ matcher->u.eq.str = g_strdup (str); ++ ++ return matcher; ++} ++ ++static MetaWindowMatcher * ++meta_window_matcher_new_glob (MatcherOperand operand, ++ const char *str) ++{ ++ MetaWindowMatcher *matcher = g_slice_new0 (MetaWindowMatcher); ++ ++ matcher->type = MATCHER_GLOB; ++ matcher->u.glob.operand = operand; ++ matcher->u.glob.str = g_strdup (str); ++ matcher->u.glob.pattern = g_pattern_spec_new (str); ++ ++ return matcher; ++} ++ ++static MetaWindowMatcher * ++meta_window_matcher_from_scanner (GScanner *scanner, ++ GError **error) ++{ ++ MetaWindowMatcher *matcher = NULL; ++ GTokenType token; ++ GTokenValue value; ++ ++ token = g_scanner_get_next_token (scanner); ++ if (token != G_TOKEN_LEFT_PAREN) ++ { ++ set_error (scanner, error, "expected '('"); ++ return NULL; ++ } ++ ++ token = g_scanner_get_next_token (scanner); ++ switch (token) ++ { ++ case MATCHER_TOKEN_AND: ++ case MATCHER_TOKEN_OR: ++ case MATCHER_TOKEN_NOT: ++ { ++ MetaWindowMatcher *a, *b; ++ ++ a = meta_window_matcher_from_scanner (scanner, error); ++ if (!a) ++ return NULL; ++ ++ if (token != MATCHER_TOKEN_NOT) ++ { ++ b = meta_window_matcher_from_scanner (scanner, error); ++ if (!b) ++ { ++ meta_window_matcher_free (a); ++ return NULL; ++ } ++ } ++ ++ switch (token) ++ { ++ case MATCHER_TOKEN_AND: ++ matcher = meta_window_matcher_new_and (a, b); ++ break; ++ case MATCHER_TOKEN_OR: ++ matcher = meta_window_matcher_new_or (a, b); ++ break; ++ case MATCHER_TOKEN_NOT: ++ matcher = meta_window_matcher_new_not (a); ++ break; ++ default: ++ g_assert_not_reached(); ++ break; ++ } ++ } ++ break; ++ case MATCHER_TOKEN_EQ: ++ case MATCHER_TOKEN_GLOB: ++ { ++ MatcherOperand operand; ++ ++ switch (g_scanner_get_next_token (scanner)) ++ { ++ case MATCHER_TOKEN_NAME: ++ operand = MATCHER_OPERAND_NAME; ++ break; ++ case MATCHER_TOKEN_CLASS: ++ operand = MATCHER_OPERAND_CLASS; ++ break; ++ default: ++ set_error (scanner, error, "expected name/class"); ++ return NULL; ++ } ++ ++ if (g_scanner_get_next_token (scanner) != G_TOKEN_STRING) ++ { ++ set_error (scanner, error, "expected string"); ++ return NULL; ++ } ++ ++ value = g_scanner_cur_value (scanner); ++ ++ switch (token) ++ { ++ case MATCHER_TOKEN_EQ: ++ matcher = meta_window_matcher_new_eq (operand, value.v_string); ++ break; ++ case MATCHER_TOKEN_GLOB: ++ matcher = meta_window_matcher_new_glob (operand, value.v_string); ++ break; ++ default: ++ g_assert_not_reached(); ++ } ++ } ++ break; ++ default: ++ set_error (scanner, error, "expected and/or/not/eq/glob"); ++ return NULL; ++ } ++ ++ if (g_scanner_get_next_token (scanner) != G_TOKEN_RIGHT_PAREN) ++ { ++ set_error (scanner, error, "expected ')'"); ++ return NULL; ++ } ++ ++ return matcher; ++} ++ ++GSList * ++meta_window_matcher_list_from_string (const char *str, ++ GError **error) ++{ ++ GScanner *scanner = g_scanner_new (&scanner_config); ++ GSList *result = NULL; ++ ++ g_scanner_scope_add_symbol (scanner, 0, "and", GINT_TO_POINTER (MATCHER_TOKEN_AND)); ++ g_scanner_scope_add_symbol (scanner, 0, "or", GINT_TO_POINTER (MATCHER_TOKEN_OR)); ++ g_scanner_scope_add_symbol (scanner, 0, "not", GINT_TO_POINTER (MATCHER_TOKEN_NOT)); ++ g_scanner_scope_add_symbol (scanner, 0, "eq", GINT_TO_POINTER (MATCHER_TOKEN_EQ)); ++ g_scanner_scope_add_symbol (scanner, 0, "glob", GINT_TO_POINTER (MATCHER_TOKEN_GLOB)); ++ g_scanner_scope_add_symbol (scanner, 0, "name", GINT_TO_POINTER (MATCHER_TOKEN_NAME)); ++ g_scanner_scope_add_symbol (scanner, 0, "class", GINT_TO_POINTER (MATCHER_TOKEN_CLASS)); ++ ++ g_scanner_input_text (scanner, str, strlen (str)); ++ ++ while (g_scanner_peek_next_token (scanner) != G_TOKEN_EOF) ++ { ++ MetaWindowMatcher *matcher = meta_window_matcher_from_scanner (scanner, error); ++ if (!matcher) ++ { ++ meta_window_matcher_list_free (result); ++ return NULL; ++ } ++ ++ result = g_slist_prepend (result, matcher); ++ } ++ ++ g_scanner_destroy (scanner); ++ ++ return g_slist_reverse (result); ++} ++ ++#ifdef BUILD_MATCHER_TESTS ++ ++static void ++append_operand_to_string (GString *string, ++ MatcherOperand operand) ++{ ++ if (operand == MATCHER_OPERAND_NAME) ++ g_string_append (string, "name"); ++ else ++ g_string_append (string, "class"); ++} ++ ++static void ++append_string_to_string (GString *str, ++ const char *to_append) ++{ ++ const char *p; ++ ++ g_string_append_c (str, '"'); ++ for (p = to_append; *p; p++) ++ { ++ if (*p == '"') ++ g_string_append (str, "\\\""); ++ else ++ g_string_append_c (str, *p); ++ } ++ g_string_append_c (str, '"'); ++} ++ ++static void ++append_matcher_to_string (GString *str, ++ MetaWindowMatcher *matcher) ++{ ++ switch (matcher->type) ++ { ++ case MATCHER_AND: ++ g_string_append (str, "(and "); ++ append_matcher_to_string (str, matcher->u.and.a); ++ g_string_append_c (str, ' '); ++ append_matcher_to_string (str, matcher->u.and.b); ++ break; ++ case MATCHER_OR: ++ g_string_append (str, "(or "); ++ append_matcher_to_string (str, matcher->u.or.a); ++ g_string_append_c (str, ' '); ++ append_matcher_to_string (str, matcher->u.or.b); ++ break; ++ case MATCHER_NOT: ++ g_string_append (str, "(not "); ++ append_matcher_to_string (str, matcher->u.not.a); ++ break; ++ case MATCHER_EQ: ++ g_string_append (str, "(eq "); ++ append_operand_to_string (str, matcher->u.eq.operand); ++ g_string_append_c (str, ' '); ++ append_string_to_string (str, matcher->u.eq.str); ++ break; ++ case MATCHER_GLOB: ++ g_string_append (str, "(glob "); ++ append_operand_to_string (str, matcher->u.glob.operand); ++ g_string_append_c (str, ' '); ++ append_string_to_string (str, matcher->u.glob.str); ++ break; ++ } ++ ++ g_string_append_c (str, ')'); ++} ++ ++static char * ++meta_window_matcher_list_to_string (GSList *list) ++{ ++ GSList *l; ++ GString *str = g_string_new (NULL); ++ ++ for (l = list; l; l = l->next) ++ { ++ if (str->len > 0) ++ g_string_append_c (str, ' '); ++ ++ append_matcher_to_string (str, l->data); ++ } ++ ++ return g_string_free (str, FALSE); ++} ++ ++static void ++test_roundtrip (const char *str) ++{ ++ GError *error = NULL; ++ GSList *list = meta_window_matcher_list_from_string (str, &error); ++ char *result; ++ ++ if (error != NULL) ++ g_error ("Failed to parse '%s': %s\n", str, error->message); ++ ++ result = meta_window_matcher_list_to_string (list); ++ if (strcmp (result, str) != 0) ++ g_error ("Round-trip conversion of '%s' gave '%s'\n", str, result); ++ ++ g_free (result); ++ meta_window_matcher_list_free (list); ++} ++ ++static void ++test_matches (const char *str, ++ const char *window_name, ++ const char *window_class, ++ gboolean expected) ++{ ++ GError *error = NULL; ++ GSList *list = meta_window_matcher_list_from_string (str, &error); ++ gboolean matches; ++ ++ if (error != NULL) ++ g_error ("Failed to parse '%s': %s\n", str, error->message); ++ ++ matches = meta_window_matcher_list_matches (list, window_name, window_class)) ++ if (matches != expected) ++ { ++ g_error ("Tested '%s' against name=%s, class=%s, expected %s, got %s\n", ++ str, window_name, window_class, ++ expected ? "true" : "false", ++ matches ? "true" : "false"); ++ } ++ ++ ++ meta_window_matcher_list_free (list); ++} ++ ++int main (int argc, char **argv) ++{ ++ test_roundtrip ("(eq name \"foo\")"); ++ test_roundtrip ("(eq name \"fo\\\"o\")"); ++ test_roundtrip ("(glob class \"*bar?baz\")"); ++ test_roundtrip ("(and (eq name \"foo\") (glob class \"*bar?baz\"))"); ++ test_roundtrip ("(or (eq name \"foo\") (glob class \"*bar?baz\"))"); ++ test_roundtrip ("(not (eq name \"foo\"))"); ++ ++ test_roundtrip ("(eq name \"foo\") (glob class \"*bar?baz\")"); ++ ++ test_matches ("(eq name 'foo')", "foo", NULL, TRUE); ++ test_matches ("(eq name 'foo')", "foob", NULL, FALSE); ++ test_matches ("(eq name 'foo')", NULL, NULL, FALSE); ++ test_matches ("(eq class 'bar')", "foo", "bar", TRUE); ++ test_matches ("(eq class 'bar')", NULL, NULL, FALSE); ++ ++ test_matches ("(glob name 'foo*')", "foooo", NULL, TRUE); ++ test_matches ("(glob name 'foo*')", NULL, NULL, FALSE); ++ test_matches ("(glob class 'b*r')", "foooo", "baaaar", TRUE); ++ test_matches ("(glob class 'b*r')", NULL, NULL, FALSE); ++ ++ test_matches ("(and (eq name 'foo') (eq class 'bar'))", "foo", "bar", TRUE); ++ test_matches ("(and (eq name 'foo') (eq class 'bar'))", "foo", "baz", FALSE); ++ test_matches ("(and (eq name 'foo') (not (eq class 'bar')))", "foo", "bar", FALSE); ++ test_matches ("(and (eq name 'foo') (not (eq class 'bar')))", "foo", "baz", TRUE); ++ ++ test_matches ("(or (eq name 'foo') (eq class 'bar'))", "foo", "baz", TRUE); ++ test_matches ("(or (eq name 'foo') (eq class 'bar'))", "fof", "baz", FALSE); ++ ++ return 0; ++} ++ ++#endif /* BUILD_MATCHER_TESTS */ +diff --git a/src/core/window-matcher.h b/src/core/window-matcher.h +new file mode 100644 +index 0000000..7fc7826 +--- /dev/null ++++ b/src/core/window-matcher.h +@@ -0,0 +1,46 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++/* Tiny language for matching against windows ++ * ++ * Expression Syntax: ++ * ++ * (and <expr> <expr>) ++ * (or <expr> <expr>) ++ * (not <expr>) ++ * (eq [name|class] "<value>") ++ * (glob [name|class] "<glob>") ++ * ++ * A "matcher list" is a whitespace-separated list of expressions that are ++ * implicitly or'ed together. Globs are shell style patterns with ++ * matching 0 or more characters and ? matching one character. ++ */ ++ ++/* ++ * Copyright (C) 2009 Red Hat, Inc. ++ * ++ * 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. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ */ ++ ++#ifndef META_WINDOW_MATCHER_H ++#define META_WINDOW_MATCHER_H ++ ++GSList * meta_window_matcher_list_from_string (const char *str, ++ GError **error); ++void meta_window_matcher_list_free (GSList *list); ++gboolean meta_window_matcher_list_matches (GSList *list, ++ const char *window_name, ++ const char *window_class); ++#endif /* META_WINDOW_MATCHER_H */ +diff --git a/src/core/window.c b/src/core/window.c +index 8d029a2..10da47a 100644 +--- a/src/core/window.c ++++ b/src/core/window.c +@@ -1965,7 +1965,14 @@ window_state_on_map (MetaWindow *window, + { + gboolean intervening_events; + +- intervening_events = intervening_user_event_occurred (window); ++ /* A 'no focus' window is a window that has been configured in GConf ++ * to never take focus on map; typically it will be a notification ++ * window from a legacy app that doesn't support _NET_WM_USER_TIME. ++ */ ++ if (meta_prefs_window_is_no_focus (window->title, window->res_class)) ++ intervening_events = TRUE; ++ else ++ intervening_events = intervening_user_event_occurred (window); + + *takes_focus = !intervening_events; + *places_on_top = *takes_focus; +diff --git a/src/include/prefs.h b/src/include/prefs.h +index a4193ff..6698dfe 100644 +--- a/src/include/prefs.h ++++ b/src/include/prefs.h +@@ -60,7 +60,8 @@ typedef enum + META_PREF_CURSOR_SIZE, + META_PREF_COMPOSITING_MANAGER, + META_PREF_RESIZE_WITH_RIGHT_BUTTON, +- META_PREF_FORCE_FULLSCREEN ++ META_PREF_FORCE_FULLSCREEN, ++ META_PREF_NO_FOCUS_WINDOWS + } MetaPreference; + + typedef void (* MetaPrefsChangedFunc) (MetaPreference pref, +@@ -106,6 +107,9 @@ MetaActionTitlebar meta_prefs_get_action_double_click_titlebar (void); + MetaActionTitlebar meta_prefs_get_action_middle_click_titlebar (void); + MetaActionTitlebar meta_prefs_get_action_right_click_titlebar (void); + ++gboolean meta_prefs_window_is_no_focus (const char *window_name, ++ const char *window_class); ++ + void meta_prefs_set_num_workspaces (int n_workspaces); + + const char* meta_prefs_get_workspace_name (int i); +diff --git a/src/metacity.schemas.in.in b/src/metacity.schemas.in.in +index a9dd397..34cd7d6 100644 +--- a/src/metacity.schemas.in.in ++++ b/src/metacity.schemas.in.in +@@ -100,6 +100,34 @@ + </schema> + + <schema> ++ <key>/schemas/apps/metacity/general/no_focus_windows</key> ++ <applyto>/apps/metacity/general/no_focus_windows</applyto> ++ <owner>metacity</owner> ++ <type>string</type> ++ <default></default> ++ <locale name="C"> ++ <short>New windows that shouldn't get focus</short> ++ <long> ++ This option provides a way to specify new windows that shouldn't get ++ focus. Normally an application specifies whether or not it gets focus ++ by setting the _NET_WM_USER_TIME property, but legacy applications ++ may not set this, which can cause unwanted focus stealing. ++ ++ The contents of this property is a space-separated list of expressions ++ to match against windows. If any of the expressions match a window ++ then the window will not get focus. The syntax of expressions is: ++ ++ (eq [name|class] "<value>"): window name (title) or the class from ++ WM_CLASS matches <value> exactly. ++ (glob [name|class] "<glob>"): window name (title) or the class from ++ WM_CLASS matches the shell-style glob pattern <glob>. ++ (and <expr> <expr>) (or <expr> <expr>) (not <expr): Boolean combinations ++ of expressions. ++ </long> ++ </locale> ++ </schema> ++ ++ <schema> + <key>/schemas/apps/metacity/general/raise_on_click</key> + <applyto>/apps/metacity/general/raise_on_click</applyto> + <owner>metacity</owner> |