Blame SOURCES/Add-nofocuswindows-preference-to-list-windows-that.patch

14ad8e
From 60d38a7c6683001ee2beb72b8f0b0beee4f04bb4 Mon Sep 17 00:00:00 2001
14ad8e
From: "Owen W. Taylor" <otaylor@fishsoup.net>
14ad8e
Date: Wed, 21 Oct 2009 18:07:12 -0400
14ad8e
Subject: [PATCH] Add no-focus-windows preference to list windows that
14ad8e
 shouldn't be focused
14ad8e
14ad8e
Notification windows from legacy software that don't set _NET_WM_USER_TIME
14ad8e
can be a huge annoyance for users, since they will pop up and steal focus.
14ad8e
14ad8e
Add:
14ad8e
14ad8e
 no-focus-windows
14ad8e
14ad8e
which is a list of expressions identifying new windows that shouldn't ever
14ad8e
be focused. For example:
14ad8e
14ad8e
 (and (eq class 'Mylegacyapp') (glob name 'New mail*'))
14ad8e
14ad8e
https://bugzilla.gnome.org/show_bug.cgi?id=599248
14ad8e
---
14ad8e
 src/Makefile.am                       |    2 +
14ad8e
 src/core/prefs.c                      |   55 +++
14ad8e
 src/core/window-matcher.c             |  582 +++++++++++++++++++++++++++++++++
14ad8e
 src/core/window-matcher.h             |   46 +++
14ad8e
 src/core/window.c                     |    9 +-
14ad8e
 src/include/prefs.h                   |    6 +-
14ad8e
 src/metacity-schemas.convert          |    1 +
14ad8e
 src/org.gnome.metacity.gschema.xml.in |   21 ++
14ad8e
 8 files changed, 720 insertions(+), 2 deletions(-)
14ad8e
 create mode 100644 src/core/window-matcher.c
14ad8e
 create mode 100644 src/core/window-matcher.h
14ad8e
14ad8e
diff --git a/src/Makefile.am b/src/Makefile.am
14ad8e
index 4d405bf..2befe33 100644
14ad8e
--- a/src/Makefile.am
14ad8e
+++ b/src/Makefile.am
14ad8e
@@ -66,6 +66,8 @@ metacity_SOURCES= 				\
14ad8e
 	core/stack.h				\
14ad8e
 	core/util.c				\
14ad8e
 	include/util.h				\
14ad8e
+	core/window-matcher.c			\
14ad8e
+	core/window-matcher.h			\
14ad8e
 	core/window-props.c			\
14ad8e
 	core/window-props.h			\
14ad8e
 	core/window.c				\
14ad8e
diff --git a/src/core/prefs.c b/src/core/prefs.c
14ad8e
index 58f11e9..24a98cd 100644
14ad8e
--- a/src/core/prefs.c
14ad8e
+++ b/src/core/prefs.c
14ad8e
@@ -26,6 +26,7 @@
14ad8e
 
14ad8e
 #include <config.h>
14ad8e
 #include "prefs.h"
14ad8e
+#include "window-matcher.h"
14ad8e
 #include "ui.h"
14ad8e
 #include "util.h"
14ad8e
 #include <glib.h>
14ad8e
@@ -70,6 +71,7 @@ static PangoFontDescription *titlebar_font = NULL;
14ad8e
 static MetaVirtualModifier mouse_button_mods = Mod1Mask;
14ad8e
 static GDesktopFocusMode focus_mode = G_DESKTOP_FOCUS_MODE_CLICK;
14ad8e
 static GDesktopFocusNewWindows focus_new_windows = G_DESKTOP_FOCUS_NEW_WINDOWS_SMART;
14ad8e
+static GSList *no_focus_windows = NULL;
14ad8e
 static gboolean raise_on_click = TRUE;
14ad8e
 static char* current_theme = NULL;
14ad8e
 static int num_workspaces = 4;
14ad8e
@@ -120,6 +122,7 @@ static void maybe_give_disable_workarounds_warning (void);
14ad8e
 
14ad8e
 static gboolean titlebar_handler (GVariant*, gpointer*, gpointer);
14ad8e
 static gboolean theme_name_handler (GVariant*, gpointer*, gpointer);
14ad8e
+static gboolean no_focus_windows_handler (GVariant*, gpointer*, gpointer);
14ad8e
 static gboolean mouse_button_mods_handler (GVariant*, gpointer*, gpointer);
14ad8e
 static gboolean button_layout_handler (GVariant*, gpointer*, gpointer);
14ad8e
 
14ad8e
@@ -367,6 +370,14 @@ static MetaStringPreference preferences_string[] =
14ad8e
       NULL,
14ad8e
     },
14ad8e
     {
14ad8e
+      { "no-focus-windows",
14ad8e
+        SCHEMA_METACITY,
14ad8e
+        META_PREF_NO_FOCUS_WINDOWS,
14ad8e
+      },
14ad8e
+      no_focus_windows_handler,
14ad8e
+      NULL
14ad8e
+    },
14ad8e
+    {
14ad8e
       { KEY_TITLEBAR_FONT,
14ad8e
         SCHEMA_GENERAL,
14ad8e
         META_PREF_TITLEBAR_FONT,
14ad8e
@@ -998,6 +1009,39 @@ theme_name_handler (GVariant *value,
14ad8e
 }
14ad8e
 
14ad8e
 static gboolean
14ad8e
+no_focus_windows_handler (GVariant *value,
14ad8e
+                          gpointer *result,
14ad8e
+                          gpointer  data)
14ad8e
+{
14ad8e
+  const gchar *string_value;
14ad8e
+
14ad8e
+  *result = NULL; /* ignored */
14ad8e
+  string_value = g_variant_get_string (value, NULL);
14ad8e
+
14ad8e
+  if (no_focus_windows)
14ad8e
+    {
14ad8e
+      meta_window_matcher_list_free (no_focus_windows);
14ad8e
+      no_focus_windows = NULL;
14ad8e
+    }
14ad8e
+
14ad8e
+  if (string_value)
14ad8e
+    {
14ad8e
+      GError *error = NULL;
14ad8e
+      no_focus_windows = meta_window_matcher_list_from_string (string_value, &error);
14ad8e
+      if (error != NULL)
14ad8e
+        {
14ad8e
+          meta_warning ("Error parsing no_focus_windows='%s': %s\n",
14ad8e
+                        string_value, error->message);
14ad8e
+          g_error_free (error);
14ad8e
+
14ad8e
+          return FALSE;
14ad8e
+        }
14ad8e
+    }
14ad8e
+
14ad8e
+  return TRUE;
14ad8e
+}
14ad8e
+
14ad8e
+static gboolean
14ad8e
 mouse_button_mods_handler (GVariant *value,
14ad8e
                            gpointer *result,
14ad8e
                            gpointer  data)
14ad8e
@@ -1414,6 +1458,9 @@ meta_preference_to_string (MetaPreference pref)
14ad8e
 
14ad8e
     case META_PREF_FORCE_FULLSCREEN:
14ad8e
       return "FORCE_FULLSCREEN";
14ad8e
+
14ad8e
+    case META_PREF_NO_FOCUS_WINDOWS:
14ad8e
+      return "NO_FOCUS_WINDOWS";
14ad8e
     }
14ad8e
 
14ad8e
   return "(unknown)";
14ad8e
@@ -1710,6 +1757,14 @@ meta_prefs_get_action_right_click_titlebar (void)
14ad8e
 }
14ad8e
 
14ad8e
 gboolean
14ad8e
+meta_prefs_window_is_no_focus (const char *window_name,
14ad8e
+                               const char *window_class)
14ad8e
+{
14ad8e
+  return meta_window_matcher_list_matches (no_focus_windows,
14ad8e
+                                           window_name, window_class);
14ad8e
+}
14ad8e
+
14ad8e
+gboolean
14ad8e
 meta_prefs_get_auto_raise (void)
14ad8e
 {
14ad8e
   return auto_raise;
14ad8e
diff --git a/src/core/window-matcher.c b/src/core/window-matcher.c
14ad8e
new file mode 100644
14ad8e
index 0000000..df889eb
14ad8e
--- /dev/null
14ad8e
+++ b/src/core/window-matcher.c
14ad8e
@@ -0,0 +1,582 @@
14ad8e
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
14ad8e
+
14ad8e
+/* Tiny language for matching against windows */
14ad8e
+
14ad8e
+/*
14ad8e
+ * Copyright (C) 2009 Red Hat, Inc.
14ad8e
+ *
14ad8e
+ * This program is free software; you can redistribute it and/or
14ad8e
+ * modify it under the terms of the GNU General Public License as
14ad8e
+ * published by the Free Software Foundation; either version 2 of the
14ad8e
+ * License, or (at your option) any later version.
14ad8e
+ *
14ad8e
+ * This program is distributed in the hope that it will be useful, but
14ad8e
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
14ad8e
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14ad8e
+ * General Public License for more details.
14ad8e
+ *
14ad8e
+ * You should have received a copy of the GNU General Public License
14ad8e
+ * along with this program; if not, write to the Free Software
14ad8e
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
14ad8e
+ * 02111-1307, USA.
14ad8e
+ */
14ad8e
+
14ad8e
+#include <glib.h>
14ad8e
+#include <string.h>
14ad8e
+
14ad8e
+#include "window-matcher.h"
14ad8e
+
14ad8e
+typedef struct _MetaWindowMatcher MetaWindowMatcher;
14ad8e
+
14ad8e
+typedef enum {
14ad8e
+  MATCHER_OPERAND_CLASS,
14ad8e
+  MATCHER_OPERAND_NAME
14ad8e
+} MatcherOperand;
14ad8e
+
14ad8e
+typedef enum {
14ad8e
+  MATCHER_TOKEN_AND = G_TOKEN_LAST + 1,
14ad8e
+  MATCHER_TOKEN_OR,
14ad8e
+  MATCHER_TOKEN_NOT,
14ad8e
+  MATCHER_TOKEN_EQ,
14ad8e
+  MATCHER_TOKEN_GLOB,
14ad8e
+  MATCHER_TOKEN_NAME,
14ad8e
+  MATCHER_TOKEN_CLASS
14ad8e
+} MatcherToken;
14ad8e
+
14ad8e
+struct _MetaWindowMatcher {
14ad8e
+  enum {
14ad8e
+    MATCHER_AND,
14ad8e
+    MATCHER_OR,
14ad8e
+    MATCHER_NOT,
14ad8e
+    MATCHER_EQ,
14ad8e
+    MATCHER_GLOB
14ad8e
+  } type;
14ad8e
+
14ad8e
+  union {
14ad8e
+    struct {
14ad8e
+      MetaWindowMatcher *a;
14ad8e
+      MetaWindowMatcher *b;
14ad8e
+    } and;
14ad8e
+    struct {
14ad8e
+      MetaWindowMatcher *a;
14ad8e
+      MetaWindowMatcher *b;
14ad8e
+    } or;
14ad8e
+    struct {
14ad8e
+      MetaWindowMatcher *a;
14ad8e
+    } not;
14ad8e
+    struct {
14ad8e
+      MatcherOperand operand;
14ad8e
+      char *str;
14ad8e
+    } eq;
14ad8e
+    struct {
14ad8e
+      MatcherOperand operand;
14ad8e
+      char *str;
14ad8e
+      GPatternSpec *pattern;
14ad8e
+    } glob;
14ad8e
+  } u;
14ad8e
+};
14ad8e
+
14ad8e
+static void
14ad8e
+meta_window_matcher_free (MetaWindowMatcher *matcher)
14ad8e
+{
14ad8e
+  switch (matcher->type)
14ad8e
+    {
14ad8e
+    case MATCHER_AND:
14ad8e
+      meta_window_matcher_free (matcher->u.and.a);
14ad8e
+      meta_window_matcher_free (matcher->u.and.b);
14ad8e
+      break;
14ad8e
+    case MATCHER_OR:
14ad8e
+      meta_window_matcher_free (matcher->u.or.a);
14ad8e
+      meta_window_matcher_free (matcher->u.or.b);
14ad8e
+      break;
14ad8e
+    case MATCHER_NOT:
14ad8e
+      meta_window_matcher_free (matcher->u.or.a);
14ad8e
+      break;
14ad8e
+    case MATCHER_EQ:
14ad8e
+      g_free (matcher->u.eq.str);
14ad8e
+      break;
14ad8e
+    case MATCHER_GLOB:
14ad8e
+      g_free (matcher->u.glob.str);
14ad8e
+      g_pattern_spec_free (matcher->u.glob.pattern);
14ad8e
+      break;
14ad8e
+    }
14ad8e
+
14ad8e
+  g_slice_free (MetaWindowMatcher, matcher);
14ad8e
+}
14ad8e
+
14ad8e
+void
14ad8e
+meta_window_matcher_list_free (GSList *list)
14ad8e
+{
14ad8e
+  g_slist_foreach (list, (GFunc)meta_window_matcher_free, NULL);
14ad8e
+  g_slist_free (list);
14ad8e
+}
14ad8e
+
14ad8e
+static gboolean
14ad8e
+meta_window_matcher_matches (MetaWindowMatcher *matcher,
14ad8e
+                             const char        *window_name,
14ad8e
+                             const char        *window_class)
14ad8e
+{
14ad8e
+  switch (matcher->type)
14ad8e
+    {
14ad8e
+    case MATCHER_AND:
14ad8e
+      return (meta_window_matcher_matches (matcher->u.and.a, window_name, window_class) &&
14ad8e
+              meta_window_matcher_matches (matcher->u.and.b, window_name, window_class));
14ad8e
+    case MATCHER_OR:
14ad8e
+      return (meta_window_matcher_matches (matcher->u.or.a, window_name, window_class) ||
14ad8e
+              meta_window_matcher_matches(matcher->u.or.b, window_name, window_class));
14ad8e
+    case MATCHER_NOT:
14ad8e
+      return !meta_window_matcher_matches (matcher->u.not.a, window_name, window_class);
14ad8e
+    case MATCHER_EQ:
14ad8e
+      if (matcher->u.eq.operand == MATCHER_OPERAND_NAME)
14ad8e
+        return window_name && strcmp (matcher->u.eq.str, window_name) == 0;
14ad8e
+      else
14ad8e
+        return window_class && strcmp (matcher->u.eq.str, window_class) == 0;
14ad8e
+    case MATCHER_GLOB:
14ad8e
+      if (matcher->u.glob.operand == MATCHER_OPERAND_NAME)
14ad8e
+        return window_name && g_pattern_match_string (matcher->u.glob.pattern, window_name);
14ad8e
+      else
14ad8e
+        return window_class && g_pattern_match_string (matcher->u.glob.pattern, window_class);
14ad8e
+    }
14ad8e
+
14ad8e
+  g_assert_not_reached();
14ad8e
+  return FALSE;
14ad8e
+}
14ad8e
+
14ad8e
+gboolean
14ad8e
+meta_window_matcher_list_matches (GSList     *list,
14ad8e
+                                  const char *window_name,
14ad8e
+                                  const char *window_class)
14ad8e
+{
14ad8e
+  GSList *l;
14ad8e
+
14ad8e
+  for (l = list; l; l = l->next)
14ad8e
+    {
14ad8e
+      if (meta_window_matcher_matches (l->data, window_name, window_class))
14ad8e
+        return TRUE;
14ad8e
+    }
14ad8e
+
14ad8e
+  return FALSE;
14ad8e
+}
14ad8e
+
14ad8e
+static const GScannerConfig scanner_config =
14ad8e
+{
14ad8e
+  " \t\r\n"             /* cset_skip_characters */,
14ad8e
+  (
14ad8e
+   G_CSET_a_2_z
14ad8e
+   "_"
14ad8e
+   G_CSET_A_2_Z
14ad8e
+   )                    /* cset_identifier_first */,
14ad8e
+  (
14ad8e
+   G_CSET_a_2_z
14ad8e
+   "_"
14ad8e
+   G_CSET_A_2_Z
14ad8e
+   G_CSET_DIGITS
14ad8e
+   G_CSET_LATINS
14ad8e
+   G_CSET_LATINC
14ad8e
+   )                    /* cset_identifier_nth */,
14ad8e
+  NULL                  /* cpair_comment_single */,
14ad8e
+  TRUE                  /* case_sensitive */,
14ad8e
+  TRUE                  /* skip_comment_multi */,
14ad8e
+  FALSE                 /* skip_comment_single */,
14ad8e
+  TRUE                  /* scan_comment_multi */,
14ad8e
+  TRUE                  /* scan_identifier */,
14ad8e
+  TRUE                  /* scan_identifier_1char */,
14ad8e
+  FALSE                 /* scan_identifier_NULL */,
14ad8e
+  TRUE                  /* scan_symbols */,
14ad8e
+  FALSE                 /* scan_binary */,
14ad8e
+  TRUE                  /* scan_octal */,
14ad8e
+  TRUE                  /* scan_float */,
14ad8e
+  TRUE                  /* scan_hex */,
14ad8e
+  FALSE                 /* scan_hex_dollar */,
14ad8e
+  TRUE                  /* scan_string_sq */,
14ad8e
+  TRUE                  /* scan_string_dq */,
14ad8e
+  TRUE                  /* numbers_2_int */,
14ad8e
+  FALSE                 /* int_2_float */,
14ad8e
+  FALSE                 /* identifier_2_string */,
14ad8e
+  TRUE                  /* char_2_token */,
14ad8e
+  TRUE                  /* symbol_2_token */,
14ad8e
+  FALSE                 /* scope_0_fallback */,
14ad8e
+  FALSE                 /* store_int64 */,
14ad8e
+};
14ad8e
+
14ad8e
+static void
14ad8e
+set_error (GScanner   *scanner,
14ad8e
+           GError    **error,
14ad8e
+           const char *message)
14ad8e
+{
14ad8e
+  g_set_error (error, 0, 0,
14ad8e
+               "Parse error at %d:%d: %s",
14ad8e
+               g_scanner_cur_line (scanner),
14ad8e
+               g_scanner_cur_position (scanner),
14ad8e
+               message);
14ad8e
+}
14ad8e
+
14ad8e
+static MetaWindowMatcher *
14ad8e
+meta_window_matcher_new_and (MetaWindowMatcher *a,
14ad8e
+                             MetaWindowMatcher *b)
14ad8e
+{
14ad8e
+  MetaWindowMatcher *matcher = g_slice_new0 (MetaWindowMatcher);
14ad8e
+
14ad8e
+  matcher->type = MATCHER_AND;
14ad8e
+  matcher->u.and.a = a;
14ad8e
+  matcher->u.and.b = b;
14ad8e
+
14ad8e
+  return matcher;
14ad8e
+}
14ad8e
+
14ad8e
+static MetaWindowMatcher *
14ad8e
+meta_window_matcher_new_or (MetaWindowMatcher *a,
14ad8e
+                            MetaWindowMatcher *b)
14ad8e
+{
14ad8e
+  MetaWindowMatcher *matcher = g_slice_new0 (MetaWindowMatcher);
14ad8e
+
14ad8e
+  matcher->type = MATCHER_OR;
14ad8e
+  matcher->u.or.a = a;
14ad8e
+  matcher->u.or.b = b;
14ad8e
+
14ad8e
+  return matcher;
14ad8e
+}
14ad8e
+
14ad8e
+static MetaWindowMatcher *
14ad8e
+meta_window_matcher_new_not (MetaWindowMatcher *a)
14ad8e
+{
14ad8e
+  MetaWindowMatcher *matcher = g_slice_new0 (MetaWindowMatcher);
14ad8e
+
14ad8e
+  matcher->type = MATCHER_NOT;
14ad8e
+  matcher->u.not.a = a;
14ad8e
+
14ad8e
+  return matcher;
14ad8e
+}
14ad8e
+
14ad8e
+static MetaWindowMatcher *
14ad8e
+meta_window_matcher_new_eq (MatcherOperand operand,
14ad8e
+                            const char    *str)
14ad8e
+{
14ad8e
+  MetaWindowMatcher *matcher = g_slice_new0 (MetaWindowMatcher);
14ad8e
+
14ad8e
+  matcher->type = MATCHER_EQ;
14ad8e
+  matcher->u.eq.operand = operand;
14ad8e
+  matcher->u.eq.str = g_strdup (str);
14ad8e
+
14ad8e
+  return matcher;
14ad8e
+}
14ad8e
+
14ad8e
+static MetaWindowMatcher *
14ad8e
+meta_window_matcher_new_glob (MatcherOperand operand,
14ad8e
+                              const char    *str)
14ad8e
+{
14ad8e
+  MetaWindowMatcher *matcher = g_slice_new0 (MetaWindowMatcher);
14ad8e
+
14ad8e
+  matcher->type = MATCHER_GLOB;
14ad8e
+  matcher->u.glob.operand = operand;
14ad8e
+  matcher->u.glob.str = g_strdup (str);
14ad8e
+  matcher->u.glob.pattern = g_pattern_spec_new (str);
14ad8e
+
14ad8e
+  return matcher;
14ad8e
+}
14ad8e
+
14ad8e
+static MetaWindowMatcher *
14ad8e
+meta_window_matcher_from_scanner (GScanner *scanner,
14ad8e
+                                  GError  **error)
14ad8e
+{
14ad8e
+  MetaWindowMatcher *matcher = NULL;
14ad8e
+  GTokenType token;
14ad8e
+  GTokenValue value;
14ad8e
+
14ad8e
+  token = g_scanner_get_next_token (scanner);
14ad8e
+  if (token != G_TOKEN_LEFT_PAREN)
14ad8e
+    {
14ad8e
+      set_error (scanner, error, "expected '('");
14ad8e
+      return NULL;
14ad8e
+    }
14ad8e
+
14ad8e
+  token = g_scanner_get_next_token (scanner);
14ad8e
+  switch ((MatcherToken) token)
14ad8e
+    {
14ad8e
+    case MATCHER_TOKEN_AND:
14ad8e
+    case MATCHER_TOKEN_OR:
14ad8e
+    case MATCHER_TOKEN_NOT:
14ad8e
+      {
14ad8e
+        MetaWindowMatcher *a, *b;
14ad8e
+
14ad8e
+        a = meta_window_matcher_from_scanner (scanner, error);
14ad8e
+        if (!a)
14ad8e
+          return NULL;
14ad8e
+
14ad8e
+        if ((MatcherToken) token != MATCHER_TOKEN_NOT)
14ad8e
+          {
14ad8e
+            b = meta_window_matcher_from_scanner (scanner, error);
14ad8e
+            if (!b)
14ad8e
+              {
14ad8e
+                meta_window_matcher_free (a);
14ad8e
+                return NULL;
14ad8e
+              }
14ad8e
+          }
14ad8e
+
14ad8e
+        switch ((MatcherToken) token)
14ad8e
+          {
14ad8e
+          case MATCHER_TOKEN_AND:
14ad8e
+            matcher = meta_window_matcher_new_and (a, b);
14ad8e
+            break;
14ad8e
+          case MATCHER_TOKEN_OR:
14ad8e
+            matcher = meta_window_matcher_new_or (a, b);
14ad8e
+            break;
14ad8e
+          case MATCHER_TOKEN_NOT:
14ad8e
+            matcher = meta_window_matcher_new_not (a);
14ad8e
+            break;
14ad8e
+          default:
14ad8e
+            g_assert_not_reached();
14ad8e
+            break;
14ad8e
+          }
14ad8e
+      }
14ad8e
+      break;
14ad8e
+    case MATCHER_TOKEN_EQ:
14ad8e
+    case MATCHER_TOKEN_GLOB:
14ad8e
+      {
14ad8e
+        MatcherOperand operand;
14ad8e
+
14ad8e
+        switch ((MatcherToken) g_scanner_get_next_token (scanner))
14ad8e
+          {
14ad8e
+          case MATCHER_TOKEN_NAME:
14ad8e
+            operand = MATCHER_OPERAND_NAME;
14ad8e
+            break;
14ad8e
+          case MATCHER_TOKEN_CLASS:
14ad8e
+            operand = MATCHER_OPERAND_CLASS;
14ad8e
+            break;
14ad8e
+          default:
14ad8e
+            set_error (scanner, error, "expected name/class");
14ad8e
+            return NULL;
14ad8e
+          }
14ad8e
+
14ad8e
+        if (g_scanner_get_next_token (scanner) != G_TOKEN_STRING)
14ad8e
+          {
14ad8e
+            set_error (scanner, error, "expected string");
14ad8e
+            return NULL;
14ad8e
+          }
14ad8e
+
14ad8e
+        value = g_scanner_cur_value (scanner);
14ad8e
+
14ad8e
+        switch ((MatcherToken) token)
14ad8e
+          {
14ad8e
+          case MATCHER_TOKEN_EQ:
14ad8e
+            matcher = meta_window_matcher_new_eq (operand, value.v_string);
14ad8e
+            break;
14ad8e
+          case MATCHER_TOKEN_GLOB:
14ad8e
+            matcher = meta_window_matcher_new_glob (operand, value.v_string);
14ad8e
+            break;
14ad8e
+          default:
14ad8e
+            g_assert_not_reached();
14ad8e
+          }
14ad8e
+      }
14ad8e
+      break;
14ad8e
+    default:
14ad8e
+      set_error (scanner, error, "expected and/or/not/eq/glob");
14ad8e
+      return NULL;
14ad8e
+    }
14ad8e
+
14ad8e
+  if (g_scanner_get_next_token (scanner) != G_TOKEN_RIGHT_PAREN)
14ad8e
+    {
14ad8e
+      set_error (scanner, error, "expected ')'");
14ad8e
+      return NULL;
14ad8e
+    }
14ad8e
+
14ad8e
+  return matcher;
14ad8e
+}
14ad8e
+
14ad8e
+GSList *
14ad8e
+meta_window_matcher_list_from_string (const char *str,
14ad8e
+                                      GError    **error)
14ad8e
+{
14ad8e
+  GScanner *scanner = g_scanner_new (&scanner_config);
14ad8e
+  GSList *result = NULL;
14ad8e
+
14ad8e
+  g_scanner_scope_add_symbol (scanner, 0, "and", GINT_TO_POINTER (MATCHER_TOKEN_AND));
14ad8e
+  g_scanner_scope_add_symbol (scanner, 0, "or", GINT_TO_POINTER (MATCHER_TOKEN_OR));
14ad8e
+  g_scanner_scope_add_symbol (scanner, 0, "not", GINT_TO_POINTER (MATCHER_TOKEN_NOT));
14ad8e
+  g_scanner_scope_add_symbol (scanner, 0, "eq", GINT_TO_POINTER (MATCHER_TOKEN_EQ));
14ad8e
+  g_scanner_scope_add_symbol (scanner, 0, "glob", GINT_TO_POINTER (MATCHER_TOKEN_GLOB));
14ad8e
+  g_scanner_scope_add_symbol (scanner, 0, "name", GINT_TO_POINTER (MATCHER_TOKEN_NAME));
14ad8e
+  g_scanner_scope_add_symbol (scanner, 0, "class", GINT_TO_POINTER (MATCHER_TOKEN_CLASS));
14ad8e
+
14ad8e
+  g_scanner_input_text (scanner, str, strlen (str));
14ad8e
+
14ad8e
+  while (g_scanner_peek_next_token (scanner) != G_TOKEN_EOF)
14ad8e
+    {
14ad8e
+      MetaWindowMatcher *matcher = meta_window_matcher_from_scanner (scanner, error);
14ad8e
+      if (!matcher)
14ad8e
+        {
14ad8e
+          meta_window_matcher_list_free (result);
14ad8e
+          return NULL;
14ad8e
+        }
14ad8e
+
14ad8e
+      result = g_slist_prepend (result, matcher);
14ad8e
+    }
14ad8e
+
14ad8e
+  g_scanner_destroy (scanner);
14ad8e
+
14ad8e
+  return g_slist_reverse (result);
14ad8e
+}
14ad8e
+
14ad8e
+#ifdef BUILD_MATCHER_TESTS
14ad8e
+
14ad8e
+static void
14ad8e
+append_operand_to_string (GString       *string,
14ad8e
+                          MatcherOperand operand)
14ad8e
+{
14ad8e
+  if (operand == MATCHER_OPERAND_NAME)
14ad8e
+    g_string_append (string, "name");
14ad8e
+  else
14ad8e
+    g_string_append (string, "class");
14ad8e
+}
14ad8e
+
14ad8e
+static void
14ad8e
+append_string_to_string (GString            *str,
14ad8e
+                         const char         *to_append)
14ad8e
+{
14ad8e
+  const char *p;
14ad8e
+
14ad8e
+  g_string_append_c (str, '"');
14ad8e
+  for (p = to_append; *p; p++)
14ad8e
+    {
14ad8e
+      if (*p == '"')
14ad8e
+        g_string_append (str, "\\\"");
14ad8e
+      else
14ad8e
+        g_string_append_c (str, *p);
14ad8e
+    }
14ad8e
+  g_string_append_c (str, '"');
14ad8e
+}
14ad8e
+
14ad8e
+static void
14ad8e
+append_matcher_to_string (GString           *str,
14ad8e
+                          MetaWindowMatcher *matcher)
14ad8e
+{
14ad8e
+  switch (matcher->type)
14ad8e
+    {
14ad8e
+    case MATCHER_AND:
14ad8e
+      g_string_append (str, "(and ");
14ad8e
+      append_matcher_to_string (str, matcher->u.and.a);
14ad8e
+      g_string_append_c (str, ' ');
14ad8e
+      append_matcher_to_string (str, matcher->u.and.b);
14ad8e
+      break;
14ad8e
+    case MATCHER_OR:
14ad8e
+      g_string_append (str, "(or ");
14ad8e
+      append_matcher_to_string (str, matcher->u.or.a);
14ad8e
+      g_string_append_c (str, ' ');
14ad8e
+      append_matcher_to_string (str, matcher->u.or.b);
14ad8e
+      break;
14ad8e
+    case MATCHER_NOT:
14ad8e
+      g_string_append (str, "(not ");
14ad8e
+      append_matcher_to_string (str, matcher->u.not.a);
14ad8e
+      break;
14ad8e
+    case MATCHER_EQ:
14ad8e
+      g_string_append (str, "(eq ");
14ad8e
+      append_operand_to_string (str, matcher->u.eq.operand);
14ad8e
+      g_string_append_c (str, ' ');
14ad8e
+      append_string_to_string (str, matcher->u.eq.str);
14ad8e
+      break;
14ad8e
+    case MATCHER_GLOB:
14ad8e
+      g_string_append (str, "(glob ");
14ad8e
+      append_operand_to_string (str, matcher->u.glob.operand);
14ad8e
+      g_string_append_c (str, ' ');
14ad8e
+      append_string_to_string (str, matcher->u.glob.str);
14ad8e
+      break;
14ad8e
+    }
14ad8e
+
14ad8e
+  g_string_append_c (str, ')');
14ad8e
+}
14ad8e
+
14ad8e
+static char *
14ad8e
+meta_window_matcher_list_to_string (GSList *list)
14ad8e
+{
14ad8e
+  GSList *l;
14ad8e
+  GString *str = g_string_new (NULL);
14ad8e
+
14ad8e
+  for (l = list; l; l = l->next)
14ad8e
+    {
14ad8e
+      if (str->len > 0)
14ad8e
+        g_string_append_c (str, ' ');
14ad8e
+
14ad8e
+      append_matcher_to_string (str, l->data);
14ad8e
+    }
14ad8e
+
14ad8e
+  return g_string_free (str, FALSE);
14ad8e
+}
14ad8e
+
14ad8e
+static void
14ad8e
+test_roundtrip (const char *str)
14ad8e
+{
14ad8e
+  GError *error = NULL;
14ad8e
+  GSList *list = meta_window_matcher_list_from_string (str, &error);
14ad8e
+  char *result;
14ad8e
+
14ad8e
+  if (error != NULL)
14ad8e
+    g_error ("Failed to parse '%s': %s\n", str, error->message);
14ad8e
+
14ad8e
+  result = meta_window_matcher_list_to_string (list);
14ad8e
+  if (strcmp (result, str) != 0)
14ad8e
+    g_error ("Round-trip conversion of '%s' gave '%s'\n", str, result);
14ad8e
+
14ad8e
+  g_free (result);
14ad8e
+  meta_window_matcher_list_free (list);
14ad8e
+}
14ad8e
+
14ad8e
+static void
14ad8e
+test_matches (const char *str,
14ad8e
+              const char *window_name,
14ad8e
+              const char *window_class,
14ad8e
+              gboolean    expected)
14ad8e
+{
14ad8e
+  GError *error = NULL;
14ad8e
+  GSList *list = meta_window_matcher_list_from_string (str, &error);
14ad8e
+  gboolean matches;
14ad8e
+
14ad8e
+  if (error != NULL)
14ad8e
+    g_error ("Failed to parse '%s': %s\n", str, error->message);
14ad8e
+
14ad8e
+  matches = meta_window_matcher_list_matches (list, window_name, window_class))
14ad8e
+  if (matches != expected)
14ad8e
+    {
14ad8e
+      g_error ("Tested '%s' against name=%s, class=%s, expected %s, got %s\n",
14ad8e
+               str, window_name, window_class,
14ad8e
+               expected ? "true" : "false",
14ad8e
+               matches ? "true" : "false");
14ad8e
+    }
14ad8e
+
14ad8e
+
14ad8e
+  meta_window_matcher_list_free (list);
14ad8e
+}
14ad8e
+
14ad8e
+int main (int argc, char **argv)
14ad8e
+{
14ad8e
+  test_roundtrip ("(eq name \"foo\")");
14ad8e
+  test_roundtrip ("(eq name \"fo\\\"o\")");
14ad8e
+  test_roundtrip ("(glob class \"*bar?baz\")");
14ad8e
+  test_roundtrip ("(and (eq name \"foo\") (glob class \"*bar?baz\"))");
14ad8e
+  test_roundtrip ("(or (eq name \"foo\") (glob class \"*bar?baz\"))");
14ad8e
+  test_roundtrip ("(not (eq name \"foo\"))");
14ad8e
+
14ad8e
+  test_roundtrip ("(eq name \"foo\") (glob class \"*bar?baz\")");
14ad8e
+
14ad8e
+  test_matches ("(eq name 'foo')", "foo", NULL, TRUE);
14ad8e
+  test_matches ("(eq name 'foo')", "foob", NULL, FALSE);
14ad8e
+  test_matches ("(eq name 'foo')", NULL, NULL, FALSE);
14ad8e
+  test_matches ("(eq class 'bar')", "foo", "bar", TRUE);
14ad8e
+  test_matches ("(eq class 'bar')", NULL, NULL, FALSE);
14ad8e
+
14ad8e
+  test_matches ("(glob name 'foo*')", "foooo", NULL, TRUE);
14ad8e
+  test_matches ("(glob name 'foo*')", NULL, NULL, FALSE);
14ad8e
+  test_matches ("(glob class 'b*r')", "foooo", "baaaar", TRUE);
14ad8e
+  test_matches ("(glob class 'b*r')", NULL, NULL, FALSE);
14ad8e
+
14ad8e
+  test_matches ("(and (eq name 'foo') (eq class 'bar'))", "foo", "bar", TRUE);
14ad8e
+  test_matches ("(and (eq name 'foo') (eq class 'bar'))", "foo", "baz", FALSE);
14ad8e
+  test_matches ("(and (eq name 'foo') (not (eq class 'bar')))", "foo", "bar", FALSE);
14ad8e
+  test_matches ("(and (eq name 'foo') (not (eq class 'bar')))", "foo", "baz", TRUE);
14ad8e
+
14ad8e
+  test_matches ("(or (eq name 'foo') (eq class 'bar'))", "foo", "baz", TRUE);
14ad8e
+  test_matches ("(or (eq name 'foo') (eq class 'bar'))", "fof", "baz", FALSE);
14ad8e
+
14ad8e
+  return 0;
14ad8e
+}
14ad8e
+
14ad8e
+#endif /* BUILD_MATCHER_TESTS */
14ad8e
diff --git a/src/core/window-matcher.h b/src/core/window-matcher.h
14ad8e
new file mode 100644
14ad8e
index 0000000..7fc7826
14ad8e
--- /dev/null
14ad8e
+++ b/src/core/window-matcher.h
14ad8e
@@ -0,0 +1,46 @@
14ad8e
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
14ad8e
+
14ad8e
+/* Tiny language for matching against windows
14ad8e
+ *
14ad8e
+ * Expression Syntax:
14ad8e
+ *
14ad8e
+ *  (and <expr> <expr>)
14ad8e
+ *  (or <expr> <expr>)
14ad8e
+ *  (not <expr>)
14ad8e
+ *  (eq [name|class] "<value>")
14ad8e
+ *  (glob [name|class] "<glob>")
14ad8e
+ *
14ad8e
+ * A "matcher list" is a whitespace-separated list of expressions that are
14ad8e
+ * implicitly or'ed together. Globs are shell style patterns with
14ad8e
+ * matching 0 or more characters and ? matching one character.
14ad8e
+ */
14ad8e
+
14ad8e
+/*
14ad8e
+ * Copyright (C) 2009 Red Hat, Inc.
14ad8e
+ *
14ad8e
+ * This program is free software; you can redistribute it and/or
14ad8e
+ * modify it under the terms of the GNU General Public License as
14ad8e
+ * published by the Free Software Foundation; either version 2 of the
14ad8e
+ * License, or (at your option) any later version.
14ad8e
+ *
14ad8e
+ * This program is distributed in the hope that it will be useful, but
14ad8e
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
14ad8e
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14ad8e
+ * General Public License for more details.
14ad8e
+ *
14ad8e
+ * You should have received a copy of the GNU General Public License
14ad8e
+ * along with this program; if not, write to the Free Software
14ad8e
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
14ad8e
+ * 02111-1307, USA.
14ad8e
+ */
14ad8e
+
14ad8e
+#ifndef META_WINDOW_MATCHER_H
14ad8e
+#define META_WINDOW_MATCHER_H
14ad8e
+
14ad8e
+GSList * meta_window_matcher_list_from_string (const char         *str,
14ad8e
+					       GError            **error);
14ad8e
+void     meta_window_matcher_list_free        (GSList             *list);
14ad8e
+gboolean meta_window_matcher_list_matches     (GSList             *list,
14ad8e
+					       const char         *window_name,
14ad8e
+					       const char         *window_class);
14ad8e
+#endif /* META_WINDOW_MATCHER_H */
14ad8e
diff --git a/src/core/window.c b/src/core/window.c
14ad8e
index 2f2f800..5440160 100644
14ad8e
--- a/src/core/window.c
14ad8e
+++ b/src/core/window.c
14ad8e
@@ -1981,7 +1981,14 @@ window_state_on_map (MetaWindow *window,
14ad8e
 {
14ad8e
   gboolean intervening_events;
14ad8e
 
14ad8e
-  intervening_events = intervening_user_event_occurred (window);
14ad8e
+  /* A 'no focus' window is a window that has been configured in GConf
14ad8e
+   * to never take focus on map; typically it will be a notification
14ad8e
+   * window from a legacy app that doesn't support _NET_WM_USER_TIME.
14ad8e
+   */
14ad8e
+  if (meta_prefs_window_is_no_focus (window->title, window->res_class))
14ad8e
+    intervening_events = TRUE;
14ad8e
+  else
14ad8e
+    intervening_events = intervening_user_event_occurred (window);
14ad8e
 
14ad8e
   *takes_focus = !intervening_events;
14ad8e
   *places_on_top = *takes_focus;
14ad8e
diff --git a/src/include/prefs.h b/src/include/prefs.h
14ad8e
index 673cb36..b86843c 100644
14ad8e
--- a/src/include/prefs.h
14ad8e
+++ b/src/include/prefs.h
14ad8e
@@ -60,7 +60,8 @@ typedef enum
14ad8e
   META_PREF_CURSOR_SIZE,
14ad8e
   META_PREF_COMPOSITING_MANAGER,
14ad8e
   META_PREF_RESIZE_WITH_RIGHT_BUTTON,
14ad8e
-  META_PREF_FORCE_FULLSCREEN
14ad8e
+  META_PREF_FORCE_FULLSCREEN,
14ad8e
+  META_PREF_NO_FOCUS_WINDOWS
14ad8e
 } MetaPreference;
14ad8e
 
14ad8e
 typedef void (* MetaPrefsChangedFunc) (MetaPreference pref,
14ad8e
@@ -105,6 +106,9 @@ GDesktopTitlebarAction       meta_prefs_get_action_double_click_titlebar (void);
14ad8e
 GDesktopTitlebarAction       meta_prefs_get_action_middle_click_titlebar (void);
14ad8e
 GDesktopTitlebarAction       meta_prefs_get_action_right_click_titlebar (void);
14ad8e
 
14ad8e
+gboolean                    meta_prefs_window_is_no_focus (const char *window_name,
14ad8e
+                                                           const char *window_class);
14ad8e
+
14ad8e
 void meta_prefs_set_num_workspaces (int n_workspaces);
14ad8e
 
14ad8e
 const char* meta_prefs_get_workspace_name    (int         i);
14ad8e
diff --git a/src/metacity-schemas.convert b/src/metacity-schemas.convert
14ad8e
index 46f3104..9c271c6 100644
14ad8e
--- a/src/metacity-schemas.convert
14ad8e
+++ b/src/metacity-schemas.convert
14ad8e
@@ -1,3 +1,4 @@
14ad8e
 [org.gnome.metacity]
14ad8e
 compositing-manager = /apps/metacity/general/compositing_manager
14ad8e
 reduced-resources = /apps/metacity/general/reduced_resources
14ad8e
+no-focus-windows = /apps/metacity/general/no_focus_windows
14ad8e
diff --git a/src/org.gnome.metacity.gschema.xml.in b/src/org.gnome.metacity.gschema.xml.in
14ad8e
index 8fcdd7c..6900fa6 100644
14ad8e
--- a/src/org.gnome.metacity.gschema.xml.in
14ad8e
+++ b/src/org.gnome.metacity.gschema.xml.in
14ad8e
@@ -22,6 +22,27 @@
14ad8e
         However, the wireframe feature is disabled when accessibility is on.
14ad8e
       </_description>
14ad8e
     </key>
14ad8e
+    <key name="no-focus-windows" type="s">
14ad8e
+      <default>''</default>
14ad8e
+      <_summary>New windows that shouldn't get focus</_summary>
14ad8e
+      <_description>
14ad8e
+        This option provides a way to specify new windows that shouldn't get
14ad8e
+	focus. Normally an application specifies whether or not it gets focus
14ad8e
+	by setting the _NET_WM_USER_TIME property, but legacy applications
14ad8e
+	may not set this, which can cause unwanted focus stealing.
14ad8e
+
14ad8e
+	The contents of this property is a space-separated list of expressions
14ad8e
+	to match against windows. If any of the expressions match a window
14ad8e
+	then the window will not get focus. The syntax of expressions is:
14ad8e
+
14ad8e
+	(eq [name|class] "<value>"): window name (title) or the class from
14ad8e
+	WM_CLASS matches <value> exactly.
14ad8e
+	(glob [name|class] "<glob>"): window name (title) or the class from
14ad8e
+	WM_CLASS matches the shell-style glob pattern <glob>.
14ad8e
+	(and <expr> <expr>) (or <expr> <expr>) (not <expr): Boolean combinations
14ad8e
+	of expressions.
14ad8e
+      </_description>
14ad8e
+    </key>
14ad8e
   </schema>
14ad8e
 
14ad8e
 </schemalist>
14ad8e
-- 
14ad8e
1.7.9
14ad8e