2003-06-12 15:12:19 +00:00
|
|
|
// ClientPattern.cc for Fluxbox Window Manager
|
2006-02-16 06:53:05 +00:00
|
|
|
// Copyright (c) 2003 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org)
|
2003-06-12 15:12:19 +00:00
|
|
|
// 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.
|
|
|
|
|
2004-11-19 11:37:27 +00:00
|
|
|
// $Id$
|
2003-06-12 15:12:19 +00:00
|
|
|
|
|
|
|
#include "ClientPattern.hh"
|
|
|
|
#include "RegExp.hh"
|
2007-10-13 21:51:37 +00:00
|
|
|
|
|
|
|
#include "FocusControl.hh"
|
|
|
|
#include "Layer.hh"
|
|
|
|
#include "Screen.hh"
|
2003-06-12 15:12:19 +00:00
|
|
|
#include "WinClient.hh"
|
2007-10-13 21:51:37 +00:00
|
|
|
#include "Workspace.hh"
|
2005-02-13 16:34:37 +00:00
|
|
|
|
2005-02-10 10:24:31 +00:00
|
|
|
#include "FbTk/StringUtil.hh"
|
2004-04-28 13:04:06 +00:00
|
|
|
#include "FbTk/App.hh"
|
2005-02-13 16:34:37 +00:00
|
|
|
#include "FbTk/stringstream.hh"
|
2003-06-12 15:12:19 +00:00
|
|
|
|
2003-06-13 12:02:00 +00:00
|
|
|
// use GNU extensions
|
|
|
|
#ifndef _GNU_SOURCE
|
|
|
|
#define _GNU_SOURCE
|
2003-06-12 15:12:19 +00:00
|
|
|
#endif // _GNU_SOURCE
|
|
|
|
|
|
|
|
#include <fstream>
|
|
|
|
#include <string>
|
|
|
|
#include <memory>
|
2004-08-31 15:26:40 +00:00
|
|
|
#ifdef HAVE_CSTDIO
|
|
|
|
#include <cstdio>
|
|
|
|
#else
|
|
|
|
#include <stdio.h>
|
|
|
|
#endif
|
2003-06-12 15:12:19 +00:00
|
|
|
|
2003-10-12 16:25:28 +00:00
|
|
|
// needed as well for index on some systems (e.g. solaris)
|
2006-10-27 06:57:43 +00:00
|
|
|
#include <strings.h>
|
2003-10-12 16:25:28 +00:00
|
|
|
|
2006-10-27 06:57:43 +00:00
|
|
|
using std::string;
|
2003-11-17 00:33:16 +00:00
|
|
|
|
2003-06-12 15:12:19 +00:00
|
|
|
|
|
|
|
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?
|
2006-10-27 06:57:43 +00:00
|
|
|
MATCH ::= '(' word ')'
|
2003-06-12 15:12:19 +00:00
|
|
|
| '(' propertyname '=' word ')'
|
|
|
|
LIMIT ::= '{' number '}'
|
2006-10-27 06:57:43 +00:00
|
|
|
|
2003-06-12 15:12:19 +00:00
|
|
|
i.e. one or more match definitions, followed by
|
|
|
|
an optional limit on the number of apps to match to
|
2006-10-27 06:57:43 +00:00
|
|
|
|
2003-06-12 15:12:19 +00:00
|
|
|
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)
|
|
|
|
*/
|
|
|
|
|
2007-10-13 21:51:37 +00:00
|
|
|
bool had_error = false;
|
2003-06-12 15:12:19 +00:00
|
|
|
|
|
|
|
int pos = 0;
|
|
|
|
string match;
|
|
|
|
int err = 1; // for starting first loop
|
2007-10-13 21:51:37 +00:00
|
|
|
while (!had_error && err > 0) {
|
2006-10-27 06:57:43 +00:00
|
|
|
err = FbTk::StringUtil::getStringBetween(match,
|
2003-06-12 15:12:19 +00:00
|
|
|
str + pos,
|
|
|
|
'(', ')', " \t\n", true);
|
|
|
|
if (err > 0) {
|
2007-10-13 21:51:37 +00:00
|
|
|
// need to determine the property used
|
|
|
|
string memstr, expr;
|
|
|
|
WinProperty prop;
|
|
|
|
string::size_type eq = match.find_first_of('=');
|
2003-06-12 15:12:19 +00:00
|
|
|
if (eq == match.npos) {
|
2007-10-13 21:51:37 +00:00
|
|
|
memstr = match;
|
|
|
|
expr = "[current]";
|
2003-06-12 15:12:19 +00:00
|
|
|
} else {
|
|
|
|
memstr.assign(match, 0, eq); // memstr = our identifier
|
|
|
|
expr.assign(match, eq+1, match.length());
|
|
|
|
}
|
2007-10-13 21:51:37 +00:00
|
|
|
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 if (strcasecmp(memstr.c_str(), "role") == 0) {
|
|
|
|
prop = ROLE;
|
|
|
|
} else if (strcasecmp(memstr.c_str(), "maximized") == 0) {
|
|
|
|
prop = MAXIMIZED;
|
|
|
|
} else if (strcasecmp(memstr.c_str(), "minimized") == 0) {
|
|
|
|
prop = MINIMIZED;
|
|
|
|
} else if (strcasecmp(memstr.c_str(), "shaded") == 0) {
|
|
|
|
prop = SHADED;
|
|
|
|
} else if (strcasecmp(memstr.c_str(), "stuck") == 0) {
|
|
|
|
prop = STUCK;
|
|
|
|
} else if (strcasecmp(memstr.c_str(), "focushidden") == 0) {
|
|
|
|
prop = FOCUSHIDDEN;
|
|
|
|
} else if (strcasecmp(memstr.c_str(), "iconhidden") == 0) {
|
|
|
|
prop = ICONHIDDEN;
|
|
|
|
} else if (strcasecmp(memstr.c_str(), "workspace") == 0) {
|
|
|
|
prop = WORKSPACE;
|
|
|
|
} else if (strcasecmp(memstr.c_str(), "head") == 0) {
|
|
|
|
prop = HEAD;
|
|
|
|
} else if (strcasecmp(memstr.c_str(), "layer") == 0) {
|
|
|
|
prop = LAYER;
|
|
|
|
} else {
|
|
|
|
prop = NAME;
|
|
|
|
expr = match;
|
|
|
|
}
|
|
|
|
had_error = !addTerm(expr, prop);
|
2003-06-12 15:12:19 +00:00
|
|
|
pos += err;
|
2006-10-27 06:57:43 +00:00
|
|
|
}
|
2003-06-12 15:12:19 +00:00
|
|
|
}
|
2007-10-13 21:51:37 +00:00
|
|
|
if (pos == 0 && !had_error) {
|
2003-06-12 15:12:19 +00:00
|
|
|
// no match terms given, this is not allowed
|
2007-10-13 21:51:37 +00:00
|
|
|
had_error = true;
|
2003-06-12 15:12:19 +00:00
|
|
|
}
|
|
|
|
|
2007-10-13 21:51:37 +00:00
|
|
|
if (!had_error) {
|
2003-06-12 15:12:19 +00:00
|
|
|
// otherwise, we check for a number
|
|
|
|
string number;
|
2006-10-27 06:57:43 +00:00
|
|
|
err = FbTk::StringUtil::getStringBetween(number,
|
2003-06-12 15:12:19 +00:00
|
|
|
str+pos,
|
|
|
|
'{', '}');
|
|
|
|
if (err > 0) {
|
2005-02-13 16:34:37 +00:00
|
|
|
FbTk_istringstream iss(number.c_str());
|
2003-06-12 15:12:19 +00:00
|
|
|
iss >> m_matchlimit;
|
|
|
|
pos+=err;
|
|
|
|
}
|
|
|
|
// we don't care if there isn't one
|
2006-10-27 06:57:43 +00:00
|
|
|
|
2003-06-12 15:12:19 +00:00
|
|
|
// there shouldn't be anything else on the line
|
|
|
|
match = str + pos;
|
2006-04-16 11:18:22 +00:00
|
|
|
size_t uerr;// need a special type here
|
|
|
|
uerr = match.find_first_not_of(" \t\n", pos);
|
|
|
|
if (uerr != match.npos) {
|
2003-06-12 15:12:19 +00:00
|
|
|
// found something, not good
|
2007-10-13 21:51:37 +00:00
|
|
|
had_error = true;
|
2003-06-12 15:12:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-10-13 21:51:37 +00:00
|
|
|
if (had_error) {
|
2003-06-12 15:12:19 +00:00
|
|
|
// delete all the terms
|
|
|
|
while (!m_terms.empty()) {
|
|
|
|
Term * term = m_terms.back();
|
|
|
|
delete term;
|
|
|
|
m_terms.pop_back();
|
|
|
|
}
|
|
|
|
}
|
2006-10-27 06:57:43 +00:00
|
|
|
}
|
2003-06-12 15:12:19 +00:00
|
|
|
|
|
|
|
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
|
2006-10-27 06:57:43 +00:00
|
|
|
string ClientPattern::toString() const {
|
2003-06-12 15:12:19 +00:00
|
|
|
string pat;
|
|
|
|
Terms::const_iterator it = m_terms.begin();
|
|
|
|
Terms::const_iterator it_end = m_terms.end();
|
|
|
|
for (; it != it_end; ++it) {
|
2005-10-20 14:50:50 +00:00
|
|
|
|
2003-06-12 15:12:19 +00:00
|
|
|
pat.append(" (");
|
2003-06-13 12:02:00 +00:00
|
|
|
|
|
|
|
switch ((*it)->prop) {
|
|
|
|
case NAME:
|
2007-04-01 21:42:01 +00:00
|
|
|
pat.append("name=");
|
2003-06-13 12:02:00 +00:00
|
|
|
break;
|
|
|
|
case CLASS:
|
2003-06-12 15:12:19 +00:00
|
|
|
pat.append("class=");
|
2003-06-13 12:02:00 +00:00
|
|
|
break;
|
|
|
|
case TITLE:
|
2003-06-12 15:12:19 +00:00
|
|
|
pat.append("title=");
|
2003-06-13 12:02:00 +00:00
|
|
|
break;
|
2004-04-28 13:04:06 +00:00
|
|
|
case ROLE:
|
|
|
|
pat.append("role=");
|
2007-10-13 21:51:37 +00:00
|
|
|
break;
|
|
|
|
case MAXIMIZED:
|
|
|
|
pat.append("maximized=");
|
|
|
|
break;
|
|
|
|
case MINIMIZED:
|
|
|
|
pat.append("minimized=");
|
|
|
|
break;
|
|
|
|
case SHADED:
|
|
|
|
pat.append("shaded=");
|
|
|
|
break;
|
|
|
|
case STUCK:
|
|
|
|
pat.append("stuck=");
|
|
|
|
break;
|
|
|
|
case FOCUSHIDDEN:
|
|
|
|
pat.append("focushidden=");
|
|
|
|
break;
|
|
|
|
case ICONHIDDEN:
|
|
|
|
pat.append("iconhidden=");
|
|
|
|
break;
|
|
|
|
case WORKSPACE:
|
|
|
|
pat.append("workspace=");
|
|
|
|
break;
|
|
|
|
case HEAD:
|
|
|
|
pat.append("head=");
|
|
|
|
break;
|
|
|
|
case LAYER:
|
|
|
|
pat.append("layer=");
|
|
|
|
break;
|
2003-06-12 15:12:19 +00:00
|
|
|
}
|
2003-06-13 12:02:00 +00:00
|
|
|
|
2007-01-02 03:12:24 +00:00
|
|
|
pat.append((*it)->orig);
|
2003-06-12 15:12:19 +00:00
|
|
|
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?
|
2007-10-13 21:51:37 +00:00
|
|
|
bool ClientPattern::match(const Focusable &win) const {
|
|
|
|
if (m_matchlimit != 0 && m_nummatches >= m_matchlimit)
|
2003-06-12 15:12:19 +00:00
|
|
|
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) {
|
2007-10-13 21:51:37 +00:00
|
|
|
if ((*it)->orig == "[current]") {
|
|
|
|
// workspaces don't necessarily have unique names, so we want to
|
|
|
|
// compare numbers instead of strings
|
|
|
|
if ((*it)->prop == WORKSPACE && (!win.fbwindow() ||
|
|
|
|
win.fbwindow()->workspaceNumber() !=
|
|
|
|
win.screen().currentWorkspaceID()))
|
|
|
|
return false;
|
|
|
|
else {
|
|
|
|
WinClient *focused = FocusControl::focusedWindow();
|
|
|
|
if (!focused || getProperty((*it)->prop, win) !=
|
|
|
|
getProperty((*it)->prop, *focused))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if (!(*it)->regexp.match(getProperty((*it)->prop, win)))
|
2003-06-12 15:12:19 +00:00
|
|
|
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.
|
2006-10-27 06:57:43 +00:00
|
|
|
bool ClientPattern::addTerm(const string &str, WinProperty prop) {
|
2003-06-12 15:12:19 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2007-10-13 21:51:37 +00:00
|
|
|
string ClientPattern::getProperty(WinProperty prop,
|
|
|
|
const Focusable &client) const {
|
|
|
|
// we need this for some of the window properties
|
|
|
|
const FluxboxWindow *fbwin = client.fbwindow();
|
|
|
|
|
2003-06-12 15:12:19 +00:00
|
|
|
switch (prop) {
|
|
|
|
case TITLE:
|
2003-12-17 01:19:39 +00:00
|
|
|
return client.title();
|
2003-06-12 15:12:19 +00:00
|
|
|
break;
|
|
|
|
case CLASS:
|
|
|
|
return client.getWMClassClass();
|
|
|
|
break;
|
|
|
|
case NAME:
|
|
|
|
return client.getWMClassName();
|
|
|
|
break;
|
2004-04-28 13:04:06 +00:00
|
|
|
case ROLE:
|
2007-10-13 21:51:37 +00:00
|
|
|
return client.getWMRole();
|
|
|
|
break;
|
|
|
|
case MAXIMIZED:
|
|
|
|
return (fbwin && fbwin->isMaximized()) ? "yes" : "no";
|
|
|
|
break;
|
|
|
|
case MINIMIZED:
|
|
|
|
return (fbwin && fbwin->isIconic()) ? "yes" : "no";
|
|
|
|
break;
|
|
|
|
case SHADED:
|
|
|
|
return (fbwin && fbwin->isShaded()) ? "yes" : "no";
|
|
|
|
break;
|
|
|
|
case STUCK:
|
|
|
|
return (fbwin && fbwin->isStuck()) ? "yes" : "no";
|
|
|
|
break;
|
|
|
|
case FOCUSHIDDEN:
|
|
|
|
return (fbwin && fbwin->isFocusHidden()) ? "yes" : "no";
|
|
|
|
break;
|
|
|
|
case ICONHIDDEN:
|
|
|
|
return (fbwin && fbwin->isIconHidden()) ? "yes" : "no";
|
|
|
|
break;
|
|
|
|
case WORKSPACE: {
|
|
|
|
if (!fbwin)
|
|
|
|
return "";
|
|
|
|
const Workspace *w = client.screen().getWorkspace(fbwin->workspaceNumber());
|
|
|
|
return w ? w->name() : "";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case HEAD: {
|
|
|
|
if (!fbwin)
|
|
|
|
return "";
|
|
|
|
int head = client.screen().getHead(fbwin->fbWindow());
|
|
|
|
char tmpstr[128];
|
|
|
|
sprintf(tmpstr, "%d", head);
|
|
|
|
return std::string(tmpstr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case LAYER:
|
|
|
|
return fbwin ? ::Layer::getString(fbwin->layerNum()) : "";
|
2004-04-28 13:04:06 +00:00
|
|
|
break;
|
2003-06-12 15:12:19 +00:00
|
|
|
}
|
2003-06-13 12:02:00 +00:00
|
|
|
return client.getWMClassName();
|
2003-06-12 15:12:19 +00:00
|
|
|
}
|
2006-04-23 14:51:04 +00:00
|
|
|
|
|
|
|
bool ClientPattern::equals(const ClientPattern &pat) const {
|
|
|
|
// we require the terms to be identical (order too)
|
|
|
|
Terms::const_iterator it = m_terms.begin();
|
|
|
|
Terms::const_iterator it_end = m_terms.end();
|
|
|
|
Terms::const_iterator other_it = pat.m_terms.begin();
|
|
|
|
Terms::const_iterator other_it_end = pat.m_terms.end();
|
2006-08-10 14:55:52 +00:00
|
|
|
for (; it != it_end && other_it != other_it_end; ++it, ++other_it) {
|
2006-04-23 14:51:04 +00:00
|
|
|
if ((*it)->orig != (*other_it)->orig)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (it != it_end || other_it != other_it_end)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|