From e139cbb0283f7480fc26c58dc3f8a48e69011eab Mon Sep 17 00:00:00 2001
From: rathnor <rathnor>
Date: Thu, 12 Jun 2003 15:12:19 +0000
Subject: add regular expression support in remember capabilities see ChangeLog
 for details

---
 ChangeLog            |  21 +++++
 configure.in         |  29 ++++++-
 src/ClientPattern.cc | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/ClientPattern.hh |  99 ++++++++++++++++++++++
 src/Makefile.am      |   6 +-
 src/RegExp.cc        |  94 +++++++++++++++++++++
 src/RegExp.hh        |  66 +++++++++++++++
 src/Remember.cc      | 199 +++++++++++++++++++------------------------
 src/Remember.hh      |  32 ++++---
 9 files changed, 657 insertions(+), 124 deletions(-)
 create mode 100644 src/ClientPattern.cc
 create mode 100644 src/ClientPattern.hh
 create mode 100644 src/RegExp.cc
 create mode 100644 src/RegExp.hh

diff --git a/ChangeLog b/ChangeLog
index a5e7369..0532bef 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,25 @@
 (Format: Year/Month/Day)
+Changes for 0.9.4:
+*03/06/13:
+   * Regular expression support for remember (Simon)
+     Also ability to limit number of matches for a given rule
+     Also ability to match several different window attributes
+     Can disable in compile using --disable-regexp (will just do plain 
+     string equality then)
+     - General format is:
+       [app] (property=expr) ... {number}
+       If "property=" is excluded, the name property is assumed.
+       If {number} is excluded, 0 = no limit is assumed.
+     - Current available properties are:
+       * name -> the name of the window - the first field of WM_CLASS
+       * class -> the class of the window - the second field of WM_CLASS
+       * title -> the title of the window - the WM_NAME property
+     - e.g. [app] (*[tT]erm) {2} 
+       will match anything ending with term, for up to 2 instances
+     - e.g. [app] (title=.*gaim.*) 
+       will match anything with gaim in the title ("gaim", "the gaim 
+       window", etc.
+     RegExp.hh/cc ClientPattern.hh/cc configure.in Makefile.am Remember.hh/cc WinClient.hh/cc StringUtil.hh/cc
 Changes for 0.9.3:
 *03/06/08:
    * Add Reconfigure and Restart Key actions, thanks Jann Fisher (Simon)
diff --git a/configure.in b/configure.in
index a301393..a521abd 100644
--- a/configure.in
+++ b/configure.in
@@ -119,6 +119,32 @@ AC_ARG_ENABLE(
 )
 AM_CONDITIONAL(REMEMBER_SRC, test x$REMEMBER_SRC = xtrue)
 
+AC_MSG_CHECKING([whether to have (POSIX) regular expression support])
+AC_ARG_ENABLE(
+  regexp,
+[  --enable-regexp           regular expression support [default=yes]],
+  if test x$enableval = "xyes"; then
+        AC_EGREP_HEADER([regex_t],regex.h,
+                AC_DEFINE(USE_REGEXP, 1, "Regular Expression support")
+        	AC_MSG_RESULT([yes])
+                REGEXP_SRC=true,
+                AC_MSG_RESULT([no])
+                REGEXP_SRC=false
+        )
+  else
+    AC_MSG_RESULT([no])	
+    REGEXP_SRC=false
+  fi,  
+  AC_EGREP_HEADER([regex_t],regex.h,
+        AC_DEFINE(USE_REGEXP, 1, "Regular Expression support")
+        AC_MSG_RESULT([yes])
+        REGEXP_SRC=true,
+        AC_MSG_RESULT([no])
+        REGEXP_SRC=false
+  )
+)
+AM_CONDITIONAL(REGEXP_SRC, test x$REGEXP_SRC = xtrue)
+
 AC_MSG_CHECKING([whether to include the new WM Spec])
 AC_ARG_ENABLE(
   newwmspec,
@@ -307,9 +333,6 @@ AC_ARG_ENABLE(
 )
 
 
-
-
-
 AC_MSG_CHECKING([whether to have Xmb (multibyte font, utf-8) support])
 AC_ARG_ENABLE(
   xmb,
diff --git a/src/ClientPattern.cc b/src/ClientPattern.cc
new file mode 100644
index 0000000..7ad3a54
--- /dev/null
+++ b/src/ClientPattern.cc
@@ -0,0 +1,235 @@
+// ClientPattern.cc for Fluxbox Window Manager
+// Copyright (c) 2003 Henrik Kinnunen (fluxgen at users.sourceforge.net)
+//                and Simon Bowden    (rathnor at users.sourceforge.net)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+// $Id: ClientPattern.cc,v 1.1 2003/06/12 15:12:19 rathnor Exp $
+
+#include "ClientPattern.hh"
+#include "RegExp.hh"
+#include "StringUtil.hh"
+#include "WinClient.hh"
+
+//use GNU extensions
+#ifndef	 _GNU_SOURCE
+#define	 _GNU_SOURCE
+#endif // _GNU_SOURCE
+
+
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <string>
+#include <memory>
+
+using namespace std;
+
+/********************************************************
+ * ClientPattern *
+ ***********/
+
+ClientPattern::ClientPattern():
+    m_matchlimit(0),
+    m_nummatches(0) {}
+
+// parse the given pattern (to end of line)
+ClientPattern::ClientPattern(const char *str):
+    m_matchlimit(0),
+    m_nummatches(0)
+{
+    /* A rough grammar of a pattern is:
+       PATTERN ::= MATCH+ LIMIT?
+       MATCH ::= '(' word ')' 
+                 | '(' propertyname '=' word ')'
+       LIMIT ::= '{' number '}'
+                 
+       i.e. one or more match definitions, followed by
+            an optional limit on the number of apps to match to
+       
+       Match definitions are enclosed in parentheses, and if no
+       property name is given, then CLASSNAME is assumed.
+       If no limit is specified, no limit is applied (i.e. limit = infinity)
+    */
+
+    int had_error = 0;
+
+    int pos = 0;
+    string match;
+    int err = 1; // for starting first loop
+    while (had_error == 0 && err > 0) {
+        err = FbTk::StringUtil::getStringBetween(match, 
+                                                 str + pos,
+                                                 '(', ')', " \t\n", true);
+        if (err > 0) {
+            size_t eq = match.find_first_of('=');
+            if (eq == match.npos) {
+                if (!addTerm(match, NAME)) {
+                    had_error = pos + match.find_first_of('(') + 1;
+                    break;
+                }
+            } else {
+                // need to determine the property used
+                string memstr, expr;
+                WinProperty prop;
+                memstr.assign(match, 0, eq); // memstr = our identifier
+                expr.assign(match, eq+1, match.length());
+                if (strcasecmp(memstr.c_str(), "name") == 0) {
+                    prop = NAME;
+                } else if (strcasecmp(memstr.c_str(), "class") == 0) {
+                    prop = CLASS;
+                } else if (strcasecmp(memstr.c_str(), "title") == 0) {
+                    prop = TITLE;
+                } else {
+                    had_error = pos + match.find_first_of('(') + 1;
+                    break;
+                }
+                if (!addTerm(expr, prop)) {
+                    had_error = pos + ((str+pos) - index(str+pos, '=')) + 1;
+                    break;
+                }
+            }
+            pos += err;
+        } 
+    }
+    if (pos == 0 && had_error == 0) {
+        // no match terms given, this is not allowed
+        had_error = 1;
+    }
+
+    if (had_error == 0) {
+        // otherwise, we check for a number
+        string number;
+        err = FbTk::StringUtil::getStringBetween(number, 
+                                             str+pos,
+                                             '{', '}');
+        if (err > 0) {
+            istringstream iss(number.c_str());
+            iss >> m_matchlimit;
+            pos+=err;
+        }
+        // we don't care if there isn't one
+        
+        // there shouldn't be anything else on the line
+        match = str + pos;
+        err = match.find_first_not_of(" \t\n", pos);
+        if ((unsigned) err != match.npos) {
+            // found something, not good
+            had_error = err;
+        }
+    }
+
+    if (had_error > 0) {
+        m_matchlimit = had_error;
+        // delete all the terms
+        while (!m_terms.empty()) {
+            Term * term = m_terms.back();
+            delete term;
+            m_terms.pop_back();
+        }
+    }
+} 
+
+ClientPattern::~ClientPattern() {
+    // delete all the terms
+    while (!m_terms.empty()) {
+        delete m_terms.back();
+        m_terms.pop_back();
+    }
+}
+
+// return a string representation of this pattern
+std::string ClientPattern::toString() const {
+    string pat;
+    Terms::const_iterator it = m_terms.begin();
+    Terms::const_iterator it_end = m_terms.end();
+    for (; it != it_end; ++it) {
+        pat.append(" (");
+        if ((*it)->prop == NAME) {
+            // do nothing -> this is the default
+        } else if ((*it)->prop == CLASS) {
+            pat.append("class=");
+        } else if ((*it)->prop == TITLE) {
+            pat.append("title=");
+        } else {
+#ifdef DEBUG
+            cerr<<"WARNING: unknown window property, can't save properly"<<endl;
+#endif //DEBUG
+        }
+        pat.append((*it)->orig);
+        pat.append(")");
+    }
+
+    if (m_matchlimit > 0) {
+        char num[20];
+        sprintf(num, " {%d}", m_matchlimit);
+        pat.append(num);
+    }
+    return pat;
+}
+
+// does this client match this pattern?
+bool ClientPattern::match(const WinClient &win) const {
+    if (m_matchlimit != 0 && m_nummatches >= m_matchlimit || 
+        m_terms.empty())
+        return false; // already matched out
+
+    // regmatch everything
+    // currently, we use an "AND" policy for multiple terms
+    // changing to OR would require minor modifications in this function only
+    Terms::const_iterator it = m_terms.begin();
+    Terms::const_iterator it_end = m_terms.end();
+    for (; it != it_end; ++it) {
+        if (!(*it)->regexp.match(getProperty((*it)->prop, win)))
+            return false;
+    }
+    return true;
+}
+
+// add an expression to match against
+// The first argument is a regular expression, the second is the member
+// function that we wish to match against.
+bool ClientPattern::addTerm(const std::string &str, WinProperty prop) {
+
+    Term *term = new Term(str, true);
+    term->orig = str;
+    term->prop = prop;
+
+    if (term->regexp.error()) {
+        delete term;
+        return false;
+    }
+    m_terms.push_back(term);
+    return true;
+}
+
+std::string ClientPattern::getProperty(WinProperty prop, const WinClient &client) const {
+    switch (prop) {
+    case TITLE:
+        return client.getTitle();
+        break;
+    case CLASS:
+        return client.getWMClassClass();
+        break;
+    case NAME:
+    default:
+        return client.getWMClassName();
+        break;
+    }
+}
diff --git a/src/ClientPattern.hh b/src/ClientPattern.hh
new file mode 100644
index 0000000..00a3127
--- /dev/null
+++ b/src/ClientPattern.hh
@@ -0,0 +1,99 @@
+// ClientPattern.hh for Fluxbox Window Manager
+// Copyright (c) 2002 Xavier Brouckaert
+// Copyright (c) 2003 Henrik Kinnunen (fluxgen at users.sourceforge.net)
+//                and Simon Bowden    (rathnor at users.sourceforge.net)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+// $Id: ClientPattern.hh,v 1.1 2003/06/12 15:12:19 rathnor Exp $
+
+#ifndef CLIENTPATTERN_HH
+#define CLIENTPATTERN_HH
+
+#include "RegExp.hh"
+
+#include <string>
+#include <list>
+
+class WinClient;
+
+/**
+ * This class represents a "pattern" that we can match against a
+ * Window based on various properties.
+ */
+class ClientPattern {
+public:
+    ClientPattern();
+    // create the pattern from the given string as it would appear in the 
+    // apps file. the bool value returns the character at which
+    // there was a parse problem, or -1.
+    explicit ClientPattern(const char * str);
+
+    ~ClientPattern();
+
+    // return a string representation of this pattern
+    std::string toString() const;
+
+    enum WinProperty { TITLE, CLASS, NAME };
+
+    // does this client match this pattern?
+    bool match(const WinClient &win) const;
+
+    // add an expression to match against
+    // The first argument is a regular expression, the second is the member
+    // function that we wish to match against.
+    // returns false if the regexp wasn't valid
+    bool addTerm(const std::string &str, WinProperty prop);
+
+    inline void addMatch() { ++m_nummatches; }
+    inline void delMatch() { --m_nummatches; }
+
+    inline bool operator == (const WinClient &win) const {
+        return match(win);
+    }
+
+    // if there are no terms, then there is assumed to be an error
+    // the column of the error is stored in m_matchlimit
+    inline int error() { return (m_terms.empty())?m_matchlimit:0; }
+
+    std::string getProperty(WinProperty prop, const WinClient &winclient) const;
+
+private:
+    // This is the type of the actual pattern we want to match against
+    // We have a "term" in the whole expression which is the full pattern
+    // we also need to keep track of the uncompiled regular expression
+    // for final output
+    
+    struct Term {
+        Term(const std::string &regstr, bool full_match) :regexp(regstr, full_match){};
+        std::string orig;
+        RegExp regexp;
+        WinProperty prop;
+    };
+
+    // our pattern is made up of a sequence of terms
+    // currently we "and" them all
+    typedef std::list<Term *> Terms;
+
+    Terms m_terms;
+
+    int m_matchlimit, m_nummatches;
+};
+
+#endif // CLIENTPATTERN_HH
diff --git a/src/Makefile.am b/src/Makefile.am
index 0b2892a..d9288f7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -48,6 +48,10 @@ gnome_SOURCE= Gnome.hh Gnome.cc
 endif
 if REMEMBER_SRC
 REMEMBER_SOURCE= Remember.hh Remember.cc
+# For now we only want regexp if we have remember
+if REGEXP_SRC
+REGEXP_SOURCE = RegExp.hh RegExp.cc ClientPattern.hh ClientPattern.cc
+endif
 endif
 
 fluxbox_SOURCES = AtomHandler.hh ArrowButton.hh ArrowButton.cc \
@@ -76,7 +80,7 @@ fluxbox_SOURCES = AtomHandler.hh ArrowButton.hh ArrowButton.cc \
 	IntResMenuItem.hh IntResMenuItem.cc FbMenu.hh \
 	WinClient.hh WinClient.cc \
 	Xinerama.hh \
-	${REMEMBER_SOURCE}
+	${REMEMBER_SOURCE} ${REGEXP_SOURCE}
 
 
 LDADD=FbTk/libFbTk.a
diff --git a/src/RegExp.cc b/src/RegExp.cc
new file mode 100644
index 0000000..2e991da
--- /dev/null
+++ b/src/RegExp.cc
@@ -0,0 +1,94 @@
+// RegExp.cc for Fluxbox Window Manager
+// Copyright (c) 2003 Henrik Kinnunen (fluxgen at users.sourceforge.net)
+//                and Simon Bowden    (rathnor at users.sourceforge.net)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+// $Id: RegExp.cc,v 1.1 2003/06/12 15:12:19 rathnor Exp $
+
+#include "RegExp.hh"
+
+//use GNU extensions
+#ifndef	 _GNU_SOURCE
+#define	 _GNU_SOURCE
+#endif // _GNU_SOURCE
+
+#include <string>
+#include <iostream>
+
+using namespace std;
+
+
+/********************************************************
+ * RegExp *
+ ***********/
+
+// full_match is to say if we match on this regexp using the full string
+// or just a substring. Substrings aren't supported if not HAVE_REGEXP
+RegExp::RegExp(const std::string &str, bool full_match):
+#ifdef USE_REGEXP
+m_regex(0) {
+    string match;
+    if (full_match) {
+        match = "^";
+        match.append(str);
+        match.append("$");
+    } else {
+        match = str;
+    }
+
+    m_regex = new regex_t;
+    int ret = regcomp(m_regex, match.c_str(), REG_NOSUB | REG_EXTENDED);
+    if (ret != 0) {
+        char *errstr = 0;
+        // gives us the length of the string
+        unsigned int size = regerror(ret, m_regex, errstr, 0);
+        errstr = new char[size];
+
+        regerror(ret, m_regex, errstr, size);
+        cerr<<"Error parsing regular expression: "<<errstr<<endl;
+        delete [] errstr;
+        delete m_regex; // I don't think I regfree a failed compile?
+        m_regex = 0;
+    }
+}
+#else // notdef USE_REGEXP
+m_str(str) {}
+#endif // USE_REGEXP
+
+RegExp::~RegExp() {
+#ifdef USE_REGEXP
+    if (m_regex != 0) {
+        regfree(m_regex);
+        delete m_regex;
+    }
+#endif // USE_REGEXP
+}
+
+bool RegExp::match(const std::string &str) {
+#ifdef USE_REGEXP
+    if (m_regex)
+        return (regexec(m_regex, str.c_str(), 0, 0, 0) == 0);
+    else
+        return false;
+#else // notdef USE_REGEXP
+    return (m_str == str);
+#endif // USE_REGEXP
+}
+
diff --git a/src/RegExp.hh b/src/RegExp.hh
new file mode 100644
index 0000000..9591bcb
--- /dev/null
+++ b/src/RegExp.hh
@@ -0,0 +1,66 @@
+// RegExp.hh for Fluxbox Window Manager
+// Copyright (c) 2002 Xavier Brouckaert
+// Copyright (c) 2003 Henrik Kinnunen (fluxgen at users.sourceforge.net)
+//                and Simon Bowden    (rathnor at users.sourceforge.net)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+// $Id: RegExp.hh,v 1.1 2003/06/12 15:12:19 rathnor Exp $
+
+#ifndef REGEXP_HH
+#define REGEXP_HH
+
+#include "config.h"
+
+#include <string>
+
+/*
+ * If USE_REGEXP isn't defined, then we match just using simple string equality
+ */
+
+#ifdef USE_REGEXP
+#include <regex.h>
+#include <sys/types.h>
+#endif // USE_REGEXP
+
+class WinClient;
+
+class RegExp {
+public:
+    RegExp(const std::string &str, bool full_match = true);
+    ~RegExp();
+
+    bool match(const std::string &str);
+
+#ifdef USE_REGEXP
+    inline bool error() { return m_regex == 0; }
+#else // notdef USE_REGEXP
+    inline bool error() { return m_str == ""; }
+#endif // USE_REGEXP
+
+private:
+#ifdef USE_REGEXP
+    regex_t* m_regex;
+#else // notdef USE_REGEXP
+    std::string m_str;
+#endif // USE_REGEXP
+
+};
+
+#endif // REGEXP_HH
diff --git a/src/Remember.cc b/src/Remember.cc
index 5480d20..a31843b 100644
--- a/src/Remember.cc
+++ b/src/Remember.cc
@@ -21,9 +21,10 @@
 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 // DEALINGS IN THE SOFTWARE.
 
-// $Id: Remember.cc,v 1.23 2003/06/06 14:07:22 rathnor Exp $
+// $Id: Remember.cc,v 1.24 2003/06/12 15:12:19 rathnor Exp $
 
 #include "Remember.hh"
+#include "ClientPattern.hh"
 #include "StringUtil.hh"
 #include "Screen.hh"
 #include "Window.hh"
@@ -125,32 +126,7 @@ FbTk::Menu *createRememberMenu(Remember &remember, FluxboxWindow &win) {
     return menu;
 };
 
-std::string getWMClass(Window w) {
-    XClassHint ch;
-    
-    if (XGetClassHint(FbTk::App::instance()->display(), w, &ch) == 0) {
-        cerr<<"Failed to read class hint!"<<endl;
-        return "";
-    } else {
-        string instance_name;
-        if (ch.res_name != 0) {
-            instance_name = const_cast<char *>(ch.res_name);
-            XFree(ch.res_name);
-        } else 
-            instance_name = "";
-        
-        if (ch.res_class != 0) {
-            //m_class_name = const_cast<char *>(ch.res_class);
-            XFree(ch.res_class);
-        } else {
-            //m_class_name = "";
-        }
-
-        return instance_name;
-    }
-}
-
-};
+}; // end anonymous namespace
 
 Application::Application() {
     workspace_remember =
@@ -165,35 +141,56 @@ Application::Application() {
 	save_on_close_remember = false;
 }
 
+/********************************************************
+ * Remember *
+ ************/
+
 Remember::Remember() {
     load();
 }
 
-Application* Remember::add(const char* app_name) {
-    if (!app_name)
-        return 0;
-    Application* a = new Application();
-    apps[app_name] = a;
-    return a;
+Remember::~Remember() {
+    // free our resources
+
+    // the patterns free the "Application"s
+    // the client mapping shouldn't need cleaning
+    Patterns::iterator it;
+    while (!m_pats.empty()) {
+        it = m_pats.begin();
+        delete it->first; // ClientPattern
+        delete it->second; // Application
+        m_pats.erase(it);
+    }
 }
 
 Application* Remember::find(WinClient &winclient) {
-    return find(getWMClass(winclient.window()).c_str());
-}
-
-Application* Remember::add(WinClient &winclient) {
-    return add(getWMClass(winclient.window()).c_str());
+    // if it is already associated with a application, return that one
+    // otherwise, check it against every pattern that we've got
+    Clients::iterator wc_it = m_clients.find(&winclient);
+    if (wc_it != m_clients.end())
+        return wc_it->second;
+    else {
+        Patterns::iterator it = m_pats.begin();
+        for (; it != m_pats.end(); it++) 
+            if (it->first->match(winclient)) {
+                it->first->addMatch();
+                m_clients[&winclient] = it->second;
+                return it->second;
+            }
+    }
+    // oh well, no matches
+    return 0;
 }
 
-
-Application* Remember::find(const char* app_name) {
-    if (!app_name)
-        return 0;
-    Apps::iterator i = apps.find(app_name);
-    if (i != apps.end())
-        return i->second;
-    else
-        return 0;
+Application * Remember::add(WinClient &winclient) {
+    ClientPattern *p = new ClientPattern();
+    Application *app = new Application();
+    // by default, we match against the WMClass of a window.
+    p->addTerm(p->getProperty(ClientPattern::NAME, winclient), ClientPattern::NAME);
+    m_clients[&winclient] = app;
+    p->addMatch();
+    m_pats.push_back(make_pair(p, app));
+    return app;
 }
 
 int Remember::parseApp(ifstream &file, Application &app) {
@@ -315,31 +312,24 @@ void Remember::load() {
                 if (line[0] == '#')
                     continue;
                 string key;
-                int pos=0;
-                int err = FbTk::StringUtil::getStringBetween(key, 
+                int err=0;
+                int pos = FbTk::StringUtil::getStringBetween(key, 
                                                              line.c_str(), 
                                                              '[', ']');
 
-                if (err > 0 && key == "app") {
-                    pos += err;
-                    string label;
-                    err = FbTk::StringUtil::getStringBetween(label, 
-                                                             line.c_str()+pos,
-                                                             '(', ')');
-                    if (err>0) {
-                        Application *app = 0;
-                        Apps::iterator i = apps.find(label);
-                        if (i == apps.end()) {
-                            app = new Application();
-                            apps[label] = app;
-                        } else
-                            app = i->second;
+                if (pos > 0 && key == "app") {
+                    ClientPattern *pat = new ClientPattern(line.c_str() + pos);
+                    if ((err = pat->error()) == 0) {
+                        Application *app = new Application();
+                        m_pats.push_back(make_pair(pat, app));
                         row += parseApp(apps_file, *app);
-                    } else
-                        cerr<<"Error1 in apps file. Line("<<row<<")"<<endl;
+                    } else {
+                        cerr<<"Error reading apps file at line "<<row<<", column "<<(err+pos)<<"."<<endl;
+                        delete pat; // since it didn't work
+                    }
                 } else
-                    cerr<<"Error2 in apps file. Line("<<row<<")"<<endl;
-
+                    cerr<<"Error in apps file on line "<<row<<"."<<endl;
+                
             }
         } else {
 #ifdef DEBUG
@@ -358,28 +348,28 @@ void Remember::save() {
     string apps_string;
     Fluxbox::instance()->getDefaultDataFilename("apps", apps_string);
     ofstream apps_file(apps_string.c_str());
-    Apps::iterator it = apps.begin();
-    Apps::iterator it_end = apps.end();
+    Patterns::iterator it = m_pats.begin();
+    Patterns::iterator it_end = m_pats.end();
     for (; it != it_end; ++it) {
-        apps_file << "[app] (" <<  it->first << ")" << endl;
-        Application *a = it->second;
-        if (a->workspace_remember) {
-            apps_file << "  [Workspace]\t{" << a->workspace << "}" << endl;
+        apps_file << "[app]"<<it->first->toString()<<endl;
+        Application &a = *it->second;
+        if (a.workspace_remember) {
+            apps_file << "  [Workspace]\t{" << a.workspace << "}" << endl;
         }
-        if (a->dimensions_remember) {
-            apps_file << "  [Dimensions]\t{" << a->w << " " << a->h << "}" << endl;
+        if (a.dimensions_remember) {
+            apps_file << "  [Dimensions]\t{" << a.w << " " << a.h << "}" << endl;
         }
-        if (a->position_remember) {
-            apps_file << "  [Position]\t{" << a->x << " " << a->y << "}" << endl;
+        if (a.position_remember) {
+            apps_file << "  [Position]\t{" << a.x << " " << a.y << "}" << endl;
         }
-        if (a->shadedstate_remember) {
-            apps_file << "  [Shaded]\t{" << ((a->shadedstate)?"yes":"no") << "}" << endl;
+        if (a.shadedstate_remember) {
+            apps_file << "  [Shaded]\t{" << ((a.shadedstate)?"yes":"no") << "}" << endl;
         }
-        if (a->tabstate_remember) {
-            apps_file << "  [Tab]\t\t{" << ((a->tabstate)?"yes":"no") << "}" << endl;
+        if (a.tabstate_remember) {
+            apps_file << "  [Tab]\t\t{" << ((a.tabstate)?"yes":"no") << "}" << endl;
         }
-        if (a->decostate_remember) {
-            switch (a->decostate) {
+        if (a.decostate_remember) {
+            switch (a.decostate) {
             case (0) :
                 apps_file << "  [Deco]\t{NONE}" << endl; 
                 break;
@@ -401,21 +391,21 @@ void Remember::save() {
                 apps_file << "  [Deco]\t{BORDER}" << endl;
                 break;
             default:
-                apps_file << "  [Deco]\t{0x"<<hex<<a->decostate<<dec<<"}"<<endl;
+                apps_file << "  [Deco]\t{0x"<<hex<<a.decostate<<dec<<"}"<<endl;
                 break;
             }
         }
-        if (a->stuckstate_remember) {
-            apps_file << "  [Sticky]\t{" << ((a->stuckstate)?"yes":"no") << "}" << endl;
+        if (a.stuckstate_remember) {
+            apps_file << "  [Sticky]\t{" << ((a.stuckstate)?"yes":"no") << "}" << endl;
         }
-        if (a->jumpworkspace_remember) {
-            apps_file << "  [Jump]\t{" << ((a->jumpworkspace)?"yes":"no") << "}" << endl;
+        if (a.jumpworkspace_remember) {
+            apps_file << "  [Jump]\t{" << ((a.jumpworkspace)?"yes":"no") << "}" << endl;
         }
-        if (a->layer_remember) {
-            apps_file << "  [Layer]\t{" << a->layer << "}" << endl;
+        if (a.layer_remember) {
+            apps_file << "  [Layer]\t{" << a.layer << "}" << endl;
         }
-        if (a->save_on_close_remember) {
-            apps_file << "  [Close]\t{" << ((a->save_on_close)?"yes":"no") << "}" << endl;
+        if (a.save_on_close_remember) {
+            apps_file << "  [Close]\t{" << ((a.save_on_close)?"yes":"no") << "}" << endl;
         }
         apps_file << "[end]" << endl;
     }
@@ -563,6 +553,7 @@ void Remember::setupWindow(FluxboxWindow &win) {
     if (winclient.transientFor()) {
         // still put something in the menu so people don't get confused
         // so, we add a disabled item...
+        // TODO: nls
         FbTk::MenuItem *item = new FbTk::MenuItem("Remember...");
         item->setEnabled(false);
         win.menu().insert(item, menupos);
@@ -623,10 +614,15 @@ void Remember::setupWindow(FluxboxWindow &win) {
 void Remember::updateWindowClose(FluxboxWindow &win) {
     // This doesn't work at present since fluxbox.cc is missing the windowclose stuff.
     // I don't trust it (particularly winClient()) while this is the case
+
     return;
 
     WinClient &winclient = win.winClient();
     Application *app = find(winclient);
+    Clients::iterator wc_it = m_clients.find(&win.winClient());
+
+    if (wc_it != m_clients.end())
+        m_clients.erase(wc_it);
 
     if (!app || !(app->save_on_close_remember && app->save_on_close))
         return;
@@ -636,23 +632,6 @@ void Remember::updateWindowClose(FluxboxWindow &win) {
             rememberAttrib(winclient, (Attribute) attrib);
         }
     }
-/*
-    if (app->workspace_remember) 
-        app->rememberWorkspace(win.workspaceNumber());
-    if (app->dimensions_remember)
-        app->rememberDimensions(win.width(), win.height());
-    if (app->position_remember)
-        app->rememberPosition(win.x(), win.y());
-    if (app->shadedstate_remember)
-        app->rememberShadedstate(win.isShaded());
-    // external tabs off atm
-    //if (app->tabstate_remember) ...
-    if (app->decostate_remember)
-        app->rememberDecostate(win.decorationMask());
-    if (app->stuckstate_remember)
-        app->rememberStuckstate(win.isStuck());
-    if (app->jumpworkspace_remember)
-        app->rememberJumpworkspace(true);
-*/
+
     save();
 }
diff --git a/src/Remember.hh b/src/Remember.hh
index 8058c15..d3a30e0 100644
--- a/src/Remember.hh
+++ b/src/Remember.hh
@@ -21,7 +21,7 @@
 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 // DEALINGS IN THE SOFTWARE.
 
-// $Id: Remember.hh,v 1.6 2003/06/05 13:33:27 fluxgen Exp $
+// $Id: Remember.hh,v 1.7 2003/06/12 15:12:19 rathnor Exp $
 
 /* Based on the original "Remember patch" by Xavier Brouckaert */
 
@@ -32,7 +32,14 @@
 
 #include <fstream>
 #include <map>
+#include <list>
 #include <string>
+#include <utility>
+
+class FluxboxWindow;
+class BScreen;
+class WinClient;
+class ClientPattern;
 
 class Application {
 public:
@@ -99,12 +106,8 @@ public:
 
     bool save_on_close_remember;
     bool save_on_close;
-};
-
 
-class FluxboxWindow;
-class BScreen;
-class WinClient;
+};
 
 /**
  * Class Remember is an atomhandler to avoid interfering with
@@ -132,13 +135,21 @@ public:
         REM_LASTATTRIB // not actually used
     };
 
-    typedef std::map<std::string, Application *> Apps;
+    // a "pattern"  to the relevant app
+    // each app exists ONLY for that pattern.
+    // And we need to keep a list of pairs as we want to keep the
+    // applications in the same order as they will be in the apps file
+    typedef std::list< std::pair<ClientPattern *, Application *> > Patterns;
+
+    // We keep track of which app is assigned to a winclient
+    // particularly useful to update counters etc on windowclose
+    typedef std::map<WinClient *, Application *> Clients;
+    
     Remember();
+    ~Remember();
 
     Application* find(WinClient &winclient);
-    Application* find(const char* app_name);
     Application* add(WinClient &winclient);
-    Application* add(const char* app_name);
 
     void load();
     void save();
@@ -176,7 +187,8 @@ private:
 
     // returns number of lines read
     int parseApp(std::ifstream &file, Application &app);
-    Apps apps;
+    Patterns m_pats;
+    Clients m_clients;
 
 };
 
-- 
cgit v0.11.2