From 60d38a7c6683001ee2beb72b8f0b0beee4f04bb4 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" 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: 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 | 55 +++ 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.convert | 1 + src/org.gnome.metacity.gschema.xml.in | 21 ++ 8 files changed, 720 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 4d405bf..2befe33 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -66,6 +66,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 58f11e9..24a98cd 100644 --- a/src/core/prefs.c +++ b/src/core/prefs.c @@ -26,6 +26,7 @@ #include #include "prefs.h" +#include "window-matcher.h" #include "ui.h" #include "util.h" #include @@ -70,6 +71,7 @@ static PangoFontDescription *titlebar_font = NULL; static MetaVirtualModifier mouse_button_mods = Mod1Mask; static GDesktopFocusMode focus_mode = G_DESKTOP_FOCUS_MODE_CLICK; static GDesktopFocusNewWindows focus_new_windows = G_DESKTOP_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; @@ -120,6 +122,7 @@ static void maybe_give_disable_workarounds_warning (void); static gboolean titlebar_handler (GVariant*, gpointer*, gpointer); static gboolean theme_name_handler (GVariant*, gpointer*, gpointer); +static gboolean no_focus_windows_handler (GVariant*, gpointer*, gpointer); static gboolean mouse_button_mods_handler (GVariant*, gpointer*, gpointer); static gboolean button_layout_handler (GVariant*, gpointer*, gpointer); @@ -367,6 +370,14 @@ static MetaStringPreference preferences_string[] = NULL, }, { + { "no-focus-windows", + SCHEMA_METACITY, + META_PREF_NO_FOCUS_WINDOWS, + }, + no_focus_windows_handler, + NULL + }, + { { KEY_TITLEBAR_FONT, SCHEMA_GENERAL, META_PREF_TITLEBAR_FONT, @@ -998,6 +1009,39 @@ theme_name_handler (GVariant *value, } static gboolean +no_focus_windows_handler (GVariant *value, + gpointer *result, + gpointer data) +{ + const gchar *string_value; + + *result = NULL; /* ignored */ + string_value = g_variant_get_string (value, NULL); + + 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); + + return FALSE; + } + } + + return TRUE; +} + +static gboolean mouse_button_mods_handler (GVariant *value, gpointer *result, gpointer data) @@ -1414,6 +1458,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)"; @@ -1710,6 +1757,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..df889eb --- /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 +#include + +#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 ((MatcherToken) 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 ((MatcherToken) token != MATCHER_TOKEN_NOT) + { + b = meta_window_matcher_from_scanner (scanner, error); + if (!b) + { + meta_window_matcher_free (a); + return NULL; + } + } + + switch ((MatcherToken) 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 ((MatcherToken) 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 ((MatcherToken) 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 ) + * (or ) + * (not ) + * (eq [name|class] "") + * (glob [name|class] "") + * + * 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 2f2f800..5440160 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -1981,7 +1981,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 673cb36..b86843c 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, @@ -105,6 +106,9 @@ GDesktopTitlebarAction meta_prefs_get_action_double_click_titlebar (void); GDesktopTitlebarAction meta_prefs_get_action_middle_click_titlebar (void); GDesktopTitlebarAction 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.convert b/src/metacity-schemas.convert index 46f3104..9c271c6 100644 --- a/src/metacity-schemas.convert +++ b/src/metacity-schemas.convert @@ -1,3 +1,4 @@ [org.gnome.metacity] compositing-manager = /apps/metacity/general/compositing_manager reduced-resources = /apps/metacity/general/reduced_resources +no-focus-windows = /apps/metacity/general/no_focus_windows diff --git a/src/org.gnome.metacity.gschema.xml.in b/src/org.gnome.metacity.gschema.xml.in index 8fcdd7c..6900fa6 100644 --- a/src/org.gnome.metacity.gschema.xml.in +++ b/src/org.gnome.metacity.gschema.xml.in @@ -22,6 +22,27 @@ However, the wireframe feature is disabled when accessibility is on. + + '' + <_summary>New windows that shouldn't get focus + <_description> + 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. + + -- 1.7.9