Windowsのキーバインドを徹底的に改造する方法

この記事について最初書こうと思ったときは、chromeのブラウザバックをやりやすくするための記事にしようと思ったのですが、それだと記事のボリュームが少なくなると思ったので、せっかくなのでWindowsのキーバインドを徹底的に改造しようって趣旨の記事にしました。

スポンサーリンク

Windowsのキーバインド変更に使用するソフト

Windowsのキーバインドを変更するにはいろいろやり方があるのですが、「keyhac」というキーバインドをソフトウェアベースで変更することができるソフトがおすすめです。このブログでも何度か紹介しています。

以下が公式ページになります。

craftware - Keyhac (日本語)
ダウンロード
スポンサーリンク

仮想デスクトップの切り替えをアプリケーションキーでできるようにする

前回の記事で書いたのですが、よかったらどうぞ。

Windowsの仮想デスクトップをアプリケーションキーで切り替えられるようにする
結構マニアックなネタになってしまうんですが、普段パソコンで作業をしていると仮想デスクトップが使いたくなりました。でもWindo...

アプリケーションキーをWindowsキーにバインドすることで実現できます。

chromeのブラウザバックをWindows+Spaceでできるようにする。

まず、chromeのブラウザバックのショートカットキーを知っておく必要があります。Windowsの場合だと以下になります。

Alt + ← : 元に戻る Alt + → : 先へ進む 基本的によく使うのは元に戻るですので、このショートカットをWindows+Spaceでできるようにしたいです。 keyhacのconfig.pyに以下のように記述します。

keymap_global[ "U0-Space" ] = "A-Left"

キーバインドがうまく動作しない理由

keyhacでconfigy.pyを編集してreloadしてもキーバインドがうまく動作しないことがあります。keyhac自体がおそらくマイナーなソフトであまり知らない方もいるかもしれませんが、keyhacのconfig.pyの中身を見てみると、デフォルトでいろいろな機能が実装されているのがわかります。

  • ウインドウの操作をするキーバインド
  • マウスカーソルを操作するためのキーバインド
  • clipbord管理ソフトを起動するキーバインド
  • アプリケーションのランチャーをたちあげるキーバインド

keyhacは実はただキーをバインドするだけでなく、ランチャーとクリップボード履歴の管理機能までそなわっているのです。であるがゆえに、デフォルトで使用されているキーバインドがあります。これを自分の手で編集する必要があるのです。

たとえばさきほど記述したkeymap_global[ “U0-Space” ]は、アプリたちあげのためのショートカットとしてすでに登録されているのです。またこのU0というのがそもそもなんなのかがわからなかったりするかもしれません。

とりあえず最後にすべてのコードはのせます

アプリケーションキー + C でウインドウをとじる

以下のように記述すれば可能です。

        keymap_global[ "LWin-C" ] = close              # Close the window
        keymap.replaceKey( "Apps", "LWin" )

わたしの場合はアプリケーションキーをウインドウズキーにバインドしていますので、このような記述になっています。

カスタマイズ後のkeyhacの全コード

わたしがWindowsのキーバインドを変更するために行ったkeyhacのカスタマイズコードをすべてのせておきます。自分のためのバックアップにもなりますから一石二鳥です。

import sys
import os
import datetime

import pyauto
from keyhac import *


def configure(keymap):

    # --------------------------------------------------------------------
    # Text editer setting for editting config.py file

    # Setting with program file path (Simple usage)
    if 1:
        keymap.editor = "notepad.exe"

    # Setting with callable object (Advanced usage)
    if 0:
        def editor(path):
            shellExecute( None, "notepad.exe", '"%s"'% path, "" )
        keymap.editor = editor

    # --------------------------------------------------------------------
    # Customizing the display

    # Font
    keymap.setFont( "MS Gothic", 12 )

    # Theme
    keymap.setTheme("black")

    # --------------------------------------------------------------------

    # Simple key replacement
    keymap.replaceKey( "LWin", 235 )
    #keymap.replaceKey( "RWin", 255 )

    # User modifier key definition
    keymap.defineModifier( 235, "User0" )

    # Global keymap which affects any windows
    if 1:
        keymap_global = keymap.defineWindowKeymap()

        # USER0-Up/Down/Left/Right : Move active window by 10 pixel unit
        keymap_global[ "U0-Left"  ] = keymap.MoveWindowCommand( -10, 0 )
        keymap_global[ "U0-Right" ] = keymap.MoveWindowCommand( +10, 0 )
        keymap_global[ "U0-Up"    ] = keymap.MoveWindowCommand( 0, -10 )
        keymap_global[ "U0-Down"  ] = keymap.MoveWindowCommand( 0, +10 )

        # USER0-Shift-Up/Down/Left/Right : Move active window by 1 pixel unit
        keymap_global[ "U0-S-Left"  ] = keymap.MoveWindowCommand( -1, 0 )
        keymap_global[ "U0-S-Right" ] = keymap.MoveWindowCommand( +1, 0 )
        keymap_global[ "U0-S-Up"    ] = keymap.MoveWindowCommand( 0, -1 )
        keymap_global[ "U0-S-Down"  ] = keymap.MoveWindowCommand( 0, +1 )

        # USER0-Ctrl-Up/Down/Left/Right : Move active window to screen edges
        keymap_global[ "U0-C-Left"  ] = keymap.MoveWindowToMonitorEdgeCommand(0)
        keymap_global[ "U0-C-Right" ] = keymap.MoveWindowToMonitorEdgeCommand(2)
        keymap_global[ "U0-C-Up"    ] = keymap.MoveWindowToMonitorEdgeCommand(1)
        keymap_global[ "U0-C-Down"  ] = keymap.MoveWindowToMonitorEdgeCommand(3)

        # Clipboard history related
        keymap_global[ "C-S-Z"   ] = keymap.command_ClipboardList     # Open the clipboard history list
        keymap_global[ "C-S-X"   ] = keymap.command_ClipboardRotate   # Move the most recent history to tail
        keymap_global[ "C-S-A-X" ] = keymap.command_ClipboardRemove   # Remove the most recent history
        keymap.quote_mark = "> "                                      # Mark for quote pasting

        # Keyboard macro
        #keymap_global[ "U0-0" ] = keymap.command_RecordToggle
        #keymap_global[ "U0-1" ] = keymap.command_RecordStart
        #keymap_global[ "U0-2" ] = keymap.command_RecordStop
        #keymap_global[ "U0-3" ] = keymap.command_RecordPlay
        #keymap_global[ "U0-4" ] = keymap.command_RecordClear


    # USER0-F1 : Test of launching application
    if 1:
        keymap_global[ "U0-F1" ] = keymap.ShellExecuteCommand( None, "notepad.exe", "", "" )


    # USER0-F2 : Test of sub thread execution using JobQueue/JobItem
    if 1:
        def command_JobTest():

            def jobTest(job_item):
                shellExecute( None, "notepad.exe", "", "" )

            def jobTestFinished(job_item):
                print( "Done." )

            job_item = JobItem( jobTest, jobTestFinished )
            JobQueue.defaultQueue().enqueue(job_item)

        keymap_global[ "U0-F2" ] = command_JobTest


    # Test of Cron (periodic sub thread procedure)
    if 0:
        def cronPing(cron_item):
            os.system( "ping -n 3 www.google.com" )

        cron_item = CronItem( cronPing, 3.0 )
        CronTable.defaultCronTable().add(cron_item)


    # USER0-F : Activation of specific window
    if 1:
        keymap_global[ "U0-F" ] = keymap.ActivateWindowCommand( "cfiler.exe", "CfilerWindowClass" )


    # USER0-E : Activate specific window or launch application if the window doesn't exist
    if 1:
        def command_ActivateOrExecuteNotepad():
            wnd = Window.find( "Notepad", None )
            if wnd:
                if wnd.isMinimized():
                    wnd.restore()
                wnd = wnd.getLastActivePopup()
                wnd.setForeground()
            else:
                executeFunc = keymap.ShellExecuteCommand( None, "notepad.exe", "", "" )
                executeFunc()

        keymap_global[ "U0-E" ] = command_ActivateOrExecuteNotepad


    # Ctrl-Tab : Switching between console related windows
    if 1:

        def isConsoleWindow(wnd):
            if wnd.getClassName() in ("PuTTY","MinTTY","CkwWindowClass"):
                return True
            return False

        keymap_console = keymap.defineWindowKeymap( check_func=isConsoleWindow )

        def command_SwitchConsole():

            root = pyauto.Window.getDesktop()
            last_console = None

            wnd = root.getFirstChild()
            while wnd:
                if isConsoleWindow(wnd):
                    last_console = wnd
                wnd = wnd.getNext()

            if last_console:
                last_console.setForeground()

        keymap_console[ "C-TAB" ] = command_SwitchConsole


    # USER0-Space : Application launcher using custom list window
    if 1:
        def command_PopApplicationList():

            # If the list window is already opened, just close it
            if keymap.isListWindowOpened():
                keymap.cancelListWindow()
                return

            def popApplicationList():

                applications = [
                    ( "Notepad", keymap.ShellExecuteCommand( None, "notepad.exe", "", "" ) ),
                    ( "Paint", keymap.ShellExecuteCommand( None, "mspaint.exe", "", "" ) ),
                ]

                websites = [
                    ( "Google", keymap.ShellExecuteCommand( None, "https://www.google.co.jp/", "", "" ) ),
                    ( "Facebook", keymap.ShellExecuteCommand( None, "https://www.facebook.com/", "", "" ) ),
                    ( "Twitter", keymap.ShellExecuteCommand( None, "https://twitter.com/", "", "" ) ),
                ]

                listers = [
                    ( "App",     cblister_FixedPhrase(applications) ),
                    ( "WebSite", cblister_FixedPhrase(websites) ),
                ]

                item, mod = keymap.popListWindow(listers)

                if item:
                    item[1]()

            # Because the blocking procedure cannot be executed in the key-hook,
            # delayed-execute the procedure by delayedCall().
            keymap.delayedCall( popApplicationList, 0 )

        #keymap_global[ "U0-Space" ] = command_PopApplicationList


    # USER0-Alt-Up/Down/Left/Right/Space/PageUp/PageDown : Virtul mouse operation by keyboard
    if 1:
        keymap_global[ "U0-A-Left"  ] = keymap.MouseMoveCommand(-10,0)
        keymap_global[ "U0-A-Right" ] = keymap.MouseMoveCommand(10,0)
        keymap_global[ "U0-A-Up"    ] = keymap.MouseMoveCommand(0,-10)
        keymap_global[ "U0-A-Down"  ] = keymap.MouseMoveCommand(0,10)
        keymap_global[ "D-U0-A-Space" ] = keymap.MouseButtonDownCommand('left')
        keymap_global[ "U-U0-A-Space" ] = keymap.MouseButtonUpCommand('left')
        keymap_global[ "U0-A-PageUp" ] = keymap.MouseWheelCommand(1.0)
        keymap_global[ "U0-A-PageDown" ] = keymap.MouseWheelCommand(-1.0)
        keymap_global[ "U0-A-Home" ] = keymap.MouseHorizontalWheelCommand(-1.0)
        keymap_global[ "U0-A-End" ] = keymap.MouseHorizontalWheelCommand(1.0)


    # Execute the System commands by sendMessage
    if 1:
        def close():
            wnd = keymap.getTopLevelWindow()
            wnd.sendMessage( WM_SYSCOMMAND, SC_CLOSE )

        def screenSaver():
            wnd = keymap.getTopLevelWindow()
            wnd.sendMessage( WM_SYSCOMMAND, SC_SCREENSAVE )

        keymap_global[ "U0-C" ] = close              # Close the window
        keymap_global[ "U0-S" ] = screenSaver        # Start the screen-saver


    # Test of text input
    if 1:
        keymap_global[ "U0-H" ] = keymap.InputTextCommand( "Hello / こんにちは" )


    # For Edit box, assigning Delete to C-D, etc
    if 1:
        keymap_edit = keymap.defineWindowKeymap( class_name="Edit" )

        keymap_edit[ "C-D" ] = "Delete"              # Delete
        keymap_edit[ "C-H" ] = "Back"                # Backspace
        keymap_edit[ "C-K" ] = "S-End","C-X"         # Removing following text


    # Customize Notepad as Emacs-ish
    # Because the keymap condition of keymap_edit overlaps with keymap_notepad,
    # both these two keymaps are applied in mixed manner.
    if 1:
        keymap_notepad = keymap.defineWindowKeymap( exe_name="notepad.exe", class_name="Edit" )

        # Define Ctrl-X as the first key of multi-stroke keys
        #keymap_notepad[ "C-X" ] = keymap.defineMultiStrokeKeymap("C-X")

        #keymap_notepad[ "C-P" ] = "Up"                  # Move cursor up
        #keymap_notepad[ "C-N" ] = "Down"                # Move cursor down
        #keymap_notepad[ "C-F" ] = "Right"               # Move cursor right
        #keymap_notepad[ "C-B" ] = "Left"                # Move cursor left
        #keymap_notepad[ "C-A" ] = "Home"                # Move to beginning of line
        #keymap_notepad[ "C-E" ] = "End"                 # Move to end of line
        #keymap_notepad[ "A-F" ] = "C-Right"             # Word right
        #keymap_notepad[ "A-B" ] = "C-Left"              # Word left
        #keymap_notepad[ "C-V" ] = "PageDown"            # Page down
        #keymap_notepad[ "A-V" ] = "PageUp"              # page up
        #keymap_notepad[ "A-Comma" ] = "C-Home"          # Beginning of the document
        #keymap_notepad[ "A-Period" ] = "C-End"          # End of the document
        #keymap_notepad[ "C-X" ][ "C-F" ] = "C-O"        # Open file
        #keymap_notepad[ "C-X" ][ "C-S" ] = "C-S"        # Save
        #keymap_notepad[ "C-X" ][ "C-W" ] = "A-F","A-A"  # Save as
        #keymap_notepad[ "C-X" ][ "U" ] = "C-Z"          # Undo
        #keymap_notepad[ "C-S" ] = "C-F"                 # Search
        #keymap_notepad[ "A-X" ] = "C-G"                 # Jump to specified line number
        #keymap_notepad[ "C-X" ][ "H" ] = "C-A"          # Select all
        #keymap_notepad[ "C-W" ] = "C-X"                 # Cut
        #keymap_notepad[ "A-W" ] = "C-C"                 # Copy
        #keymap_notepad[ "C-Y" ] = "C-V"                 # Paste
        #keymap_notepad[ "C-X" ][ "C-C" ] = "A-F4"       # Exit

        keymap_global[ "C-A" ] = "Home"                # Move to beginning of line
        keymap_global[ "C-E" ] = "End"                 # Move to end of line
        keymap_global[ "C-Q" ] = "Delete"    
        keymap_global[ "C-W" ] = "Enter"  
        keymap_global[ "C-R" ] = "C-A"

        keymap_global[ "U0-A" ] = "Home"                # Move to beginning of line
        keymap_global[ "U0-E" ] = "End"                 # Move to end of lines
        keymap_global[ "U0-Q" ] = "Delete"
        keymap_global[ "U0-W" ] = "Enter"
        keymap_global[ "U0-R" ] = "C-A"
        keymap_global[ "U0-C" ] = "C-C"
        keymap_global[ "U0-F" ] = "C-F"
        keymap_global[ "U0-X" ] = "C-X"
        keymap_global[ "U0-V" ] = "C-V"
        keymap_global[ "U0-S" ] = "C-S"
        keymap_global[ "U0-P" ] = "C-P"
        keymap_global[ "U0-D" ] = "C-D"
        keymap_global[ "U0-K" ] = "C-K"
        keymap_global[ "U0-Shift-A" ] = "Shift-Home"
        keymap_global[ "U0-Shift-E" ] = "Shift-End"
        keymap_global[ "BackQuote" ] = "Tab"

        keymap_global[ "Tab" ] = "Left"
        keymap_global[ "F1" ] = "Up"
        keymap_global[ "LAlt" ] = "Down"
        keymap_global[ "RAlt" ] = "Right"
        keymap_global[ "Caps" ] = "Alt-BackQuote"
        keymap_global[ "LWin-Right" ] = "LWin-C-Right"
        keymap_global[ "LWin-Left" ] = "LWin-C-Left"
        keymap_global[ "LWin-Up" ] = "Home"
        keymap_global[ "LWin-Down" ] = "End"
        keymap_global[ "U0-Space" ] = "A-Left"
        keymap_global[ "LWin-C" ] = close              # Close the window
        

        keymap.replaceKey( "Apps", "LWin" )

    # Customizing clipboard history list
    if 1:
        # Enable clipboard monitoring hook (Default:Enabled)
        keymap.clipboard_history.enableHook(True)

        # Maximum number of clipboard history (Default:1000)
        keymap.clipboard_history.maxnum = 1000

        # Total maximum size of clipboard history (Default:10MB)
        keymap.clipboard_history.quota = 10*1024*1024

        # Fixed phrases
        fixed_items = [
            ( "name@server.net",     "name@server.net" ),
            ( "Address",             "San Francisco, CA 94128" ),
            ( "Phone number",        "03-4567-8901" ),
        ]

        # Return formatted date-time string
        def dateAndTime(fmt):
            def _dateAndTime():
                return datetime.datetime.now().strftime(fmt)
            return _dateAndTime

        # Date-time
        datetime_items = [
            ( "YYYY/MM/DD HH:MM:SS",   dateAndTime("%Y/%m/%d %H:%M:%S") ),
            ( "YYYY/MM/DD",            dateAndTime("%Y/%m/%d") ),
            ( "HH:MM:SS",              dateAndTime("%H:%M:%S") ),
            ( "YYYYMMDD_HHMMSS",       dateAndTime("%Y%m%d_%H%M%S") ),
            ( "YYYYMMDD",              dateAndTime("%Y%m%d") ),
            ( "HHMMSS",                dateAndTime("%H%M%S") ),
        ]

        # Add quote mark to current clipboard contents
        def quoteClipboardText():
            s = getClipboardText()
            lines = s.splitlines(True)
            s = ""
            for line in lines:
                s += keymap.quote_mark + line
            return s

        # Indent current clipboard contents
        def indentClipboardText():
            s = getClipboardText()
            lines = s.splitlines(True)
            s = ""
            for line in lines:
                if line.lstrip():
                    line = " " * 4 + line
                s += line
            return s

        # Unindent current clipboard contents
        def unindentClipboardText():
            s = getClipboardText()
            lines = s.splitlines(True)
            s = ""
            for line in lines:
                for i in range(4+1):
                    if i>=len(line) : break
                    if line[i]=='\t':
                        i+=1
                        break
                    if line[i]!=' ':
                        break
                s += line[i:]
            return s

        full_width_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&’()*+,−./:;<=>?@[¥]^_‘{|}~0123456789 "
        half_width_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~0123456789 "

        # Convert to half-with characters
        def toHalfWidthClipboardText():
            s = getClipboardText()
            s = s.translate(str.maketrans(full_width_chars,half_width_chars))
            return s

        # Convert to full-with characters
        def toFullWidthClipboardText():
            s = getClipboardText()
            s = s.translate(str.maketrans(half_width_chars,full_width_chars))
            return s

        # Save the clipboard contents as a file in Desktop directory
        def command_SaveClipboardToDesktop():

            text = getClipboardText()
            if not text: return

            # Convert to utf-8 / CR-LF
            utf8_bom = b"\xEF\xBB\xBF"
            text = text.replace("\r\n","\n")
            text = text.replace("\r","\n")
            text = text.replace("\n","\r\n")
            text = text.encode( encoding="utf-8" )

            # Save in Desktop directory
            fullpath = os.path.join( getDesktopPath(), datetime.datetime.now().strftime("clip_%Y%m%d_%H%M%S.txt") )
            fd = open( fullpath, "wb" )
            fd.write(utf8_bom)
            fd.write(text)
            fd.close()

            # Open by the text editor
            keymap.editTextFile(fullpath)

        # Menu item list
        other_items = [
            ( "Quote clipboard",            quoteClipboardText ),
            ( "Indent clipboard",           indentClipboardText ),
            ( "Unindent clipboard",         unindentClipboardText ),
            ( "",                           None ),
            ( "To Half-Width",              toHalfWidthClipboardText ),
            ( "To Full-Width",              toFullWidthClipboardText ),
            ( "",                           None ),
            ( "Save clipboard to Desktop",  command_SaveClipboardToDesktop ),
            ( "",                           None ),
            ( "Edit config.py",             keymap.command_EditConfig ),
            ( "Reload config.py",           keymap.command_ReloadConfig ),
        ]

        # Clipboard history list extensions
        keymap.cblisters += [
            ( "Fixed phrase", cblister_FixedPhrase(fixed_items) ),
            ( "Date-time", cblister_FixedPhrase(datetime_items) ),
            ( "Others", cblister_FixedPhrase(other_items) ),
        ]

まとめ

keyhacの作者は天才だと思います。 keyhacを使って自分好みのキーバインドに変更してみてください。では。

コメント

タイトルとURLをコピーしました