Deamon management

This commit is contained in:
2026-10-17 03:53:51 +02:00
parent 335a4674d1
commit b77b4a3fc6
35 changed files with 2001 additions and 172 deletions

View File

@@ -10,6 +10,9 @@
#include <glib/gi18n.h>
#include "keymap.h"
#include "keys.h"
namespace gkeymapper {
namespace {
@@ -18,9 +21,14 @@ struct CaptureSession {
KeyRow* row = nullptr;
GtkWidget* displayLabel = nullptr;
std::vector<std::string> steps;
std::set<guint> heldKeys; // non-modifier keyvals currently held (ignores auto-repeat)
std::set<guint> heldKeys; // keycodes currently held (ignores auto-repeat)
bool altGrHeld = false; // ISO_Level3_Shift / Mode_switch is down
};
bool is_altgr_keyval(guint keyval) {
return keyval == GDK_KEY_ISO_Level3_Shift || keyval == GDK_KEY_Mode_switch;
}
bool is_modifier_keyval(guint keyval) {
switch (keyval) {
case GDK_KEY_Control_L:
@@ -34,34 +42,60 @@ bool is_modifier_keyval(guint keyval) {
case GDK_KEY_Meta_L:
case GDK_KEY_Meta_R:
case GDK_KEY_ISO_Level3_Shift: // AltGr
case GDK_KEY_Mode_switch: // AltGr on some layouts
return true;
default:
return false;
}
}
// One keymap for the whole app; building it takes a moment, so it is created
// lazily on the first captured key.
const Keymap& capture_keymap() {
static Keymap keymap;
static bool initialized = false;
if (!initialized) {
keymap.init();
initialized = true;
}
return keymap;
}
// Format one step like the reference project: modifiers joined with '+',
// e.g. "ctrl+shift+t". Multiple steps are joined with ',' ("ctrl+c,ctrl+v").
std::string format_capture_step(GdkModifierType state, guint keyval) {
std::string step;
//
// The key is stored as its *base* (level 0) name, so Shift+s-sharp is recorded
// as "shift+ssharp" rather than the shifted symbol "question" (GDK's keyval is
// already shifted, so using it would double the shift on replay). AltGr
// (level 3) becomes the "altgr" modifier plus the base key, e.g. "altgr+7".
std::string format_capture_step(GdkModifierType state, guint keycode, guint keyval,
bool altgr) {
unsigned modifiers = 0;
if (state & GDK_CONTROL_MASK) {
step += "ctrl+";
modifiers |= kModCtrl;
}
if (state & GDK_ALT_MASK) {
step += "alt+";
modifiers |= kModAlt;
}
if (state & GDK_SHIFT_MASK) {
step += "shift+";
modifiers |= kModShift;
}
if (state & (GDK_SUPER_MASK | GDK_META_MASK)) {
step += "super+";
modifiers |= kModSuper;
}
const char* name = gdk_keyval_name(keyval);
std::string key = name ? name : "?";
std::transform(key.begin(), key.end(), key.begin(),
[](unsigned char c) { return std::tolower(c); });
step += key;
return step;
// GTK4 no longer exposes a Mod5 mask, so AltGr is tracked via its key.
if (altgr) {
modifiers |= kModAltGr;
}
std::string key = capture_keymap().base_key_name(keycode);
if (key.empty()) {
const char* name = gdk_keyval_name(keyval);
key = name ? name : "?";
std::transform(key.begin(), key.end(), key.begin(),
[](unsigned char c) { return std::tolower(c); });
}
return format_shortcut_step(modifiers, key);
}
std::string join_capture_steps(const std::vector<std::string>& steps) {
@@ -84,7 +118,7 @@ void update_capture_display(CaptureSession* session) {
}
gboolean on_capture_key_pressed(GtkEventControllerKey* controller,
guint keyval, guint /*keycode*/,
guint keyval, guint keycode,
GdkModifierType state, gpointer userData) {
auto* session = static_cast<CaptureSession*>(userData);
@@ -96,27 +130,36 @@ gboolean on_capture_key_pressed(GtkEventControllerKey* controller,
return TRUE;
}
if (is_altgr_keyval(keyval)) {
// GTK4 dropped the Mod5 mask, so AltGr is tracked through its own key.
session->altGrHeld = true;
return TRUE;
}
if (is_modifier_keyval(keyval)) {
return TRUE; // modifiers are recorded as part of the next real key
}
if (session->heldKeys.count(keyval)) {
if (session->heldKeys.count(keycode)) {
return TRUE; // ignore auto-repeat while the key is held down
}
session->heldKeys.insert(keyval);
session->heldKeys.insert(keycode);
constexpr GdkModifierType kModMask = static_cast<GdkModifierType>(
GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_ALT_MASK |
GDK_SUPER_MASK | GDK_META_MASK);
session->steps.push_back(format_capture_step(
static_cast<GdkModifierType>(state & kModMask), keyval));
static_cast<GdkModifierType>(state & kModMask), keycode, keyval,
session->altGrHeld));
update_capture_display(session);
return TRUE;
}
gboolean on_capture_key_released(GtkEventControllerKey* /*controller*/,
guint keyval, guint /*keycode*/,
guint keyval, guint keycode,
GdkModifierType /*state*/, gpointer userData) {
auto* session = static_cast<CaptureSession*>(userData);
session->heldKeys.erase(keyval);
if (is_altgr_keyval(keyval)) {
session->altGrHeld = false;
}
session->heldKeys.erase(keycode);
return TRUE;
}
@@ -174,7 +217,7 @@ void open_key_capture_dialog(KeyRow* row, GtkWindow* parent) {
gtk_label_set_xalign(GTK_LABEL(title), 0.0f);
gtk_box_append(GTK_BOX(outer), title);
GtkWidget* info = gtk_label_new(_("Press keys in order (modifiers like Ctrl, Alt, Shift and Super are recorded too). Click Done to apply or Cancel to discard."));
GtkWidget* info = gtk_label_new(_("Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are recorded too). Click Done to apply or Cancel to discard."));
gtk_label_set_wrap(GTK_LABEL(info), TRUE);
gtk_label_set_xalign(GTK_LABEL(info), 0.0f);
gtk_widget_add_css_class(info, "dim-label");

View File

@@ -4,9 +4,9 @@
#include <gtk/gtk.h>
namespace gkeymapper {
#include "mapping.h" // kNumKeys, ActionType
constexpr int kNumKeys = 9;
namespace gkeymapper {
// Widgets belonging to a single G key row.
struct KeyRow {

View File

@@ -11,6 +11,7 @@
#include "capturedialog.h"
#include "keyrow.h"
#include "mapping.h"
#include "mappings.h"
#ifndef PACKAGE

View File

@@ -1,84 +1,55 @@
// gKeyMapper - mappings configuration file I/O.
// gKeyMapper - GUI glue between the mappings file and the row widgets.
#include "mappings.h"
#include <fstream>
#include <string>
#include <glib/gi18n.h>
#include "mapping.h"
namespace gkeymapper {
std::string default_config_path() {
const char* home = g_get_home_dir();
return std::string(home ? home : "") + "/.config/gKeyMapper/mappings.conf";
}
bool write_mappings(const std::string& path, const std::vector<KeyRow>& rows) {
std::ofstream out(path);
if (!out) {
return false;
}
MappingSet set;
set.keys.assign(kNumKeys, Mapping{});
out << "# gKeyMapper mappings (G1..G9)\n";
for (const auto& row : rows) {
const char* type = "disabled";
for (const KeyRow& row : rows) {
if (row.index < 0 || row.index >= kNumKeys) {
continue;
}
Mapping mapping;
switch (gtk_drop_down_get_selected(GTK_DROP_DOWN(row.typeDrop))) {
case 0: type = "key"; break;
case 1: type = "command"; break;
default: type = "disabled"; break;
case 0: mapping.type = ActionType::Key; break;
case 1: mapping.type = ActionType::Command; break;
default: mapping.type = ActionType::Disabled; break;
}
const char* target = gtk_editable_get_text(GTK_EDITABLE(row.targetEntry));
out << "G" << (row.index + 1) << "=" << type << ":" << (target ? target : "") << "\n";
mapping.target = target ? target : "";
set.keys[row.index] = mapping;
}
return true;
return save_mappings_file(path, set);
}
void read_mappings(const std::string& path, const std::vector<KeyRow>& rows) {
std::ifstream in(path);
if (!in) {
MappingSet set;
if (!load_mappings_file(path, set)) {
g_printerr(_("Could not open %s.\n"), path.c_str());
return;
}
std::string line;
while (std::getline(in, line)) {
if (line.empty() || line[0] == '#') {
for (const KeyRow& row : rows) {
if (row.index < 0 || row.index >= static_cast<int>(set.keys.size())) {
continue;
}
auto eq = line.find('=');
if (eq == std::string::npos) {
continue;
}
std::string key = line.substr(0, eq);
if (key.size() < 2 || key[0] != 'G') {
continue;
}
int n = 0;
try {
n = std::stoi(key.substr(1)) - 1;
} catch (...) {
continue;
}
if (n < 0 || n >= static_cast<int>(rows.size())) {
continue;
}
std::string rest = line.substr(eq + 1);
auto col = rest.find(':');
std::string typeToken = (col == std::string::npos) ? rest : rest.substr(0, col);
std::string target = (col == std::string::npos) ? std::string() : rest.substr(col + 1);
guint idx = 2;
if (typeToken == "key") {
idx = 0;
} else if (typeToken == "command") {
idx = 1;
} else if (typeToken == "disabled") {
idx = 2;
switch (set.keys[row.index].type) {
case ActionType::Key: idx = 0; break;
case ActionType::Command: idx = 1; break;
default: idx = 2; break;
}
gtk_drop_down_set_selected(GTK_DROP_DOWN(rows[n].typeDrop), idx);
gtk_editable_set_text(GTK_EDITABLE(rows[n].targetEntry), target.c_str());
gtk_drop_down_set_selected(GTK_DROP_DOWN(row.typeDrop), idx);
gtk_editable_set_text(GTK_EDITABLE(row.targetEntry),
set.keys[row.index].target.c_str());
}
}

View File

@@ -1,4 +1,4 @@
// gKeyMapper - reading and writing the mappings configuration file.
// gKeyMapper - GUI glue between the mappings file and the row widgets.
#ifndef GKEYMAPPER_MAPPINGS_H
#define GKEYMAPPER_MAPPINGS_H
@@ -9,14 +9,10 @@
namespace gkeymapper {
// Default config file: ~/.config/gKeyMapper/mappings.conf.
std::string default_config_path();
// Writes all rows as "G1=type:target" lines. Returns false on I/O error.
// Collects the current widget state and writes it to path.
bool write_mappings(const std::string& path, const std::vector<KeyRow>& rows);
// Loads mappings from path into the rows' widgets. Missing/invalid lines are
// skipped; a missing file is reported and otherwise ignored.
// Loads path into the rows' widgets (type dropdown + target entry).
void read_mappings(const std::string& path, const std::vector<KeyRow>& rows);
} // namespace gkeymapper