From 7450117f32c0bf55462b697cf1e049c76884cbb0 Mon Sep 17 00:00:00 2001 From: Patrick Spek Date: Fri, 15 Apr 2022 11:31:33 +0200 Subject: Add post about my tremc adventures --- content/posts/2022/2022-04-15-fixing-w-in-tremc.md | 140 +++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 content/posts/2022/2022-04-15-fixing-w-in-tremc.md diff --git a/content/posts/2022/2022-04-15-fixing-w-in-tremc.md b/content/posts/2022/2022-04-15-fixing-w-in-tremc.md new file mode 100644 index 0000000..31adf13 --- /dev/null +++ b/content/posts/2022/2022-04-15-fixing-w-in-tremc.md @@ -0,0 +1,140 @@ +--- +date: 2022-04-15 +title: Fixing ^w in tremc +tags: +- python +- transmission +- tremc +--- + +I like collecting GNU+Linux ISOs. I'd like to think I have a pretty serious +collection of these things. I have a pretty good and stable Internet connection, +so I collect them in my torrent client, and let them seed pretty much forever, +so other people can enjoy them too. + +For this hobby, I'm using [Transmission](https://transmissionbt.com/), with +[tremc](https://github.com/tremc/tremc) as the frontend. I've been using it for +a while, but there's always been a small thing bothering me. Whenever you get a +prompt to specify the name of the torrent, or the directory to which its +contents should be downloaded, `^w` immediately kills the entire application. + +By regular shell standards, I'm used to `^w` not killing the application, but +just removing from the cursor to the start of the previous word. I don't often +change the names of the torrents, but when I do, I often use `^w` to quickly +remove one or two words in the path. With tremc, this sadly means me killing the +application, being upset for a little while as I restart it, and hold down the +backspace for a while to get the intended effect. + +Until last night. I set out to read the documentation to fix this issue once and +for all. I dug into the manual, which specifies that a configuration file at +`~/.config/tremc/settings.cfg` is read at startup. However, the manual +doesn't specify anything about the format of this file. There's also no other +manual pages included in the package. + +The Gentoo package specifies a homepage for this project on Github, so I open up +my least-hated browser and see if there's anything there. Good news, the +repository contains a sample `settings.cfg`, so I can read that and get an idea +on how to do keybinds. And the examples do show how to _set_ keybinds. But it +doesn't seem to be able to _remove_ keybinds. This is kind of a bummer. Setting +a keybind to an empty value didn't seem to do the trick either. This leaves only +one option, patching the defaults. + +This was actually pretty straightforward, just look for the list of default +keybinds, and remove a single line. + +```patch +@@ -222,7 +222,6 @@ class GConfig: + # First in list: 0=all 1=list 2=details 3=files 4=tracker 16=movement + # +256 for RPC>=14, +512 for RPC>=16 + 'list_key_bindings': [0, ['F1', '?'], 'List key bindings'], +- 'quit_now': [0, ['^w'], 'Quit immediately'], + 'quit': [1, ['q'], 'Quit'], + 'leave_details': [2, ['BACKSPACE', 'q'], 'Back to torrent list'], + 'go_back_or_unfocus': [2, ['ESC', 'BREAK'], 'Unfocus or back to torrent list'], +``` + +Since I'm just testing, and this program is a single Python file, I just edit +the file in-place, and delay making a proper patch out of for later. Restarting +tremc, going to the new torrent, pressing `m` (for "move"), and hitting `^w` to +try and remove a single word, hoping for a quick and easy fix, I was met with +tremc just quitting again. This was not what I wanted. + +So opening up the file again with everyone's favourite editor, I search around +for the `quit_now` function. Surely that'll bring me closer? The `quit_now` +string doesn't seem to be used anywhere else, apart from the function that +defines what the keybind action should do, `action_quit_now`. This seems to +simply defer to `exit_now`, which leads me to a bit of code with an special case +to `exit_now` if a certain character is detected. This character appears to be a +`^w`, which is exactly what I'm trying to stop. So, let's patch that out too. + +```patch +@@ -3760,10 +3759,7 @@ class Interface: + self.update_torrent_list([win]) + + def wingetch(self, win): +- c = win.getch() +- if c == K.W_: +- self.exit_now = True +- return c ++ return win.getch() + + def win_message(self, win, height, width, message, first=0): + ypos = 1 +``` + +Restart tremc and test again. Still exiting immediately upon getting a `^w`. +This bit of code gives me some new insights, it appears `K.W_` is related to the +keycode of a `^w`. So I continue the search, this time looking for `K.W_`. This +appears to be used later on to create a list of characters which should act as +`esc` in certain contexts. Removing the `K.W_` from this is simple enough. + +```patch +@@ -5039,7 +5047,7 @@ def parse_config_key(interface, config, gconfig, common_keys, details_keys, list + else: + gconfig.esc_keys = (K.ESC, K.q, curses.KEY_BREAK) + gconfig.esc_keys_no_ascii = tuple(x for x in gconfig.esc_keys if x not in range(32, 127)) +- gconfig.esc_keys_w = gconfig.esc_keys + (K.W_,) ++ gconfig.esc_keys_w = gconfig.esc_keys + gconfig.esc_keys_w_enter = gconfig.esc_keys_w + (K.LF, K.CR, curses.KEY_ENTER) + gconfig.esc_keys_w_no_ascii = tuple(x for x in gconfig.esc_keys_w if x not in range(32, 127)) +``` + +Restart, test, and... Nothing. This is an improvement, the program isn't exiting +immediately, it just does nothing. I know that `^u` works as I expect, so +perhaps it needs some love to have `^w` work properly as well. I search for +`K.U_`, and indeed, there is code dedicated to this keypress, in a long `elif` +construct. So I add some code to get `^w` working as well. After a bit of +fiddling, and realizing I've spent way too much time on adding a torrent, I've +settled on this little bit of love. + +```patch +@@ -3957,6 +3953,18 @@ class Interface: + # Delete from cursor until beginning of line + text = text[index:] + index = 0 ++ elif c == K.W_: ++ # Delete from cursor to beginning of previous word... mostly ++ text_match = re.search("[\W\s]+", text[::-1]) ++ ++ if text_match.span()[0] == 0: ++ # This means the match was found immediately, I can't be ++ # bothered to make this any nicer. ++ text = text[:-1] ++ index -= 1 ++ else: ++ text = text[:-text_match.span()[0]] ++ index -= text_match.span()[0] + elif c in (curses.KEY_HOME, K.A_): + index = 0 + elif c in (curses.KEY_END, K.E_): +``` + +It looks for the first non-word character or a space, starting from the end of +the string (`[::-1]` reverses a string in Python). The resulting `Match` object +can tell me how many characters I need to delete in the `.span()[0]` value. A +small exception is created if that value is `0`, otherwise the logic below +doesn't work well. + +It's not perfect, but it gets the job done well enough, and I don't like Python +enough to spend more time on it than I've already done. I am open for better +solutions that work better, though! -- cgit v1.1