Single instance

This commit is contained in:
2026-10-18 16:59:51 +02:00
parent 798708b3e7
commit 74503c27dd
19 changed files with 670 additions and 99 deletions

View File

@@ -7,6 +7,7 @@
// execution), M3 (config watching + lifecycle), M4 (D-Bus GUI channel) and
// M5 (hot-plug, repeat/chord decoding, error reporting to the GUI) are done.
#include <algorithm>
#include <csignal>
#include <cstdint>
#include <cstdio>
@@ -81,8 +82,10 @@ int main(int argc, char** argv) {
// Runs the mapping of G<index+1> (0-based). A real keypress and the D-Bus
// TestMapping call both go through here, so a test fires exactly the same
// path. Returns whether there was an action to run.
auto run_mapping = [&](int index, bool test) -> bool {
// path. `hold` is set for a real press, where a "keyhold" mapping keeps its
// keys down until the G key is released; a test always taps. Returns whether
// there was an action to run.
auto run_mapping = [&](int index, bool test, bool hold) -> bool {
if (index < 0 || index >= static_cast<int>(mappings.keys.size())) {
return false;
}
@@ -94,7 +97,11 @@ int main(int argc, char** argv) {
test ? " (test)" : "");
std::fflush(stdout);
if (usable) {
actions.run(mapping);
if (hold && mapping.type == gkeymapper::ActionType::KeyHold) {
actions.begin_hold(index, mapping);
} else {
actions.run(mapping);
}
}
return usable;
};
@@ -110,7 +117,7 @@ int main(int argc, char** argv) {
return profile != nullptr ? std::string(profile->name) : std::string();
});
service.set_test([&](int index, std::string& action) {
if (!run_mapping(index, true)) {
if (!run_mapping(index, true, false)) {
return false;
}
const gkeymapper::Mapping& mapping = mappings.keys[index];
@@ -194,6 +201,8 @@ int main(int argc, char** argv) {
// Lost the keyboard (unplugged, or the kernel took the interface
// back). Report it and keep looking; the daemon stays up.
std::fprintf(stderr, "gkeymapper-daemon: read error (%d), device lost\n", n);
// A held key would stay down forever without its release report.
actions.release_held();
service.notify_device_changed(false, profile != nullptr ? profile->name : "");
device.close();
profile = nullptr;
@@ -207,19 +216,29 @@ int main(int argc, char** argv) {
}
uint32_t state = 0;
if (gkeymapper::daemon::decode_gkey_state(*profile, buffer, n, state)) {
// Only a key that was not pressed before fires: a held key repeats its
// report, and a chord arrives as one report with several bits set, so
// every key of it fires exactly once.
for (size_t i = 0; i < mappings.keys.size() && i < gkeymapper::kNumKeys; ++i) {
// Only a *change* of a bit acts: a held key repeats its report, and a
// chord arrives as one report with several bits set, so every key of
// it fires exactly once. The falling edge matters for "keyhold",
// which lets its keys go when the G key comes up.
const size_t count =
std::min(mappings.keys.size(), static_cast<size_t>(gkeymapper::kNumKeys));
for (size_t i = 0; i < count; ++i) {
const uint32_t bit = 1u << i;
if ((state & bit) == 0 || (pressed & bit) != 0) {
const bool down = (state & bit) != 0;
const bool was_down = (pressed & bit) != 0;
if (down == was_down) {
continue;
}
if (!down) {
// No-op unless this G key holds a "keyhold" mapping.
actions.end_hold(static_cast<int>(i));
continue;
}
const gkeymapper::Mapping& mapping = mappings.keys[i];
const std::string action =
std::string(gkeymapper::action_type_token(mapping.type)) + ":" +
mapping.target;
run_mapping(static_cast<int>(i), false);
run_mapping(static_cast<int>(i), false, true);
service.notify_key_pressed(static_cast<int>(i), action);
}
pressed = state;
@@ -247,6 +266,9 @@ int main(int argc, char** argv) {
}
}
// Shutting down: a "keyhold" key that is still down belongs to the session,
// not to the daemon, so let it go before the virtual keyboard disappears.
actions.release_held();
service.notify_device_changed(false, profile != nullptr ? profile->name : "");
service.pump();
device.close();