Files
2025-09-29 00:52:08 +02:00

363 lines
9.8 KiB
Plaintext
Executable File

/**
* KeyboardActionManager by P.J. Janssen, www.threesixty.nl
* Feel free to reuse or modify this code, but please leave the credits in.
*/
/**
* The KeyboardAction represents a single action assigned to a shortcut key combination.
*
* The shortcut is a combination of mod_key_code and key_code. Both of these can be easily obtained from a string or flags
* through the KeyboardActionManager.getModKeyCode and similar functions.
*
* A KeyboardAction should always have a table_id, but depending on the type (Action or Macro), it can have either a persisten_id,
* or a combination of macro_name and macro_category.
*
* The run() function executes the action or macro.
*/
struct KeyboardAction
(
mod_key_code,
key_code,
table_id,
persistent_id,
macro_name,
macro_category,
fn isAction =
(
(persistent_id != undefined AND table_id != 647394);
),
fn isMacro =
(
(macro_name != undefined AND macro_category != undefined AND table_id == 647394);
),
fn run =
(
if (isAction()) then
actionMan.executeAction table_id persistent_id;
else if (isMacro()) then
macros.run macro_category macro_name;
),
fn compare a1 a2 =
(
case of
(
(a1.table_id < a2.table_id): -1
(a1.table_id > a2.table_id): 1
default: 0
)
)
)
/**
* The KeyboardActionManager struct handles reading and writing kbd files.
*
* After being instantiated, the readActions functions should be run, to load the users shortcuts.
* The actions property is an array containing all loaded actions.
*
* This struct was written for some specific needs, and not necessarily to provide a complete interface to kbd files.
*/
struct KeyboardActionManager
(
actions,
main_table_id = 0,
macro_table_id = 647394,
/**
* GET (MOD)KEYCODE FUNCTIONS
*/
-- Returns the mod_key_code based on the modifier key flags provided to the function.
fn getModKeyCode altPressed:false ctrlPressed:false shiftPressed:false =
(
local mod_key_code = 3;
if (shiftPressed) do mod_key_code = bit.or mod_key_code 4;
if (ctrlPressed) do mod_key_code = bit.or mod_key_code 8;
if (altPressed) do mod_key_code = bit.or mod_key_code 16;
-- Return mod_key_code.
mod_key_code;
),
--Returns the mod_key_code for the supplied string. Format: "ctrl+alt+x"
fn getModKeyCodeFromString key_str =
(
key_str = toUpper key_str;
local str_split = filterString key_str "+";
local mod_key_code = 3;
for key in str_split do
(
case key of
(
"SHIFT" : mod_key_code = bit.or mod_key_code 4;
"CTRL" : mod_key_code = bit.or mod_key_code 8;
"CONTROL" : mod_key_code = bit.or mod_key_code 8;
"ALT" : mod_key_code = bit.or mod_key_code 16;
)
)
-- Return mod_key_code.
mod_key_code;
),
--Returns the uppercase key_code of the first occurrence of a single character in a string with the format : "ctrl+alt+x"
fn getKeyCodeFromString key_str =
(
key_str = toUpper key_str;
local str_split = filterString key_str "+";
local notfound = true;
local key_code = 0;
for key in str_split while notfound do
(
if (key.count == 1) do
(
key_code = bit.charasint key;
notfound = false;
)
)
--Return the key_code.
key_code
),
/**
* GET / RUN ACTIONS FUNCTIONS
*/
fn getActionFromKeyCode mod_key_code key_code table_id1:undefined table_id2:undefined =
(
if (actions == undefined) do
throw "No actions loaded.";
local notfound = true;
local action;
for a in actions while notfound do
(
if (a.key_code == key_code AND a.mod_key_code == mod_key_code) do
(
if (table_id1 == undefined OR a.table_id == table_id1 OR a.table_id == table_id2) do
(
action = a;
notfound = false;
)
)
)
-- Return found action (or undefined if no action was found).
action;
),
fn runActionFromKeyCode mod_key_code key_code table_id1:undefined table_id2:undefined =
(
local action = getActionFromKeyCode mod_key_code key_code table_id1:table_id1 table_id2:table_id2;
if (action != undefined) do
action.run();
),
fn runActionFromKeyString str =
(
local mod_key_code = getModKeyCodeFromString str;
local key_code = getKeyCodeFromString str;
runActionFromKeyCode mod_key_code key_code table_id1:main_table_id table_id2:macro_table_id;
),
/**
* ADD ACTION TO ACTIONSET
*/
fn addAction mod_key_code key_code table_id persistent_id:undefined macro_name:undefined macro_category:undefined replace:false =
(
if (persistent_id == undefined AND macro_name == undefined) do
throw "Either persistent_id or macro_name + macro_category parameter required.";
if ((macro_name != undefined AND macro_category == undefined) OR (macro_name == undefined AND macro_category != undefined)) do
throw "macro_name and macro_category have to be used together.";
if (persistent_id != undefined AND macro_name != undefined AND macro_category != undefined) do
throw "Using both persistent_id and macro_name + macro_category is not allowed.";
if (actions == undefined) do
throw "No actions defined.";
-- Actions that have to be removed are stored in this array to be removed after iteration is completed.
local removeActions = #();
-- Iterate through actions and check for conflicting actions.
for a = 1 to actions.count do
(
local action = actions[a];
if (action.table_id == table_id) do
(
local conflict = false;
-- Check for duplicate key combination.
if (action.mod_key_code == mod_key_code AND action.key_code == key_code) do conflict = true;
-- Check for duplicate persistent_id if necessary.
if (persistent_id != undefined) do
if (action.persistent_id == persistent_id) do
conflict = true;
-- Check for duplicate macro_name and macro_category if necessary.
if (macro_name != undefined AND macro_category != undefined) do
if (action.macro_name == macro_name AND action.macro_category == macro_category) do
conflict = true;
-- Flag action for removal if it is conflicting with new action and replace is true.
-- If there are conflicts and replace is false, return false.
if (conflict AND not replace) then
return false;
else if (conflict AND replace) do
append removeActions a;
)
)
-- Remove conflicting actions.
for a in removeActions do deleteItem actions a;
-- Append new action.
append actions (KeyboardAction mod_key_code:mod_key_code key_code:key_code table_id:table_id persistent_id:persistent_id macro_name:macro_name macro_category:macro_category);
--Adding was successful, return true;
true;
),
fn addActionFromKeyString key_str table_id persistent_id:undefined macro_name:undefined macro_category:undefined replace:false =
(
local mod_key_code = getModKeyCodeFromString key_str;
local key_code = getKeyCodeFromString key_str;
addAction mod_key_code key_code table_id persistent_id:persistent_id macro_name:macro_name macro_category:macro_category replace:replace;
),
/**
* WRITE ACTIONS FILE
*/
fn writeActions file:(actionMan.getKeyboardFile()) =
(
if (actions == undefined) do
throw "No actions defined.";
qsort actions KeyboardAction.compare;
--Create a backup of the file we're going to write to.
local backup_file = file + ".bak";
if ((getFileSize file) > 0) do
(
if ((getFileSize backup_file) > 0) do
deleteFile backup_file;
if (not (copyFile file backup_file)) do
throw "Unable to make a backup kbd file. This is too tricky to do without man.";
)
local kbd_fileStream = openFile file mode:"w";
if (kbd_fileStream == undefined) do
throw "Unable to write to file." kbdFile;
try (
local i = 0;
local prev_table_id;
for a in actions do
(
if (a.table_id != prev_table_id) do
i = 0;
if (a.isAction()) then
format "%=% % % %\n" i a.mod_key_code a.key_code a.persistent_id a.table_id to:kbd_fileStream;
else if (a.isMacro()) then
format "%=% % %`% %\n" i a.mod_key_code a.key_code a.macro_name a.macro_category a.table_id to:kbd_fileStream;
prev_table_id = a.table_id;
i += 1;
)
)
catch
(
-- Restore backup and throw exception.
close kbd_fileStream;
deleteFile file;
copyFile backup_file file;
--deleteFile backup_file;
throw();
)
close kbd_fileStream;
),
/**
* READ & PARSE ACTIONS FILE
*/
fn readActions kbdFile:(actionMan.getKeyboardFile()) =
(
local kbd_fileStream = openFile kbdFile mode:"rS"
if (kbd_fileStream == undefined) do
throw "Keyboard-File could not be opened." kbdFile;
actions = #();
while (not eof kbd_fileStream) do
(
local kbd_line = readLine kbd_fileStream;
local split_line = filterString kbd_line "= ";
if (split_line.count > 4) do
(
local action = KeyboardAction mod_key_code:(split_line[2] as integer) key_code:(split_line[3] as integer) table_id:(split_line[split_line.count] as integer);
if (split_line.count > 5 OR (matchPattern split_line[4] pattern:"*`*")) then
(
--Macro.
local macro = split_line[4];
if (split_line.count > 5) do
(
for i = 5 to (split_line.count - 1) do
macro += " " + split_line[i];
)
local split_macro = filterString macro "`";
action.macro_name = split_macro[1];
action.macro_category = split_macro[2];
)
else
(
--Action.
action.persistent_id = split_line[4];
)
append actions action;
)
)
close kbd_fileStream;
),
fn maxReloadKeyboardFile =
(
actionMan.loadKeyboardFile (actionMan.getKeyboardFile());
)
)