From 9798cc561d4ef6590e6d9e2ab33a6452cb6f89a8 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 9 Feb 2024 16:05:45 -0500 Subject: [PATCH] feature: custom actions --- USERGUIDE.md | 45 +++++++-- src/miracle_config.cpp | 112 +++++++++++++++++++++++ src/miracle_config.h | 8 ++ src/miracle_window_management_policy.cpp | 7 ++ 4 files changed, 165 insertions(+), 7 deletions(-) diff --git a/USERGUIDE.md b/USERGUIDE.md index 09850f6e..7cf1acf6 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -1,7 +1,7 @@ > This user manual will keep up to date with the current state of the project. > Please note that the information in here is likely to change with future release. -# Default Key Commands +# Built-in Key Commands - `meta + enter`: Open new terminal - `meta + h`: Switch current lane to horizontal layout mode - `meta + v`: Switch current lane to vertical layout mode @@ -30,10 +30,12 @@ - Window CANNOT be resized or moved with the pointer # Configuration File + +## Location The configuration file will be written blank the first time that you start the compositor. The file is named `miracle-wm.yaml` and it is written to your config directory, most likely at `~/.config/miracle-wm.yaml`. -## Data Types +## Types First, let's define some reoccurring data types in the configuration file: - `ModifierKey`: represents a modifier to be used in conjunction with another key press (e.g. Ctrl + Alt + Delete; Ctrl and Alt would be modifiers) @@ -89,19 +91,40 @@ First, let's define some reoccurring data types in the configuration file: }; ``` -## Configuration Definition +- `CustomAction`: defines a custom action to execute when the provided keybind is inputted. + ```c++ + struct CustomAction + { + // Action to execute + command: String + + // Action will fire based on this key event + action: "up" | "down" | "repeat" | "modifiers"; + + // Modifiers required for the action to trigger + modifiers: Modifier[]; + + // Name of the keycode that the action should respond to. + // See https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h + // for the list of available keycodes (e.g. KEY_ENTER, KEY_Z, etc.) + key: KeyCodeName; + }; + ``` + +## Definition With those types defined, the following table defines the allowed key/value pairs: | Key | Default | Type | Description | |--------------------------|---------|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | action_key | `meta` | `Modifier` | The default key that is used to initate any action. | | default_action_overrides | `[]` | `DefaultActionOverride[]` | A list overrides to apply to built-in actions. Actions may be overridden more than once and will respond to multiple key combinations as a result. Defining at least one override disables the default action defined in [Default Key Commands](#default-key-commands) | -| gap_size_x | 10 | `int` | Size of the gaps in pixels horizontally between windows | | -| gap_size_y | 10 | `int` | Size of the gaps in pixels vertically between windows | | -| startup_apps | [] | `String[]` | List of applications to be started when the compositor starts | +| custom_actions | [] | `CustomAction[]` | A list of custom applications that I user can execute. These actions always have precedence over built-in actions. | +| gap_size_x | 10 | `int` | Size of the gaps in pixels horizontally between windows | | +| gap_size_y | 10 | `int` | Size of the gaps in pixels vertically between windows | | +| startup_apps | [] | `String[]` | List of applications to be started when the compositor starts | -## Example Configuration +## Example ```yaml action_key: alt # Set the primary action key to alt default_action_overrides: @@ -111,6 +134,14 @@ default_action_overrides: - ctrl - shift key: KEY_ENTER + +custom_actions: # Set meta + D to open wofi + - command: wofi --show=drun + action: down + modifiers: + - primary + key: KEY_D + gap_size_x: 20 gap_size_y: 20 startup_apps: diff --git a/src/miracle_config.cpp b/src/miracle_config.cpp index b8adabb8..38462805 100644 --- a/src/miracle_config.cpp +++ b/src/miracle_config.cpp @@ -377,6 +377,95 @@ MiracleConfig::MiracleConfig() key_commands[i].push_back(default_key_commands[i]); } + // Custom actions + if (config["custom_actions"]) + { + auto const custom_actions = config["custom_actions"]; + if (!custom_actions.IsSequence()) + { + mir::log_error("custom_actions: value must be an array"); + return; + } + + for (auto i = 0; i < custom_actions.size(); i++) + { + auto sub_node = custom_actions[i]; + if (!sub_node["command"]) + { + mir::log_error("custom_actions: missing command"); + continue; + } + + if (!sub_node["action"]) + { + mir::log_error("custom_actions: missing action"); + continue; + } + + if (!sub_node["modifiers"]) + { + mir::log_error("custom_actions: missing modifiers"); + continue; + } + + if (!sub_node["key"]) + { + mir::log_error("custom_actions: missing key"); + continue; + } + + // TODO: Copy & paste here + auto command = sub_node["command"].as(); + auto action = sub_node["action"].as(); + MirKeyboardAction keyboard_action; + if (action == "up") + keyboard_action = MirKeyboardAction::mir_keyboard_action_up; + else if (action == "down") + keyboard_action = MirKeyboardAction::mir_keyboard_action_down; + else if (action == "repeat") + keyboard_action = MirKeyboardAction::mir_keyboard_action_repeat; + else if (action == "modifiers") + keyboard_action = MirKeyboardAction::mir_keyboard_action_modifiers; + else { + mir::log_error("custom_actions: Unknown keyboard action: %s", action.c_str()); + continue; + } + + auto key = sub_node["key"].as(); + auto code = libevdev_event_code_from_name(EV_KEY, + key.c_str()); //https://stackoverflow.com/questions/32059363/is-there-a-way-to-get-the-evdev-keycode-from-a-string + if (code < 0) + { + mir::log_error( + "custom_actions: Unknown keyboard code in configuration: %s. See the linux kernel for allowed codes: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h", + key.c_str()); + continue; + } + + auto modifiers_node = sub_node["modifiers"]; + + if (!modifiers_node.IsSequence()) + { + mir::log_error("custom_actions: Provided modifiers is not an array"); + continue; + } + + uint modifiers = 0; + for (auto j = 0; j < modifiers_node.size(); j++) + { + auto modifier = modifiers_node[j].as(); + modifiers = modifiers | parse_modifier(modifier); + } + + custom_key_commands.push_back({ + keyboard_action, + modifiers, + code, + command + }); + } + } + // Gap sizes if (config["gap_size_x"]) { @@ -445,6 +534,29 @@ MirInputEventModifier MiracleConfig::get_input_event_modifier() const return (MirInputEventModifier)primary_modifier; } +CustomKeyCommand const* +MiracleConfig::matches_custom_key_command(MirKeyboardAction action, int scan_code, unsigned int modifiers) const +{ + // TODO: Copy & paste + for (auto const& command : custom_key_commands) + { + if (action != command.action) + continue; + + auto command_modifiers = command.modifiers; + if (command_modifiers & miracle_input_event_modifier_default) + command_modifiers = command_modifiers & ~miracle_input_event_modifier_default | get_input_event_modifier(); + + if (command_modifiers != modifiers) + continue; + + if (scan_code == command.key) + return &command; + } + + return nullptr; +} + DefaultKeyCommand MiracleConfig::matches_key_command(MirKeyboardAction action, int scan_code, unsigned int modifiers) const { for (int i = 0; i < DefaultKeyCommand::MAX; i++) diff --git a/src/miracle_config.h b/src/miracle_config.h index 08654d5e..7e7a5182 100644 --- a/src/miracle_config.h +++ b/src/miracle_config.h @@ -57,6 +57,11 @@ struct KeyCommand int key; }; +struct CustomKeyCommand : KeyCommand +{ + std::string command; +}; + typedef std::vector KeyCommandList; class MiracleConfig @@ -64,6 +69,7 @@ class MiracleConfig public: MiracleConfig(); [[nodiscard]] MirInputEventModifier get_input_event_modifier() const; + CustomKeyCommand const* matches_custom_key_command(MirKeyboardAction action, int scan_code, unsigned int modifiers) const; [[nodiscard]] DefaultKeyCommand matches_key_command(MirKeyboardAction action, int scan_code, unsigned int modifiers) const; [[nodiscard]] int get_gap_size_x() const; [[nodiscard]] int get_gap_size_y() const; @@ -74,6 +80,8 @@ class MiracleConfig static const uint miracle_input_event_modifier_default = 1 << 18; uint primary_modifier = mir_input_event_modifier_meta; + + std::vector custom_key_commands; KeyCommandList key_commands[DefaultKeyCommand::MAX]; int gap_size_x = 10; diff --git a/src/miracle_window_management_policy.cpp b/src/miracle_window_management_policy.cpp index be5d6636..35395a26 100644 --- a/src/miracle_window_management_policy.cpp +++ b/src/miracle_window_management_policy.cpp @@ -92,6 +92,13 @@ bool MiracleWindowManagementPolicy::handle_keyboard_event(MirKeyboardEvent const auto const scan_code = miral::toolkit::mir_keyboard_event_scan_code(event); auto const modifiers = miral::toolkit::mir_keyboard_event_modifiers(event) & MODIFIER_MASK; + auto custom_key_command = config.matches_custom_key_command(action, scan_code, modifiers); + if (custom_key_command != nullptr) + { + external_client_launcher.launch(custom_key_command->command); + return true; + } + auto key_command = config.matches_key_command(action, scan_code, modifiers); if (key_command == DefaultKeyCommand::MAX) return false;