aboutsummaryrefslogtreecommitdiff
path: root/src/ClientPattern.cc
diff options
context:
space:
mode:
authorMathias Gumz <akira at fluxbox dot org>2010-09-11 15:09:07 (GMT)
committerMathias Gumz <akira at fluxbox dot org>2010-09-11 15:09:07 (GMT)
commitfaa4c978885ceacfb75b0088e8c5a362a41794f6 (patch)
tree600e09fafca10e702150abac9e852f8aa6ef38c3 /src/ClientPattern.cc
parent4e2c7e2167a0e0efbfc73c1b226eaafa808736ee (diff)
downloadfluxbox-faa4c978885ceacfb75b0088e8c5a362a41794f6.zip
fluxbox-faa4c978885ceacfb75b0088e8c5a362a41794f6.tar.bz2
added 'SetXProp' action and (@PROP=foo) clientpattern
these two allow 'tagging' of arbitrary windows with 'tags' (or 'labels'). such 'tagged' windows can then be used in ':NextWindow (@PROP=foo)' commands to quickly cycle through a subset of available windows. since the 'tags' are applied as real xproperties to a window they survive a restart of fluxbox or even another windowmanager. the user can also set the tags by using xprop(1). the next step regarding the UI should be to visualize the tags of a window.
Diffstat (limited to 'src/ClientPattern.cc')
-rw-r--r--src/ClientPattern.cc188
1 files changed, 115 insertions, 73 deletions
diff --git a/src/ClientPattern.cc b/src/ClientPattern.cc
index 7e7e715..71725fb 100644
--- a/src/ClientPattern.cc
+++ b/src/ClientPattern.cc
@@ -66,7 +66,7 @@ struct Name2WinProperty {
66 ClientPattern::WinProperty prop; 66 ClientPattern::WinProperty prop;
67}; 67};
68 68
69Name2WinProperty name_2_winproperties[] = { // sorted for 'bsearch' 69const Name2WinProperty name_2_winproperties[] = { // sorted for 'bsearch'
70 { "class", ClientPattern::CLASS }, 70 { "class", ClientPattern::CLASS },
71 { "focushidden", ClientPattern::FOCUSHIDDEN }, 71 { "focushidden", ClientPattern::FOCUSHIDDEN },
72 { "head", ClientPattern::HEAD }, 72 { "head", ClientPattern::HEAD },
@@ -92,29 +92,43 @@ int name_2_winproperty_cmp(const void* a, const void* b) {
92 reinterpret_cast<const Name2WinProperty*>(b)->name); 92 reinterpret_cast<const Name2WinProperty*>(b)->name);
93} 93}
94 94
95const Name2WinProperty* find_winproperty_by_name(const FbTk::FbString& name) {
96
97 const Name2WinProperty key = { name.c_str(), ClientPattern::CLASS };
98 const Name2WinProperty* result = reinterpret_cast<Name2WinProperty*>(
99 bsearch(&key, name_2_winproperties,
100 sizeof(name_2_winproperties) / sizeof(Name2WinProperty),
101 sizeof(Name2WinProperty),
102 name_2_winproperty_cmp));
103
104 return result;
105}
106
107
95struct Prop2String { 108struct Prop2String {
96 ClientPattern::WinProperty prop; 109 ClientPattern::WinProperty prop;
97 const char* str; 110 const char* str;
98}; 111};
99 112
100Prop2String property_2_strings[] = { // sorted by 'prop' 113Prop2String property_2_strings[] = { // sorted by 'prop'
101 { ClientPattern::TITLE, "title=" }, 114 { ClientPattern::TITLE, "title" },
102 { ClientPattern::CLASS, "class=" }, 115 { ClientPattern::CLASS, "class" },
103 { ClientPattern::NAME, "name=" }, 116 { ClientPattern::NAME, "name" },
104 { ClientPattern::ROLE, "role=" }, 117 { ClientPattern::ROLE, "role" },
105 { ClientPattern::TRANSIENT, "transient=" }, 118 { ClientPattern::TRANSIENT, "transient" },
106 { ClientPattern::MAXIMIZED, "maximized=" }, 119 { ClientPattern::MAXIMIZED, "maximized" },
107 { ClientPattern::MINIMIZED, "minimized=" }, 120 { ClientPattern::MINIMIZED, "minimized" },
108 { ClientPattern::SHADED, "shaded=" }, 121 { ClientPattern::SHADED, "shaded" },
109 { ClientPattern::STUCK, "stuck=" }, 122 { ClientPattern::STUCK, "stuck" },
110 { ClientPattern::FOCUSHIDDEN, "focushidden=" }, 123 { ClientPattern::FOCUSHIDDEN, "focushidden" },
111 { ClientPattern::ICONHIDDEN, "iconhidden=" }, 124 { ClientPattern::ICONHIDDEN, "iconhidden" },
112 { ClientPattern::WORKSPACE, "workspace=" }, 125 { ClientPattern::WORKSPACE, "workspace" },
113 { ClientPattern::WORKSPACENAME, "workspacename=" }, 126 { ClientPattern::WORKSPACENAME, "workspacename" },
114 { ClientPattern::HEAD, "head=" }, 127 { ClientPattern::HEAD, "head" },
115 { ClientPattern::LAYER, "layer=" }, 128 { ClientPattern::LAYER, "layer" },
116 { ClientPattern::URGENT, "urgent=" }, 129 { ClientPattern::URGENT, "urgent" },
117 { ClientPattern::SCREEN, "screen=" } 130 { ClientPattern::SCREEN, "screen" },
131 { ClientPattern::XPROP, "@" },
118}; 132};
119 133
120 134
@@ -129,16 +143,21 @@ Prop2String property_2_strings[] = { // sorted by 'prop'
129 */ 143 */
130struct ClientPattern::Term { 144struct ClientPattern::Term {
131 145
132 Term(const FbTk::FbString& _regstr, WinProperty _prop, bool _negate) : 146 Term(const FbTk::FbString& _regstr, WinProperty _prop, bool _negate, const FbTk::FbString& _xprop) :
133 orig(_regstr), 147 regstr(_regstr),
148 xpropstr(_xprop),
134 regexp(_regstr, true), 149 regexp(_regstr, true),
135 prop(_prop), 150 prop(_prop),
136 negate(_negate) { 151 negate(_negate) {
137 152
153 xprop = XInternAtom(FbTk::App::instance()->display(), xpropstr.c_str(), False);
138 } 154 }
139 155
140 FbTk::FbString orig; 156 // (title=.*bar) or (@FOO=.*bar)
141 FbTk::RegExp regexp; 157 FbTk::FbString regstr; // .*bar
158 FbTk::FbString xpropstr; // @FOO=.*bar
159 Atom xprop; // Atom of 'FOO'
160 FbTk::RegExp regexp; // compiled version of '.*bar'
142 WinProperty prop; 161 WinProperty prop;
143 bool negate; 162 bool negate;
144}; 163};
@@ -175,51 +194,65 @@ ClientPattern::ClientPattern(const char *str):
175 err = FbTk::StringUtil::getStringBetween(match, 194 err = FbTk::StringUtil::getStringBetween(match,
176 str + pos, 195 str + pos,
177 '(', ')', " \t\n", true); 196 '(', ')', " \t\n", true);
197
178 if (err > 0) { 198 if (err > 0) {
179 // need to determine the property used 199
180 string memstr, expr; 200 WinProperty prop = NAME;
181 WinProperty prop; 201 std::string expr;
182 string::size_type eq = match.find_first_of('='); 202 std::string xprop;
183 if (eq == match.npos) {
184 memstr = match;
185 expr = "[current]";
186 } else {
187 memstr.assign(match, 0, eq); // memstr = our identifier
188 expr.assign(match, eq+1, match.length());
189 }
190 bool negate = false; 203 bool negate = false;
191 if (!memstr.empty() && memstr[memstr.length()-1] == '!') { 204
192 negate = true; 205 // need to determine the property used, potential patterns:
193 memstr.assign(memstr, 0, memstr.length()-1); 206 //
207 // A) foo (short for 'title=foo')
208 // B) foo=bar
209 // C) foo!=bar
210 //
211 // D) @foo=bar (xproperty 'foo' equal to 'bar')
212 //
213
214 string propstr = match;
215 string::size_type eq = propstr.find_first_of('=');
216
217 if (eq == propstr.npos) { // A
218 expr = "[current]";
219 } else { // B or C, so strip away the '='
220
221 // 'bar'
222 expr.assign(propstr.begin() + eq + 1, propstr.end());
223
224 // 'foo' or 'foo!'
225 propstr.resize(eq);
226 if (propstr.rfind("!", propstr.npos, 1) != propstr.npos) { // C 'foo!'
227 negate = true;
228 propstr.resize(propstr.size()-1);
229 }
194 } 230 }
195 231
196 memstr = FbTk::StringUtil::toLower(memstr); 232 if (propstr[0] != '@') { // not D
197 233
198 Name2WinProperty key = { memstr.c_str(), CLASS }; 234 const Name2WinProperty* p = find_winproperty_by_name(FbTk::StringUtil::toLower(propstr));
199 Name2WinProperty* i = reinterpret_cast<Name2WinProperty*>(
200 bsearch(&key, name_2_winproperties,
201 sizeof(name_2_winproperties) / sizeof(Name2WinProperty),
202 sizeof(Name2WinProperty),
203 name_2_winproperty_cmp));
204 235
205 if (i) { 236 if (p) {
206 prop = i->prop; 237 prop = p->prop;
207 } else { 238 } else {
208 prop = NAME; 239 expr = match;
209 expr = match; 240 }
241 } else { // D
242 prop = XPROP;
243 xprop.assign(propstr, 1, propstr.size());
210 } 244 }
211 245
212 had_error = !addTerm(expr, prop, negate); 246 had_error = !addTerm(expr, prop, negate, xprop);
213 pos += err; 247 pos += err;
214 } 248 }
215 } 249 }
216 if (pos == 0 && !had_error) { 250 if (pos == 0 && !had_error) { // no match terms given, this is not allowed
217 // no match terms given, this is not allowed
218 had_error = true; 251 had_error = true;
219 } 252 }
220 253
221 if (!had_error) { 254 if (!had_error) { // otherwise, we check for a number
222 // otherwise, we check for a number 255
223 string number; 256 string number;
224 err = FbTk::StringUtil::getStringBetween(number, 257 err = FbTk::StringUtil::getStringBetween(number,
225 str+pos, 258 str+pos,
@@ -251,23 +284,26 @@ ClientPattern::~ClientPattern() {
251 284
252// return a string representation of this pattern 285// return a string representation of this pattern
253string ClientPattern::toString() const { 286string ClientPattern::toString() const {
254 string pat; 287 string result;
255 Terms::const_iterator it = m_terms.begin(); 288 Terms::const_iterator it = m_terms.begin();
256 Terms::const_iterator it_end = m_terms.end(); 289 Terms::const_iterator it_end = m_terms.end();
257 for (; it != it_end; ++it) { 290 for (; it != it_end; ++it) {
258 291 const Term& term = *(*it);
259 pat.append(" ("); 292 result.append(" (");
260 pat.append(property_2_strings[(*it)->prop].str); 293 result.append(property_2_strings[term.prop].str);
261 pat.append((*it)->orig); 294 if (term.prop == XPROP)
262 pat.append(")"); 295 result.append(term.xpropstr);
296 result.append(term.negate ? "!=" : "=");
297 result.append(term.regstr);
298 result.append(")");
263 } 299 }
264 300
265 if (m_matchlimit > 0) { 301 if (m_matchlimit > 0) {
266 pat.append(" {"); 302 result.append(" {");
267 pat.append(FbTk::StringUtil::number2String(m_matchlimit)); 303 result.append(FbTk::StringUtil::number2String(m_matchlimit));
268 pat.append("}"); 304 result.append("}");
269 } 305 }
270 return pat; 306 return result;
271} 307}
272 308
273// does this client match this pattern? 309// does this client match this pattern?
@@ -282,7 +318,10 @@ bool ClientPattern::match(const Focusable &win) const {
282 Terms::const_iterator it_end = m_terms.end(); 318 Terms::const_iterator it_end = m_terms.end();
283 for (; it != it_end; ++it) { 319 for (; it != it_end; ++it) {
284 const Term& term = *(*it); 320 const Term& term = *(*it);
285 if (term.orig == "[current]") { 321 if (term.prop == XPROP) {
322 if (!term.negate ^ (term.regexp.match(win.getTextProperty(term.xprop))))
323 return false;
324 } else if (term.regstr == "[current]") {
286 WinClient *focused = FocusControl::focusedWindow(); 325 WinClient *focused = FocusControl::focusedWindow();
287 if (term.prop == WORKSPACE) { 326 if (term.prop == WORKSPACE) {
288 if (!term.negate ^ (getProperty(term.prop, win) == FbTk::StringUtil::number2String(win.screen().currentWorkspaceID()))) 327 if (!term.negate ^ (getProperty(term.prop, win) == FbTk::StringUtil::number2String(win.screen().currentWorkspaceID())))
@@ -293,7 +332,7 @@ bool ClientPattern::match(const Focusable &win) const {
293 return false; 332 return false;
294 } else if (!focused || (!term.negate ^ (getProperty(term.prop, win) == getProperty(term.prop, *focused)))) 333 } else if (!focused || (!term.negate ^ (getProperty(term.prop, win) == getProperty(term.prop, *focused))))
295 return false; 334 return false;
296 } else if (term.prop == HEAD && term.orig == "[mouse]") { 335 } else if (term.prop == HEAD && term.regstr == "[mouse]") {
297 if (!term.negate ^ (getProperty(term.prop, win) == FbTk::StringUtil::number2String(win.screen().getCurrHead()))) 336 if (!term.negate ^ (getProperty(term.prop, win) == FbTk::StringUtil::number2String(win.screen().getCurrHead())))
298 return false; 337 return false;
299 338
@@ -307,7 +346,7 @@ bool ClientPattern::dependsOnFocusedWindow() const {
307 Terms::const_iterator it = m_terms.begin(), it_end = m_terms.end(); 346 Terms::const_iterator it = m_terms.begin(), it_end = m_terms.end();
308 for (; it != it_end; ++it) { 347 for (; it != it_end; ++it) {
309 if ((*it)->prop != WORKSPACE && (*it)->prop != WORKSPACENAME && 348 if ((*it)->prop != WORKSPACE && (*it)->prop != WORKSPACENAME &&
310 (*it)->orig == "[current]") 349 (*it)->regstr == "[current]")
311 return true; 350 return true;
312 } 351 }
313 return false; 352 return false;
@@ -317,7 +356,7 @@ bool ClientPattern::dependsOnCurrentWorkspace() const {
317 Terms::const_iterator it = m_terms.begin(), it_end = m_terms.end(); 356 Terms::const_iterator it = m_terms.begin(), it_end = m_terms.end();
318 for (; it != it_end; ++it) { 357 for (; it != it_end; ++it) {
319 if (((*it)->prop == WORKSPACE || (*it)->prop == WORKSPACENAME) && 358 if (((*it)->prop == WORKSPACE || (*it)->prop == WORKSPACENAME) &&
320 (*it)->orig == "[current]") 359 (*it)->regstr == "[current]")
321 return true; 360 return true;
322 } 361 }
323 return false; 362 return false;
@@ -326,17 +365,16 @@ bool ClientPattern::dependsOnCurrentWorkspace() const {
326// add an expression to match against 365// add an expression to match against
327// The first argument is a regular expression, the second is the member 366// The first argument is a regular expression, the second is the member
328// function that we wish to match against. 367// function that we wish to match against.
329bool ClientPattern::addTerm(const FbTk::FbString &str, WinProperty prop, bool negate) { 368bool ClientPattern::addTerm(const FbTk::FbString &str, WinProperty prop, bool negate, const FbTk::FbString& xprop) {
330 369
331 bool rc = false; 370 bool rc = false;
332 Term* term = new Term(str, prop, negate); 371 Term* term = new Term(str, prop, negate, xprop);
333 372
334 if (!term) 373 if (!term)
335 return rc; 374 return rc;
336 375
337 if (!term->regexp.error()) { 376 if (rc = !term->regexp.error()) {
338 m_terms.push_back(term); 377 m_terms.push_back(term);
339 rc = true;
340 } else { 378 } else {
341 delete term; 379 delete term;
342 } 380 }
@@ -424,8 +462,11 @@ bool ClientPattern::operator ==(const ClientPattern &pat) const {
424 Terms::const_iterator other_it = pat.m_terms.begin(); 462 Terms::const_iterator other_it = pat.m_terms.begin();
425 Terms::const_iterator other_it_end = pat.m_terms.end(); 463 Terms::const_iterator other_it_end = pat.m_terms.end();
426 for (; it != it_end && other_it != other_it_end; ++it, ++other_it) { 464 for (; it != it_end && other_it != other_it_end; ++it, ++other_it) {
427 if ((*it)->orig != (*other_it)->orig || 465 const Term& i = *(*it);
428 (*it)->negate != (*other_it)->negate) 466 const Term& o = *(*other_it);
467 if (i.regstr != o.regstr ||
468 i.negate != o.negate ||
469 i.xpropstr != o.xpropstr)
429 return false; 470 return false;
430 } 471 }
431 if (it != it_end || other_it != other_it_end) 472 if (it != it_end || other_it != other_it_end)
@@ -433,3 +474,4 @@ bool ClientPattern::operator ==(const ClientPattern &pat) const {
433 474
434 return true; 475 return true;
435} 476}
477