From faa4c978885ceacfb75b0088e8c5a362a41794f6 Mon Sep 17 00:00:00 2001 From: Mathias Gumz Date: Sat, 11 Sep 2010 17:09:07 +0200 Subject: 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. --- doc/asciidoc/client-patterns.txt | 9 ++ doc/asciidoc/fluxbox-keys.txt | 8 ++ src/ClientPattern.cc | 188 ++++++++++++++++++++++++--------------- src/ClientPattern.hh | 7 +- src/CurrentWindowCmd.cc | 69 ++++++++++++-- src/Focusable.hh | 2 + src/WinClient.hh | 1 + src/Window.cc | 4 + src/Window.hh | 1 + 9 files changed, 206 insertions(+), 83 deletions(-) diff --git a/doc/asciidoc/client-patterns.txt b/doc/asciidoc/client-patterns.txt index 85dac7b..5b00e0c 100644 --- a/doc/asciidoc/client-patterns.txt +++ b/doc/asciidoc/client-patterns.txt @@ -64,6 +64,9 @@ The following values are accepted for 'propertyname'::: *Layer*;; The string name of the window's layer, which is one of *AboveDock*, *Dock*, *Top*, *Normal*, *Bottom*, *Desktop* +*@XPROP*;; + A string, corresponding to any xproperty (Use either the *xprop(1)* + utility or the 'SetXProp' command to set a xproperty to a window) .Matches any windows with the CLASSNAME of "xterm" .......... @@ -79,3 +82,9 @@ The following values are accepted for 'propertyname'::: ........... (Head=[mouse]) (Layer!=[current]) ........... + +.Matches any windows having a xproperty named FOO with "bar" in it +.............. +(@FOO=.*bar.*) +.............. + diff --git a/doc/asciidoc/fluxbox-keys.txt b/doc/asciidoc/fluxbox-keys.txt index 41ad1e2..16bc18c 100644 --- a/doc/asciidoc/fluxbox-keys.txt +++ b/doc/asciidoc/fluxbox-keys.txt @@ -327,6 +327,10 @@ two arguments;; heads. If this takes the window beyond the total number of heads, it will wrap around to the beginning. +*SetXProp* 'PROP=value':: + Sets the xproperty 'PROP' of the current window to 'value'. Delete the + content of 'PROP' by using 'PROP='. + Workspace Commands ~~~~~~~~~~~~~~~~~~ These commands affect the entire workspace (or "desktop" as it is sometimes @@ -643,8 +647,12 @@ Mod4 t :If {Some Matches (xterm)} {NextWindow (xterm)} {Exec xterm} # Set a different wallpaper on every workspace: ChangeWorkspace :Exec fbsetbg ~/.fluxbox/bg$(xprop -root _NET_CURRENT_DESKTOP | awk '{print $3}').png + +# Focusses the next window with it's xproperty 'PROP' set to 'foo' +Mod4 p Mod4 Tab :NextWindow (@PROP=foo) .................. + AUTHORS ------- - Jim Ramsay (>fluxbox-1.0.0) 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 { ClientPattern::WinProperty prop; }; -Name2WinProperty name_2_winproperties[] = { // sorted for 'bsearch' +const Name2WinProperty name_2_winproperties[] = { // sorted for 'bsearch' { "class", ClientPattern::CLASS }, { "focushidden", ClientPattern::FOCUSHIDDEN }, { "head", ClientPattern::HEAD }, @@ -92,29 +92,43 @@ int name_2_winproperty_cmp(const void* a, const void* b) { reinterpret_cast(b)->name); } +const Name2WinProperty* find_winproperty_by_name(const FbTk::FbString& name) { + + const Name2WinProperty key = { name.c_str(), ClientPattern::CLASS }; + const Name2WinProperty* result = reinterpret_cast( + bsearch(&key, name_2_winproperties, + sizeof(name_2_winproperties) / sizeof(Name2WinProperty), + sizeof(Name2WinProperty), + name_2_winproperty_cmp)); + + return result; +} + + struct Prop2String { ClientPattern::WinProperty prop; const char* str; }; Prop2String property_2_strings[] = { // sorted by 'prop' - { ClientPattern::TITLE, "title=" }, - { ClientPattern::CLASS, "class=" }, - { ClientPattern::NAME, "name=" }, - { ClientPattern::ROLE, "role=" }, - { ClientPattern::TRANSIENT, "transient=" }, - { ClientPattern::MAXIMIZED, "maximized=" }, - { ClientPattern::MINIMIZED, "minimized=" }, - { ClientPattern::SHADED, "shaded=" }, - { ClientPattern::STUCK, "stuck=" }, - { ClientPattern::FOCUSHIDDEN, "focushidden=" }, - { ClientPattern::ICONHIDDEN, "iconhidden=" }, - { ClientPattern::WORKSPACE, "workspace=" }, - { ClientPattern::WORKSPACENAME, "workspacename=" }, - { ClientPattern::HEAD, "head=" }, - { ClientPattern::LAYER, "layer=" }, - { ClientPattern::URGENT, "urgent=" }, - { ClientPattern::SCREEN, "screen=" } + { ClientPattern::TITLE, "title" }, + { ClientPattern::CLASS, "class" }, + { ClientPattern::NAME, "name" }, + { ClientPattern::ROLE, "role" }, + { ClientPattern::TRANSIENT, "transient" }, + { ClientPattern::MAXIMIZED, "maximized" }, + { ClientPattern::MINIMIZED, "minimized" }, + { ClientPattern::SHADED, "shaded" }, + { ClientPattern::STUCK, "stuck" }, + { ClientPattern::FOCUSHIDDEN, "focushidden" }, + { ClientPattern::ICONHIDDEN, "iconhidden" }, + { ClientPattern::WORKSPACE, "workspace" }, + { ClientPattern::WORKSPACENAME, "workspacename" }, + { ClientPattern::HEAD, "head" }, + { ClientPattern::LAYER, "layer" }, + { ClientPattern::URGENT, "urgent" }, + { ClientPattern::SCREEN, "screen" }, + { ClientPattern::XPROP, "@" }, }; @@ -129,16 +143,21 @@ Prop2String property_2_strings[] = { // sorted by 'prop' */ struct ClientPattern::Term { - Term(const FbTk::FbString& _regstr, WinProperty _prop, bool _negate) : - orig(_regstr), + Term(const FbTk::FbString& _regstr, WinProperty _prop, bool _negate, const FbTk::FbString& _xprop) : + regstr(_regstr), + xpropstr(_xprop), regexp(_regstr, true), prop(_prop), negate(_negate) { + xprop = XInternAtom(FbTk::App::instance()->display(), xpropstr.c_str(), False); } - FbTk::FbString orig; - FbTk::RegExp regexp; + // (title=.*bar) or (@FOO=.*bar) + FbTk::FbString regstr; // .*bar + FbTk::FbString xpropstr; // @FOO=.*bar + Atom xprop; // Atom of 'FOO' + FbTk::RegExp regexp; // compiled version of '.*bar' WinProperty prop; bool negate; }; @@ -175,51 +194,65 @@ ClientPattern::ClientPattern(const char *str): err = FbTk::StringUtil::getStringBetween(match, str + pos, '(', ')', " \t\n", true); + if (err > 0) { - // need to determine the property used - string memstr, expr; - WinProperty prop; - string::size_type eq = match.find_first_of('='); - if (eq == match.npos) { - memstr = match; - expr = "[current]"; - } else { - memstr.assign(match, 0, eq); // memstr = our identifier - expr.assign(match, eq+1, match.length()); - } + + WinProperty prop = NAME; + std::string expr; + std::string xprop; bool negate = false; - if (!memstr.empty() && memstr[memstr.length()-1] == '!') { - negate = true; - memstr.assign(memstr, 0, memstr.length()-1); + + // need to determine the property used, potential patterns: + // + // A) foo (short for 'title=foo') + // B) foo=bar + // C) foo!=bar + // + // D) @foo=bar (xproperty 'foo' equal to 'bar') + // + + string propstr = match; + string::size_type eq = propstr.find_first_of('='); + + if (eq == propstr.npos) { // A + expr = "[current]"; + } else { // B or C, so strip away the '=' + + // 'bar' + expr.assign(propstr.begin() + eq + 1, propstr.end()); + + // 'foo' or 'foo!' + propstr.resize(eq); + if (propstr.rfind("!", propstr.npos, 1) != propstr.npos) { // C 'foo!' + negate = true; + propstr.resize(propstr.size()-1); + } } - memstr = FbTk::StringUtil::toLower(memstr); + if (propstr[0] != '@') { // not D - Name2WinProperty key = { memstr.c_str(), CLASS }; - Name2WinProperty* i = reinterpret_cast( - bsearch(&key, name_2_winproperties, - sizeof(name_2_winproperties) / sizeof(Name2WinProperty), - sizeof(Name2WinProperty), - name_2_winproperty_cmp)); + const Name2WinProperty* p = find_winproperty_by_name(FbTk::StringUtil::toLower(propstr)); - if (i) { - prop = i->prop; - } else { - prop = NAME; - expr = match; + if (p) { + prop = p->prop; + } else { + expr = match; + } + } else { // D + prop = XPROP; + xprop.assign(propstr, 1, propstr.size()); } - had_error = !addTerm(expr, prop, negate); + had_error = !addTerm(expr, prop, negate, xprop); pos += err; } } - if (pos == 0 && !had_error) { - // no match terms given, this is not allowed + if (pos == 0 && !had_error) { // no match terms given, this is not allowed had_error = true; } - if (!had_error) { - // otherwise, we check for a number + if (!had_error) { // otherwise, we check for a number + string number; err = FbTk::StringUtil::getStringBetween(number, str+pos, @@ -251,23 +284,26 @@ ClientPattern::~ClientPattern() { // return a string representation of this pattern string ClientPattern::toString() const { - string pat; + string result; Terms::const_iterator it = m_terms.begin(); Terms::const_iterator it_end = m_terms.end(); for (; it != it_end; ++it) { - - pat.append(" ("); - pat.append(property_2_strings[(*it)->prop].str); - pat.append((*it)->orig); - pat.append(")"); + const Term& term = *(*it); + result.append(" ("); + result.append(property_2_strings[term.prop].str); + if (term.prop == XPROP) + result.append(term.xpropstr); + result.append(term.negate ? "!=" : "="); + result.append(term.regstr); + result.append(")"); } if (m_matchlimit > 0) { - pat.append(" {"); - pat.append(FbTk::StringUtil::number2String(m_matchlimit)); - pat.append("}"); + result.append(" {"); + result.append(FbTk::StringUtil::number2String(m_matchlimit)); + result.append("}"); } - return pat; + return result; } // does this client match this pattern? @@ -282,7 +318,10 @@ bool ClientPattern::match(const Focusable &win) const { Terms::const_iterator it_end = m_terms.end(); for (; it != it_end; ++it) { const Term& term = *(*it); - if (term.orig == "[current]") { + if (term.prop == XPROP) { + if (!term.negate ^ (term.regexp.match(win.getTextProperty(term.xprop)))) + return false; + } else if (term.regstr == "[current]") { WinClient *focused = FocusControl::focusedWindow(); if (term.prop == WORKSPACE) { if (!term.negate ^ (getProperty(term.prop, win) == FbTk::StringUtil::number2String(win.screen().currentWorkspaceID()))) @@ -293,7 +332,7 @@ bool ClientPattern::match(const Focusable &win) const { return false; } else if (!focused || (!term.negate ^ (getProperty(term.prop, win) == getProperty(term.prop, *focused)))) return false; - } else if (term.prop == HEAD && term.orig == "[mouse]") { + } else if (term.prop == HEAD && term.regstr == "[mouse]") { if (!term.negate ^ (getProperty(term.prop, win) == FbTk::StringUtil::number2String(win.screen().getCurrHead()))) return false; @@ -307,7 +346,7 @@ bool ClientPattern::dependsOnFocusedWindow() const { Terms::const_iterator it = m_terms.begin(), it_end = m_terms.end(); for (; it != it_end; ++it) { if ((*it)->prop != WORKSPACE && (*it)->prop != WORKSPACENAME && - (*it)->orig == "[current]") + (*it)->regstr == "[current]") return true; } return false; @@ -317,7 +356,7 @@ bool ClientPattern::dependsOnCurrentWorkspace() const { Terms::const_iterator it = m_terms.begin(), it_end = m_terms.end(); for (; it != it_end; ++it) { if (((*it)->prop == WORKSPACE || (*it)->prop == WORKSPACENAME) && - (*it)->orig == "[current]") + (*it)->regstr == "[current]") return true; } return false; @@ -326,17 +365,16 @@ bool ClientPattern::dependsOnCurrentWorkspace() 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. -bool ClientPattern::addTerm(const FbTk::FbString &str, WinProperty prop, bool negate) { +bool ClientPattern::addTerm(const FbTk::FbString &str, WinProperty prop, bool negate, const FbTk::FbString& xprop) { bool rc = false; - Term* term = new Term(str, prop, negate); + Term* term = new Term(str, prop, negate, xprop); if (!term) return rc; - if (!term->regexp.error()) { + if (rc = !term->regexp.error()) { m_terms.push_back(term); - rc = true; } else { delete term; } @@ -424,8 +462,11 @@ bool ClientPattern::operator ==(const ClientPattern &pat) const { Terms::const_iterator other_it = pat.m_terms.begin(); Terms::const_iterator other_it_end = pat.m_terms.end(); for (; it != it_end && other_it != other_it_end; ++it, ++other_it) { - if ((*it)->orig != (*other_it)->orig || - (*it)->negate != (*other_it)->negate) + const Term& i = *(*it); + const Term& o = *(*other_it); + if (i.regstr != o.regstr || + i.negate != o.negate || + i.xpropstr != o.xpropstr) return false; } if (it != it_end || other_it != other_it_end) @@ -433,3 +474,4 @@ bool ClientPattern::operator ==(const ClientPattern &pat) const { return true; } + diff --git a/src/ClientPattern.hh b/src/ClientPattern.hh index 6aa2e11..315edb1 100644 --- a/src/ClientPattern.hh +++ b/src/ClientPattern.hh @@ -54,7 +54,8 @@ public: enum WinProperty { TITLE = 0, CLASS, NAME, ROLE, TRANSIENT, MAXIMIZED, MINIMIZED, SHADED, STUCK, FOCUSHIDDEN, ICONHIDDEN, - WORKSPACE, WORKSPACENAME, HEAD, LAYER, URGENT, SCREEN + WORKSPACE, WORKSPACENAME, HEAD, LAYER, URGENT, SCREEN, + XPROP }; /// Does this client match this pattern? @@ -70,9 +71,11 @@ public: * Add an expression to match against * @param str is a regular expression * @param prop is the member function that we wish to match against + * @param negate is if the term should be negated + * @param xprop is the name of the prop if prop is XPROP * @return false if the regexp wasn't valid */ - bool addTerm(const FbTk::FbString &str, WinProperty prop, bool negate = false); + bool addTerm(const FbTk::FbString &str, WinProperty prop, bool negate = false, const FbTk::FbString& xprop = FbTk::FbString()); void addMatch() { ++m_nummatches; } void removeMatch() { --m_nummatches; } diff --git a/src/CurrentWindowCmd.cc b/src/CurrentWindowCmd.cc index e2fdb94..1851f2d 100644 --- a/src/CurrentWindowCmd.cc +++ b/src/CurrentWindowCmd.cc @@ -227,23 +227,75 @@ REGISTER_COMMAND_PARSER(focus, parseFocusCmd, void); class ActivateTabCmd: public WindowHelperCmd { public: - ActivateTabCmd() { } + explicit ActivateTabCmd() { } protected: - void real_execute(); + void real_execute() { + WinClient* winclient = fbwindow().winClientOfLabelButtonWindow( + Fluxbox::instance()->lastEvent().xany.window); + + if (winclient && winclient != &fbwindow().winClient()) { + fbwindow().setCurrentClient(*winclient, true); + } + + } +}; + + +REGISTER_COMMAND(activatetab, ActivateTabCmd, void); + +class SetXPropCmd: public WindowHelperCmd { +public: + explicit SetXPropCmd(const FbTk::FbString& name, const FbTk::FbString& value) : + m_name(name), m_value(value) { } + +protected: + void real_execute() { + + WinClient& client = fbwindow().winClient(); + Atom prop = XInternAtom(client.display(), m_name.c_str(), False); + + client.changeProperty(prop, XInternAtom(client.display(), "UTF8_STRING", False), 8, + PropModeReplace, (unsigned char*)m_value.c_str(), m_value.size()); + } + +private: + FbTk::FbString m_name; + FbTk::FbString m_value; }; +FbTk::Command *parseSetXPropCmd(const string &command, const string &args, bool trusted) { + + SetXPropCmd* cmd = 0; + + if (trusted) { + + FbTk::FbString name = args; + + FbTk::StringUtil::removeFirstWhitespace(name); + FbTk::StringUtil::removeTrailingWhitespace(name); + + if (name.size() > 1 && name[0] != '=') { // the smallest valid argument is 'X=' + + FbTk::FbString value; -void ActivateTabCmd::real_execute() { + size_t eq = name.find('='); + if (eq != name.npos && eq != name.size()) { - WinClient* winclient = fbwindow().winClientOfLabelButtonWindow( - Fluxbox::instance()->lastEvent().xany.window); + value.assign(name, eq + 1, name.size()); + name.resize(eq); + } - if (winclient && winclient != &fbwindow().winClient()) { - fbwindow().setCurrentClient(*winclient, true); + cmd = new SetXPropCmd(name, value); + + } } + + return cmd; } -REGISTER_COMMAND(activatetab, ActivateTabCmd, void); +REGISTER_COMMAND_PARSER(setxprop, parseSetXPropCmd, void); + + } // end anonymous namespace @@ -677,6 +729,7 @@ void SetAlphaCmd::real_execute() { : m_unfocus); } + REGISTER_COMMAND_WITH_ARGS(matches, MatchCmd, bool); bool MatchCmd::real_execute() { diff --git a/src/Focusable.hh b/src/Focusable.hh index 47f14d3..4583a62 100644 --- a/src/Focusable.hh +++ b/src/Focusable.hh @@ -88,6 +88,8 @@ public: /// @return wm role string (for pattern matching) virtual std::string getWMRole() const { return "Focusable"; } + virtual FbTk::FbString getTextProperty(Atom prop) const { return ""; } + /// @return whether this window is a transient (for pattern matching) virtual bool isTransient() const { return false; } diff --git a/src/WinClient.hh b/src/WinClient.hh index ebe61a5..157278e 100644 --- a/src/WinClient.hh +++ b/src/WinClient.hh @@ -96,6 +96,7 @@ public: std::string getWMRole() const; WindowState::WindowType getWindowType() const { return m_window_type; } void setWindowType(WindowState::WindowType type) { m_window_type = type; } + FbTk::FbString getTextProperty(Atom prop) const { return FbTk::FbWindow::textProperty(prop); } WinClient *transientFor() { return transient_for; } const WinClient *transientFor() const { return transient_for; } diff --git a/src/Window.cc b/src/Window.cc index 5f64705..1837cee 100644 --- a/src/Window.cc +++ b/src/Window.cc @@ -3363,6 +3363,10 @@ FbTk::FbString FluxboxWindow::getWMRole() const { return (m_client ? m_client->getWMRole() : "FluxboxWindow"); } +FbTk::FbString FluxboxWindow::getTextProperty(Atom prop) const { + return (m_client ? m_client->getTextProperty(prop) : Focusable::getTextProperty(prop)); +} + bool FluxboxWindow::isTransient() const { return (m_client && m_client->isTransient()); } diff --git a/src/Window.hh b/src/Window.hh index 080ceac..7f8133e 100644 --- a/src/Window.hh +++ b/src/Window.hh @@ -422,6 +422,7 @@ public: const FbTk::FbString &getWMClassName() const; const FbTk::FbString &getWMClassClass() const; std::string getWMRole() const; + FbTk::FbString getTextProperty(Atom prop) const; void setWindowType(WindowState::WindowType type); bool isTransient() const; -- cgit v0.11.2