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