6
6
* GPLv3 or later.
7
7
*/
8
8
9
+ #include < algorithm>
10
+ #include < cstdlib>
9
11
#include < fstream>
10
12
#include < iostream>
11
13
#include < memory>
12
14
#include < regex>
13
15
#include < stdexcept>
16
+ #include < stdexcept>
14
17
#include < string>
15
- #include < algorithm>
16
18
#include < string_view>
17
- #include < cstdlib >
19
+ #include < getopt.h >
18
20
19
21
#include < X11/XKBlib.h>
20
22
#include < X11/Xlib.h>
25
27
26
28
using namespace std ::literals;
27
29
30
+ struct args {
31
+ std::string config;
32
+ bool custom_config = false ;
33
+ };
34
+
35
+ args parse_args (int argc, char ** argv) {
36
+ args ret;
37
+
38
+ int c;
39
+
40
+ while (1 ) {
41
+ int option_index = 0 ;
42
+ static struct option long_options[] = {
43
+ {" help" , no_argument, 0 , ' h' },
44
+ {" config" , required_argument, 0 , ' c' },
45
+ {0 , 0 , 0 , 0 }
46
+ };
47
+
48
+ c = getopt_long (argc, argv, " c:h" ,
49
+ long_options, &option_index);
50
+ if (c == -1 )
51
+ break ;
52
+
53
+ switch (c) {
54
+ case ' h' :
55
+ std::cout << " usage: " << argv[0 ]
56
+ << " [--help] [-c config_file]" << std::endl;
57
+ exit (0 );
58
+ break ;
59
+
60
+ case ' c' :
61
+ ret.config = std::string{optarg };
62
+ ret.custom_config = true ;
63
+ break ;
64
+ }
65
+ }
66
+
67
+ if (optind < argc) {
68
+ std::cout << " invalid non-option arguments" << std::endl;
69
+ exit (1 );
70
+ }
71
+
72
+ // set defaults
73
+ if (not ret.config .size ()) {
74
+ std::ostringstream cfgpathss;
75
+ const char *home = std::getenv (" HOME" );
76
+ if (not home) {
77
+ std::cout << " HOME env not set, can't locate config." << std::endl;
78
+ exit (1 );
79
+ }
80
+ cfgpathss << home << " /.config/xautocfg.cfg" ;
81
+ ret.config = std::move (cfgpathss).str ();
82
+ }
83
+
84
+ return ret;
85
+ }
86
+
87
+
28
88
struct config {
29
- uint32_t delay = 200 ;
30
- uint32_t interval = 20 ;
89
+ struct keyboard {
90
+ uint32_t delay = 200 ;
91
+ uint32_t interval = 20 ;
92
+ } keyboard;
93
+ };
94
+
95
+ enum class config_section {
96
+ none,
97
+ keyboard,
31
98
};
32
99
33
100
34
101
void parse_config_entry (config *config,
102
+ config_section section,
35
103
const std::string& key,
36
104
const std::string& val) {
37
105
std::istringstream vals{val};
38
106
39
- if (key == " delay" sv) {
40
- vals >> config->delay ;
41
- }
42
- else if (key == " rate" sv) {
43
- uint32_t rate;
44
- vals >> rate;
45
- // xserver wants the repeat-interval in ms,
46
- // but xset r rate delay repeat rate,
47
- // so interval = 1000Hz / rate
48
- config->interval = 1000 .f / rate;
107
+ if (section == config_section::keyboard) {
108
+ if (key == " delay" sv) {
109
+ vals >> config->keyboard .delay ;
110
+ }
111
+ else if (key == " rate" sv) {
112
+ uint32_t rate;
113
+ vals >> rate;
114
+ // xserver wants the repeat-interval in ms,
115
+ // but xset r rate delay repeat rate,
116
+ // so interval = 1000Hz / rate
117
+ config->keyboard .interval = 1000 .f / rate;
118
+ }
119
+ } else if (section == config_section::none) {
120
+ std::cout << " not in a config section: "
121
+ << key << " = " << val << std::endl;
122
+ exit (1 );
123
+ } else {
124
+ throw std::logic_error{" unknown config section" };
49
125
}
50
126
}
51
127
52
128
53
- config parse_config (const std::string &filename ) {
129
+ config parse_config (const args &args ) {
54
130
config ret{};
55
131
56
- std::ifstream file{filename , std::ios::binary};
132
+ std::ifstream file{args. config , std::ios::binary};
57
133
if (not file.is_open ()) {
58
- std::cout << " failed to open " << filename << " !\n "
59
- << " using default config." << std::endl;
134
+ std::cout << " failed to open config file '" << args.config << " '!" << std::endl;
135
+ if (args.custom_config ) {
136
+ exit (1 );
137
+ }
138
+ std::cout << " using default config." << std::endl;
60
139
return ret;
61
140
}
62
141
63
- const std::regex comment_re (" ^([^#]*)#?.*" );
64
- const std::regex kv_re (" ^ *([^= ]+) *= *([^ ]+) *" );
65
- std::smatch comment_match;
66
- std::smatch kv_match;
142
+
143
+ const std::regex comment_re (" ^ *([^#]*) *#?.*" );
144
+ const std::regex section_re (" ^\\ [([^\\ ]]+)\\ ]$" );
145
+ const std::regex kv_re (" ^([^= ]+) *= *([^ /]+)$" );
146
+ config_section current_secion = config_section::none;
67
147
68
148
std::string fullline{};
69
149
int linenr = 0 ;
70
150
while (std::getline (file, fullline)) {
71
151
linenr += 1 ;
72
152
// filter comments
153
+ std::smatch comment_match;
73
154
std::regex_match (fullline, comment_match, comment_re);
74
155
if (not comment_match.ready () or comment_match.size () != 2 ) {
75
156
std::cout << " error in config file line " << linenr << " :\n "
@@ -86,38 +167,52 @@ config parse_config(const std::string &filename) {
86
167
continue ;
87
168
}
88
169
89
- // parse 'key = value'
90
- std::regex_match (line, kv_match, kv_re);
91
- if (not kv_match.ready () or kv_match.size () != 3 ) {
92
- std::cout << " invalid syntax in line " << linenr << " :\n "
93
- << fullline << std::endl;
94
- exit (1 );
170
+ // parse '[section]'
171
+ {
172
+ std::smatch match;
173
+ std::regex_match (line, match, section_re);
174
+ if (match.ready () and match.size () == 2 ) {
175
+ const std::string& section_name{match[1 ]};
176
+ if (section_name == " keyboard" ) {
177
+ current_secion = config_section::keyboard;
178
+ }
179
+ else {
180
+ std::cout << " unknown section name: " << fullline << std::endl;
181
+ exit (1 );
182
+ }
183
+ continue ;
184
+ }
95
185
}
96
186
97
- const std::string& key{kv_match[1 ]};
98
- const std::string& val{kv_match[2 ]};
187
+ // parse 'key = value'
188
+ {
189
+ std::smatch match;
190
+ std::regex_match (line, match, kv_re);
191
+ if (match.ready () and match.size () == 3 ) {
192
+ const std::string& key{match[1 ]};
193
+ const std::string& val{match[2 ]};
194
+
195
+ parse_config_entry (&ret, current_secion, key, val);
196
+ continue ;
197
+ }
198
+ }
99
199
100
- parse_config_entry (&ret, key, val);
200
+ std::cout << " invalid syntax in line " << linenr << " :\n "
201
+ << fullline << std::endl;
202
+ exit (1 );
101
203
}
102
204
103
205
return ret;
104
206
}
105
207
106
208
107
- int main () {
108
- // TODO: argparsing?
209
+ int main (int argc, char **argv ) {
210
+ args args = parse_args (argc, argv);
109
211
110
- std::ostringstream cfgpath;
111
- const char *home = std::getenv (" HOME" );
112
- if (not home) {
113
- std::cout << " HOME env not set, can't locate config." << std::endl;
114
- exit (1 );
115
- }
116
- cfgpath << home << " /.config/xautocfg.cfg" ;
117
- config cfg = parse_config (cfgpath.str ());
118
- std::cout << " config: "
119
- << " delay=" << cfg.delay
120
- << " , interval=" << cfg.interval
212
+ config cfg = parse_config (args);
213
+ std::cout << " keyboard config: "
214
+ << " delay=" << cfg.keyboard .delay
215
+ << " , interval=" << cfg.keyboard .interval
121
216
<< std::endl;
122
217
123
218
std::cout << " connecting to x..." << std::endl;
@@ -134,7 +229,7 @@ int main() {
134
229
if (enabled) {
135
230
// we could use XkbUseCoreKbd as deviceid to always target the core
136
231
std::cout << " setting repeat rate on device=" << deviceid << std::endl;
137
- XkbSetAutoRepeatRate (display, deviceid, cfg.delay , cfg.interval );
232
+ XkbSetAutoRepeatRate (display, deviceid, cfg.keyboard . delay , cfg. keyboard .interval );
138
233
}
139
234
};
140
235
0 commit comments