rbreaves / kinto

Mac-style shortcut keys for Linux & Windows.
http://kinto.sh
GNU General Public License v2.0
4.25k stars 213 forks source link

Fix for using Enter key to rename in file managers #734

Open RedBearAK opened 1 year ago

RedBearAK commented 1 year ago

@rbreaves @joshgoebel

This is just an FYI that may become a PR at some point, to enhance the Finder Mods. (Requires keyszer.)

I think I have a pretty solid (draft) fix for the issue of wanting to use the Enter key to rename files in file managers (like the Finder), but also needing it to still be the Enter key at times. It's a sort of smart toggle function that decides what to do with a variable state based on the key being passed to it, and two separate keymaps for when the variable is True or False. Still a work in progress, but it basically does what I want in Nautilus already. It should no longer be necessary to have an "alternate" shortcut like Shift+Cmd+Enter to actually send Enter while renaming.

Nautilus currently starts searching automatically when you start typing, so you don't need the Enter key after Cmd+F to find files. But other file managers will need testing and probably some other logical actions with different combos.

I think if I make this more generic it may be useful in other situations as well.


_enter_key_to_rename = True

def switch_enter_key_to_rename(combo):
    """Switch the state of using the Enter key as F2 to rename files"""
    def _switch_enter_key_to_rename():
        global _enter_key_to_rename
        if combo == Key.F2:
            _enter_key_to_rename = False
        elif combo == Key.ESC:
            _enter_key_to_rename = True
        else:
            _enter_key_to_rename = not _enter_key_to_rename
        combo_list = [combo]
        return combo_list
    return _switch_enter_key_to_rename

keymap("Nautilus Enter key to rename is True",{
    C("Enter"):     switch_enter_key_to_rename(Key.F2),
    C("RC-L"):      switch_enter_key_to_rename(C("RC-L")),
}, when = lambda ctx: ctx.wm_class == "Org.gnome.Nautilus" and _enter_key_to_rename)

keymap("Nautilus Enter key to rename is False",{
    C("Enter"):     switch_enter_key_to_rename(Key.ENTER),
    C("Esc"):       switch_enter_key_to_rename(Key.ESC),
}, when = lambda ctx: ctx.wm_class == "Org.gnome.Nautilus" and not _enter_key_to_rename)
joshgoebel commented 1 year ago

Nice. You could do this without two keymaps too... just dealing with the state 100% inside the helper function... not sure if that would be better or worse.

RedBearAK commented 1 year ago

@joshgoebel

Nice. You could do this without two keymaps too... just dealing with the state 100% inside the helper function... not sure if that would be better or worse.

I tried to do it with a single keymap at first, but couldn't quite figure out the logic. Since it wasn't just a simple on/off toggle I needed, it seemed easier to keep track of the state of things by separating the input combos into True and False keymaps. But still experimenting with different methods. Kind of amazed I got it to work as well as it does. LOL.

Sort of considering passing in two objects like (key, state) and see how I could make that work.

joshgoebel commented 1 year ago

Well you wouldn't pass state, state is global... but you'd likely have to pass a config that told it which keys to ultimately map to in either state:

keymap("Nautilus Enter key to rename is True",{
    C("Enter"):     switch_enter_key_to_rename(
        {"default": Key.F2, "whileRenaming": Key.Enter}
    ),
}, when = lambda ctx: ctx.wm_class == "Org.gnome.Nautilus")

Not sure it's better... but the fact that the left side of the mapping (the input keys/combos) are the same means that you only need a single map - it's just about making the right side functions smart enough.

If the mark system were made slightly more generic it could probably handle things like this in an abstract way.

RedBearAK commented 1 year ago

@joshgoebel

Well you wouldn't pass state, state is global... but you'd likely have to pass a config that told it which keys to ultimately map to in either state:

Hmm, yes. Interesting. I'd need to study a function that's already set up to receive something like that to figure out how to work with that sort of structure. Like the mark functions.

The "state" I was thinking of passing to make the logic inside the function simpler was just something like True/False/Not to tell it how to set the state of the boolean var. Rather than having multiple branches inside the function for specific combo/key inputs.

But actually with the separate keymaps I just realized the function can be much simpler and it still performs the same actions:

def switch_enter_key_to_rename(combo):
    """Switch the state of using the Enter key as F2 to rename files"""
    def _switch_enter_key_to_rename():
        global _enter_key_to_rename
        _enter_key_to_rename = not _enter_key_to_rename
        combo_list = [combo]
        return combo_list
    return _switch_enter_key_to_rename

keymap("Enter to Rename is True - Nautilus",{
    C("Enter"):     switch_enter_key_to_rename(Key.F2),
    C("RC-L"):      switch_enter_key_to_rename(C("RC-L")),
}, when = lambda ctx: ctx.wm_class == "Org.gnome.Nautilus" and _enter_key_to_rename)

keymap("Enter to Rename is False - Nautilus",{
    C("Enter"):     switch_enter_key_to_rename(Key.ENTER),
    C("Esc"):       switch_enter_key_to_rename(Key.ESC),
}, when = lambda ctx: ctx.wm_class == "Org.gnome.Nautilus" and not _enter_key_to_rename)

The unnecessary if-elif tree must have been a remnant of trying to make it all work with a single keymap. If a combo acts from within the "False" keymap, it naturally changes the state of Enter back to F2 by reversing the variable to True, and vice versa. The function really is back to just being a simple toggle.

I don't think I can justify making a more complex function just to be able to avoid using separate keymaps. It's so simple and straightforward to use it this way. I'll have to test all the other file managers supported in the Finder Mods to see what kind of quirks pop up, but I think this should work for all of them, with minor override variations.

joshgoebel commented 1 year ago

don't think I can justify making a more complex function just to be able to avoid using separate keymaps.

I think if you have to repeat this over and over that it might make sense to see if it could be done with a single keymap... imagine if you had a few more stateful things (now you have keymaps a zillion)... and do any of the regular combos still work during renaming? Now you'd have to repeat them in BOTH keymaps, etc... :-)

But your code, your call. :) I'm not the boss of kinto. :)

RedBearAK commented 1 year ago

Good questions, but I don't think it's as complicated as you might think. All the usual things like select-all, undo/cut/copy/paste, Option+arrows work fine while renaming. None of that is affected by the state of the enter-to-rename variable or what the Enter key will do next. Anything that already exists in a higher-level keymap (like "General GUI") and isn't being specifically overridden by one of these True/False keymaps should work as it normally would.

There is one keymap each for True and False for the whole "General File Managers" group, using the constructed filemanagerStr string to match any of them.

keymap("Enter to Rename is True - General File Managers",{
    C("Enter"):     switch_enter_key_to_rename(Key.F2),
    C("RC-L"):      switch_enter_key_to_rename(C("RC-L")),
}, when = lambda ctx: ctx.wm_class.casefold() in filemanagerStr and _enter_key_to_rename)

keymap("Enter to Rename is False - General File Managers",{
    C("Enter"):     switch_enter_key_to_rename(Key.ENTER),
    C("Esc"):       switch_enter_key_to_rename(Key.ESC),
}, when = lambda ctx: ctx.wm_class.casefold() in filemanagerStr and not _enter_key_to_rename)

Then I think at worst some of the individual file managers will need one or two combos in an override keymap. For instance Caja, the MATE file manager, wants you to hit Enter when searching, while Nautilus just starts searching while you type, and Enter would open the highlighted file instead of searching. So Caja needs an override for Cmd+F to toggle Enter back to Enter like Cmd+L already does in the general keymap.

keymap("Enter to Rename is True - Caja",{
    C("RC-F"):      switch_enter_key_to_rename(C("RC-F")),
}, when = lambda ctx: ctx.wm_class.casefold() == "caja" and _enter_key_to_rename)

If several file managers end up needing the same override(s) compared to Nautilus I can always make a new list and string and consolidate them into a single keymap. A "zillion" keymaps is certainly not going to be the end result. Thunar does inline searching and does not appear to need an override.

Actually I think there only needs to be one extra keymap and the "if True" combos can probably be consolidated into the normal keymap that doesn't care about the variable, leaving only the separate keymap that cares if the variable is False. But I'm too tired to think it through completely right now.

Maybe I'll be able to take a look at the mark functions and get a single-keymap approach to work later on.

joshgoebel commented 1 year ago

I don't think it's as complicated as you might think.

It may not be. :-)

RedBearAK commented 1 year ago

@joshgoebel

As I suspected the "if True" combos can be merged with the normal keymaps, since "if True" is the default. So far I only have a need for a single additional keymap for the "if False" state, with just the two combos inside. Individual apps that need it get an override inside their pre-existing keymap blocks for things like Cmd+F to revert Enter back to Enter temporarily.

It's turning out to be even simpler than I thought it would be, so far.

RedBearAK commented 1 year ago

@joshgoebel @rbreaves

Updated version of the Enter-to-rename function that dispenses with the need for the separate "if False" keymaps. Usage is slightly more complicated, but still relatively logical.

var = _enter_is_F2 (boolean)

So combo_if_false can be either a bool or a secondary combo.

Pretty sure this performs in all cases like the dual-keymap version.

_enter_is_F2 = True

def is_Enter_F2(combo_if_true, combo_if_false):
    """
    Send a different combo for Enter key depending on state of _enter_is_F2 variable, 
    or latch the variable to True or False to change the Enter key state on next use.
    """
    def _is_Enter_F2():
        global _enter_is_F2
        combo_list = [combo_if_true]
        if combo_if_false is False:
            _enter_is_F2 = False
        elif combo_if_false is True:
            _enter_is_F2 = True
        elif _enter_is_F2:
            _enter_is_F2 = False
        else:
            combo_list = [combo_if_false]
            _enter_is_F2 = True
        return combo_list
    return _is_Enter_F2

Usage examples: (This is actually every line in the Finder Mods that needs to use the function. There are no other examples.)

define_keymap(re.compile("dolphin", re.IGNORECASE),{
…
    C("Shift-RC-N"):    is_Enter_F2(C("F10"), False),       # Create new folder, toggle Enter to be Enter
…
},"Overrides for Dolphin - Finder Mods")

define_keymap(re.compile("spacefm", re.IGNORECASE),{
…
    C("Shift-RC-N"):    is_Enter_F2(C("RC-F"), False),        # Switch Enter to Enter. New folder is Ctrl+F(???)
…
},"Overrides for SpaceFM - Finder Mods")

define_keymap(re.compile(filemanStr, re.IGNORECASE),{
…
    C("Enter"):         is_Enter_F2(C("F2"),C("Enter")),        # Send F2 to rename files, unless var is False
    C("Shift-RC-N"):    is_Enter_F2(C("Shift-RC-N"), False),    # New folder, set Enter to Enter
    C("RC-L"):          is_Enter_F2(C("RC-L"), False),          # Set Enter to Enter for Location field
    C("RC-F"):          is_Enter_F2(C("RC-F"), False),          # Set Enter to Enter for Find field
    C("Esc"):           is_Enter_F2(C("Esc"), True),            # Send Escape, make sure Enter is back to F2
…
},"General File Managers - Finder Mods")
hyuri commented 9 months ago

This would be great