1 #include "config.h"
2 
3 #include "upath.h"
4 #include "ykey.h"
5 #include "yconfig.h"
6 #include "ypaint.h"
7 #include "yprefs.h"
8 #include "sysdep.h"
9 #include "binascii.h"
10 #include "yapp.h"
11 #include "intl.h"
12 #include "ascii.h"
13 #include "argument.h"
14 
getArgument(Argument * dest,char * source,bool comma)15 char *YConfig::getArgument(Argument *dest, char *source, bool comma) {
16     char *p = source;
17     while (ASCII::isSpaceOrTab(*p))
18         p++;
19 
20     dest->reset();
21     for (; *p; p = *p ? 1 + p : p) {
22         if (*p == '\'') {
23             while (*++p && *p != '\'') {
24                 *dest += *p;
25             }
26         }
27         else if (*p == '"') {
28             while (*++p && *p != '"') {
29                 if (*p == '\\' && p[1] == '"')
30                     ++p;
31                 *dest += *p;
32             }
33         }
34         else if (*p == '\\' && p[1] && p[1] != '\n' && p[1] != '\r') {
35             // add any char protected by backslash and move forward
36             // exception: line ending (unwanted, may do bad things).
37             // OTOH, if the two last checks are disable, it will cause a
38             // side effect (multiline argument parsing with \n after \).
39             ++p;
40             *dest += *p;
41         }
42         else if (ASCII::isWhiteSpace(*p) || (*p == ',' && comma))
43             break;
44         else {
45             *dest += *p;
46         }
47     }
48     return p;
49 }
50 
51 // FIXME: P1 - parse keys later, not when loading
parseKey(const char * arg,KeySym * key,unsigned int * mod)52 bool YConfig::parseKey(const char *arg, KeySym *key, unsigned int *mod) {
53     const char *orig_arg = arg;
54     static const struct {
55         const char key[7];
56         unsigned char flag;
57     } mods[] = {
58         { "Alt+",   kfAlt   },
59         { "AltGr+", kfAltGr },
60         { "Ctrl+",  kfCtrl  },
61         { "Hyper+", kfHyper },
62         { "Meta+",  kfMeta  },
63         { "Shift+", kfShift },
64         { "Super+", kfSuper },
65     };
66     *key = NoSymbol;
67     *mod = 0;
68     for (int k = 0; k < (int) ACOUNT(mods); ++k) {
69         for (int i = 0; arg[i] == mods[k].key[i]; ++i) {
70             if (arg[i] == '+') {
71                 *mod |= mods[k].flag;
72                 arg += i + 1;
73                 k = -1;
74                 break;
75             }
76         }
77     }
78 
79     if (modSuperIsCtrlAlt && (*mod & kfSuper)) {
80         *mod &= ~kfSuper;
81         *mod |= kfAlt | kfCtrl;
82     }
83 
84     if (*arg == 0)
85         *key = NoSymbol;
86     else if (strcmp(arg, "Esc") == 0)
87         *key = XK_Escape;
88     else if (strcmp(arg, "Enter") == 0)
89         *key = XK_Return;
90     else if (strcmp(arg, "Space") == 0)
91         *key = ' ';
92     else if (strcmp(arg, "BackSp") == 0)
93         *key = XK_BackSpace;
94     else if (strcmp(arg, "Del") == 0)
95         *key = XK_Delete;
96     else if (ASCII::isUpper(arg[0]) && arg[1] == 0) {
97         char s[2];
98         s[0] = ASCII::toLower(arg[0]);
99         s[1] = 0;
100         *key = XStringToKeysym(s);
101     } else {
102         *key = XStringToKeysym(arg);
103     }
104 
105     if (*key == NoSymbol && !strncmp(arg, "Pointer_Button", 14)) {
106         int button = 0;
107         if (sscanf(arg + 14, "%d", &button) == 1 && button > 0) {
108             *key = button + XK_Pointer_Button1 - 1;
109         }
110     }
111 
112     if (*key == NoSymbol && *arg) {
113         msg(_("Unknown key name %s in %s"), arg, orig_arg);
114         return false;
115     }
116     return true;
117 }
118 
setOption(char * arg,bool append,cfoption * opt)119 void YConfig::setOption(char* arg, bool append, cfoption* opt) {
120     MSG(("SET %s := %s ;", opt->name, arg));
121 
122     switch (opt->type) {
123         case cfoption::CF_BOOL:
124             if (opt->v.b.bool_value) {
125                 if ((arg[0] == '1' || arg[0] == '0') && arg[1] == 0) {
126                     *(opt->v.b.bool_value) = (arg[0] == '1');
127                 } else {
128                     msg(_("Bad argument: %s for %s [%d,%d]"), arg, opt->name, 0, 1);
129                 }
130             }
131             break;
132         case cfoption::CF_INT:
133             if (opt->v.i.int_value) {
134                 int const v(atoi(arg));
135 
136                 if (v >= opt->v.i.min && v <= opt->v.i.max)
137                     *(opt->v.i.int_value) = v;
138                 else {
139                     msg(_("Bad argument: %s for %s [%d,%d]"), arg, opt->name,
140                             opt->v.i.min, opt->v.i.max);
141                 }
142             }
143             break;
144         case cfoption::CF_UINT:
145             if (opt->v.u.uint_value) {
146                 unsigned const v(strtoul(arg, nullptr, 0));
147 
148                 if (v >= opt->v.u.min && v <= opt->v.u.max)
149                     *(opt->v.u.uint_value) = v;
150                 else {
151                     msg(_("Bad argument: %s for %s [%d,%d]"), arg, opt->name,
152                             int(opt->v.u.min), int(opt->v.u.max));
153                 }
154             }
155             break;
156         case cfoption::CF_STR:
157             if (opt->v.s.string_value) {
158                 if (!opt->v.s.initial)
159                     delete[] const_cast<char *>(*opt->v.s.string_value);
160                 *opt->v.s.string_value = newstr(arg);
161                 opt->v.s.initial = false;
162             }
163             break;
164         case cfoption::CF_KEY:
165             if (opt->v.k.key_value) {
166                 WMKey *wk = opt->v.k.key_value;
167 
168                 if (YConfig::parseKey(arg, &wk->key, &wk->mod)) {
169                     if (!wk->initial)
170                         delete[] const_cast<char *>(wk->name);
171                     wk->name = newstr(arg);
172                     wk->initial = false;
173                 }
174             }
175             break;
176         case cfoption::CF_FUNC:
177             opt->fun()(opt->name, arg, append);
178         case cfoption::CF_NONE:
179             break;
180     }
181 }
182 
findOption(char * name,size_t length)183 cfoption* YConfig::findOption(char* name, size_t length) {
184     if (length && *name) {
185         for (cfoption* opt = options; opt->type; ++opt) {
186             if (opt->size == 1+length &&
187                 *opt->name == *name &&
188                 memcmp(name, opt->name, length) == 0)
189             {
190                 return opt;
191             }
192         }
193     }
194     return nullptr;
195 }
196 
skipLine(char * p)197 char* YConfig::skipLine(char* p) {
198     if (p) {
199         // ignore this line.
200         p = strchr(p, '\n');
201         while (p && (p[-1] == '\r' ? p[-2] == '\\' : p[-1] == '\\')) {
202             p = strchr(p + 1, '\n');
203         }
204     }
205     return p;
206 }
207 
208 // Parse one option name at 'str' and its argument(s).
209 // The name is a string without spaces up to '='.
210 // Option is a quoted string or characters up to next space.
parseOption(char * str)211 char* YConfig::parseOption(char* str) {
212     size_t len = strcspn(str, "= \t\n\r\f\v\\");
213     if (len == 0 || len >= 40) {
214         return skipLine(str + len);
215     }
216 
217     char* p = str + len + strspn(str + len, " \t");
218     if (*p != '=') {
219         return skipLine(p);
220     }
221 
222     cfoption* found = findOption(str, len);
223     if (found == nullptr) {
224         return skipLine(p);
225     }
226 
227     Argument argument;
228     for (bool append = false; append == (*p == ',') && *++p; append = true) {
229         if (append) {
230             while (ASCII::isWhiteSpace(*p) || ASCII::isEscapedLineEnding(p))
231                 ++p;
232         }
233 
234         p = YConfig::getArgument(&argument, p, true);
235         if (p == nullptr)
236             break;
237 
238         setOption(argument, append, found);
239 
240         while (ASCII::isSpaceOrTab(*p))
241             p++;
242     }
243 
244     return p;
245 }
246 
parseConfiguration(char * data)247 void YConfig::parseConfiguration(char *data) {
248     for (char *p = data; p && *p; ) {
249         if (ASCII::isWhiteSpace(*p)) {
250             ++p;
251         }
252         else if (*p == '\\') {
253             ++p;
254             if (*p == '\r' && p[1] == '\n')
255                 ++p;
256             if (*p == '\n')
257                 ++p;
258         }
259         else if (*p == '#') {
260             p = skipLine(p);
261         }
262         else {
263             p = parseOption(p);
264         }
265     }
266 }
267 
loadConfigFile(cfoption * opts,upath fileName,cfoption * more,cfoption * xtra)268 bool YConfig::loadConfigFile(cfoption* opts, upath fileName,
269                              cfoption* more, cfoption* xtra)
270 {
271     YTraceConfig trace(fileName.string());
272     auto buf(fileName.loadText());
273     if (buf) {
274         YConfig(opts).parseConfiguration(buf);
275         if (more) {
276             YConfig(more).parseConfiguration(buf);
277         }
278         if (xtra) {
279             YConfig(xtra).parseConfiguration(buf);
280         }
281     }
282     return buf;
283 }
284 
freeConfig(cfoption * options)285 void YConfig::freeConfig(cfoption *options) {
286     for (cfoption* o = options; o->type != cfoption::CF_NONE; ++o) {
287         if (o->type == cfoption::CF_STR &&
288             !o->v.s.initial &&
289             *o->v.s.string_value)
290         {
291             delete[] const_cast<char *>(*o->v.s.string_value);
292             *o->v.s.string_value = nullptr;
293         }
294     }
295 }
296 
findLoadConfigFile(cfoption * options,const char * name)297 bool YConfig::findLoadConfigFile(cfoption* options, const char* name) {
298     upath conf = YApplication::locateConfigFile(name);
299     return conf.nonempty() && YConfig::loadConfigFile(options, conf);
300 }
301 
findLoadThemeFile(cfoption * options)302 bool YConfig::findLoadThemeFile(cfoption* options) {
303     upath init(themeName);
304     upath name(init.isAbsolute() ? init : upath("themes") + init);
305     upath conf = YApplication::locateConfigFile(name);
306     if (conf.isEmpty() || false == conf.fileExists()) {
307         if (name.getExtension() != ".theme")
308             conf = YApplication::locateConfigFile(name + "default.theme");
309     }
310     return conf.nonempty() && YConfig::loadConfigFile(options, conf);
311 }
312 
cfoptionSize()313 size_t YConfig::cfoptionSize() {
314     return sizeof(cfoption);
315 }
316 
load(const char * file)317 YConfig& YConfig::load(const char* file) {
318     success = findLoadConfigFile(options, file);
319     return *this;
320 }
321 
loadTheme()322 YConfig& YConfig::loadTheme() {
323     success = findLoadThemeFile(options);
324     return *this;
325 }
326 
loadOverride()327 YConfig& YConfig::loadOverride() {
328     return load("prefoverride");
329 }
330 
operator ==(const cfoption & r) const331 bool cfoption::operator==(const cfoption& r) const {
332     if (type == r.type) {
333         switch (type) {
334             case CF_NONE:
335                 return true;
336             case CF_BOOL:
337                 return boolval() == r.boolval();
338             case CF_INT:
339                 return intval() == r.intval();
340             case CF_UINT:
341                 return uintval() == r.uintval();
342             case CF_STR:
343                 return str() == r.str() ||
344                     (isEmpty(str()) ? isEmpty(r.str()) :
345                     (nonempty(r.str()) && 0 == strcmp(str(), r.str())));
346             case CF_KEY:
347                 return key()->operator==(*r.key())
348                     && 0 == strcmp(key()->name, r.key()->name);
349             case CF_FUNC:
350                 return false;
351         }
352     }
353     return false;
354 }
355 
356 // vim: set sw=4 ts=4 et:
357