1
// ClientPattern.cc for Fluxbox Window Manager
2
// Copyright (c) 2003 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org)
3
// and Simon Bowden (rathnor at users.sourceforge.net)
5
// Permission is hereby granted, free of charge, to any person obtaining a
6
// copy of this software and associated documentation files (the "Software"),
7
// to deal in the Software without restriction, including without limitation
8
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
// and/or sell copies of the Software, and to permit persons to whom the
10
// Software is furnished to do so, subject to the following conditions:
12
// The above copyright notice and this permission notice shall be included in
13
// all copies or substantial portions of the Software.
15
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
// DEALINGS IN THE SOFTWARE.
23
#include "ClientPattern.hh"
26
#include "FocusControl.hh"
29
#include "WinClient.hh"
30
#include "Workspace.hh"
32
#include "FbTk/StringUtil.hh"
33
#include "FbTk/App.hh"
34
#include "FbTk/stringstream.hh"
50
// needed as well for index on some systems (e.g. solaris)
56
ClientPattern::ClientPattern():
60
// parse the given pattern (to end of line)
61
ClientPattern::ClientPattern(const char *str):
65
/* A rough grammar of a pattern is:
66
PATTERN ::= MATCH+ LIMIT?
67
MATCH ::= '(' word ')'
68
| '(' propertyname '=' word ')'
69
LIMIT ::= '{' number '}'
71
i.e. one or more match definitions, followed by
72
an optional limit on the number of apps to match to
74
Match definitions are enclosed in parentheses, and if no
75
property name is given, then CLASSNAME is assumed.
76
If no limit is specified, no limit is applied (i.e. limit = infinity)
79
bool had_error = false;
83
int err = 1; // for starting first loop
84
while (!had_error && err > 0) {
85
err = FbTk::StringUtil::getStringBetween(match,
87
'(', ')', " \t\n", true);
89
// need to determine the property used
92
string::size_type eq = match.find_first_of('=');
93
if (eq == match.npos) {
97
memstr.assign(match, 0, eq); // memstr = our identifier
98
expr.assign(match, eq+1, match.length());
101
if (!memstr.empty() && memstr[memstr.length()-1] == '!') {
103
memstr.assign(memstr, 0, memstr.length()-1);
105
if (strcasecmp(memstr.c_str(), "name") == 0) {
107
} else if (strcasecmp(memstr.c_str(), "class") == 0) {
109
} else if (strcasecmp(memstr.c_str(), "title") == 0) {
111
} else if (strcasecmp(memstr.c_str(), "role") == 0) {
113
} else if (strcasecmp(memstr.c_str(), "transient") == 0) {
115
} else if (strcasecmp(memstr.c_str(), "maximized") == 0) {
117
} else if (strcasecmp(memstr.c_str(), "minimized") == 0) {
119
} else if (strcasecmp(memstr.c_str(), "shaded") == 0) {
121
} else if (strcasecmp(memstr.c_str(), "stuck") == 0) {
123
} else if (strcasecmp(memstr.c_str(), "focushidden") == 0) {
125
} else if (strcasecmp(memstr.c_str(), "iconhidden") == 0) {
127
} else if (strcasecmp(memstr.c_str(), "workspace") == 0) {
129
} else if (strcasecmp(memstr.c_str(), "workspacename") == 0) {
130
prop = WORKSPACENAME;
131
} else if (strcasecmp(memstr.c_str(), "head") == 0) {
133
} else if (strcasecmp(memstr.c_str(), "layer") == 0) {
135
} else if (strcasecmp(memstr.c_str(), "urgent") == 0) {
141
had_error = !addTerm(expr, prop, negate);
145
if (pos == 0 && !had_error) {
146
// no match terms given, this is not allowed
151
// otherwise, we check for a number
153
err = FbTk::StringUtil::getStringBetween(number,
157
FbTk_istringstream iss(number.c_str());
161
// we don't care if there isn't one
163
// there shouldn't be anything else on the line
165
size_t uerr;// need a special type here
166
uerr = match.find_first_not_of(" \t\n", pos);
167
if (uerr != match.npos) {
168
// found something, not good
174
// delete all the terms
175
while (!m_terms.empty()) {
176
Term * term = m_terms.back();
183
ClientPattern::~ClientPattern() {
184
// delete all the terms
185
while (!m_terms.empty()) {
186
delete m_terms.back();
191
// return a string representation of this pattern
192
string ClientPattern::toString() const {
194
Terms::const_iterator it = m_terms.begin();
195
Terms::const_iterator it_end = m_terms.end();
196
for (; it != it_end; ++it) {
200
switch ((*it)->prop) {
205
pat.append("class=");
208
pat.append("title=");
214
pat.append("transient=");
217
pat.append("maximized=");
220
pat.append("minimized=");
223
pat.append("shaded=");
226
pat.append("stuck=");
229
pat.append("focushidden=");
232
pat.append("iconhidden=");
235
pat.append("workspace=");
238
pat.append("workspacename=");
244
pat.append("layer=");
247
pat.append("urgent=");
250
pat.append((*it)->orig);
254
if (m_matchlimit > 0) {
256
sprintf(num, " {%d}", m_matchlimit);
262
// does this client match this pattern?
263
bool ClientPattern::match(const Focusable &win) const {
264
if (m_matchlimit != 0 && m_nummatches >= m_matchlimit)
265
return false; // already matched out
267
// regmatch everything
268
// currently, we use an "AND" policy for multiple terms
269
// changing to OR would require minor modifications in this function only
270
Terms::const_iterator it = m_terms.begin();
271
Terms::const_iterator it_end = m_terms.end();
272
for (; it != it_end; ++it) {
273
if ((*it)->orig == "[current]") {
274
WinClient *focused = FocusControl::focusedWindow();
275
if ((*it)->prop == WORKSPACE) {
277
sprintf(tmpstr, "%d", win.screen().currentWorkspaceID());
278
if (!(*it)->negate ^ (getProperty((*it)->prop, win) == tmpstr))
280
} else if ((*it)->prop == WORKSPACENAME) {
281
const Workspace *w = win.screen().currentWorkspace();
282
if (!w || (!(*it)->negate ^
283
(getProperty((*it)->prop, win) == w->name())))
285
} else if (!focused || (!(*it)->negate ^
286
(getProperty((*it)->prop, win) ==
287
getProperty((*it)->prop, *focused))))
289
} else if ((*it)->prop == HEAD &&
290
(*it)->orig == "[mouse]") {
291
int mouse_head = win.screen().getCurrHead();
293
sprintf(num, "%d", mouse_head);
294
if (!(*it)->negate ^ (getProperty((*it)->prop, win) == num))
297
} else if (!(*it)->negate ^
298
(*it)->regexp.match(getProperty((*it)->prop, win)))
304
bool ClientPattern::dependsOnFocusedWindow() const {
305
Terms::const_iterator it = m_terms.begin(), it_end = m_terms.end();
306
for (; it != it_end; ++it) {
307
if ((*it)->prop != WORKSPACE && (*it)->prop != WORKSPACENAME &&
308
(*it)->orig == "[current]")
314
bool ClientPattern::dependsOnCurrentWorkspace() const {
315
Terms::const_iterator it = m_terms.begin(), it_end = m_terms.end();
316
for (; it != it_end; ++it) {
317
if (((*it)->prop == WORKSPACE || (*it)->prop == WORKSPACENAME) &&
318
(*it)->orig == "[current]")
324
// add an expression to match against
325
// The first argument is a regular expression, the second is the member
326
// function that we wish to match against.
327
bool ClientPattern::addTerm(const string &str, WinProperty prop, bool negate) {
329
Term *term = new Term(str, true);
332
term->negate = negate;
334
if (term->regexp.error()) {
338
m_terms.push_back(term);
342
string ClientPattern::getProperty(WinProperty prop, const Focusable &client) {
343
// we need this for some of the window properties
344
const FluxboxWindow *fbwin = client.fbwindow();
348
return client.title();
351
return client.getWMClassClass();
354
return client.getWMClassName();
357
return client.getWMRole();
360
return client.isTransient() ? "yes" : "no";
363
return (fbwin && fbwin->isMaximized()) ? "yes" : "no";
366
return (fbwin && fbwin->isIconic()) ? "yes" : "no";
369
return (fbwin && fbwin->isShaded()) ? "yes" : "no";
372
return (fbwin && fbwin->isStuck()) ? "yes" : "no";
375
return (fbwin && fbwin->isFocusHidden()) ? "yes" : "no";
378
return (fbwin && fbwin->isIconHidden()) ? "yes" : "no";
384
sprintf(tmpstr, "%d", fbwin->workspaceNumber());
385
return std::string(tmpstr);
388
case WORKSPACENAME: {
391
const Workspace *w = client.screen().getWorkspace(fbwin->workspaceNumber());
392
return w ? w->name() : "";
398
int head = client.screen().getHead(fbwin->fbWindow());
400
sprintf(tmpstr, "%d", head);
401
return std::string(tmpstr);
405
return fbwin ? ::Layer::getString(fbwin->layerNum()) : "";
408
return Fluxbox::instance()->attentionHandler()
409
.isDemandingAttention(client) ? "yes" : "no";
412
return client.getWMClassName();
415
bool ClientPattern::operator ==(const ClientPattern &pat) const {
416
// we require the terms to be identical (order too)
417
Terms::const_iterator it = m_terms.begin();
418
Terms::const_iterator it_end = m_terms.end();
419
Terms::const_iterator other_it = pat.m_terms.begin();
420
Terms::const_iterator other_it_end = pat.m_terms.end();
421
for (; it != it_end && other_it != other_it_end; ++it, ++other_it) {
422
if ((*it)->orig != (*other_it)->orig ||
423
(*it)->negate != (*other_it)->negate)
426
if (it != it_end || other_it != other_it_end)