mirror of
https://github.com/swaywm/sway.git
synced 2026-02-05 04:06:25 -05:00
Merge ea65d090ea into e3c2412565
This commit is contained in:
commit
8565f1832a
9 changed files with 247 additions and 22 deletions
24
config.in
24
config.in
|
|
@ -239,3 +239,27 @@ bar {
|
|||
}
|
||||
|
||||
include @sysconfdir@/sway/config.d/*
|
||||
|
||||
# Basic clipboard operations (non-conflicting)
|
||||
remap Super-c C-c
|
||||
remap Super-x C-x
|
||||
remap Super-z C-z
|
||||
remap Super-Shift-z C-Shift-z
|
||||
|
||||
remap Super-t C-t # New tab
|
||||
remap Super-n C-n # New window
|
||||
remap Super-o C-o # Open file
|
||||
remap Super-q C-q # Quit
|
||||
|
||||
# Tab navigation
|
||||
remap Super-Shift-bracketleft C-Page_Up
|
||||
remap Super-Shift-bracketright C-Page_Down
|
||||
|
||||
|
||||
# Terminal-specific
|
||||
remap Super-c C-Shift-c for foot
|
||||
remap Super-x C-Shift-x for foot
|
||||
|
||||
remap Super-t C-Shift-t for com.mitchellh.ghostty
|
||||
remap Super-n C-Shift-n for com.mitchellh.ghostty
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,16 @@ void container_resize_tiled(struct sway_container *parent, uint32_t axis,
|
|||
struct sway_container *container_find_resize_parent(struct sway_container *con,
|
||||
uint32_t edge);
|
||||
|
||||
// Keysym to keycode translation (used by bind.c and keyboard.c)
|
||||
struct keycode_matches {
|
||||
xkb_keysym_t keysym;
|
||||
xkb_keycode_t keycode;
|
||||
int count;
|
||||
};
|
||||
|
||||
void find_keycode(struct xkb_keymap *keymap, xkb_keycode_t keycode, void *data);
|
||||
struct keycode_matches get_keycode_for_keysym(xkb_keysym_t keysym);
|
||||
|
||||
/**
|
||||
* Handlers shared by exec and exec_always.
|
||||
*/
|
||||
|
|
@ -168,6 +178,7 @@ sway_cmd cmd_popup_during_fullscreen;
|
|||
sway_cmd cmd_primary_selection;
|
||||
sway_cmd cmd_reject;
|
||||
sway_cmd cmd_reload;
|
||||
sway_cmd cmd_remap;
|
||||
sway_cmd cmd_rename;
|
||||
sway_cmd cmd_resize;
|
||||
sway_cmd cmd_scratchpad;
|
||||
|
|
|
|||
|
|
@ -94,6 +94,17 @@ struct sway_gesture_binding {
|
|||
char *command;
|
||||
};
|
||||
|
||||
/**
|
||||
* A key remap rule for transforming key combinations
|
||||
*/
|
||||
struct sway_key_remap {
|
||||
uint32_t from_modifiers;
|
||||
xkb_keysym_t from_keysym;
|
||||
uint32_t to_modifiers;
|
||||
xkb_keysym_t to_keysym;
|
||||
char *app_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus on window activation.
|
||||
*/
|
||||
|
|
@ -504,6 +515,7 @@ struct sway_config {
|
|||
list_t *criteria;
|
||||
list_t *no_focus;
|
||||
list_t *active_bar_modifiers;
|
||||
list_t *key_remaps;
|
||||
struct sway_mode *current_mode;
|
||||
struct bar_config *current_bar;
|
||||
uint32_t floating_mod;
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ static const struct cmd_handler handlers[] = {
|
|||
{ "no_focus", cmd_no_focus },
|
||||
{ "output", cmd_output },
|
||||
{ "popup_during_fullscreen", cmd_popup_during_fullscreen },
|
||||
{ "remap", cmd_remap },
|
||||
{ "seat", cmd_seat },
|
||||
{ "set", cmd_set },
|
||||
{ "show_marks", cmd_show_marks },
|
||||
|
|
|
|||
|
|
@ -647,21 +647,11 @@ void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding)
|
|||
transaction_commit_dirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* The last found keycode associated with the keysym
|
||||
* and the total count of matches.
|
||||
*/
|
||||
struct keycode_matches {
|
||||
xkb_keysym_t keysym;
|
||||
xkb_keycode_t keycode;
|
||||
int count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterate through keycodes in the keymap to find ones matching
|
||||
* the specified keysym.
|
||||
*/
|
||||
static void find_keycode(struct xkb_keymap *keymap,
|
||||
void find_keycode(struct xkb_keymap *keymap,
|
||||
xkb_keycode_t keycode, void *data) {
|
||||
xkb_keysym_t keysym = xkb_state_key_get_one_sym(
|
||||
config->keysym_translation_state, keycode);
|
||||
|
|
@ -680,7 +670,7 @@ static void find_keycode(struct xkb_keymap *keymap,
|
|||
/**
|
||||
* Return the keycode for the specified keysym.
|
||||
*/
|
||||
static struct keycode_matches get_keycode_for_keysym(xkb_keysym_t keysym) {
|
||||
struct keycode_matches get_keycode_for_keysym(xkb_keysym_t keysym) {
|
||||
struct keycode_matches matches = {
|
||||
.keysym = keysym,
|
||||
.keycode = XKB_KEYCODE_INVALID,
|
||||
|
|
|
|||
101
sway/commands/remap.c
Normal file
101
sway/commands/remap.c
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include "sway/commands.h"
|
||||
#include "sway/config.h"
|
||||
#include "sway/input/keyboard.h"
|
||||
#include "list.h"
|
||||
#include "log.h"
|
||||
#include "stringop.h"
|
||||
|
||||
// Parse a key combination string like "Super-Shift-c" into modifiers and keysym
|
||||
static bool parse_key_combo(const char *combo_str, uint32_t *modifiers, xkb_keysym_t *keysym) {
|
||||
*modifiers = 0;
|
||||
*keysym = XKB_KEY_NoSymbol;
|
||||
|
||||
list_t *split = split_string(combo_str, "-");
|
||||
if (!split || split->length == 0) {
|
||||
list_free_items_and_destroy(split);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse all but last component as modifiers
|
||||
for (int i = 0; i < split->length - 1; i++) {
|
||||
char *mod_name = split->items[i];
|
||||
uint32_t mod = get_modifier_mask_by_name(mod_name);
|
||||
if (mod == 0) {
|
||||
// Special case for "C" and "Ctrl" shorthand
|
||||
if (strcasecmp(mod_name, "C") == 0 || strcasecmp(mod_name, "Ctrl") == 0) {
|
||||
mod = WLR_MODIFIER_CTRL;
|
||||
} else {
|
||||
list_free_items_and_destroy(split);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*modifiers |= mod;
|
||||
}
|
||||
|
||||
// Last component is the key
|
||||
char *key_name = split->items[split->length - 1];
|
||||
*keysym = xkb_keysym_from_name(key_name, XKB_KEYSYM_CASE_INSENSITIVE);
|
||||
|
||||
list_free_items_and_destroy(split);
|
||||
|
||||
if (*keysym == XKB_KEY_NoSymbol) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Implements the 'remap' command
|
||||
// Syntax: remap <from-combo> <to-combo> [for <app_id>]
|
||||
// Example: remap Super-c C-c for firefox
|
||||
struct cmd_results *cmd_remap(int argc, char **argv) {
|
||||
struct cmd_results *error = NULL;
|
||||
if ((error = checkarg(argc, "remap", EXPECTED_AT_LEAST, 2))) {
|
||||
return error;
|
||||
}
|
||||
|
||||
const char *from_str = argv[0];
|
||||
const char *to_str = argv[1];
|
||||
const char *app_id = NULL;
|
||||
|
||||
if (argc >= 4 && strcasecmp(argv[2], "for") == 0) {
|
||||
app_id = argv[3];
|
||||
}
|
||||
|
||||
struct sway_key_remap *remap = calloc(1, sizeof(struct sway_key_remap));
|
||||
if (!remap) {
|
||||
return cmd_results_new(CMD_FAILURE, "Unable to allocate key remap");
|
||||
}
|
||||
|
||||
if (!parse_key_combo(from_str, &remap->from_modifiers, &remap->from_keysym)) {
|
||||
free(remap);
|
||||
return cmd_results_new(CMD_FAILURE, "Invalid 'from' key combination: %s", from_str);
|
||||
}
|
||||
|
||||
if (!parse_key_combo(to_str, &remap->to_modifiers, &remap->to_keysym)) {
|
||||
free(remap);
|
||||
return cmd_results_new(CMD_FAILURE, "Invalid 'to' key combination: %s", to_str);
|
||||
}
|
||||
|
||||
if (app_id) {
|
||||
remap->app_id = strdup(app_id);
|
||||
// App-specific remaps go at the front (higher priority)
|
||||
list_insert(config->key_remaps, 0, remap);
|
||||
} else {
|
||||
remap->app_id = NULL;
|
||||
// Global remaps go at the back (lower priority)
|
||||
list_add(config->key_remaps, remap);
|
||||
}
|
||||
|
||||
sway_log(SWAY_DEBUG, "Added key remap: 0x%x+0x%x -> 0x%x+0x%x%s%s",
|
||||
remap->from_modifiers, remap->from_keysym,
|
||||
remap->to_modifiers, remap->to_keysym,
|
||||
app_id ? " for " : "", app_id ? app_id : "");
|
||||
|
||||
return cmd_results_new(CMD_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -100,6 +100,14 @@ static void free_mode(struct sway_mode *mode) {
|
|||
free(mode);
|
||||
}
|
||||
|
||||
static void free_key_remap(struct sway_key_remap *remap) {
|
||||
if (!remap) {
|
||||
return;
|
||||
}
|
||||
free(remap->app_id);
|
||||
free(remap);
|
||||
}
|
||||
|
||||
void free_config(struct sway_config *config) {
|
||||
if (!config) {
|
||||
return;
|
||||
|
|
@ -166,6 +174,12 @@ void free_config(struct sway_config *config) {
|
|||
}
|
||||
list_free(config->criteria);
|
||||
}
|
||||
if (config->key_remaps) {
|
||||
for (int i = 0; i < config->key_remaps->length; i++) {
|
||||
free_key_remap(config->key_remaps->items[i]);
|
||||
}
|
||||
list_free(config->key_remaps);
|
||||
}
|
||||
list_free(config->no_focus);
|
||||
list_free(config->active_bar_modifiers);
|
||||
list_free_items_and_destroy(config->config_chain);
|
||||
|
|
@ -228,6 +242,7 @@ static void config_defaults(struct sway_config *config) {
|
|||
if (!(config->input_configs = create_list())) goto cleanup;
|
||||
|
||||
if (!(config->cmd_queue = create_list())) goto cleanup;
|
||||
if (!(config->key_remaps = create_list())) goto cleanup;
|
||||
|
||||
if (!(config->current_mode = malloc(sizeof(struct sway_mode))))
|
||||
goto cleanup;
|
||||
|
|
|
|||
|
|
@ -9,12 +9,15 @@
|
|||
#include <wlr/types/wlr_keyboard_group.h>
|
||||
#include <xkbcommon/xkbcommon-names.h>
|
||||
#include "sway/commands.h"
|
||||
#include "sway/config.h"
|
||||
#include "sway/input/input-manager.h"
|
||||
#include "sway/input/keyboard.h"
|
||||
#include "sway/input/seat.h"
|
||||
#include "sway/input/cursor.h"
|
||||
#include "sway/ipc-server.h"
|
||||
#include "sway/server.h"
|
||||
#include "sway/tree/container.h"
|
||||
#include "sway/tree/view.h"
|
||||
#include "log.h"
|
||||
|
||||
#if WLR_HAS_SESSION
|
||||
|
|
@ -408,6 +411,75 @@ static void update_keyboard_state(struct sway_keyboard *keyboard,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void send_key_with_remap_check(struct sway_keyboard *keyboard, struct sway_seat *seat,
|
||||
struct wlr_seat *wlr_seat, uint32_t time_msec, uint32_t keycode, uint32_t state,
|
||||
const xkb_keysym_t *keysyms, size_t keysyms_len, uint32_t modifiers) {
|
||||
|
||||
// Check for remap on key press
|
||||
if (state == WL_KEYBOARD_KEY_STATE_PRESSED && config->key_remaps && keysyms_len > 0) {
|
||||
|
||||
// Get focused app for app-specific remaps
|
||||
struct sway_container *focused = seat_get_focused_container(seat);
|
||||
const char *focused_app_id = NULL;
|
||||
if (focused && focused->view) {
|
||||
focused_app_id = view_get_app_id(focused->view);
|
||||
if (!focused_app_id) {
|
||||
focused_app_id = view_get_class(focused->view);
|
||||
}
|
||||
}
|
||||
|
||||
// Check each remap rule (app-specific are at front, so checked first)
|
||||
for (int i = 0; i < config->key_remaps->length; i++) {
|
||||
struct sway_key_remap *remap = config->key_remaps->items[i];
|
||||
|
||||
// Skip if app-specific and doesn't match focused app
|
||||
if (remap->app_id && (!focused_app_id || !strstr(focused_app_id, remap->app_id))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if keysym and modifiers match
|
||||
for (size_t j = 0; j < keysyms_len; j++) {
|
||||
if (keysyms[j] == remap->from_keysym && modifiers == remap->from_modifiers) {
|
||||
sway_log(SWAY_DEBUG, "Remap: 0x%x+0x%x -> 0x%x+0x%x%s%s",
|
||||
remap->from_modifiers, remap->from_keysym,
|
||||
remap->to_modifiers, remap->to_keysym,
|
||||
remap->app_id ? " (app:" : "", remap->app_id ? remap->app_id : "");
|
||||
|
||||
// Build remapped modifiers
|
||||
struct wlr_keyboard_modifiers new_mods = keyboard->wlr->modifiers;
|
||||
uint32_t new_mod_mask = modifiers;
|
||||
new_mod_mask &= ~remap->from_modifiers; // Remove source mods
|
||||
new_mod_mask |= remap->to_modifiers; // Add target mods
|
||||
new_mods.depressed = new_mod_mask;
|
||||
|
||||
// Find the keycode for the target keysym using existing bind.c logic
|
||||
struct keycode_matches matches = get_keycode_for_keysym(remap->to_keysym);
|
||||
|
||||
if (matches.count != 1) {
|
||||
sway_log(SWAY_ERROR, "Failed to find unique keycode for remapped keysym 0x%x (found %d matches)",
|
||||
remap->to_keysym, matches.count);
|
||||
return;
|
||||
}
|
||||
|
||||
// XKB keycodes are offset by 8 from evdev keycodes
|
||||
uint32_t target_keycode = matches.keycode - 8;
|
||||
|
||||
// Send with remapped modifiers AND keycode
|
||||
wlr_seat_set_keyboard(wlr_seat, keyboard->wlr);
|
||||
wlr_seat_keyboard_notify_modifiers(wlr_seat, &new_mods);
|
||||
wlr_seat_keyboard_notify_key(wlr_seat, time_msec, target_keycode, state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No remap - send normally
|
||||
wlr_seat_set_keyboard(wlr_seat, keyboard->wlr);
|
||||
wlr_seat_keyboard_notify_key(wlr_seat, time_msec, keycode, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get keyboard grab of the seat from sway_keyboard if we should forward events
|
||||
* to it.
|
||||
|
|
@ -545,17 +617,14 @@ static void handle_key_event(struct sway_keyboard *keyboard,
|
|||
event->state);
|
||||
}
|
||||
|
||||
if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED) {
|
||||
// If the pressed event was sent to a client and we have a focused
|
||||
// surface immediately before this event, also send the released
|
||||
// event. In particular, don't send the released event to the IM grab.
|
||||
if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED && !handled) {
|
||||
bool pressed_sent = update_shortcut_state(
|
||||
&keyboard->state_pressed_sent, event->keycode,
|
||||
event->state, keyinfo.keycode, 0);
|
||||
if (pressed_sent && seat->wlr_seat->keyboard_state.focused_surface) {
|
||||
wlr_seat_set_keyboard(wlr_seat, keyboard->wlr);
|
||||
wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec,
|
||||
event->keycode, event->state);
|
||||
send_key_with_remap_check(keyboard, seat, wlr_seat, event->time_msec,
|
||||
event->keycode, event->state, keyinfo.raw_keysyms,
|
||||
keyinfo.raw_keysyms_len, keyinfo.raw_modifiers);
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -577,9 +646,10 @@ static void handle_key_event(struct sway_keyboard *keyboard,
|
|||
update_shortcut_state(
|
||||
&keyboard->state_pressed_sent, event->keycode, event->state,
|
||||
keyinfo.keycode, 0);
|
||||
wlr_seat_set_keyboard(wlr_seat, keyboard->wlr);
|
||||
wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec,
|
||||
event->keycode, event->state);
|
||||
|
||||
send_key_with_remap_check(keyboard, seat, wlr_seat, event->time_msec,
|
||||
event->keycode, event->state, keyinfo.raw_keysyms,
|
||||
keyinfo.raw_keysyms_len, keyinfo.raw_modifiers);
|
||||
}
|
||||
|
||||
free(device_identifier);
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ sway_sources = files(
|
|||
'commands/popup_during_fullscreen.c',
|
||||
'commands/primary_selection.c',
|
||||
'commands/reload.c',
|
||||
'commands/remap.c',
|
||||
'commands/rename.c',
|
||||
'commands/resize.c',
|
||||
'commands/scratchpad.c',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue