1 #include <wayfire/config/types.hpp>
2 #include <vector>
3 #include <map>
4 #include <cmath>
5 #include <algorithm>
6 
7 #include <libevdev/libevdev.h>
8 #include <sstream>
9 
10 /* --------------------------- Primitive types ------------------------------ */
11 template<>
from_string(const std::string & value)12 stdx::optional<bool> wf::option_type::from_string(const std::string& value)
13 {
14     std::string lowercase = value;
15     for (auto& c : lowercase)
16     {
17         c = std::tolower(c);
18     }
19 
20     if ((lowercase == "true") || (lowercase == "1"))
21     {
22         return true;
23     }
24 
25     if ((lowercase == "false") || (lowercase == "0"))
26     {
27         return false;
28     }
29 
30     return {};
31 }
32 
33 template<>
from_string(const std::string & value)34 stdx::optional<int> wf::option_type::from_string(const std::string& value)
35 {
36     std::istringstream in{value};
37     int result;
38     in >> result;
39 
40     if (value != std::to_string(result))
41     {
42         return {};
43     }
44 
45     return result;
46 }
47 
48 /** Attempt to parse a string as an double value */
49 template<>
from_string(const std::string & value)50 stdx::optional<double> wf::option_type::from_string(const std::string& value)
51 {
52     auto old = std::locale::global(std::locale::classic());
53     std::istringstream in{value};
54     double result;
55     in >> result;
56     std::locale::global(old);
57 
58     if (!in.eof() || in.fail() || value.empty())
59     {
60         /* XXX: is the check above enough??? Overflow? Underflow? */
61         return {};
62     }
63 
64     return result;
65 }
66 
67 template<>
from_string(const std::string & value)68 stdx::optional<std::string> wf::option_type::from_string(const std::string& value)
69 {
70     return value;
71 }
72 
73 template<>
to_string(const bool & value)74 std::string wf::option_type::to_string(
75     const bool& value)
76 {
77     return value ? "true" : "false";
78 }
79 
80 template<>
to_string(const int & value)81 std::string wf::option_type::to_string(
82     const int& value)
83 {
84     return std::to_string(value);
85 }
86 
87 template<>
to_string(const double & value)88 std::string wf::option_type::to_string(
89     const double& value)
90 {
91     return std::to_string(value);
92 }
93 
94 template<>
to_string(const std::string & value)95 std::string wf::option_type::to_string(
96     const std::string& value)
97 {
98     return value;
99 }
100 
101 /* ----------------------------- wf::color_t -------------------------------- */
color_t()102 wf::color_t::color_t() :
103     color_t(0.0, 0.0, 0.0, 0.0)
104 {}
105 
color_t(double r,double g,double b,double a)106 wf::color_t::color_t(double r, double g, double b, double a)
107 {
108     this->r = r;
109     this->g = g;
110     this->b = b;
111     this->a = a;
112 }
113 
color_t(const glm::vec4 & value)114 wf::color_t::color_t(const glm::vec4& value) :
115     color_t(value.r, value.g, value.b, value.a)
116 {}
117 
hex_to_double(std::string value)118 static double hex_to_double(std::string value)
119 {
120     char *dummy;
121     return std::strtol(value.c_str(), &dummy, 16);
122 }
123 
try_parse_rgba(const std::string & value)124 static stdx::optional<wf::color_t> try_parse_rgba(const std::string& value)
125 {
126     wf::color_t parsed = {0, 0, 0, 0};
127     std::stringstream ss(value);
128 
129     auto old = std::locale::global(std::locale::classic());
130     bool valid_color =
131         (bool)(ss >> parsed.r >> parsed.g >> parsed.b >> parsed.a);
132 
133     /* Check nothing else after that */
134     std::string dummy;
135     valid_color &= !(bool)(ss >> dummy);
136 
137     std::locale::global(old);
138 
139     return valid_color ? parsed : stdx::optional<wf::color_t>{};
140 }
141 
142 #include <iostream>
143 static const std::string hex_digits = "0123456789ABCDEF";
144 template<>
from_string(const std::string & param_value)145 stdx::optional<wf::color_t> wf::option_type::from_string(
146     const std::string& param_value)
147 {
148     auto value = param_value;
149     for (auto& ch : value)
150     {
151         ch = std::toupper(ch);
152     }
153 
154     auto as_rgba = try_parse_rgba(value);
155     if (as_rgba)
156     {
157         return as_rgba;
158     }
159 
160     /* Either #RGBA or #RRGGBBAA */
161     if ((value.size() != 5) && (value.size() != 9))
162     {
163         return {};
164     }
165 
166     if (value[0] != '#')
167     {
168         return {};
169     }
170 
171     if (value.find_first_not_of(hex_digits, 1) != std::string::npos)
172     {
173         return {};
174     }
175 
176     double r, g, b, a;
177 
178     /* #RRGGBBAA case */
179     if (value.size() == 9)
180     {
181         r = hex_to_double(value.substr(1, 2)) / 255.0;
182         g = hex_to_double(value.substr(3, 2)) / 255.0;
183         b = hex_to_double(value.substr(5, 2)) / 255.0;
184         a = hex_to_double(value.substr(7, 2)) / 255.0;
185     } else
186     {
187         assert(value.size() == 5);
188         r = hex_to_double(value.substr(1, 1)) / 15.0;
189         g = hex_to_double(value.substr(2, 1)) / 15.0;
190         b = hex_to_double(value.substr(3, 1)) / 15.0;
191         a = hex_to_double(value.substr(4, 1)) / 15.0;
192     }
193 
194     return wf::color_t{r, g, b, a};
195 }
196 
197 template<>
to_string(const color_t & value)198 std::string wf::option_type::to_string(const color_t& value)
199 {
200     const int max_byte = 255;
201     const int min_byte = 0;
202 
203     auto to_hex = [=] (double number_d)
204     {
205         int number = std::round(number_d);
206         /* Clamp */
207         number = std::min(number, max_byte);
208         number = std::max(number, min_byte);
209 
210         std::string result;
211         result += hex_digits[number / 16];
212         result += hex_digits[number % 16];
213         return result;
214     };
215 
216     return "#" + to_hex(value.r * max_byte) + to_hex(value.g * max_byte) +
217            to_hex(value.b * max_byte) + to_hex(value.a * max_byte);
218 }
219 
operator ==(const color_t & other) const220 bool wf::color_t::operator ==(const color_t& other) const
221 {
222     constexpr double epsilon = 1e-6;
223 
224     bool equal = true;
225     equal &= std::abs(this->r - other.r) < epsilon;
226     equal &= std::abs(this->g - other.g) < epsilon;
227     equal &= std::abs(this->b - other.b) < epsilon;
228     equal &= std::abs(this->a - other.a) < epsilon;
229 
230     return equal;
231 }
232 
233 /* ------------------------- wf::keybinding_t ------------------------------- */
234 struct general_binding_t
235 {
236     bool enabled;
237     uint32_t mods;
238     uint32_t value;
239 };
240 
241 /**
242  * Split @value at non-empty tokens when encountering any of the characters
243  * in @at
244  */
split_at(std::string value,std::string at,bool allow_empty=false)245 static std::vector<std::string> split_at(std::string value, std::string at,
246     bool allow_empty = false)
247 {
248     /* Trick: add a delimiter at position 0 and at the end
249      * to avoid special casing */
250     value = at[0] + value + at[0];
251 
252     size_t current = 0;
253     std::vector<size_t> split_positions = {0};
254     while (current < value.size() - 1)
255     {
256         size_t next_split = value.find_first_of(at, current + 1);
257         split_positions.push_back(next_split);
258         current = next_split;
259     }
260 
261     assert(split_positions.size() >= 2);
262     std::vector<std::string> tokens;
263     for (size_t i = 1; i < split_positions.size(); i++)
264     {
265         if ((split_positions[i] == split_positions[i - 1] + 1) && !allow_empty)
266         {
267             continue; // skip empty tokens
268         }
269 
270         tokens.push_back(value.substr(split_positions[i - 1] + 1,
271             split_positions[i] - split_positions[i - 1] - 1));
272     }
273 
274     return tokens;
275 }
276 
277 static std::map<std::string, wf::keyboard_modifier_t> modifier_names =
278 {
279     {"ctrl", wf::KEYBOARD_MODIFIER_CTRL},
280     {"alt", wf::KEYBOARD_MODIFIER_ALT},
281     {"shift", wf::KEYBOARD_MODIFIER_SHIFT},
282     {"super", wf::KEYBOARD_MODIFIER_LOGO},
283 };
284 
binding_to_string(general_binding_t binding)285 static std::string binding_to_string(general_binding_t binding)
286 {
287     std::string result = "";
288     for (auto& pair : modifier_names)
289     {
290         if (binding.mods & pair.second)
291         {
292             result += "<" + pair.first + "> ";
293         }
294     }
295 
296     if (binding.value > 0)
297     {
298         auto evdev_name = libevdev_event_code_get_name(EV_KEY, binding.value);
299         result += evdev_name ?: "NULL";
300     }
301 
302     return result;
303 }
304 
305 /**
306  * @return A string which consists of the characters of value, but without
307  *  those contained in filter.
308  */
filter_out(std::string value,std::string filter)309 static std::string filter_out(std::string value, std::string filter)
310 {
311     std::string result;
312     for (auto& c : value)
313     {
314         if (filter.find(c) != std::string::npos)
315         {
316             continue;
317         }
318 
319         result += c;
320     }
321 
322     return result;
323 }
324 
325 static const std::string whitespace_chars = " \t\n\r\v\b";
326 
parse_binding(std::string binding_description)327 static stdx::optional<general_binding_t> parse_binding(
328     std::string binding_description)
329 {
330     /* Handle disabled bindings */
331     auto binding_descr_no_whitespace =
332         filter_out(binding_description, whitespace_chars);
333     if ((binding_descr_no_whitespace == "none") ||
334         (binding_descr_no_whitespace == "disabled"))
335     {
336         return general_binding_t{false, 0, 0};
337     }
338 
339     /* Strategy: split the binding at modifier begin/end markings and spaces,
340      * and then drop empty tokens. The tokens that are left should be either a
341      * binding or something recognizable by evdev. */
342     static const std::string delims = "<>" + whitespace_chars;
343     auto tokens = split_at(binding_description, delims);
344     if (tokens.empty())
345     {
346         return {};
347     }
348 
349     general_binding_t result = {true, 0, 0};
350     for (size_t i = 0; i < tokens.size() - 1; i++)
351     {
352         if (modifier_names.count(tokens[i]))
353         {
354             result.mods |= modifier_names[tokens[i]];
355         } else
356         {
357             return {}; // invalid modifier
358         }
359     }
360 
361     int code = libevdev_event_code_from_name(EV_KEY, tokens.back().c_str());
362     if (code == -1)
363     {
364         /* Last token might either be yet another modifier (in case of modifier
365          * bindings) or it may be KEY_*. If neither, we have invalid binding */
366         if (modifier_names.count(tokens.back()))
367         {
368             result.mods |= modifier_names[tokens.back()];
369             code = 0;
370         } else
371         {
372             return {}; // not found
373         }
374     }
375 
376     result.value = code;
377 
378     /* Do one last check: if we remove whitespaces, and add a whitespace after
379      * each > character, then the resulting string should be almost equal to the
380      * minimal description, generated by binding_to_string().
381      *
382      * Since we have already checked all identifiers and they are valid
383      * modifiers, it is enough to check just that the lengths are matching.
384      * Note we can't directly compare because the order may be different. */
385     auto filtered_descr = filter_out(binding_description, whitespace_chars);
386     std::string filtered_with_spaces;
387     for (auto c : filtered_descr)
388     {
389         filtered_with_spaces += c;
390         if (c == '>')
391         {
392             filtered_with_spaces += " ";
393         }
394     }
395 
396     auto minimal_descr = binding_to_string(result);
397     if (filtered_with_spaces.length() == minimal_descr.length())
398     {
399         return result;
400     }
401 
402     return {};
403 }
404 
keybinding_t(uint32_t modifier,uint32_t keyval)405 wf::keybinding_t::keybinding_t(uint32_t modifier, uint32_t keyval)
406 {
407     this->mod    = modifier;
408     this->keyval = keyval;
409 }
410 
411 template<>
from_string(const std::string & description)412 stdx::optional<wf::keybinding_t> wf::option_type::from_string(
413     const std::string& description)
414 {
415     auto parsed_opt = parse_binding(description);
416     if (!parsed_opt)
417     {
418         return {};
419     }
420 
421     auto parsed = parsed_opt.value();
422 
423     /* Disallow buttons, because evdev treats buttons and keys the same */
424     if (parsed.enabled && (parsed.value > 0) &&
425         (description.find("KEY") == std::string::npos))
426     {
427         return {};
428     }
429 
430     if (parsed.enabled && (parsed.mods == 0) && (parsed.value == 0))
431     {
432         return {};
433     }
434 
435     return wf::keybinding_t{parsed.mods, parsed.value};
436 }
437 
438 template<>
to_string(const wf::keybinding_t & value)439 std::string wf::option_type::to_string(const wf::keybinding_t& value)
440 {
441     if ((value.get_modifiers() == 0) && (value.get_key() == 0))
442     {
443         return "none";
444     }
445 
446     return binding_to_string({true, value.get_modifiers(), value.get_key()});
447 }
448 
operator ==(const keybinding_t & other) const449 bool wf::keybinding_t::operator ==(const keybinding_t& other) const
450 {
451     return this->mod == other.mod && this->keyval == other.keyval;
452 }
453 
454 /** @return The modifiers of the keybinding */
get_modifiers() const455 uint32_t wf::keybinding_t::get_modifiers() const
456 {
457     return this->mod;
458 }
459 
460 /** @return The key of the keybinding */
get_key() const461 uint32_t wf::keybinding_t::get_key() const
462 {
463     return this->keyval;
464 }
465 
466 /* -------------------------- wf::buttonbinding_t --------------------------- */
buttonbinding_t(uint32_t modifier,uint32_t buttonval)467 wf::buttonbinding_t::buttonbinding_t(uint32_t modifier, uint32_t buttonval)
468 {
469     this->mod    = modifier;
470     this->button = buttonval;
471 }
472 
473 template<>
from_string(const std::string & description)474 stdx::optional<wf::buttonbinding_t> wf::option_type::from_string(
475     const std::string& description)
476 {
477     auto parsed_opt = parse_binding(description);
478     if (!parsed_opt)
479     {
480         return {};
481     }
482 
483     auto parsed = parsed_opt.value();
484     if (!parsed.enabled)
485     {
486         return wf::buttonbinding_t{0, 0};
487     }
488 
489     /* Disallow keys, because evdev treats buttons and keys the same */
490     if (description.find("BTN") == std::string::npos)
491     {
492         return {};
493     }
494 
495     if (parsed.value == 0)
496     {
497         return {};
498     }
499 
500     return wf::buttonbinding_t{parsed.mods, parsed.value};
501 }
502 
503 template<>
to_string(const wf::buttonbinding_t & value)504 std::string wf::option_type::to_string(
505     const wf::buttonbinding_t& value)
506 {
507     if ((value.get_modifiers() == 0) && (value.get_button() == 0))
508     {
509         return "none";
510     }
511 
512     return binding_to_string({true, value.get_modifiers(), value.get_button()});
513 }
514 
operator ==(const buttonbinding_t & other) const515 bool wf::buttonbinding_t::operator ==(const buttonbinding_t& other) const
516 {
517     return this->mod == other.mod && this->button == other.button;
518 }
519 
get_modifiers() const520 uint32_t wf::buttonbinding_t::get_modifiers() const
521 {
522     return this->mod;
523 }
524 
get_button() const525 uint32_t wf::buttonbinding_t::get_button() const
526 {
527     return this->button;
528 }
529 
touchgesture_t(touch_gesture_type_t type,uint32_t direction,int finger_count)530 wf::touchgesture_t::touchgesture_t(touch_gesture_type_t type, uint32_t direction,
531     int finger_count)
532 {
533     this->type = type;
534     this->direction    = direction;
535     this->finger_count = finger_count;
536 }
537 
538 static const std::map<std::string, wf::touch_gesture_direction_t>
539 touch_gesture_direction_string_map =
540 {
541     {"up", wf::GESTURE_DIRECTION_UP},
542     {"down", wf::GESTURE_DIRECTION_DOWN},
543     {"left", wf::GESTURE_DIRECTION_LEFT},
544     {"right", wf::GESTURE_DIRECTION_RIGHT}
545 };
546 
parse_single_direction(const std::string & direction)547 static wf::touch_gesture_direction_t parse_single_direction(
548     const std::string& direction)
549 {
550     if (touch_gesture_direction_string_map.count(direction))
551     {
552         return touch_gesture_direction_string_map.at(direction);
553     }
554 
555     throw std::domain_error("invalid swipe direction");
556 }
557 
parse_direction(const std::string & direction)558 uint32_t parse_direction(const std::string& direction)
559 {
560     size_t hyphen = direction.find("-");
561     if (hyphen == std::string::npos)
562     {
563         return parse_single_direction(direction);
564     } else
565     {
566         /* we support up to 2 directions, because >= 3 will be invalid anyway */
567         auto first  = direction.substr(0, hyphen);
568         auto second = direction.substr(hyphen + 1);
569 
570         uint32_t mask =
571             parse_single_direction(first) | parse_single_direction(second);
572 
573         const uint32_t both_horiz =
574             wf::GESTURE_DIRECTION_LEFT | wf::GESTURE_DIRECTION_RIGHT;
575         const uint32_t both_vert =
576             wf::GESTURE_DIRECTION_UP | wf::GESTURE_DIRECTION_DOWN;
577 
578         if (((mask & both_horiz) == both_horiz) ||
579             ((mask & both_vert) == both_vert))
580         {
581             throw std::domain_error("Cannot have two opposing directions in the"
582                                     "same gesture");
583         }
584 
585         return mask;
586     }
587 }
588 
parse_gesture(const std::string & value)589 wf::touchgesture_t parse_gesture(const std::string& value)
590 {
591     if (value.empty())
592     {
593         return {wf::GESTURE_TYPE_NONE, 0, 0};
594     }
595 
596     auto tokens = split_at(value, " \t\v\b\n\r");
597     assert(!tokens.empty());
598 
599     if (tokens.size() != 3)
600     {
601         return {wf::GESTURE_TYPE_NONE, 0, 0};
602     }
603 
604     try {
605         wf::touch_gesture_type_t type;
606         uint32_t direction = 0;
607         int32_t finger_count;
608 
609         if (tokens[0] == "pinch")
610         {
611             type = wf::GESTURE_TYPE_PINCH;
612             if (tokens[1] == "in")
613             {
614                 direction = wf::GESTURE_DIRECTION_IN;
615             } else if (tokens[1] == "out")
616             {
617                 direction = wf::GESTURE_DIRECTION_OUT;
618             } else
619             {
620                 throw std::domain_error("Invalid pinch direction: " + tokens[1]);
621             }
622         } else if (tokens[0] == "swipe")
623         {
624             type = wf::GESTURE_TYPE_SWIPE;
625             direction = parse_direction(tokens[1]);
626         } else if (tokens[0] == "edge-swipe")
627         {
628             type = wf::GESTURE_TYPE_EDGE_SWIPE;
629             direction = parse_direction(tokens[1]);
630         } else
631         {
632             throw std::domain_error("Invalid gesture type:" + tokens[0]);
633         }
634 
635         // TODO: instead of atoi, check properly
636         finger_count = std::atoi(tokens[2].c_str());
637         return wf::touchgesture_t{type, direction, finger_count};
638     } catch (std::exception& e)
639     {
640         // XXX: show error?
641         // ignore it, will return GESTURE_NONE
642     }
643 
644     return wf::touchgesture_t{wf::GESTURE_TYPE_NONE, 0, 0};
645 }
646 
647 template<>
from_string(const std::string & description)648 stdx::optional<wf::touchgesture_t> wf::option_type::from_string(
649     const std::string& description)
650 {
651     auto as_binding = parse_binding(description);
652     if (as_binding && !as_binding.value().enabled)
653     {
654         return touchgesture_t{GESTURE_TYPE_NONE, 0, 0};
655     }
656 
657     auto gesture = parse_gesture(description);
658     if (gesture.get_type() == GESTURE_TYPE_NONE)
659     {
660         return {};
661     }
662 
663     return gesture;
664 }
665 
direction_to_string(uint32_t direction)666 static std::string direction_to_string(uint32_t direction)
667 {
668     std::string result = "";
669     for (auto& pair : touch_gesture_direction_string_map)
670     {
671         if (direction & pair.second)
672         {
673             result += pair.first + "-";
674         }
675     }
676 
677     if (result.size() > 0)
678     {
679         /* Remove trailing - */
680         result.pop_back();
681     }
682 
683     return result;
684 }
685 
686 template<>
to_string(const touchgesture_t & value)687 std::string wf::option_type::to_string(const touchgesture_t& value)
688 {
689     std::string result;
690     switch (value.get_type())
691     {
692       case GESTURE_TYPE_NONE:
693         return "";
694 
695       case GESTURE_TYPE_EDGE_SWIPE:
696         result += "edge-";
697 
698       // fallthrough
699       case GESTURE_TYPE_SWIPE:
700         result += "swipe ";
701         result += direction_to_string(value.get_direction()) + " ";
702         break;
703 
704       case GESTURE_TYPE_PINCH:
705         result += "pinch ";
706 
707         if (value.get_direction() == GESTURE_DIRECTION_IN)
708         {
709             result += "in ";
710         }
711 
712         if (value.get_direction() == GESTURE_DIRECTION_OUT)
713         {
714             result += "out ";
715         }
716 
717         break;
718     }
719 
720     result += std::to_string(value.get_finger_count());
721     return result;
722 }
723 
get_type() const724 wf::touch_gesture_type_t wf::touchgesture_t::get_type() const
725 {
726     return this->type;
727 }
728 
get_finger_count() const729 int wf::touchgesture_t::get_finger_count() const
730 {
731     return this->finger_count;
732 }
733 
get_direction() const734 uint32_t wf::touchgesture_t::get_direction() const
735 {
736     return this->direction;
737 }
738 
operator ==(const touchgesture_t & other) const739 bool wf::touchgesture_t::operator ==(const touchgesture_t& other) const
740 {
741     return type == other.type && finger_count == other.finger_count &&
742            (direction == 0 || other.direction == 0 || direction == other.direction);
743 }
744 
745 /* --------------------------- activatorbinding_t --------------------------- */
746 struct wf::activatorbinding_t::impl
747 {
748     std::vector<keybinding_t> keys;
749     std::vector<buttonbinding_t> buttons;
750     std::vector<touchgesture_t> gestures;
751     std::vector<hotspot_binding_t> hotspots;
752 };
753 
activatorbinding_t()754 wf::activatorbinding_t::activatorbinding_t()
755 {
756     this->priv = std::make_unique<impl>();
757 }
758 
759 wf::activatorbinding_t::~activatorbinding_t() = default;
760 
activatorbinding_t(const activatorbinding_t & other)761 wf::activatorbinding_t::activatorbinding_t(const activatorbinding_t& other)
762 {
763     this->priv = std::make_unique<impl>(*other.priv);
764 }
765 
operator =(const activatorbinding_t & other)766 wf::activatorbinding_t& wf::activatorbinding_t::operator =(
767     const activatorbinding_t& other)
768 {
769     if (&other != this)
770     {
771         this->priv = std::make_unique<impl>(*other.priv);
772     }
773 
774     return *this;
775 }
776 
777 template<class Type>
try_add_binding(std::vector<Type> & to,const std::string & value)778 bool try_add_binding(
779     std::vector<Type>& to, const std::string& value)
780 {
781     auto binding = wf::option_type::from_string<Type>(value);
782     if (binding)
783     {
784         to.push_back(binding.value());
785         return true;
786     }
787 
788     return false;
789 }
790 
791 template<>
from_string(const std::string & string)792 stdx::optional<wf::activatorbinding_t> wf::option_type::from_string(
793     const std::string& string)
794 {
795     activatorbinding_t binding;
796 
797     if (filter_out(string, whitespace_chars) == "")
798     {
799         return binding; // empty binding
800     }
801 
802     auto tokens = split_at(string, "|", true);
803     for (auto& token : tokens)
804     {
805         bool is_valid_binding =
806             try_add_binding(binding.priv->keys, token) ||
807             try_add_binding(binding.priv->buttons, token) ||
808             try_add_binding(binding.priv->gestures, token) ||
809             try_add_binding(binding.priv->hotspots, token);
810 
811         if (!is_valid_binding)
812         {
813             return {};
814         }
815     }
816 
817     return binding;
818 }
819 
820 template<class Type>
concatenate_bindings(const std::vector<Type> & bindings)821 static std::string concatenate_bindings(const std::vector<Type>& bindings)
822 {
823     std::string repr = "";
824     for (auto& b : bindings)
825     {
826         repr += wf::option_type::to_string<Type>(b);
827         repr += " | ";
828     }
829 
830     return repr;
831 }
832 
833 template<>
to_string(const activatorbinding_t & value)834 std::string wf::option_type::to_string(
835     const activatorbinding_t& value)
836 {
837     std::string repr =
838         concatenate_bindings(value.priv->keys) +
839         concatenate_bindings(value.priv->buttons) +
840         concatenate_bindings(value.priv->gestures) +
841         concatenate_bindings(value.priv->hotspots);
842 
843     /* Remove trailing " | " */
844     if (repr.size() >= 3)
845     {
846         repr.erase(repr.size() - 3);
847     }
848 
849     return repr;
850 }
851 
852 template<class Type>
find_in_container(const std::vector<Type> & haystack,Type needle)853 bool find_in_container(const std::vector<Type>& haystack,
854     Type needle)
855 {
856     return std::find(haystack.begin(), haystack.end(), needle) != haystack.end();
857 }
858 
has_match(const keybinding_t & key) const859 bool wf::activatorbinding_t::has_match(const keybinding_t& key) const
860 {
861     return find_in_container(priv->keys, key);
862 }
863 
has_match(const buttonbinding_t & button) const864 bool wf::activatorbinding_t::has_match(const buttonbinding_t& button) const
865 {
866     return find_in_container(priv->buttons, button);
867 }
868 
has_match(const touchgesture_t & gesture) const869 bool wf::activatorbinding_t::has_match(const touchgesture_t& gesture) const
870 {
871     return find_in_container(priv->gestures, gesture);
872 }
873 
operator ==(const activatorbinding_t & other) const874 bool wf::activatorbinding_t::operator ==(const activatorbinding_t& other) const
875 {
876     return priv->keys == other.priv->keys &&
877            priv->buttons == other.priv->buttons &&
878            priv->gestures == other.priv->gestures &&
879            priv->hotspots == other.priv->hotspots;
880 }
881 
get_hotspots() const882 const std::vector<wf::hotspot_binding_t>& wf::activatorbinding_t::get_hotspots()
883 const
884 {
885     return priv->hotspots;
886 }
887 
hotspot_binding_t(uint32_t edges,int32_t along_edge,int32_t away_from_edge,int32_t timeout)888 wf::hotspot_binding_t::hotspot_binding_t(uint32_t edges,
889     int32_t along_edge, int32_t away_from_edge, int32_t timeout)
890 {
891     this->edges   = edges;
892     this->along   = along_edge;
893     this->away    = away_from_edge;
894     this->timeout = timeout;
895 }
896 
operator ==(const hotspot_binding_t & other) const897 bool wf::hotspot_binding_t::operator ==(const hotspot_binding_t& other) const
898 {
899     return edges == other.edges && along == other.along && away == other.away &&
900            timeout == other.timeout;
901 }
902 
get_timeout() const903 int32_t wf::hotspot_binding_t::get_timeout() const
904 {
905     return timeout;
906 }
907 
get_size_away_from_edge() const908 int32_t wf::hotspot_binding_t::get_size_away_from_edge() const
909 {
910     return away;
911 }
912 
get_size_along_edge() const913 int32_t wf::hotspot_binding_t::get_size_along_edge() const
914 {
915     return along;
916 }
917 
get_edges() const918 uint32_t wf::hotspot_binding_t::get_edges() const
919 {
920     return edges;
921 }
922 
923 static std::map<std::string, wf::output_edge_t> hotspot_edges =
924 {
925     {"top", wf::OUTPUT_EDGE_TOP},
926     {"bottom", wf::OUTPUT_EDGE_BOTTOM},
927     {"left", wf::OUTPUT_EDGE_LEFT},
928     {"right", wf::OUTPUT_EDGE_RIGHT},
929 };
930 
931 template<>
from_string(const std::string & description)932 stdx::optional<wf::hotspot_binding_t> wf::option_type::from_string(
933     const std::string& description)
934 {
935     std::istringstream stream{description};
936     std::string token;
937     stream >> token; // "hotspot"
938     if (token != "hotspot")
939     {
940         return {};
941     }
942 
943     stream >> token; // direction
944 
945     uint32_t edges = 0;
946 
947     size_t hyphen = token.find("-");
948     if (hyphen == token.npos)
949     {
950         if (hotspot_edges.count(token) == 0)
951         {
952             return {};
953         }
954 
955         edges = hotspot_edges[token];
956     } else
957     {
958         std::string first_direction  = token.substr(0, hyphen);
959         std::string second_direction = token.substr(hyphen + 1);
960 
961         if ((hotspot_edges.count(first_direction) == 0) ||
962             (hotspot_edges.count(second_direction) == 0))
963         {
964             return {};
965         }
966 
967         edges = hotspot_edges[first_direction] | hotspot_edges[second_direction];
968     }
969 
970     stream >> token;
971     int32_t along, away;
972     if (2 != sscanf(token.c_str(), "%dx%d", &along, &away))
973     {
974         return {};
975     }
976 
977     stream >> token;
978     auto timeout = wf::option_type::from_string<int>(token);
979 
980     if (!timeout || stream >> token) // check for trailing characters
981     {
982         return {};
983     }
984 
985     return wf::hotspot_binding_t(edges, along, away, timeout.value());
986 }
987 
988 template<>
to_string(const wf::hotspot_binding_t & value)989 std::string wf::option_type::to_string(
990     const wf::hotspot_binding_t& value)
991 {
992     std::ostringstream out;
993     out << "hotspot ";
994 
995     uint32_t remaining_edges = value.get_edges();
996 
997     const auto& find_edge = [&] (bool need_hyphen)
998     {
999         for (const auto& edge : hotspot_edges)
1000         {
1001             if (remaining_edges & edge.second)
1002             {
1003                 remaining_edges &= ~edge.second;
1004                 if (need_hyphen)
1005                 {
1006                     out << "-";
1007                 }
1008 
1009                 out << edge.first;
1010                 break;
1011             }
1012         }
1013     };
1014 
1015     find_edge(false);
1016     find_edge(true);
1017 
1018     out << " " << value.get_size_along_edge() << "x" <<
1019         value.get_size_away_from_edge() <<
1020         " " << value.get_timeout();
1021     return out.str();
1022 }
1023 
1024 /* ------------------------- Output config types ---------------------------- */
mode_t(bool auto_on)1025 wf::output_config::mode_t::mode_t(bool auto_on)
1026 {
1027     this->type = auto_on ? MODE_AUTO : MODE_OFF;
1028 }
1029 
mode_t(int32_t width,int32_t height,int32_t refresh)1030 wf::output_config::mode_t::mode_t(int32_t width, int32_t height, int32_t refresh)
1031 {
1032     this->type    = MODE_RESOLUTION;
1033     this->width   = width;
1034     this->height  = height;
1035     this->refresh = refresh;
1036 }
1037 
1038 /**
1039  * Initialize a mirror mode.
1040  */
mode_t(const std::string & mirror_from)1041 wf::output_config::mode_t::mode_t(const std::string& mirror_from)
1042 {
1043     this->type = MODE_MIRROR;
1044     this->mirror_from = mirror_from;
1045 }
1046 
1047 /** @return The type of this mode. */
get_type() const1048 wf::output_config::mode_type_t wf::output_config::mode_t::get_type() const
1049 {
1050     return type;
1051 }
1052 
get_width() const1053 int32_t wf::output_config::mode_t::get_width() const
1054 {
1055     return width;
1056 }
1057 
get_height() const1058 int32_t wf::output_config::mode_t::get_height() const
1059 {
1060     return height;
1061 }
1062 
get_refresh() const1063 int32_t wf::output_config::mode_t::get_refresh() const
1064 {
1065     return refresh;
1066 }
1067 
get_mirror_from() const1068 std::string wf::output_config::mode_t::get_mirror_from() const
1069 {
1070     return mirror_from;
1071 }
1072 
operator ==(const mode_t & other) const1073 bool wf::output_config::mode_t::operator ==(const mode_t& other) const
1074 {
1075     if (type != other.get_type())
1076     {
1077         return false;
1078     }
1079 
1080     switch (type)
1081     {
1082       case MODE_RESOLUTION:
1083         return width == other.width && height == other.height &&
1084                refresh == other.refresh;
1085 
1086       case MODE_MIRROR:
1087         return mirror_from == other.mirror_from;
1088 
1089       case MODE_AUTO:
1090       case MODE_OFF:
1091         return true;
1092     }
1093 
1094     return false;
1095 }
1096 
1097 template<>
from_string(const std::string & string)1098 stdx::optional<wf::output_config::mode_t> wf::option_type::from_string(
1099     const std::string& string)
1100 {
1101     if (string == "off")
1102     {
1103         return wf::output_config::mode_t{false};
1104     }
1105 
1106     if ((string == "auto") || (string == "default"))
1107     {
1108         return wf::output_config::mode_t{true};
1109     }
1110 
1111     if (string.substr(0, 6) == "mirror")
1112     {
1113         std::stringstream ss(string);
1114         std::string from, dummy;
1115         ss >> from; // the mirror word
1116         if (!(ss >> from))
1117         {
1118             return {};
1119         }
1120 
1121         // trailing garbage
1122         if (ss >> dummy)
1123         {
1124             return {};
1125         }
1126 
1127         return wf::output_config::mode_t{from};
1128     }
1129 
1130     int w, h, rr = 0;
1131     char next;
1132 
1133     int read = std::sscanf(string.c_str(), "%d x %d @ %d%c", &w, &h, &rr, &next);
1134     if ((read < 2) || (read > 3))
1135     {
1136         return {};
1137     }
1138 
1139     if ((w < 0) || (h < 0) || (rr < 0))
1140     {
1141         return {};
1142     }
1143 
1144     // Ensure refresh rate in mHz
1145     if (rr < 1000)
1146     {
1147         rr *= 1000;
1148     }
1149 
1150     return wf::output_config::mode_t{w, h, rr};
1151 }
1152 
1153 /** Represent the activator binding as a string. */
1154 template<>
to_string(const output_config::mode_t & value)1155 std::string wf::option_type::to_string(const output_config::mode_t& value)
1156 {
1157     switch (value.get_type())
1158     {
1159       case output_config::MODE_AUTO:
1160         return "auto";
1161 
1162       case output_config::MODE_OFF:
1163         return "off";
1164 
1165       case output_config::MODE_RESOLUTION:
1166         if (value.get_refresh() <= 0)
1167         {
1168             return to_string(value.get_width()) + "x" +
1169                    to_string(value.get_height());
1170         } else
1171         {
1172             return to_string(value.get_width()) + "x" +
1173                    to_string(value.get_height()) + "@" + to_string(
1174                 value.get_refresh());
1175         }
1176 
1177       case output_config::MODE_MIRROR:
1178         return "mirror " + value.get_mirror_from();
1179     }
1180 
1181     return {};
1182 }
1183 
position_t()1184 wf::output_config::position_t::position_t()
1185 {
1186     this->automatic = true;
1187 }
1188 
position_t(int32_t x,int32_t y)1189 wf::output_config::position_t::position_t(int32_t x, int32_t y)
1190 {
1191     this->automatic = false;
1192     this->x = x;
1193     this->y = y;
1194 }
1195 
get_x() const1196 int32_t wf::output_config::position_t::get_x() const
1197 {
1198     return x;
1199 }
1200 
get_y() const1201 int32_t wf::output_config::position_t::get_y() const
1202 {
1203     return y;
1204 }
1205 
is_automatic_position() const1206 bool wf::output_config::position_t::is_automatic_position() const
1207 {
1208     return automatic;
1209 }
1210 
operator ==(const position_t & other) const1211 bool wf::output_config::position_t::operator ==(const position_t& other) const
1212 {
1213     if (is_automatic_position() != other.is_automatic_position())
1214     {
1215         return false;
1216     }
1217 
1218     if (is_automatic_position())
1219     {
1220         return true;
1221     }
1222 
1223     return x == other.x && y == other.y;
1224 }
1225 
1226 template<>
from_string(const std::string & string)1227 stdx::optional<wf::output_config::position_t> wf::option_type::from_string(
1228     const std::string& string)
1229 {
1230     if ((string == "auto") || (string == "default"))
1231     {
1232         return wf::output_config::position_t();
1233     }
1234 
1235     int x, y;
1236     char r;
1237     if (sscanf(string.c_str(), "%d , %d%c", &x, &y, &r) != 2)
1238     {
1239         return {};
1240     }
1241 
1242     return wf::output_config::position_t(x, y);
1243 }
1244 
1245 /** Represent the activator binding as a string. */
1246 template<>
to_string(const output_config::position_t & value)1247 std::string wf::option_type::to_string(const output_config::position_t& value)
1248 {
1249     if (value.is_automatic_position())
1250     {
1251         return "auto";
1252     }
1253 
1254     return to_string(value.get_x()) + ", " + to_string(value.get_y());
1255 }
1256