[PYTHON] keyhac Personal settings summary

If I could bring only one to the uninhabited island, I would bring keyhac.

basic configuration

Edit the configure () function in config.py, which is located in the same directory as the exe.


import sys
import os
import datetime
import re #add to
import time #add to
import urllib.parse #add to

import pyauto
from keyhac import *

def configure(keymap):

    #Edit with vscode obtained from scoop (notepad if not)
    EDITOR_PATH = r"C:\Users\{}\scoop\apps\vscode\current\Code.exe".format(os.environ.get("USERNAME"))
    if not os.path.exists(EDITOR_PATH):
        EDITOR_PATH = "notepad.exe"
    keymap.editor = EDITOR_PATH

    # theme
    keymap.setFont("HackGen Console", 16)

    #Set the conversion / non-conversion key as user modifier
    keymap.replaceKey("(29)", 235)      #Let unconverted (29) be 235
    keymap.defineModifier(235, "User0") #Use no conversion as a U0 modifier key
    keymap.replaceKey("(28)", 236)      #Let the conversion (28) be 236
    keymap.defineModifier(236, "User1") #Use the transform as a U1 modifier key

    #Clipboard history enabled

    #Maximum size of history
    keymap.clipboard_history.maxnum = 200
    keymap.clipboard_history.quota = 10*1024*1024

    #Ctrl from history+Symbol when pasting a quote with Enter
    keymap.quote_mark = "> "

I like the font HackGen.

Below, all customization details will be written in configure (). Please close your eyes that there are no rules for variable names and function names.

Move cursor


    #Kili map that is always valid
    keymap_global = keymap.defineWindowKeymap()
    for modifier in ("", "S-", "C-", "A-", "C-S-", "C-A-", "S-A-", "C-A-S-"):
        #Up down left right
        keymap_global[modifier + "U0-H"] = modifier + "Left"
        keymap_global[modifier + "U0-J"] = modifier + "Down"
        keymap_global[modifier + "U0-K"] = modifier + "Up"
        keymap_global[modifier + "U0-L"] = modifier + "Right"
        # Home / End
        keymap_global[modifier + "U0-A"] = modifier + "Home"
        keymap_global[modifier + "U0-E"] = modifier + "End"
        # Enter
        keymap_global[modifier + "U0-Space"] = modifier + "Enter"

        #Thoroughly ignore the "Katakana Hiragana Rome" key
        keymap_global["D-" + modifier + "(240)"] = lambda: None
        keymap_global["U-" + modifier + "(240)"] = lambda: None
        keymap_global["D-" + modifier + "(241)"] = lambda: None
        keymap_global["U-" + modifier + "(241)"] = lambda: None
        keymap_global["D-" + modifier + "(242)"] = lambda: None
        keymap_global["U-" + modifier + "(242)"] = lambda: None

    # [B]ackSpace / [D]elete
    keymap_global["U0-D"] = "Delete"
    keymap_global["U0-B"] = "Back"
    keymap_global["C-U0-D"] = "C-Delete"
    keymap_global["C-U0-B"] = "C-Back"
    keymap_global["S-U0-B"] = "S-Home", "C-X" #Cut to the beginning
    keymap_global["S-U0-D"] = "S-End", "C-X" #Cut to the end
    keymap_global["C-S-U0-B"] = "Home", "Back", "End" #Concatenate with previous line
    keymap_global["C-S-U0-D"] = "End", "Delete", "End" #Concatenate with next line

Refer to this site for how to define cursor movement by modifier keys collectively. In Autohotkey, you can make modifier keys transparent by adding {blind}.

IME control

IME on / off is controlled by conversion + J and no conversion + F. When I thought about Japanese and Foreign, it was useful because the key had a protrusion for the home position.


    # [J]apanese / [F]oreign
    keymap_global["U1-J"] = lambda: keymap.getWindow().setImeStatus(1)
    keymap_global["U0-F"] = lambda: keymap.getWindow().setImeStatus(0)

Other key remap


    #1 line selection
    keymap_global["U1-A"] = "End", "S-Home"

    #Original reconversion
    keymap_global["U0-R"] = "LWin-Slash"

    #The one I saw on emacs
    keymap_global["LC-H"] = "Back"

    #Focus on taskbar
    keymap_global["C-U0-W"] = "W-T"

    #Insert one line up and down
    keymap_global["U0-I"]   = "End", "Enter"
    keymap_global["S-U0-I"] = "Home", "Enter", "Up"

    # escape
    keymap_global["O-(235)"] = "Esc"
    keymap_global["U0-X"]    = "Esc"

    #List of open windows
    keymap_global["U0-W"] = "LCtrl-LAlt-Tab", "U-LAlt" #Explicitly raise Alt to avoid holding down

    #Confirmed by alphanumeric
    keymap_global["U1-N"]   = "F10", "(243)"
    keymap_global["S-U1-N"] = "F10", "Enter"

    #Context menu
    keymap_global["U0-C"] = "S-F10"

    keymap_global["U0-N"] = "F2", "Right"
    keymap_global["S-U0-N"] = "F2", "C-Home"
    keymap_global["C-U0-N"] = "F2"

Self-made hotkey

General function

Create the following functions to hotkey frequently used functions.


    def delay(sec = 0.05):

    def get_clippedText():
        return (getClipboardText() or "")

    def paste_string(s):

    def copy_string(sec = 0.05):
        return get_clippedText()

    def send_input(ime_mode, keys, sleep = 0.01):
        if ime_mode is not None:
            if keymap.getWindow().getImeStatus() != ime_mode:
        for key in keys:

keyhac treats the key input ʻInputKeyCommand () and the string input ʻInputTextCommand () separately. I created the final send_input () function to implement input specifications like Send, ABC {Enter} in Autohotkey. IME can also be specified ((243) is a half-width / full-width key).

Character input system

Use the above send_input () to reduce the effort of entering characters as much as possible.

Enter parentheses


    #Enter parentheses and move the cursor in between
    def wrap_with_brackets(pair, after_ime_mode):
        keys = [pair, "Left"]
        if after_ime_mode == 1:
        return lambda: send_input(0, keys, 0.05)
    brackets = [
        ("U0-2"            , '""'          , 0),
        ("U0-7"            , "''"          , 0),
        ("U0-8"            , "\u300E\u300F", 1), # WHITE CORNER BRACKET 『』
        ("U0-9"            , "\u3010\u3011", 1), # BLACK LENTICULAR BRACKET 【】
        ("U0-AtMark"       , "``"          , 0),
        ("U1-2"            , "\u201C\u201D", 1), # DOUBLE QUOTATION MARK “”
        ("U1-7"            , "\u3014\u3015", 1), # TORTOISE SHELL BRACKET 〔〕
        ("U1-8"            , "\uFF08\uFF09", 1), # FULLWIDTH PARENTHESIS ()
        ("U1-9"            , "()"          , 0),
        ("U0-OpenBracket"  , "\u300c\u300d", 1), # CORNER BRACKET 「」
        ("U1-OpenBracket"  , "\uFF3B\uFF3D", 1), # FULLWIDTH SQUARE BRACKET []
        ("U0-CloseBracket" , "[]"          , 0),
        ("U1-CloseBracket" , "{}"          , 0),
        ("C-U0-Comma"      , "<>"          , 0),
        ("C-U0-Period"     , "</>"         , 0),
        ("U0-Y"            , "\u3008\u3009", 1), # Angle Bracket 〈〉
        ("U1-Y"            , "\u300A\u300B", 1), # Double Angle Bracket 《》
    for brc in brackets:
        keymap_global[brc[0]] = wrap_with_brackets(brc[1], brc[2])

Specifying special parentheses with unicode code points is a matter of appearance in the editor. There is no problem with direct send_input (0, ["U0-8 "," "" "," (243) "]).

Direct input of symbols

Since it is rare to enter punctuation marks, commas, colons, etc. with keys and then convert them further, we make it possible to enter them directly.

The mechanism is simple, if you look at the IME state at the time of input and it is on, you just press Ctrl + M (shortcut for confirmation in most IME) additionally. IME can be turned off automatically after entering characters such as @ that are rarely entered in Japanese.

There is a reason why Enter is not confirmed, and it is a device to prevent accidentally pressing Enter when it is already in the direct input state such as a password input form. However, be aware that some browsers have a function assigned to Ctrl + M (toggle the mute function in Firefox). For Google Japanese input, pressing the half-width / full-width key (243) again confirms the input content at that time, but this method is adopted in consideration of versatility.


    #Enter directly regardless of whether IME is on or off
    def direct_input(key, turnoff_ime_later = False):
        key_list = [key]
        if keymap.getWindow().getImeStatus() == 1:
            if turnoff_ime_later:
        send_input(None, key_list)

    for key in [
        ("AtMark"      , True),
        ("Caret"       , False),
        ("CloseBracket", False),
        ("Colon"       , False),
        ("Comma"       , False),
        ("LS-AtMark"   , True),
        ("LS-Caret"    , False),
        ("LS-Colon"    , False),
        ("LS-Comma"    , False),
        ("LS-Minus"    , False),
        ("LS-Period"   , False),
        ("LS-SemiColon", False),
        ("LS-Slash"    , False),
        ("LS-Yen"      , True),
        ("OpenBracket" , False),
        ("Period"      , False),
        ("SemiColon"   , False),
        ("Slash"       , False),
        ("Yen"         , True),
        def _wrapper(k, i):
            return lambda: direct_input(k, i)
        keymap_global[key[0]] = _wrapper(key[0], key[1])

    #Left shift+Symbol with number keys
    for n in "123456789":
        def _wrapper(k, i):
            return lambda: direct_input(k, i)
        key = "LS-" + n
        if n in ("2", "3", "4"):
            keymap_global[key] = _wrapper(key, True)
            keymap_global[key] = _wrapper(key, False)

    #If you enter an uppercase alphabet with a left shift, turn off subsequent IMEs.
        def _wrapper(k):
            return lambda: direct_input(k, True)
        key = "LS-" + alphabet
        keymap_global[key] = _wrapper(key)

I learned how to create _wrapper () when assigning a lambda expression in a loop at this site.

We will make it possible to directly enter other symbols (the following is an example). Since the IME state can be specified with the first argument of send_input () above, the character string is directly input in a pseudo manner by selecting "IME off-> character input-> half-width / full-width key press". ..


    keymap_global["BackSlash"]      = lambda: direct_input("S-BackSlash", False)
    keymap_global["U0-Minus"]       = lambda: send_input(0, ["\u2015\u2015", "(243)"]) # HORIZONTAL BAR * 2
    keymap_global["U1-Minus"]       = lambda: send_input(0, ["Minus"])
    keymap_global["U0-U"]           = lambda: send_input(0, "_")
    keymap_global["U0-Colon"]       = lambda: send_input(0, ["Colon"])
    keymap_global["U0-Comma"]       = lambda: send_input(0, ["\uFF0C", "(243)"]) # FULLWIDTH COMMA ,
    keymap_global["U0-Period"]      = lambda: send_input(0, ["Period"])
    keymap_global["U0-Slash"]       = lambda: send_input(0, ["Slash"])

Date input


    def input_date(fmt):
        d = datetime.datetime.today()
        if fmt == "jp":
            date_str = "{}Year{}Month{}Day".format(d.year, d.month, d.day)
            send_input(0, [date_str, "(243)"])
            date_str = d.strftime(fmt)
            send_input(0, date_str, 0)
    keymap_global["U1-D"] = keymap.defineMultiStrokeKeymap("date format: 1=>YYYYMMDD, 2=>YYYY/MM/DD, 3=>YYYY.MM.DD, 4=>YYYY-MM-DD, 5=>YYYY MM month DD day")
    keymap_global["U1-D"]["1"] = lambda: input_date(r"%Y%m%d")
    keymap_global["U1-D"]["2"] = lambda: input_date(r"%Y/%m/%d")
    keymap_global["U1-D"]["3"] = lambda: input_date(r"%Y.%m.%d")
    keymap_global["U1-D"]["4"] = lambda: input_date(r"%Y-%m-%d")
    keymap_global["U1-D"]["5"] = lambda: input_date("jp")

Efficiency of various daily work


    #Paste as plain text
    keymap_global["U0-V"] = lambda: paste_string(get_clippedText())

    #Remove whitespace and paste as plain text
    keymap_global["U1-V"] = lambda: paste_string(re.sub(r"\s", "", get_clippedText()))

    #IME off mom When you type, select the last word and IME on
    keymap_global["U1-Space"] = lambda: send_input(1, ["C-S-Left"])

    #Open the selected URL
    def open_url():
        url = (copy_string()).strip()
        if url.startswith("http"):
            run_url = url
        elif url.startswith("file:///"):
            local_path = url.replace("file:///", "")
            local_path = urllib.parse.unquote(local_path)
            if not os.path.exists(local_path):
                return None
            run_url = local_path
            return None
        keymap.ShellExecuteCommand("open", run_url, None, None)()
    keymap_global["D-U0-O"] = open_url

    #Re-enter the selected half-width alphanumeric characters with IME on
    def re_input_as_kana():
        origin = get_clippedText()
        if origin:
        selection = copy_string(0.1)
        if selection:
            key_list = []
            noblank = re.sub(r"\s", "", selection)
            for k in noblank:
                if k == "-":
            send_input(1, key_list, 0)
        if origin:
    keymap_global["U1-I"] = re_input_as_kana

Web search system

If you press the key after no conversion + S in the selected state, it will be searched by various engines (if it is not copied correctly, it will be searched by the character string that was in the clipboard immediately before).

Hold down Shift when pressing the second key to search for keywords by enclosing each word in double quotes, and hold down Ctrl to erase hiragana.

Unless you search for keywords with Shift, punctuation marks such as commas are converted to spaces, so it's okay if the string selection is a little appropriate.


    def quote_each_word(s):
        ret = []
        for w in re.split(r"\s+", s):
        return " ".join(ret)

    def punctuation_to_space(s):
        ret = re.sub(r"[\W_]+", " ", s)
        return ret.strip()

    def search_on_web(URLstr, mode = None):
        selection = copy_string(0.1)
        if len(selection) < 200:
            single_spaced = (re.sub(r"\s+", " ", selection)).strip()
            if mode == "strict":
                search_str = quote_each_word(single_spaced)
                search_str = punctuation_to_space(single_spaced)
                if mode == "without_hira":
                    search_str = re.sub(r"[Ah-Hmm]+", " ", search_str)
                    search_str = re.sub(r"\s+", " ", search_str)
                    search_str = search_str.strip()
            encoded = urllib.parse.quote(search_str)
            keymap.ShellExecuteCommand("open", URLstr + encoded, None, None)()

    engine_url = [
        ("A", r"https://www.amazon.co.jp/s?i=stripbooks&k="),
        ("C", r"https://ci.nii.ac.jp/books/search?q="),
        ("E", r"http://webcatplus.nii.ac.jp/pro/?q="),
        ("G", r"http://www.google.co.jp/search?q="),
        ("I", r"https://www.google.com/search?tbm=isch&q="),
        ("M", r"https://www.google.co.jp/maps/search/"),
        ("N", r"https://iss.ndl.go.jp/books?any="),
        ("O", r"https://map.goo.ne.jp/search/q/"),
        ("S", r"https://scholar.google.co.jp/scholar?q="),
        ("T", r"https://twitter.com/search?f=live&q="),
        ("Y", r"http://www.google.co.jp/search?tbs=li:1&q=site%3Ayuhikaku.co.jp%20intitle%3A"),
        ("W", r"https://www.worldcat.org/search?q="),
    mode_modifier = [
        (""  , None),
        ("S-", "strict"),
        ("C-", "without_hira"),

    keymap_global["U0-S"] = keymap.defineMultiStrokeKeymap('S-:quote-each, C-:without-hiragana')
    for e in engine_url:
        for m in mode_modifier:
            search_key = m[0] + e[0]
            engine_url = e[1]
            mode = m[1]
            def _wrapper(url, md):
                return lambda: search_on_web(url, md)
            keymap_global["U0-S"][search_key] = _wrapper(engine_url, mode)

Google search from anywhere

With no conversion + Q, if the chrome window is open, activate it to open a new tab, if not, launch chrome.


    def find_window(arg_exe, arg_class):
        wnd = pyauto.Window.getDesktop().getFirstChild()
        last_found = None
        while wnd:
            if wnd.isVisible() and not wnd.getOwner():
                if wnd.getClassName() == arg_class and wnd.getProcessName() == arg_exe:
                    last_found = wnd
            wnd = wnd.getNext()
        return last_found

    def google_search():
        if keymap.getWindow().getProcessName() == "chrome.exe":
            send_input(1, ["C-T", "C-K"])
            wnd = find_window("chrome.exe", "Chrome_WidgetWin_1")
            if wnd:
                send_input(1, ["C-LWin-1", "C-T", "C-K"], 0.05)
                send_input(1, ["LWin-1"])
    keymap_global["U0-Q"] = google_search

When I tried to activate the window with the provided ʻActivateWindowCommand ()`, the phenomenon that "the taskbar only blinks and does not go into the foreground" occurred at a frequency that cannot be ignored, so call the chrome registered in the taskbar with Win + numbers. I am doing it. (If the window can be forcibly and surely brought to the front by the program, it can be used for malicious viruses, so it is unavoidable for security.)

There is also a method called pyauto.Window.find () that identifies the window, but only the class name can be specified as an argument. Chromium apps such as slack and vscode that have a window class of Chrome_WidgetWin_1 cannot be identified from chrome, so I tried to loop through the open window and search.

Just adding a commentary to the contents of my daily life has made it quite long ...

In addition, I am customizing various things such as processing and pasting the character string of the clipboard, activating a specific window, manipulating the window size, etc. (If you are motivated) I will summarize it in another article.

Recommended Posts

keyhac Personal settings summary
Python3 programming functions personal summary
Personal Ubuntu & WSL2 setup summary
[Linux] [Initial Settings] [Flutter] Summary
Summary of Proxy connection settings
Jupyter Notebook Magic Command Personal Summary
Python package management tool personal summary
Django static file (static) related settings summary