diff options
authorPatrick Spek <>2022-04-15 11:31:33 +0200
committerPatrick Spek <>2022-04-15 11:31:33 +0200
commit7450117f32c0bf55462b697cf1e049c76884cbb0 (patch)
parent136e24bb61f5594f8b4f018d7e9c572a33863b25 (diff)
Add post about my tremc adventures
1 files changed, 140 insertions, 0 deletions
diff --git a/content/posts/2022/ b/content/posts/2022/
new file mode 100644
index 0000000..31adf13
--- /dev/null
+++ b/content/posts/2022/
@@ -0,0 +1,140 @@
+date: 2022-04-15
+title: Fixing ^w in tremc
+- 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](, with
+[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.
+@@ -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.
+@@ -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.
+@@ -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.
+@@ -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 ="[\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!