diff options
author | Mathias Gumz <akira at fluxbox dot org> | 2010-09-11 15:09:07 (GMT) |
---|---|---|
committer | Mathias Gumz <akira at fluxbox dot org> | 2010-09-11 15:09:07 (GMT) |
commit | faa4c978885ceacfb75b0088e8c5a362a41794f6 (patch) | |
tree | 600e09fafca10e702150abac9e852f8aa6ef38c3 /src/ClientPattern.cc | |
parent | 4e2c7e2167a0e0efbfc73c1b226eaafa808736ee (diff) | |
download | fluxbox-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.cc | 188 |
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 | ||
69 | Name2WinProperty name_2_winproperties[] = { // sorted for 'bsearch' | 69 | const 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 | ||
95 | const 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 | |||
95 | struct Prop2String { | 108 | struct Prop2String { |
96 | ClientPattern::WinProperty prop; | 109 | ClientPattern::WinProperty prop; |
97 | const char* str; | 110 | const char* str; |
98 | }; | 111 | }; |
99 | 112 | ||
100 | Prop2String property_2_strings[] = { // sorted by 'prop' | 113 | Prop2String 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 | */ |
130 | struct ClientPattern::Term { | 144 | struct 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 |
253 | string ClientPattern::toString() const { | 286 | string 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. |
329 | bool ClientPattern::addTerm(const FbTk::FbString &str, WinProperty prop, bool negate) { | 368 | bool 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 | |||