Skip to content

Commit 1b8eaab

Browse files
committed
implement argparsing and config sections
1 parent 628deaa commit 1b8eaab

File tree

4 files changed

+188
-47
lines changed

4 files changed

+188
-47
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ xautocfg: xautocfg.o
1313
${CXX} ${BUILDFLAGS} ${CXXFLAGS} ${LIBS} $^ -o $@
1414

1515
%.o: %.cpp
16-
${CXX} -c ${BUILDFLAGS} ${CXXFLAGS} $^ -o $@
16+
${CXX} ${BUILDFLAGS} ${CXXFLAGS} -c $^ -o $@
1717

1818

1919
.PHONY: clean

etc/xautocfg.cfg

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# xautocfg config
2+
3+
[keyboard]
24
# set keyboard repeat rate for every keyboard automatically
35
# equivalent one-time invocation:
46
# xset r rate 220 45
5-
#
7+
68
# when to start repeating
79
delay = 220
10+
811
# rate in hz for repetitions
912
rate = 45

xautocfg.1

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.TH XAUTOCFG 1
2+
.SH NAME
3+
xautocfg \- automatically apply X11 device settings
4+
.SH SYNOPSIS
5+
.B xautocfg
6+
.RI [ options ]
7+
.SH DESCRIPTION
8+
xautocfg is a daemon that can automatically set the key repeat rate to newly connected keyboards.
9+
.PP
10+
Usually, it's invoked as systemd user \fBxautocfg.service\fR, or from the user's \fB~/.xsession\fR file.
11+
.PP
12+
.PP
13+
Additional information can be found on the project's website:
14+
\fIhttps://github.com/SFTtech/xautocfg\fR
15+
.SH OPTIONS
16+
.TP
17+
\fB\-c\fR, \fB\-\-config\fR=\fIFILE\fR
18+
Load settings from \fIFILE\fR (default is \fB~/.config/xautocfg.cfg\fR).
19+
.TP
20+
\fB\-h\fR, \fB\-\-help\fR
21+
Display a help message and exit.
22+
.SH BUGS
23+
Report at \fIhttps://github.com/SFTtech/xautocfg/issues\fR
24+
.SH EXAMPLE
25+
Example content for \fBxautocfg.cfg\fR config file:
26+
.PP
27+
.nf
28+
# xautocfg config
29+
30+
[keyboard]
31+
# set keyboard repeat rate for every keyboard automatically
32+
# equivalent one-time invocation:
33+
# xset r rate 220 45
34+
35+
# when to start repeating
36+
delay = 220
37+
38+
# rate in hz for repetitions
39+
rate = 45
40+
.fi
41+
.SH AUTHOR
42+
xautocfg was written by Jonas Jelten <jj@sft.lol>.
43+

xautocfg.cpp

Lines changed: 140 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
* GPLv3 or later.
77
*/
88

9+
#include <algorithm>
10+
#include <cstdlib>
911
#include <fstream>
1012
#include <iostream>
1113
#include <memory>
1214
#include <regex>
1315
#include <stdexcept>
16+
#include <stdexcept>
1417
#include <string>
15-
#include <algorithm>
1618
#include <string_view>
17-
#include <cstdlib>
19+
#include <getopt.h>
1820

1921
#include <X11/XKBlib.h>
2022
#include <X11/Xlib.h>
@@ -25,51 +27,130 @@
2527

2628
using namespace std::literals;
2729

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+
2888
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,
3198
};
3299

33100

34101
void parse_config_entry(config *config,
102+
config_section section,
35103
const std::string& key,
36104
const std::string& val) {
37105
std::istringstream vals{val};
38106

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"};
49125
}
50126
}
51127

52128

53-
config parse_config(const std::string &filename) {
129+
config parse_config(const args &args) {
54130
config ret{};
55131

56-
std::ifstream file{filename, std::ios::binary};
132+
std::ifstream file{args.config, std::ios::binary};
57133
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;
60139
return ret;
61140
}
62141

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;
67147

68148
std::string fullline{};
69149
int linenr = 0;
70150
while (std::getline(file, fullline)) {
71151
linenr += 1;
72152
// filter comments
153+
std::smatch comment_match;
73154
std::regex_match(fullline, comment_match, comment_re);
74155
if (not comment_match.ready() or comment_match.size() != 2) {
75156
std::cout << "error in config file line " << linenr << ":\n"
@@ -86,38 +167,52 @@ config parse_config(const std::string &filename) {
86167
continue;
87168
}
88169

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+
}
95185
}
96186

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+
}
99199

100-
parse_config_entry(&ret, key, val);
200+
std::cout << "invalid syntax in line " << linenr << ":\n"
201+
<< fullline << std::endl;
202+
exit(1);
101203
}
102204

103205
return ret;
104206
}
105207

106208

107-
int main() {
108-
// TODO: argparsing?
209+
int main(int argc, char **argv) {
210+
args args = parse_args(argc, argv);
109211

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
121216
<< std::endl;
122217

123218
std::cout << "connecting to x..." << std::endl;
@@ -134,7 +229,7 @@ int main() {
134229
if (enabled) {
135230
// we could use XkbUseCoreKbd as deviceid to always target the core
136231
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);
138233
}
139234
};
140235

0 commit comments

Comments
 (0)