Skip to content

Commit b61e248

Browse files
committed
yaft: Watch for config changes
1 parent c0d35c2 commit b61e248

10 files changed

Lines changed: 178 additions & 69 deletions

File tree

apps/yaft/YaftWidget.cpp

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
#include <Device.h>
1212

13-
#include <sstream>
13+
#include <sys/inotify.h>
14+
15+
#include <unistdpp/file.h>
1416

1517
using namespace rmlib;
1618

@@ -69,6 +71,15 @@ forkAndExec(int* master,
6971
}
7072
} // namespace
7173

74+
YaftConfigAndError
75+
Yaft::getConfigAndError() const {
76+
if (std::holds_alternative<YaftConfigAndError>(configOrPath)) {
77+
return std::get<YaftConfigAndError>(configOrPath);
78+
}
79+
80+
return loadConfigOrMakeDefault(std::get<std::filesystem::path>(configOrPath));
81+
}
82+
7283
YaftState::~YaftState() {
7384
if (term) {
7485
term_die(term.get());
@@ -87,15 +98,30 @@ YaftState::init(rmlib::AppContext& ctx, const rmlib::BuildContext& /*unused*/) {
8798
// term_init needs the maximum size of the terminal.
8899
int maxSize = std::max(ctx.getFbCanvas().width(), ctx.getFbCanvas().height());
89100
if (!term_init(term.get(), maxSize, maxSize)) {
90-
std::cout << "Error init term\n";
101+
std::cerr << "Error init term\n";
91102
ctx.stop();
92103
return;
93104
}
94105

95-
if (const auto& err = getWidget().configError; err.has_value()) {
106+
auto cfgAndError = getWidget().getConfigAndError();
107+
config = std::move(cfgAndError.config);
108+
109+
if (const auto& err = cfgAndError.err; err.has_value()) {
96110
logTerm(err->msg);
97111
}
98112

113+
if (std::holds_alternative<std::filesystem::path>(getWidget().configOrPath)) {
114+
watchPath = std::get<std::filesystem::path>(getWidget().configOrPath);
115+
inotifyFd = unistdpp::FD(inotify_init());
116+
if (inotifyFd.isValid()) {
117+
inotifyWd = inotify_add_watch(inotifyFd.fd,
118+
watchPath.parent_path().c_str(),
119+
IN_MODIFY | IN_CREATE | IN_CLOSE_WRITE);
120+
121+
ctx.listenFd(inotifyFd.fd, [this, &ctx] { readInotify(ctx); });
122+
}
123+
}
124+
99125
initSignalHandler(ctx);
100126

101127
if (!forkAndExec(&term->fd,
@@ -139,8 +165,6 @@ YaftState::init(rmlib::AppContext& ctx, const rmlib::BuildContext& /*unused*/) {
139165

140166
void
141167
YaftState::checkLandscape(rmlib::AppContext& ctx) {
142-
const auto& config = getWidget().config;
143-
144168
if (config.autoRotate) {
145169
const auto hasKeyboard =
146170
ctx.getInputManager().getBaseDevices().pogoKeyboard != nullptr;
@@ -151,6 +175,46 @@ YaftState::checkLandscape(rmlib::AppContext& ctx) {
151175
}
152176
}
153177

178+
void
179+
YaftState::readInotify(rmlib::AppContext& ctx) const {
180+
union {
181+
std::array<char, 4096> buf;
182+
inotify_event ev;
183+
} evUnion;
184+
185+
auto read = unistdpp::read(inotifyFd, &evUnion, sizeof(evUnion));
186+
if (!read.has_value()) {
187+
std::cerr << "inotify read failed: " << to_string(read.error()) << "\n";
188+
return;
189+
}
190+
191+
bool shouldReload = false;
192+
const inotify_event* event;
193+
for (char* ptr = evUnion.buf.begin(); ptr < evUnion.buf.begin() + *read;
194+
ptr += sizeof(struct inotify_event) + event->len) {
195+
event = (const struct inotify_event*)ptr;
196+
197+
if (event->wd == inotifyWd && event->name == watchPath.filename()) {
198+
shouldReload = true;
199+
}
200+
}
201+
202+
if (!shouldReload) {
203+
return;
204+
}
205+
206+
std::cerr << "inotify: Config updated, reloading\n";
207+
setState([&](auto& self) {
208+
auto cfgAndErr = loadConfig(self.watchPath);
209+
if (cfgAndErr.err.has_value()) {
210+
self.logTerm(cfgAndErr.err->msg);
211+
}
212+
213+
self.config = cfgAndErr.config;
214+
self.checkLandscape(ctx);
215+
});
216+
}
217+
154218
YaftState
155219
Yaft::createState() {
156220
return {};

apps/yaft/YaftWidget.h

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,26 @@
1010
#include <UI/Rotate.h>
1111
#include <UI/StatefulWidget.h>
1212

13+
#include <filesystem>
14+
1315
class YaftState;
1416

1517
class Yaft : public rmlib::StatefulWidget<Yaft> {
1618
public:
1719
Yaft(const char* cmd, char* const argv[], YaftConfigAndError config)
18-
: config(std::move(config.config))
19-
, configError(std::move(config.err))
20-
, cmd(cmd)
21-
, argv(argv) {}
20+
: configOrPath(std::move(config)), cmd(cmd), argv(argv) {}
21+
22+
Yaft(const char* cmd, char* const argv[], std::filesystem::path configPath)
23+
: configOrPath(std::move(configPath)), cmd(cmd), argv(argv) {}
2224

2325
static YaftState createState();
2426

27+
YaftConfigAndError getConfigAndError() const;
28+
2529
private:
2630
friend class YaftState;
2731

28-
YaftConfig config;
29-
std::optional<YaftConfigError> configError;
32+
std::variant<YaftConfigAndError, std::filesystem::path> configOrPath;
3033

3134
const char* cmd;
3235
char* const* argv;
@@ -41,15 +44,11 @@ class YaftState : public rmlib::StateBase<Yaft> {
4144

4245
void init(rmlib::AppContext& ctx, const rmlib::BuildContext& /*unused*/);
4346

44-
void checkLandscape(rmlib::AppContext& ctx);
45-
4647
auto build(rmlib::AppContext& ctx,
4748
const rmlib::BuildContext& buildCtx) const {
4849
using namespace rmlib;
4950

50-
const auto& cfg = getWidget().config;
51-
52-
const auto& layout = [this, &cfg]() -> const Layout& {
51+
const auto& layout = [this]() -> const Layout& {
5352
if (hideKeyboard) {
5453
return empty_layout;
5554
}
@@ -58,31 +57,41 @@ class YaftState : public rmlib::StateBase<Yaft> {
5857
return hidden_layout;
5958
}
6059

61-
return *cfg.layout;
60+
return *config.layout;
6261
}();
6362

64-
const auto repeatRateMs = std::chrono::milliseconds(1000) / cfg.repeatRate;
65-
return Rotated(rotation,
66-
Column(Expanded(Screen(term.get(), false, cfg.autoRefresh)),
67-
Keyboard(term.get(),
68-
KeyboardParams{
69-
.layout = layout,
70-
.keymap = *cfg.keymap,
71-
.repeatDelay = std::chrono::milliseconds(
72-
cfg.repeatDelay),
73-
.repeatTime = repeatRateMs,
74-
},
75-
[this](int num) {
76-
setState([](auto& self) {
77-
self.smallKeyboard = !self.smallKeyboard;
78-
});
79-
})));
63+
const auto repeatRateMs =
64+
std::chrono::milliseconds(1000) / config.repeatRate;
65+
return Rotated(
66+
rotation,
67+
Column(
68+
Expanded(Screen(term.get(), false, config.autoRefresh)),
69+
Keyboard(term.get(),
70+
KeyboardParams{
71+
.layout = layout,
72+
.keymap = *config.keymap,
73+
.repeatDelay = std::chrono::milliseconds(config.repeatDelay),
74+
.repeatTime = repeatRateMs,
75+
},
76+
[this](int num) {
77+
setState([](auto& self) {
78+
self.smallKeyboard = !self.smallKeyboard;
79+
});
80+
})));
8081
}
8182

8283
private:
84+
void checkLandscape(rmlib::AppContext& ctx);
85+
void readInotify(rmlib::AppContext& ctx) const;
86+
8387
std::unique_ptr<terminal_t> term;
8488
rmlib::TimerHandle pogoTimer;
8589

90+
std::filesystem::path watchPath;
91+
unistdpp::FD inotifyFd;
92+
int inotifyWd;
93+
94+
YaftConfig config;
8695
rmlib::Rotation rotation = rmlib::Rotation::None;
8796
bool smallKeyboard = false;
8897
bool hideKeyboard = false;

apps/yaft/config.cpp

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ layout = "qwerty"
1313
# Keymap for any physical keyboards. Defaults to the us rM pogo keyboard.
1414
keymap = "rm-qwerty"
1515
16-
# Orientation of yaft:
17-
# Auto rotate if keyboard is connected
16+
# Auto rotate if keyboard is connected.
1817
auto-rotate = true
19-
# Orientation if no keyboard is connected
18+
19+
# Orientation if no keyboard is connected:
20+
# * none, clockwise, inverted, counterclockwise
2021
rotation = "none"
2122
2223
# Do a full refresh after 1024 updates.
@@ -29,25 +30,6 @@ repeat-delay = 600
2930
repeat-rate = 25
3031
)";
3132

32-
std::filesystem::path
33-
getConfigPath() {
34-
const auto configDir = [] {
35-
if (const auto* xdgCfg = getenv("XDG_CONFIG_HOME");
36-
xdgCfg != nullptr && xdgCfg[0] != 0) {
37-
return std::filesystem::path(xdgCfg);
38-
}
39-
40-
const auto* home = getenv("HOME");
41-
if (home == nullptr || home[0] == 0) {
42-
home = "/home/root";
43-
}
44-
45-
return std::filesystem::path(home) / ".config" / "yaft";
46-
}();
47-
48-
return configDir / "config.toml";
49-
}
50-
5133
void
5234
addError(std::optional<YaftConfigError>& error, YaftConfigError err) {
5335
if (!error.has_value()) {
@@ -135,24 +117,42 @@ getConfig(const toml::table& input) {
135117

136118
} // namespace
137119

120+
std::filesystem::path
121+
getConfigPath() {
122+
const auto configDir = [] {
123+
if (const auto* xdgCfg = getenv("XDG_CONFIG_HOME");
124+
xdgCfg != nullptr && xdgCfg[0] != 0) {
125+
return std::filesystem::path(xdgCfg);
126+
}
127+
128+
const auto* home = getenv("HOME");
129+
if (home == nullptr || home[0] == 0) {
130+
home = "/home/root";
131+
}
132+
133+
return std::filesystem::path(home) / ".config" / "yaft";
134+
}();
135+
136+
return configDir / "config.toml";
137+
}
138+
138139
YaftConfig
139140
YaftConfig::getDefault() {
140141
auto tbl = toml::parse(default_config);
141142
return getConfig(tbl).config;
142143
}
143144

144145
YaftConfigAndError
145-
loadConfig() {
146+
loadConfig(const std::filesystem::path& path) {
146147
toml::table tbl;
147148

148-
const auto path = getConfigPath();
149149
if (!std::filesystem::exists(path)) {
150150
return { YaftConfig::getDefault(),
151151
YaftConfigError{ YaftConfigError::Missing, path.string() } };
152152
}
153153

154154
try {
155-
tbl = toml::parse_file(getConfigPath().c_str());
155+
tbl = toml::parse_file(path.c_str());
156156
} catch (const toml::parse_error& err) {
157157
return { YaftConfig::getDefault(),
158158
YaftConfigError{ YaftConfigError::Syntax,
@@ -164,8 +164,7 @@ loadConfig() {
164164
}
165165

166166
OptError<>
167-
saveDefaultConfig() {
168-
const auto path = getConfigPath();
167+
saveDefaultConfig(const std::filesystem::path& path) {
169168
const auto dir = path.parent_path();
170169

171170
std::error_code ec;
@@ -189,16 +188,16 @@ saveDefaultConfig() {
189188
}
190189

191190
YaftConfigAndError
192-
loadConfigOrMakeDefault() {
193-
auto result = loadConfig();
191+
loadConfigOrMakeDefault(const std::filesystem::path& path) {
192+
auto result = loadConfig(path);
194193
if (!result.err.has_value()) {
195194
return result;
196195
}
197196

198197
auto& err = *result.err;
199198
if (err.type == YaftConfigError::Missing) {
200199
err.msg = "No config, creating new one\r\n";
201-
if (const auto optErr = saveDefaultConfig(); !optErr.has_value()) {
200+
if (const auto optErr = saveDefaultConfig(path); !optErr.has_value()) {
202201
err.msg += optErr.error().msg + "\r\n";
203202
}
204203
} else {

apps/yaft/config.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "Error.h"
44

5+
#include <filesystem>
56
#include <optional>
67
#include <string>
78

@@ -43,16 +44,19 @@ struct YaftConfigAndError {
4344
std::optional<YaftConfigError> err;
4445
};
4546

47+
std::filesystem::path
48+
getConfigPath();
49+
4650
/// Load the config from the `~/.config/yaft/config.toml` location.
4751
YaftConfigAndError
48-
loadConfig();
52+
loadConfig(const std::filesystem::path& path);
4953

5054
OptError<>
51-
saveDefaultConfig();
55+
saveDefaultConfig(const std::filesystem::path& path);
5256

5357
/// Always returns a config, either the default one or the one on the file
5458
/// system. Will also make a new config file if it didn't exist.
5559
///
5660
/// If any error occured during the loading of the config, it's also returned.
5761
YaftConfigAndError
58-
loadConfigOrMakeDefault();
62+
loadConfigOrMakeDefault(const std::filesystem::path& path);

apps/yaft/main.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@ main(int argc, char* argv[]) {
3636
args = const_cast<char* const*>(shellArgs);
3737
}
3838

39-
auto cfg = loadConfigOrMakeDefault();
40-
41-
unistdpp::fatalOnError(runApp(Yaft(cmd, args, std::move(cfg))));
39+
unistdpp::fatalOnError(runApp(Yaft(cmd, args, getConfigPath())));
4240

4341
return 0;
4442
}

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
packages = with pkgs; [
7272
clang-tools
7373
libllvm
74+
lldb
7475
];
7576
};
7677
}

0 commit comments

Comments
 (0)