summaryrefslogtreecommitdiff
path: root/content/posts/2022/2022-04-15-fixing-w-in-tremc.md
blob: c434a6e7f474717641a60527c681c5d2870aa7d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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 a 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!