1 #include "rulemanager.h"
2
3 #include <algorithm>
4 #include <string>
5
6 #include "completion.h"
7 #include "globals.h"
8 #include "ipc-protocol.h"
9 #include "utils.h"
10
11 using std::string;
12 using std::to_string;
13 using std::endl;
14 using std::unique_ptr;
15
16 /**
17 * @brief RuleManager::parseRule
18 * @param input
19 * @param output
20 * @param the rule to modify
21 * @param whether the 'prepend' flag was given in input
22 * @return
23 */
parseRule(Input input,Output output,Rule & rule,bool & prepend)24 int RuleManager::parseRule(Input input, Output output, Rule& rule, bool& prepend)
25 {
26 // Possible flags that apply to the rule as a whole:
27 std::map<string, bool> ruleFlags = {
28 {"once", false},
29 {"printlabel", false},
30 {"prepend", false},
31 };
32
33 for (auto argIter = input.begin(); argIter != input.end(); argIter++) {
34 auto arg = *argIter;
35
36 // Whether this argument is negated (only applies to conditions)
37 bool negated = false;
38
39 // Check if arg is a flag for the whole rule
40 if (ruleFlags.count(arg)) {
41 ruleFlags[arg] = true;
42 continue;
43 }
44
45 // Check if arg is a condition negator
46 if (arg == "not" || arg == "!") {
47 // Make sure there is another argument coming:
48 if (argIter + 1 == input.end()) {
49 output << "Expected another argument after \""<< arg << "\" flag\n";
50 return HERBST_INVALID_ARGUMENT;
51 }
52
53 // Skip forward to next argument, but remember that it is negated:
54 negated = true;
55 arg = *(++argIter);
56 }
57
58 // Tokenize arg, expect something like foo=bar or foo~bar:
59 char oper;
60 string lhs, rhs;
61 try {
62 std::tie(lhs, oper, rhs) = tokenizeArg(arg);
63 } catch (std::invalid_argument &error) {
64 output << input.command() << ": " << error.what() << endl;
65 return HERBST_INVALID_ARGUMENT;
66 }
67
68 // Check if lhs is a condition name
69 if (Condition::matchers.count(lhs)) {
70 bool success = rule.addCondition(lhs, oper, rhs.c_str(), negated, output);
71 if (!success) {
72 return HERBST_INVALID_ARGUMENT;
73 }
74 continue;
75 }
76
77 // Check if lhs is a consequence name
78 if (Consequence::appliers.count(lhs)) {
79 if (oper == '~') {
80 output << input.command() << ": Operator ~ not valid for consequence \"" << lhs << "\"\n";
81 return HERBST_INVALID_ARGUMENT;
82 }
83
84 bool success = rule.addConsequence(lhs, oper, rhs.c_str(), output);
85 if (!success) {
86 return HERBST_INVALID_ARGUMENT;
87 }
88 continue;
89 }
90
91
92 // Check if arg is a custom label for this rule
93 if (lhs == "label") {
94 bool success = rule.setLabel(oper, rhs, output);
95 if (!success) {
96 return HERBST_INVALID_ARGUMENT;
97 }
98 continue;
99 }
100
101 output << input.command() << ": Unknown argument \"" << arg << "\"\n";
102 return HERBST_INVALID_ARGUMENT;
103 }
104
105 // Store "once" flag in rule
106 rule.once = ruleFlags["once"];
107
108 // Comply with "printlabel" flag
109 if (ruleFlags["printlabel"]) {
110 output << rule.label << "\n";
111 }
112 prepend = ruleFlags["prepend"];
113 return 0;
114 }
115
addRuleCommand(Input input,Output output)116 int RuleManager::addRuleCommand(Input input, Output output) {
117 Rule rule;
118 bool prepend = false;
119
120 // Assign default label (index will be incremented if adding the rule
121 // actually succeeds)
122 rule.label = to_string(rule_label_index_);
123 int status = parseRule(input, output, rule, prepend);
124 if (status != 0) {
125 return status;
126 }
127
128 // At this point, adding the rule will be successful, so increment the
129 // label index (as it says in the docs):
130 rule_label_index_++;
131
132 // Insert rule into list according to "prepend" flag
133 auto insertAt = prepend ? rules_.begin() : rules_.end();
134 rules_.insert(insertAt, make_unique<Rule>(rule));
135
136 return HERBST_EXIT_SUCCESS;
137 }
138
139 /*!
140 * Implements the "unrule" IPC command
141 */
unruleCommand(Input input,Output output)142 int RuleManager::unruleCommand(Input input, Output output) {
143 string arg;
144 if (!(input >> arg)) {
145 return HERBST_NEED_MORE_ARGS;
146 }
147
148 if (arg == "--all" || arg == "-F") {
149 rules_.clear();
150 rule_label_index_ = 0;
151 } else {
152 // Remove rule specified by argument
153 auto removedCount = removeRules(arg);
154 if (removedCount == 0) {
155 output << "Couldn't find any rules with label \"" << arg << "\"";
156 return HERBST_INVALID_ARGUMENT;
157 }
158 }
159
160 return HERBST_EXIT_SUCCESS;
161 }
162
163 /*!
164 * Implements the "list_rules" IPC command
165 */
listRulesCommand(Output output)166 int RuleManager::listRulesCommand(Output output) {
167 for (auto& rule : rules_) {
168 rule->print(output);
169 }
170
171 return HERBST_EXIT_SUCCESS;
172 }
173
174 /*!
175 * Removes all rules with the given label
176 *
177 * \returns number of removed rules
178 */
removeRules(string label)179 size_t RuleManager::removeRules(string label) {
180 auto countBefore = rules_.size();
181
182 for (auto ruleIter = rules_.begin(); ruleIter != rules_.end();) {
183 if ((*ruleIter)->label == label) {
184 ruleIter = rules_.erase(ruleIter);
185 } else {
186 ruleIter++;
187 }
188 }
189
190 auto countAfter = rules_.size();
191
192 return countAfter - countBefore;
193 }
194
tokenizeArg(string arg)195 std::tuple<string, char, string> RuleManager::tokenizeArg(string arg) {
196 if (arg.substr(0, 2) == "--") {
197 arg.erase(0, 2);
198 }
199
200 auto operPos = arg.find_first_of("~=");
201 if (operPos == string::npos) {
202 throw std::invalid_argument("No operator in given arg: " + arg);
203 }
204 auto lhs = arg.substr(0, operPos);
205 auto oper = arg[operPos];
206 auto rhs = arg.substr(operPos + 1);
207
208 return std::make_tuple(lhs, oper, rhs);
209 }
210
unruleCompletion(Completion & complete)211 void RuleManager::unruleCompletion(Completion& complete) {
212 complete.full({ "-F", "--all" });
213 for (auto& it : rules_) {
214 complete.full(it->label);
215 }
216 }
217
addRuleCompletion(Completion & complete)218 void RuleManager::addRuleCompletion(Completion& complete) {
219 complete.full({ "not", "!", "prepend", "once", "printlabel" });
220 complete.partial("label=");
221 for (auto&& matcher : Condition::matchers) {
222 auto condName = matcher.first;
223 complete.partial(condName + "=");
224 complete.partial(condName + "~");
225 }
226 for (auto&& applier : Consequence::appliers) {
227 complete.partial(applier.first + "=");
228 }
229 }
230
231
232 //! Evaluate rules against a given client
evaluateRules(Client * client,Output output,ClientChanges changes)233 ClientChanges RuleManager::evaluateRules(Client* client, Output output, ClientChanges changes) {
234 // go through all rules and remove those that expired.
235 // Here, we use erase + remove_if because it uses a Forward Iterator
236 // and so it is ensured that the rules are evaluated in the correct order.
237 auto forEachRule = [&](unique_ptr<Rule>& rule) {
238 rule->evaluate(client, changes, output);
239 return rule->expired();
240 };
241 rules_.erase(std::remove_if(rules_.begin(), rules_.end(), forEachRule),
242 rules_.end());
243 return changes;
244 }
245
246
247