この記事について最初書こうと思ったときは、chromeのブラウザバックをやりやすくするための記事にしようと思ったのですが、それだと記事のボリュームが少なくなると思ったので、せっかくなのでWindowsのキーバインドを徹底的に改造しようって趣旨の記事にしました。
Windowsのキーバインド変更に使用するソフト
Windowsのキーバインドを変更するにはいろいろやり方があるのですが、「keyhac」というキーバインドをソフトウェアベースで変更することができるソフトがおすすめです。このブログでも何度か紹介しています。
以下が公式ページになります。
仮想デスクトップの切り替えをアプリケーションキーでできるようにする
前回の記事で書いたのですが、よかったらどうぞ。

アプリケーションキーを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を使って自分好みのキーバインドに変更してみてください。では。
コメント