Deamon management
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "capturedialog.h"
|
||||
#include "keyrow.h"
|
||||
#include "mapping.h"
|
||||
#include "mappings.h"
|
||||
|
||||
#ifndef PACKAGE
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user