torazaemon2016’s blog

手書き文字認識メモ開発

AutohotkeyのスクリプトからHotkeyやHotstringsの一覧表示を作成しダブルクリックでエディタ画面の該当行に行けるahkスクリプト

はじめに

Autohotkeyのおかげで快適なWindows生活を送ることができて、大変感謝しています。 しかし、Autohotkeyも、どんどん定義していくと、なにがどれだったかわからなくなったりしていきます。

そこで、Hotkeyの一覧が表示できるahkスクリプトを作成しました。

ListViewなので、タイトル行の「Key」とかをクリックすれば、ソートされるので、複数のファイルに分かれた定義での重複なども分かりやすくなりました。

Hotkey一覧表示例

Hotstrings表示例

Keyword: ahk, V2, Hotkey, Hotstrings, 一覧表示, VScode

Hotkeyの一覧表示が可能なものを探しましたが

ListHotkeys

Autohotkeyの「ListHotkeys」で一覧は出せますが、それの説明はないので、定義済みっていうことだけは、わかる。

Output list of all hotkeys used in ahk

Output list of all hotkeys used in ahk : r/AutoHotkey

V2のもので、起動するとカーソルのある位置に黒い箱でHotkeyの行が提示されるようになります。

でも、「!4」や日本語キーボードの無変換キーを使った「vk1D & 4::」などがうまく表示されていません。 いろいろと手を入れれば可能と思いますが。。。

Hotkey Help - Display Active AHK Hotkeys and Hotstrings

https://www.autohotkey.com/board/topic/93889-hotkey-help-display-active-ahk-hotkeys-and-hotstrings/

Autohotkey V1で動くもの

Win + F1 で起動したけど、Hotkey定義が殆ど出てこなかった。

Hotキーが(Shift,Alt,Ctrl,Win)決め打ちっぽいところもあるので、 Hotkeyを(日本語キーボードの)vk1C,vk1Dとかでやっているようだと、ダメっぽい。

HotKey Tool

GitHub - kunkel321/HotKey-Tool: A tool to list, filter, and launch hotkeys from running scripts.

これは、一覧を出せるし、「!g」の行をダブルクリックすると「実行」できるものです。

このほかにも、WindowsのスタートメニューなどからLinkとかも拾ってきたり、 ICON表示もあって「ラウンチャー」として使えるものなのです。

が、vk1D & のものが実行できなかったので、一覧表示というなら使えるかです。

AutoHotkeyのコメントからヘルプを作成する

https://qiita.com/draganmaistir/items/36bd90fa6c5a93736c70

これが、Hotkeyの一覧表示としてはわかりやすいものでした。

でもこれも、V1のスクリプトなので、その環境が要ること。 また、ahkスクリプト中に「指定の形式でコメント」を書いて表示するものなので、ちょっと手間がかかるものです。

なので結局、自分で作ることにしました。

作ったもの

Hotkey一覧表示例

  • 書かれているスクリプトをそのまま使って、そこからHotkeyなどを見つけて一覧にする。
  • Hotkeyの定義は「 :: 」が目印。
  • 定義と同じ行に 「 ; 」以降のコメントとして書かれたものを説明文として表示する。 vk1D & q::  ; 迷子になったカーソルを見つける

  • 一覧表示されている行を(ダブル)クリックすると、エディタのその該当行のところに飛ぶ (2026/02/09 UpdateしEnterキーでも飛ぶようになりました) torazaemon2016.hatenablog.jp

  • Hotstringsもtype:Hotstringsとして一覧となる。

  • 関数定義もtype:Functionとして一覧に載せる。関数の定義は、行頭からアルファベットで始まり()があり ^[a-zA-Z].*\(.*\) 、 := の記載のない行です。
  • コメントアウトしたHotkeyも一覧にtype:commentoutとして載ります。条件は行の先頭に「;」(セミコロン)が入っている「::」の行です。
  • Include も一覧にして、どのファイルをIncludeしているかもわかるようにしました。
  • 起動するたびに一覧ウィンドウが増えていくので、適宜Closeしてください。

と、「編集作業のヘルプ」としての要素が強いものになりました。

スクリプト

editor変数で、使用しているエディタを定義

最初にeditor変数にコマンドへのファイルパスを指定しています。 現在は、ユーザインストールのVScodeになっています。

エディタによって、指定行番号へ飛ぶという処理が違うので、スクリプトの最後のほうにあるEdit_file()の中も修正してください。 これにより、ダブルクリックでその該当行に飛んでいきます。

VScodeの場合:

 target := "--goto `"" A_ScriptDir "\" FileName ":" Line "`""    ; VScode

Notepad++の場合:

 target := "`"" A_ScriptDir "\" Filename "`"" " -n" Line     ; notepad++

その他のエディタについては、各自で修正してみてください。

一覧にしたいahkスクリプトは、target変数にて設定

target := A_ScriptDir "\*.ahk"              ; Folder + \ + *.ahk
;target := A_ScriptDir "\Autohotkey.ahk"    ; only 1 File

ワイルドカードで指定するか、1つのファイルを指定するです。

上の場合は、ahkスクリプトのあるフォルダ内の拡張子が.ahkのものすべての一覧を作成することになります。

以下 Autohotkey-ListingHotkeys.ahk

以下のスクリプトを右上のコピーボタンを押して、クリップボードにコピーし、適当なエディタに貼り付けて、 Autohotkey-ListingHotkeys.ahk という名前で保存してください。

Autohotkey V2がインストールされていれば、保存したAutohotkey-ListingHotkeys.ahkをダブルクリックすると、起動されます。 起動したら、「Alt + 4」で一覧ウィンドウが表示されます。

表示された行をダブルクリックすると、エディタが起動して、その行のところで表示されているはずです。

; Autohotkey Listing Hotkeys and Hotstrings + Functions
; 2025-11-28 
; by torazemon2016
; https://www.autohotkey.com/docs/v2/lib/ListView.htm

#Requires AutoHotkey 2.0+
#SingleInstance Force

;;VScode
;editor := "C:\Program Files\Microsoft VS Code\Code.exe"
editor := "C:\Users\" A_UserName "\AppData\Local\Programs\Microsoft VS Code\Code.exe"
;;Notepad++
;editor := "C:\Program Files\Notepad++\notepad++.exe"

!4::    ; Autohotkey.ahk Hotkey List
{
    CreateHotkeyMapGui()
}

CreateHotkeyMapGui(*)
{
    target := A_ScriptDir "\*.ahk"              ; Folder + \ + *.ahk
    ;target := A_ScriptDir "\Autohotkey.ahk"    ; only 1 File

    title := "AHK Hotkey List"
    MyGui := Gui("+Resize",title)       ; Allow window maximizing
    MyGui.OnEvent("Close", Gui_Close)   ; _ □ X (Close)
    MyGui.OnEvent("Escape", Gui_Close)   ; Press [ESC] key (Close)
    MyGui.OnEvent("Size", Gui_Size)     ; Resize
    MyGui.SetFont("s16")                ; Font Size

    ; ListView
    LV := MyGui.Add("ListView", "+Grid r20 w1400 h600", ["Filename","Line","Type","Key", "Description"])
    ;LV.OnEvent("Click", Edit_File)         ; 
    LV.OnEvent("DoubleClick", Edit_File)    ; Run Editor

    ; Main
    Load_hotkeys(target)                

    ; The column size is not determined until after the data is loaded, so ModifyCol will be executed later.
    LV.ModifyCol()                      ; Auto-size each column to fit its contents.
    LV.ModifyCol(2, "70 Integer")       ; Line Number
    LV.ModifyCol(4, 400)                ; Type (Hotkey,Function,etc)

    MyGui.Show("x0 y200 AutoSize")      ; 

    ;----------------------------
    Load_hotkeys(files)
    {
        Loop Files, files
        {
            ;MsgBox(A_LoopFileName)
            Read_file(A_LoopFileName)
        }
    }

    Read_file(filename)
    {
        Loop read, filename ; Read line by line from a file
        {
            line := A_index

            if RegExMatch(A_LoopReadLine, '^(\s)', &m)  ; If the beginning of the line is blank
                continue                                ; next line

            if(InStr(A_LoopReadLine,"#Include") == 1)   ; ==1 If the first character is #Include
                Store_LV(filename, line, "Include", A_LoopReadLine)
            
            if(InStr(A_LoopReadLine,"::") != 0)         ; Hotkeys and Hotstrings found ('!= 0' means found)
            {
                if(InStr(A_LoopReadLine,";") == 1)      ; If the first character is ;
                    Store_LV(filename, line, "commentout", A_LoopReadLine)
                else if(InStr(A_LoopReadLine,":") == 1) ; If the first character is :
                    Store_LV(filename, line, "Hotstrings", A_LoopReadLine)
                else
                    Store_LV(filename, line, "Hotkey", A_LoopReadLine)
            }
            else if RegExMatch(A_LoopReadLine, '^[a-zA-Z].*\(.*\)', &m) ; Function found 
            {
                if(InStr(A_LoopReadLine,":=") == 0)     ; If it is not an assignment expression ('==0' means not found)
                    Store_LV(filename, line, "Function", A_LoopReadLine)
            }
        }
    }

    Store_LV(filename, line, type, str)
    {
        ;MsgBox(filename " " line " " kind " " str)
        parts := StrSplit(str, ";")
        key := ""
        comment := ""
        if(type == "commentout")    ; ex. ;F1:: ; comment1 -> parts[1] == "" ; parts[2] == F1 ; parts[3] == comment1
        {
            key := parts[2]
            if(parts.Length >= 3)
                comment := parts[3]
             else
                comment := ""
        }
        else                        ; ex. F1::  ; comment1 ; comment2 -> parts[1] == F1 ; parts[2] == comment1 ; parts[3] == comment2
        {
            key := parts[1]
            if(parts.length >= 3)
                comment := parts[2] . " " . ";" . " " . parts[3]
            else if(parts.length == 2)
                comment := parts[2]
            else
                comment := ""
        }

        LV.add(, filename, line, type, key, comment)
    }

    ;----------------------------
    Gui_Close(*)
    { 
        MyGui.Destroy()
    }


    Gui_Size(thisGui, MinMax, Width, Height)  
    {
        if(MinMax == -1)  ; The window has been minimized. No action needed.
            return
        else
            LV.Move(,, Width - 20, Height - 20) ; Expand/Shrink ListView
    }

    ;----------------------------
    Edit_File(LV, RowNumber)
    {
        FileName := LV.GetText(RowNumber, 1)
        Line := LV.GetText(RowNumber, 2)
        ;MsgBox(editor " " Filename " " Line , 0x40000)
        try
        {
            editor := "C:\Users\" A_UserName "\AppData\Local\Programs\Microsoft VS Code\Code.exe"

            target := "--goto `"" A_ScriptDir "\" FileName ":" Line "`""    ; VScode
            ;target := "`"" A_ScriptDir "\" Filename "`"" " -n" Line        ; notepad++
            Run(editor " " target)
        }
        catch
            MsgBox("Could not " editor " " target)
    }

}

Autohotkeyの学習の一環として、今回はListViewを理解することが出来ました。 (できるだけGUIとListViewの扱いがわかりやすくなるよう、クラスとかではない形で作成しています)

ご利用いただき、改良などコメントいただけると幸いです。

AutohotkeyでGUIメニューを表示し、ペン1本で PCを操作する Ver.3.5

Autohotkeyを使ってGUIメニューを出すことでキーボードがなくてもPCを操作する環境を作る(Ver.3.5)

先日の3回目のUpdateのあと、ICON表示や、実行関数を本体autohotkeyのものと共通化するなどしてこんな風になりました。

3回目の記事(2025-11-21):AutohotkeyでGUIメニューを表示し、ペン1本で PCを操作する Ver.3 - torazaemon2016’s blog

前回の記事(2024-11-22):AutohotkeyでGUIメニューを表示し、ペン1本で PCを操作する Ver.2 - torazaemon2016’s blog

前々回の記事(2023-11-26):AutohotkeyでGUIメニューを表示し、ペン1本で PCを操作する - torazaemon2016’s blog

以下 サンプルとして項目を減らした版

実際のスクリプトは最後に載せてあります。

サンプルで表示されるメニュー画面は以下です。

ボタンの定義

ボタンの定義には、

  1. どのタブのグループかの番号
  2. プログラム内部GUI登録用名
  3. 表示名
  4. クリックされた時の実行関数名
  5. 実行時の処理方法(0,1,2)
  6. アイコンのPath(ファイルパス or UWP登録名)
  7. アイコン番号(Icon? or UWP)
  8. コメント(省略可能)

の記載が必要です。

記載例:

4,btn_Google,Chrome,Func_chrome,1,C:\Program Files\Google\Chrome\Application\chrome.exe,Icon1,ブラウザ

4.の実行関数

ボタンを定義して、ボタンを押したときの処理を個々に記載する必要があります。

例をいくつか挙げておきます。

; アプリ (Kobo) Program Files以下のexe直接
Func_Kobo(*)
{
    Run("C:\Program Files (x86)\Kobo\Kobo.exe") ;
}

; TaskManager (スタート、すべて、Windowsツール、タスクマネージャーで右ボタン「ファイルの場所を開く」にあるショートカットの右ボタン「パスのコピー」
Func_TaskManager(*)
{
    Run("C:\ProgramData\Microsoft\Windows\Start Menu\Programs\System Tools\Task Manager.lnk")
}

; キー送出
Func_MS_Voice_Recognizer(*)
{
    Send("#h")      ; 音声認識
}

;PomodoroTimer (autohotkeyのスクリプト)
Func_PomodoroTimer(*)
{
    Run(A_ScriptDir "\pomodoro-timer.ahk")
}

; UWPアプリ呼び出し
Func_HWRinput(*)
{
    Run(Home "\Desktop\HWRinput - ショートカット.lnk") ; 
}

ストアアプリ(UWPアプリなど)をAutohotkeyスクリプトから呼び出すのは、ちょっと難しいので、 簡単にやるには、ショートカットを作成することです。

Windowsキー + R を押して「ファイル名を指定して実行」を開き、「shell:AppsFolder」と入力してEnterキーを押します。

表示されたアプリ一覧の中から起動したいアプリのアイコンを右クリックし、「ショートカットの作成」を選択します。

作成されたショートカットをデスクトップに移動させ、そのショートカットをダブルクリックしてアプリが起動することを確認します。

そのショートカットへのパスを記載してください。

プログラム的には

Autohotkey V2でのプログラムとして面白いところは、 関数名として渡された文字列から、% %によって関数オブジェクトを取得して、それで呼び出すことができる点です。

これにより、個々の実行関数でメニューを消してから実行とか不要になり、実行関数の記載が容易にできるようになりました。

(本来のAutohotkeyでのキーでのショートカット設定と同じ関数を呼び出せるようになったので、キーのショートカット定義とGUIメニューの共通化が可能になった(#include "functions-hatena.ahk"))

また、ボタンの定義の5番目の.処理方法で、「0 メニューを消す。1 メニュー表示を消さない。2 関数にGuiそのものを渡す(GUIの色を変えたりが可能) 」と設定します。通常は0か1でOKです。Func_ahk_Reload_GUI()のところで、2の場合の処理の記述例になります。

btn.OnEvent('Click', FuncCall.Bind(function,isGui))
 FuncCall(funcname,isgui,*)
    {
        ;MsgBox(funcname " " ishide ,0x40000)
        if(isgui == 0)
            ShowHideWindow()    ; Guiを隠す
        
        try {
            ;https://ahkscript.github.io/ja/docs/v2/Functions.htm#DynCall
            ; %記法で関数オブジェクトを取得し、呼び出しを試みる
            ; これが存在しない場合、TypeErrorやUnsetErrorが発生する可能性がある
            funcObj := %funcname%   ; csvでの定義でfuncnameのものが正しく定義してあることが前提 名前間違っていたらエラー発生
            if(isgui == 2)
                funcObj.Call(MyGui)
            else
                funcObj.Call()
        } catch as e {
            ; エラーが発生
            MsgBox("関数が見当たりません: " funcname, ,0x40000)
        }
    
    }   

アイコン表示

メニューを文字だけではなく、そのアプリのアイコンを出すことにより、ぐっとわかりやすくなりました。

アイコンは、個々の環境で登録のパスなどが変わります。

デスクトップにあるショートカット

アイコンを確認する方法の1つとして、デスクトップにあるショートカットを右ボタン押して、プロパティ出して、 アイコンの変更で、見る方法です。

番号は、左上から1番で下に4つで、次の右の列が5番からになるので、数えるという方法です。

%SystemRoot%\System32\SHELL32.dll などのWindows標準アイコン

フォルダなど、標準のアイコンは、%SystemRoot%\System32\SHELL32.dll などのWindows標準のものが利用できます。

アイコンが一覧になっている良いページなどを参考にしてやるのも便利です。

www.losttechnology.jp

Autohotkey製のPomodoroTimerは、内部でとして以下のようにアイコン登録しているので、

TraySetIcon("%SystemRoot%\System32\mmcndmgr.dll",15) ; 赤い時計

それを指定しています。

Autohotkeyで 自分好みの ポモドーロタイマー 作成 - torazaemon2016’s blog

Program File以下の実行ファイルの場合

アイコンの確認の方法は、実行ファイルのプロパティを見ることです。

スタートメニューからのアイコンの上で、右ボタンを押して、詳細、ファイルの場所を開く。でその場所を開いて、そこにあるアイコンを右ボタンからプロパティで、アイコンの変更を押すと、パスと、出てくる場所から何番目かを確認する。

Google Chromeの場合

Chrome.exeは、上記の方法でアイコンの確認は容易ですが、GmailやCalendarは、ちょっと工夫が必要です。

まず、ChromeGmailを開いて、ブラウザの右上の3点リーダー(点が縦に3つ)を押して、プルダウンメニューの「キャスト・保存・共有」のサブメニュー「ショートカットの作成」で、デスクトップにショートカットを作ります。

出来たショートカットを、これまでと同様に右ボタンでプロパティ見て、アイコンの変更からアイコンPathの取得ができます。

UWPアプリの場合

UWPアプリの場合は、素直にアイコンが出せないので、ちょっと複雑です。

UWPの場合は、UWPアプリ番号を見つける必要があります。

例えば電卓であれば、

Microsoft.WindowsCalculator_8wekyb3d8bbwe

となります。

この番号の確認の方法として簡単なのは、UWPアプリのショートカットから知ることです。

上記での実行関数のためにショートカットを作成しているかと思いますので、 ショートカットを右ボタンで押して、プロパティを見ると「ショートカットのタブ」の「種類」のところに、番号の記載があります。 それを、ahkに登録して、最後に 「!App」をつけて、アイコン番号は「UWP」と記載して保存してください。

ストアアプリである画面キャプチャ(Snipping Tool)の例です。

2,btn_scrcapture,Win+Shift+s,Func_Screen_Capture,0,Microsoft.ScreenSketch_8wekyb3d8bbwe!App,UWP,  ; Snipping Tool

スクリプト

以下を GUI-menu.ahkと、functions-hatena.ahk と名前を付けて保存して、autohotkeyがインストールされていれば、保存したGUI-menu-ahkファイルをダブルクリックすることで、メニューが表示されてくるかと思います。

functions-hatena.ahkの最初の方に、自分のホームフォルダを指定するところがありますので、 自分に合うように書き換えてください。

; Username を自分のWindowsでの名前(ホームの場所)に書き換えてください。
Home := "C:\Users\Username" 

GUI-menu.ahk

;https://zenn.dev/kaerunrun/articles/31b6155765f2747d4a43
; 2023-11-21
; 2024-11-21 Ver.2 Tabによるカラー化
; 2025-11-21 Ver.3 Tabの縦長化
; 2025-11-23 Ver.3.5 ICON表示
; 2025-11-25 Ver.3.5.1 ICON生成時エラー処理、FunCallエラー処理追加

#Requires AutoHotkey 2.0+       ; Version 2以上
#SingleInstance Force

#include "functions-hatena.ahk" ; 実行関数群

;========================================
GUI_menu()  ; 常駐版 (Hide,Show)
;========================================
GUI_menu()
{
    ; Hotキーの定義
    toggle_hotkey := "F6"               ; keyboard on/off

    gui_BackColor := "Silver"           ; 背景色 + Show()のあとに透明化も
    gui_Font := "s10 bold"                      ; フォントサイズ(ポイント)を設定

    tab_width := 120
    tab_height := 320
    btn_width := 80
    btn_height := 40
    
    ;----------------------------------------------------------------------------
    ; Tab群
    ; 1:tab名, 2:表示名, 3:色, 4:Tab番号, 5:(コメント) の項目  
    ; AutoHotkey では、( で始めて ) で終わる部分を「行継続セクション」でこの中はそのまま文字列リテラルとして扱われる
    ; csv := と 次の行の ( と 次の行の " は、このとおりにしておかないとエラー。同様に、一番最後の " と ) も独立行
    tab_csv := 
    (
    "
    tab_System,System,Yellow,1,
    tab_Explorer,Explorer,00FF00,2,
    tab_ahk,Autohotkey,FF8800,3,
    tab_App,App,FF00FF,4,
    "
    )

    ;----------------------------------------------------------------------------
    ; Button群
    ; 1:どのTabか(Tab番号), 2:button名, 3:表示名, 4:function名,  5: GUI消す0,GUI消さない=1,MyGui渡す=2 6:ICON path, 7:ICON番号orUWP, 8:(コメント) の項目  
    ; アイコンのパスにおいて Username を自分のWindowsでの名前(ホームの場所)に書き換えてください。
    button_csv := 
    (
    "
    ;tab_System,System,Yellow,,,,,
    1,btn_wintab,Win\n+Tab,Func_Key_Win_Tab,0,%SystemRoot%\System32\SHELL32.dll,Icon99,   ; 改行は\nとして記載する
    1,btn_taskmgr,Task Mgr,Func_TaskManager,0,%windir%\system32\Taskmgr.exe,Icon1,
    1,btn_scrcapture,Win+Shift+s,Func_Screen_Capture,0,Microsoft.ScreenSketch_8wekyb3d8bbwe!App,UWP,
    1,btn_onsei,音声入力,Func_MS_Voice_Recognizer,0,%systemroot%\system32\ddores.dll,Icon4,
    1,btn_hwrinput,HWRinput,Func_HWRinput,0,6564torazaemon2016.HWRinput_1904v668w4nzt!App,UWP,      ; UWP例
    1,btn_PomodoroTimer,Pomodoro\nStart,Func_PomodoroTimer,0,%SystemRoot%\System32\mmcndmgr.dll,Icon15,

    ;tab_Explorer,Explorer,00FF00,,,,
    2,btn_Exp_Desktop,Desktop,Func_Exp_Desktop,0,%SystemRoot%\System32\SHELL32.dll,Icon4,
    2,btn_Exp_Download,Download,Func_Exp_Download,0,%SystemRoot%\System32\SHELL32.dll,Icon4,
    2,btn_Exp_MyDoc,MyDoc,Func_Exp_MyDoc,0,%SystemRoot%\System32\SHELL32.dll,Icon4,

    ;tab_ahk,Autohotkey,FF8800,,,,,
    3,btn_ahk_Edit,Edit ahk,Func_Ahk_Edit,1,C:\Program Files\AutoHotkey\UX\AutoHotkeyUX.exe,Icon2,  ; 0 でメニュー残す
    3,btn_ahk_Edit_GUI,Edit GUI,Func_Ahk_Edit_GUI,1,C:\Program Files\AutoHotkey\UX\AutoHotkeyUX.exe,Icon2,
    3,btn_ahk_Reload,Reload GUI,Func_Ahk_Reload_GUI,2,C:\Program Files\AutoHotkey\UX\AutoHotkeyUX.exe,Icon1,    : 2でGUI渡す
    3,btn_ahk_Spy,Spy,Func_Ahk_Spy,0,C:\Program Files\AutoHotkey\UX\inc\spy.ico,Icon1,

    ;tab_App,App,FF00FF,,,,
    4,btn_Google,Chrome,Func_chrome,0,C:\Program Files\Google\Chrome\Application\chrome.exe,Icon1, ; ブラウザ
    4,btn_Gmail,Gmail,Func_Gmail,0,C:\Users\level\AppData\Local\Google\Chrome\User Data\Default\Web Shortcut Icons\4262469187\shortcut.ico,Icon1, ; Username は個々で違う
    4,btn_Calendar,Calendar,Func_Calendar,0,C:\Users\level\AppData\Local\Google\Chrome\User Data\Default\Web Shortcut Icons\52575180\shortcut.ico,Icon1,  ; Username は個々で違う
    4,btn_obsidian,Obsidian,Func_Obsidian,0,C:\Users\level\AppData\Local\Obsidian\Obsidian.exe,Icon1,  ; Username は個々で違う
    "
    )

    ;===ここから本体===================================================================================
    MyGui := Gui("-Resize +AlwaysOnTop +E0x08000000","AHK Menu")    ; メニューはこれでないと、カーソルなどが動かせない

    MyGui.BackColor := gui_BackColor 
    MyGui.SetFont(gui_Font)

    tabs := Array()
    make_tabs(tab_csv)

    make_buttons(button_csv)

    set_Tray()
    Hotkey("*" toggle_hotkey, ShowHideWindow)  ; keyboard on/off
    MyGui.OnEvent("Escape", ShowHideWindow)    ; [ESC] キーで閉じる
    MyGui.OnEvent("Close", ShowHideWindow)     ; X で閉じる
    OnExit(clear_ctrlalt)                      ; 終了するときにも一応CtrlやAltの状態をクリア

    MyGui.Show('Center NoActivate')         ; 中央に表示
    WinSetTransparent 220, "AHK Menu"       ; ちょっと透明 0-255 Show()したあとでないとエラーになるよう
        
    ;================================================================================
    ; 以下GUI関数群
    make_tabs(csv)
    {
        ; csv定義からボタンを生成
        max_height := 5
        y := 0
        tabnum := 1
        ;tabs := Array()

        Loop Parse, csv, "`n", "`r"
        {
            line := Trim(A_LoopField)
            if ( (line = "") || (InStr(line, ";", 0, 1) = 1) )  ; 読み飛ばし定義: 空行 または 1文字目が「;」
                continue

            ;MsgBox("line=|" line "|")
            parts := StrSplit(line, ",")    ; , で分ける
            if (parts.Length >= 4) {     ; 4項目以上
                name        := parts[1]
                displayname := parts[2]
                color       := parts[3]
                function    := parts[4]
                comment     := parts[5]

                if(InStr(name, "tab") == 1)
                {
                    ;MsgBox("Tab: " name " " tab_width " " tab_height " " function)
                    t3 := MyGui.AddTab3("X+0 +Background" color " W" tab_width " H" tab_height,[ displayname ])
                    tabs.Push(t3)
                    tabnum := tabnum + 1
                }
            }
        }
    }

    make_buttons(csv)
    {
        ; csv定義からボタンを生成
        Loop Parse, csv, "`n", "`r"
        {
            line := Trim(A_LoopField)
            if ( (line = "") || (InStr(line, ";", 0, 1) = 1) )  ; 読み飛ばし定義: 空行 または 1文字目が「;」
                continue

            tooltip(line)   ; for Debug 生成中の行を表示
            ;MsgBox("line=|" line "|")
            
            parts := StrSplit(line, ",")    ; , で分ける
            if (parts.Length >= 7) {
                tabnum      := parts[1]
                name        := parts[2]
                displayname := parts[3]
                function    := parts[4]
                isGui       := parts[5]
                icon_path   := parts[6]
                icon_number := parts[7]
                comment     := parts[8]

                ;MsgBox("Button: " name " " displayname " " function " " tabnum "   " comment)

                if(InStr(name, "btn") != 1) ; btn_???
                    continue

                tabs[tabnum].useTab(1)  ; どのTabに入れるか

                if(icon_path != "")
                {
                    hbmp := ""
                    if(icon_number != "UWP")
                    {
                        hBmp := LoadPicture(icon_path, icon_number . " w64 h64")    ; ディスプレイ 200%
                        ;hBmp := LoadPicture(icon_path, icon_number . " w48 h48")   ; ディスプレイ 150%
                        ;hBmp := LoadPicture(icon_path, icon_number . " w32 h32")   ; ディスプレイ 100%
                    }
                    else
                    {
                        hBmp := GetUwpIcon(icon_path, 64)
                    }
                    ;MsgBox(icon_path . " " . icon_number . " " . hBmp)
                    ; ボタン作成(BS_BITMAP スタイル)
                    iconbtn := MyGui.Add("Button", "x" 16 + tab_width * (tabnum - 1) " y+8 w40 h40 +0x80 ", )  ; +0x80 = BS_BITMAP
                    SendMessage(0xF7, 0, hBmp, iconbtn.Hwnd) ;BM_SETIMAGE := 0xF7, IMAGE_BITMAP := 0
                    iconbtn.OnEvent('Click', FuncCall.Bind(function,isGui))

                    ; ボタンの表示の際に二行以上にするため (\n をcsvで書くと、ここで改行に変える) csvでで`nでうまくいかなかったため
                    if InStr(displayname, "\n")
                        displayname := StrReplace(displayname,"\n","`n")

                    btn := MyGui.AddButton('x+0 w' btn_width ' h' btn_height ' v' name , displayname)
                    btn.OnEvent('Click', FuncCall.Bind(function,isGui))
                    
                    tooltip()   ; for Debug うまく行ったら表示を消す (残る場合は、そこでエラー発生)
                }               
                else    ; error
                {
                    MsgBox("No path error:" line)
                }
            }
            else    ; error
            {
                MsgBox("error: 7 > " parts.Length " " line)
            }
        }
    }

    FuncCall(funcname,isgui,*)
    {
        ;MsgBox(funcname " " ishide ,0x40000)
        if(isgui == 0)
            ShowHideWindow()    ; Guiを隠す
        
        try {
            ;https://ahkscript.github.io/ja/docs/v2/Functions.htm#DynCall
            ; %記法で関数オブジェクトを取得し、呼び出しを試みる
            ; これが存在しない場合、TypeErrorやUnsetErrorが発生する可能性がある
            funcObj := %funcname%   ; csvでの定義でfuncnameのものが正しく定義してあることが前提 名前間違っていたらエラー発生
            if(isgui == 2)
                funcObj.Call(MyGui)
            else
                funcObj.Call()
        } catch as e {
            ; エラーが発生
            MsgBox("関数が見当たりません: " funcname, ,0x40000)
        }
    
    }
    
    ;==UWPアプリのアイコンを取得======================================================
    GetUwpIcon(AppID, size := 64) {
        static IID_IShellItemImageFactory := "{bcc18b79-ba16-442f-80c4-8a59c30c463b}"
        static IID_IShellItem := "{43826D1E-E718-42EE-BC55-A1E261C37BFE}"

        try {
            ; AppsFolder の仮想パスを作成
            path := "shell:AppsFolder\" AppID

            ; SHParseDisplayName → PIDL を取得
            pidl := 0   ; PIDL を受け取る変数は "数値 0" でなければいけない
            hr := DllCall("Shell32\SHParseDisplayName"
                , "wstr", path
                , "ptr",  0
                , "ptr*", &pidl
                , "uint", 0
                , "ptr",  0)

            if (hr != 0 or pidl = 0)
                throw Error("SHParseDisplayName 失敗 HR=" hr)

            ; PIDL → IShellItem
            item := 0
            hr := DllCall("Shell32\SHCreateItemFromIDList"
                , "ptr", pidl
                , "ptr", IIDFromString(IID_IShellItem)
                , "ptr*", &item)

            if (hr != 0 or item = 0)
                throw Error("IShellItem 取得失敗 HR=" hr)

            ; IShellItemImageFactory の取得
            img := ComObjQuery(item, IID_IShellItemImageFactory)
            if !img
                throw Error("ImageFactory 取得失敗")

            ; 画像サイズ
            sizeBuf := Buffer(8)
            NumPut("uint", size, sizeBuf, 0)
            NumPut("uint", size, sizeBuf, 4)

            hIcon := 0
            hr := ComCall(3, img
                , "ptr", sizeBuf
                , "uint", 0
                , "ptr*", &hIcon)

            if hr != 0
                throw Error("アイコン取得失敗 HR=" hr)

            return hIcon
        } Catch as e {
            MsgBox("UWPのアイコンが生成できません: " AppID, ,0x40000)
        }
        
    }

    IIDFromString(str) {
        buf := Buffer(16)
        DllCall("ole32\IIDFromString", "wstr", str, "ptr", buf)
        return buf
    }
    ;==========================================================
    ; -システムトレイでのメニュー表示-
    set_Tray()
    {
        ; 常駐アイコン
        TraySetIcon("%SystemRoot%\System32\shell32.dll",42) ; 緑の木

        A_TrayMenu.Delete
        A_TrayMenu.Add "&Show/Hide", (*) => ShowHideWindow()        ; &S キーボードのSをショートカット指定
        A_TrayMenu.Add                                      ; -----
        A_TrayMenu.Add "&Reload", (*) => Reload()            ; &R キーボードのRをショートカット指定
        A_TrayMenu.Add "&Edit", (*) => Edit()                ; 
        A_TrayMenu.Add "E&xit", (*) => ExitApp()             ; 
        A_TrayMenu.Default := "&Show/Hide"
    }

    ;---MyGui を表示・隠す関数---
    ShowHideWindow(*)
    {
        if WinExist('ahk_id ' MyGui.hwnd) ; 表示されていれば隠す
        {
            MyGui.Hide()                  ; 隠す
            clear_ctrlalt()               ; Ctrl,ALtの状態をクリア
            MyGui.BackColor := "default"
        }
        else
        {   
            MyGui.Show('NoActivate')     ; 表示へ
        }
    }
    
    ;---終了時関数---
    clear_ctrlalt(*) 
    {
        for _, key in ['LCtrl', 'LAlt']
            if GetKeyState(key)             ; もし押されたままの状態だったら
                SendInput('{' key ' Up}')   ; 上げる(押されてない)にする
    }
}

;=================================================================
; ---MyGuiオブジェクトにアクセスできないといけない処理のため、guiを持ってくる
Func_Ahk_Reload_GUI(guix,*) ; GUI.ahk
{
    guix.BackColor := "Red"
    Sleep(1000)
    Reload()    ;スクリプトをリロードして適用
    guix.BackColor := "FF8800"  ;
}

/* END */

functions-hatena.ahk

; functions-hatena.ahk

#Requires AutoHotkey 2.0+       ; Version 2以上

;=================================================================
; Username を自分のWindowsでの名前(ホームの場所)に書き換えてください。
Home := "C:\Users\Username" 

;browser := "C:\Program Files\Google\Chrome\Application\chrome.exe --disk-cache-dir=R:\Temp"
browser := "C:\Program Files\Google\Chrome\Application\chrome.exe"

;--------------------
Func_Key_Win_Tab(*)
{
    Send("#{Tab}")
}

; TaskManagerを起動
Func_TaskManager(*)
{
    Run("C:\ProgramData\Microsoft\Windows\Start Menu\Programs\System Tools\Task Manager.lnk")
}

Func_Screen_Capture()
{
    ; Snipping Toolそのものの起動じゃなくて Win+Shift+sのショートカットのほうで
    ToolTip("Shift + Win + s")
    Send("+#s") ; Shift+Win+s
    ToolTip()
    ; 以下確認用に
    Sleep(3000)
    Send("#v")  ; Win+v (クリップボード一覧) 
}

Func_MS_Voice_Recognizer(*)
{
    Send("#h")      ; 音声認識
}

;PomodoroTimer
Func_PomodoroTimer(*)
{
    Run(A_ScriptDir "\pomodoro-timer.ahk")
}

;--------------------
; UWPアプリ呼び出し
Func_HWRinput(*)
{
    Run(Home "\Desktop\HWRinput - ショートカット.lnk") ; 
}

;------------------
; Explorerでフォルダ表示
Func_Exp_Download(*)
{
    Run(Home "\Downloads")
}
Func_Exp_MyDoc(*)
{
    Run(Home "\Documents")
}
Func_Exp_Desktop(*)
{
    Run(Home "\Desktop")
}

;---------------
; Autohotkey関係
Func_Ahk_Edit(*)
{
    Run("notepad++.exe `"" A_ScriptDir "\AutoHotkey_V2.ahk`"") ;ahkのスクリプトを編集 
}

Func_Ahk_Edit_GUI(*)
{
    Run("notepad++.exe `"" A_ScriptDir "\GUI.ahk`"") ;このスクリプトを編集 
}

;Func_Ahk_Reload_GUI(guix,*) ; GUI.ahkのほうに記載

Func_Ahk_Spy(*)
{
    Run("C:\ProgramData\Microsoft\Windows\Start Menu\Programs\AutoHotkey Window Spy.lnk")
}

;-----------------
; Google Chrome関係
Func_Chrome(*)
{
    Run(browser " `"https://www.google.co.jp/`"")
}

Func_Gmail(*)
{
    Run(browser " `"https://mail.google.com/mail/u/0/#inbox`"")
}

Func_Calendar(*)
{
    Run(browser " `"https://calendar.google.com/calendar/u/0/r`"")
}

; Obsidian
Func_Obsidian(*)
{
    SetTitleMatchMode(2)
    If WinExist("ahk_exe Obsidian.exe")
        WinActivate()
    Else
        Run(Home "\AppData\Local\Obsidian\Obsidian.exe")
}

;  End

さまざまに拡張してみてください。

AutohotkeyでGUIメニューを表示し、ペン1本で PCを操作する Ver.3

Autohotkeyを使ってGUIメニューを出すことでキーボードがなくてもPCを操作する環境を作る。(Ver.3)

Autohotkeyを使ってGUIメニューを出すことでキーボードがなくてもPCを操作する環境を作る。って書き始めて、 前にこの話を書いて、またちょうど1年。

これで3回目のUpdateです。

今(Ver.3)はこんなふうになっています。

2025/11/24 Ver.3.5にUpdateです。

torazaemon2016.hatenablog.jp

以下、もう古くなりましたが、参考までに。

Ver.2での横並びが縦になった形です。 また、ボタンの大きさを大きく統一して整然と並ぶようにして、押しやすくなることと、メニューの整理・変更などが容易にしています。

(下はVer.2)

前回の記事:AutohotkeyでGUIメニューを表示し、ペン1本で PCを操作する Ver.2 - torazaemon2016’s blog

前々回の記事:AutohotkeyでGUIメニューを表示し、ペン1本で PCを操作する - torazaemon2016’s blog

以下 スクリプト

タブやボタンの生成の部分について、CSVで定義するやり方になりました。

直接ButtonなどのGUI部品を記載するのではなく、CSVで列挙することでボタンの追加変更を 容易にしています。

(一部func_xxx()は,コメントアウトしていますので、ご自身の環境に合わせてインストールや、パスの設定などを実装ください(Run("実行パス")))

;https://zenn.dev/kaerunrun/articles/31b6155765f2747d4a43
; 2023-11-21
; 2024-11-21 Ver.2
; 2025-11-21 Ver.3

#Requires AutoHotkey 2.0+       ; Version 2以上
#SingleInstance Force

SetTitleMatchMode 2  ;部分一致OK

GUI_menu()  ; 2025-09-18 常駐版 (Hide,Show)

GUI_menu()
{
    ; Hotキーの定義
    toggle_hotkey := "F6"               ; keyboard on/off
    gui_BackColor := "Silver"           ; 背景色 + Show()のあとに透明化も
    gui_Font := "s10 bold"                      ; フォントサイズ(ポイント)を設定

    tab_width := 90
    tab_height := 300
    btn_width := 70
    btn_height := 35
    
    ; 自分のホーム(Username)を指定
    Home := "C:\Users\Username\"   
    browser := "C:\Program Files\Google\Chrome\Application\chrome.exe"

    ; 常駐アイコン
    TraySetIcon("%SystemRoot%\System32\shell32.dll",42) ; 緑の木
    ; システムトレイでのメニュー表示
    A_TrayMenu.Delete
    A_TrayMenu.Add "&Show/Hide", (*) => CloseWindow()        ; &S キーボードのSをショートカット指定
    A_TrayMenu.Add                                      ; -----
    A_TrayMenu.Add "&Reload", (*) => Reload()            ; &R キーボードのRをショートカット指定
    A_TrayMenu.Add "&Edit", (*) => Edit()                ; 
    A_TrayMenu.Add "E&xit", (*) => ExitApp()             ; 
    A_TrayMenu.Default := "&Show/Hide"
    
    MyGui := Gui("-Resize +AlwaysOnTop +E0x08000000","AHK Menu")    ; メニューはこれでないと、カーソルなどが動かせない

    MyGui.BackColor := gui_BackColor 
    MyGui.SetFont(gui_Font)

    ; Tabの縦長化
    ; csv化によるtab
    ; 1:tab名, 2:表示名, 3:色, 4:Tab番号, 5:(コメント) の項目  
    ; AutoHotkey では、( で始めて ) で終わる部分を「行継続セクション」でこの中はそのまま文字列リテラルとして扱われる
    ; csv := と 次の行の ( と 次の行の " は、このとおりにしておかないとエラー。同様に、一番最後の " と ) も独立行
    tab_csv := 
    (
    "
    tab_Desktop,Desktop,Yellow,1,
    tab_System,System,Yellow,2,
    tab_Cursor,Cursor,Aqua,3,
    tab_Keys,Keys,Aqua,4,
    tab_Explorer,Explorer,00FF00,5,
    tab_Google,Google,Red,6,
    tab_App,App,FF00FF,7,
    tab_Obsidian,Obsidian,080808,8,
    tab_ahk,Autohotkey,FF8800,9,
    "
    )

    ; csv定義からボタンを生成
    button_num := 0
    max_height := 5
    y := 0
    tabnum := 1
    tabs := Array()

    Loop Parse, tab_csv, "`n", "`r"
    {
        line := Trim(A_LoopField)
        
        if ( (line = "") || (InStr(line, ";", 0, 1) = 1) )  ; 読み飛ばし定義: 空行 または 1文字目が「;」
            continue

        ;MsgBox("line=|" line "|")
        parts := StrSplit(line, ",")    ; , で分ける
        if (parts.Length >= 4) {     ; 4項目以上
            name := parts[1]
            displayname := parts[2]
            color := parts[3]
            function := parts[4]
            comment := parts[5]

            if(InStr(name, "tab") == 1)
            {
                ;MsgBox("Tab: " name " " tab_width " " tab_height " " function)
                t3 := MyGui.AddTab3("X+0 +Background" color " W" tab_width " H" tab_height,[ displayname ])
                tabs.Push(t3)
                tabnum := tabnum + 1
            }
        }
    }

    ; csv化によるButton
    ; 1:どのTabか(Tab番号), 2:button名, 3:表示名, 4:function名,  5:(コメント) の項目  
    ; AutoHotkey では、( で始めて ) で終わる部分を「行継続セクション」でこの中はそのまま文字列リテラルとして扱われる
    ; csv := と 次の行の ( と 次の行の " は、このとおりにしておかないとエラー。同様に、一番最後の " と ) も独立行
    csv := 
    (
    "
    ;tab_Desktop,Desktop,Yellow,,,,,,
    1,btn_1,VD\n1番,Win_1,
    1,btn_2,VD\n2番,Win_2,
    ;1,btn_3,VD\n3番,Win_3,
    1,btn_wintab,Win\n+Tab,Win_Tab, ; Win+Tab
    1,btn_WinD,Desktop\n表示,Key_WinD,
    1,btn_DeskIcon,ICON表示\n非表示,Key_ShiftF10VD,
    
    ;tab_System,System,Yellow,,,,,
    2,btn_scrcapture,Win+Shift+s,Capture_click,
    2,btn_onsei,音声入力,Mic_click,
    2,btn_taskmgr,Task Mgr,TaskManager_click,
    2,btn_vk40,40%KB,VK40keyboard_click,

    ;tab_Cursor,Cursor,080808,,,,
    3,btn_ctrlHome,Ctrl+Home,Goto_Head,
    3,btn_ctrlEnd,Ctrl+End,Goto_Tail,
    3,btn_Up,↑,Arrow_Up,
    3,btn_Right,→,Arrow_Right,
    3,btn_Left,←,Arrow_Left,
    3,btn_Down,↓,Arrow_Down,

    ;tab_Keys,Keys,080808,,,,
    ;4,btn_1secCtrl,1sec after\nCtrl+Click,Key_CtrlClick,   ; 改行は\nとして記載する
    4,btn_Ctrl,Ctrl\n(扱い注意),Ctrl_Hold,                      ; 改行は\nとして記載する
    4,btn_Alt,Alt\n(扱い注意),Alt_Hold,                     ; 改行は\nとして記載する
    4,btn_Tab,Tab,Key_Tab,
    4,btn_ZenkakuSPC,全角空白,Zenkaku_space,
    4,btn_Esc,Esc,Key_Escape,4,

    ;tab_Explorer,Explorer,00FF00,,,,
    5,btn_Exp_Home,Home,Exp_Home,
    5,btn_Exp_Download,Download,Exp_Download,
    5,btn_Exp_MyDoc,MyDoc,Exp_MyDOc,
    5,btn_Exp_Desktop,Desktop,Exp_Desktop,
    5,btn_Exp_Picture,Picture,Exp_Picture,
    5,btn_Exp_Stnology,Synology,Exp_Synology,

    ;tab_Google,Google,Red,,,,
    6,btn_Google,Chrome,Chrome_click,
    6,btn_Crome_capture,WebClip,Chrome_get_click,
    6,btn_F11,Chrome\n全画面/戻,Key_F11,                            ; 改行は\nとして記載する
    6,btn_Gmail,Gmail,Gmail_click,
    6,btn_Calendar,Calendar,Calendar_click,

    ;tab_App,App,FF00FF,,,,
    7,btn_Kobo,kobo,Kobo_Click,
    7,btn_hwrinput,HWRinput,HWRinput_Click,
    7,btn_OnseiMemo,音声メモ,OnseiMemo_Click,
    7,btn_SyncTrayzor,SyncTrayzor,SyncTrayzor_Click,
    7,btn_WinMerge,WinMerge,WinMerge_Click,
    7,btn_ActiveMail,ActiveMail,ActiveMail_Click,

    ;tab_Obsidian,Obsidian,Maroon,,,,
    8,btn_obsidian,Obsidian,Obsidian_click,
    8,btn_Exp_obs_vault,Vault,Exp_obs_Vault,
    8,btn_Exp_obs_daily,daily,Exp_obs_daily,
    8,btn_Exp_iPhonePic,iPhone,Exp_obs_iPhone,
    8,btn_menu_Gesture,Gesture\nHelp,Gesture_menu_Click,

    ;tab_ahk,Autohotkey,FF8800,,,,,
    9,btn_ahk_Edit,Edit ahk,Ahk_Edit_click,
    9,btn_ahk_Edit_GUI,Edit GUI,Ahk_Edit_gui_click,
    9,btn_ahk_Spy,Spy,Ahk_Spy_click,
    9,btn_ahk_Reload,Reload,Ahk_Reload_click,
    9,btn_Exp_ahk,ahk,Exp_ahk,
    "
    )

    ; csv定義からボタンを生成
    button_num := 0

    Loop Parse, csv, "`n", "`r"
    {
        line := Trim(A_LoopField)
        
        if ( (line = "") || (InStr(line, ";", 0, 1) = 1) )  ; 読み飛ばし定義: 空行 または 1文字目が「;」
            continue

        ;MsgBox("line=|" line "|")
        parts := StrSplit(line, ",")    ; , で分ける
        if (parts.Length >= 5) {     ; 5項目以上
            tabnum := parts[1]
            name := parts[2]
            displayname := parts[3]
            function := parts[4]
            comment := parts[5]

            if(InStr(name, "btn") == 1)
            {
                ;MsgBox("Button: " name " " displayname " " function " " tabnum "   " comment)

                button_num := button_num + 1

                ; ボタンの表示の際に二行以上にするため (\n をcsvで書くと、ここで改行に変える) csvで`nではうまくいかなかったためorz
                if InStr(displayname, "\n")
                {
                    displayname := StrReplace(displayname,"\n","`n")
                }
                tabs[tabnum].useTab(1)
                con := MyGui.AddButton('w' btn_width ' h' btn_height ' v' name, displayname)

                ;https://ahkscript.github.io/ja/docs/v2/Functions.htm#DynCall
                ;関数呼び出しのターゲットとして、ダブルデフを含む他の式を使用することができます。
                ;例えば、MyArray[1]()はMyArrayの最初の要素に含まれる関数を呼び出し、
                ;%MyVar%()はMyVarに含まれる名前の 変数に含まれる関数を呼び出します。
                ;つまり、パラメータリストの前にある式をまず評価して関数オブジェクトを取得し、そのオブジェクトを呼び出すのです。
                func_obj := %function%
                con.OnEvent('Click', func_obj)
            }
        }
    }

    Hotkey("*" toggle_hotkey, CloseWindow)  ; keyboard on/off
    MyGui.OnEvent("Escape", CloseWindow)    ; [ESC] キーで閉じる
    MyGui.OnEvent("Close", CloseWindow)     ; X で閉じる
    OnExit(clear_mods)                      ; 終了するときにも一応CtrlやAltの状態をクリア

    MyGui.Show('Center NoActivate')         ; 中央に表示
    ;WinSetTransparent 220, "AHK Menu"      ; ちょっと透明 0-255 Show()したあとでないとエラーになるよう
    
    ;---MyGui を閉じる関数-----
    CloseWindow(*)
    {
        if WinExist('ahk_id ' MyGui.hwnd) ; 表示されていれば隠す
        {
            MyGui.Hide()                  ; 隠す
            clear_mods()                  ; Ctrl,ALtの状態をクリア
            MyGui.BackColor := "default"
        }
        else
        {   
            MyGui.Show('NoActivate')     ; 表示へ
        }
    }

    ; --- Ctrl,ALtキーなどが押されたままになっていたりするのを外す ---
    clear_mods(*) 
    {
        for _, key in ['LCtrl', 'LAlt' , 'LWin']
            if GetKeyState(key)             ; もし押されたままの状態だったら
                SendInput('{' key ' Up}')   ; 上げる(押されてない)にする
    }

    ;============================================================
    ;---MyGuiオブジェクトにアクセスできないといけない処理
    ; GUI_menu()の中で定義が必要
    Ctrl_Hold(*)    
    {
        key := "LCtrl"
        if GetKeyState(key)
        {
            clear_mods()                    ; Altの状態もクリアしたいので
            ;state := 'Up'                  ; 上でUpしているの
            ;SendInput('{' key ' ' state '}')
            MyGui.BackColor := "default"
        }
        else
        {
            clear_mods()                    ; Altの状態もクリアしたいので
            state := 'Down'                 ; LCtrlだけを押した状態へ
            SendInput('{' key ' ' state '}')
            MyGui.BackColor := "Yellow"
        }
    }

    ; 
    Alt_Hold(*) 
    {
        key := "LAlt"
        if GetKeyState(key)
        {
            clear_mods()                    ; Ctrlの状態もクリアしたいので
            ;state := 'Up'                  ; 上でUpしているので
            ;SendInput('{' key ' ' state '}')
            MyGui.BackColor := "default"
        }
        else
        {
            clear_mods()                    ; Ctrlの状態もクリアしたいので
            state := 'Down'                 ; LAltだけを押した状態へ
            SendInput('{' key ' ' state '}')
            MyGui.BackColor := "Olive"
        }
    }

    Capture_click(*)
    {
        CloseWindow()
        Send "#+s"      ; Win+Shift+s
    }

    Key_WinD(*)
    {
        CloseWindow()
        Send "#d"       ; デスクトップ表示
    }

    Key_ShiftF10VD(*)
    {
        CloseWindow()
        Send "#d"       ; デスクトップ表示
        Sleep(1000)
        Send "+{F10}"   ; 
        Sleep(1000)
        Send "V"        ;
        Sleep(1000)
        Send "D"        ;
    }
    
    Key_F11(*)
    {
        CloseWindow()
        Send("{F11}")   ; F11 (Chrome FullScreen)
    }

    Mic_click(*)
    {
        CloseWindow()
        Send("#h")      ; 音声認識
    }

    ;-----------------
    ; Google Chrome関係
    Chrome_click(*)
    {
        Run(browser " `"https://www.google.co.jp/`"")
    }

    Chrome_get_click(*)
    {
        ; Obsidian Web Clipper拡張機能を追加 
        Send("+!O") ; Alt+Shift+o でクリップ (obdisian/Clippings 以下に入る
    }

    Gmail_click(*)
    {
        ;func_gmail_open()
        Run(browser " `"https://www.google.co.jp/`"")
    }

    Calendar_click(*)
    {
        ;func_calendar_open()
        Run(browser " `"https://www.google.co.jp/`"")
    }

    ;----------------
    ; Obsidian関係
    Obsidian_Click(*)
    {
        SetTitleMatchMode(2)
        If WinExist("ahk_exe Obsidian.exe")
        {
            WinActivate()

            Send("!+t") ; 2023-09-06 Today (shortcutの設定がしてある場合)
            Sleep(2000)
            Send("^{End}")
        }
        Else
        {
            Run(Home "AppData\Local\Obsidian\Obsidian.exe")

            Sleep(5000)
        
            Send("!+t") ; 2023-09-06 Today (shortcutの設定がしてある場合)
            Sleep(2000)
            Send("^{End}")
        }
    }

    WinMerge_Click(*)
    {
        ;func_WinMerge()
    }

    SyncTrayzor_Click(*)
    {
        ;func_SyncTrayzor()
    }

    Gesture_menu_Click(*)
    {
        ;func_Gesture_menu()
    }
    ;------------
    ; Kobo
    Kobo_Click(*)
    {
        CloseWindow()
        Run("C:\Program Files (x86)\Kobo\Kobo.exe") ;
    }

    ;------------------
    ; Explorerでフォルダ表示
    Exp_Home(*)
    {
        Run(Home)
    }
    Exp_Download(*)
    {
        Run(Home "Downloads")
    }
    Exp_MyDoc(*)
    {
        Run(Home "Documents")
    }
    Exp_Desktop(*)
    {
        Run(Home "Desktop")
    }

    Exp_Picture(*)
    {
        Run(Home "Pictures\Screenshots")
    }

    Exp_Synology(*)
    {
        Run(Home "SynologyDrive")
    }

    Exp_ahk(*)
    {
        Run(Home "SynologyDrive\tool\ahk")
    }

    Exp_obs_Vault(*)
    {
        Run(Home "SynologyDrive\obs")
    }
    Exp_obs_daily(*)
    {
        Run(Home "SynologyDrive\obs\daily")
        
    }
    Exp_obs_iPhone(*)
    {
        Run(Home "Desktop\iPhone14 All Photos")
    }
        
    ;--------------------
    ; アプリ呼び出し
    HWRinput_Click(*)
    {
        CloseWindow()

        ;ストアアプリを起動するショートカットを作成する:
        ;Windowsキー + R を押して「ファイル名を指定して実行」を開き、「shell:AppsFolder」と入力してEnterキーを押します。
        ;表示されたアプリ一覧の中から起動したいUWPアプリのアイコンを右クリックし、「ショートカットの作成」を選択します。
        ;作成されたショートカットをデスクトップに移動させ、そのショートカットをダブルクリックしてアプリを起動します。
        Run(Home "Desktop\HWRinput - ショートカット.lnk") ; 
    }

    OnseiMemo_Click(*)
    {
        CloseWindow()
        Run(Home "Desktop\音声認識メモ - ショートカット.lnk") ; 
    }

    ActiveMail_Click(*)
    {
        CloseWindow()
        Run(Home "Desktop\Active! mail.lnk")
    }

    ; TaskManagerを起動
    TaskManager_Click(*)
    {
        CloseWindow()
        Run("C:\ProgramData\Microsoft\Windows\Start Menu\Programs\System Tools\Task Manager.lnk")
    }

    ;vk40keyboardを起動
    VK40Keyboard_Click(*)
    {
        ;CloseWindow()
        if WinExist("AHK Keyboard") ;
        {
            WinActivate()
        }
        else
        {   
            Run(Home "Desktop\virtualkeyboard-40qwerty.ahk")
        }
    }

    ;---------------
    ; Autohotkey関係
    Ahk_Edit_click(*)
    {
        CloseWindow()
        Run("notepad++.exe `"" A_ScriptDir "\AutoHotkey_V2.ahk`"") ;ahkのスクリプトを編集 
    }

    Ahk_Edit_gui_click(*)
    {
        CloseWindow()
        Run("notepad++.exe `"" A_ScriptDir "\GUI.ahk`"") ;このスクリプトを編集 
    }

    Ahk_Spy_Click(*)
    {
        CloseWindow()
        Run("C:\ProgramData\Microsoft\Windows\Start Menu\Programs\AutoHotkey Window Spy.lnk")
    }

    Ahk_Reload_click(*)
    {
        CloseWindow()
        msg := "`"Reload Autohotkey `"" 
        ToolTip(msg)
        Sleep(500)
        ToolTip()
        Reload()    ;スクリプトをリロードして適用
        return
    }
}   

;----------------
; カーソルとかキー入力とか
Arrow_Left(*)
{
    Send "{Left}"   ; ←移動
}
Arrow_Right(*)
{   
    Send "{Right}"  ; →移動
}
Arrow_Down(*)
{
    Send "{Down}"   ; ↓移動
}
Arrow_Up(*)
{
    Send "{Up}"     ; ↑移動
}

Goto_Head(*)
{
    Send "^{Home}"  ; 先頭へ移動
}
Goto_Tail(*)
{
    Send "^{End}"   ; 末尾へ移動
}

Zenkaku_space(*)
{
    Send " "      ; 全角の空白
}

Key_Tab(*)
{
    Send("{Tab}")   ; Tab
}

Key_CtrlClick(*)
{
    MouseGetPos &xpos, &ypos 
    msg := "`"chrome`"" xpos " " ypos   ; 2024-06-05 
    ToolTip(msg)
    Sleep(1000)
    ToolTip()
    Send("^{Click}")    ; Ctrl + Click
}

Key_Escape(*)
{
    Send("{Esc}")
}


; 仮想デスクトップ移動
Win_1(*)
{
    Send("#^{Left}")
    ;Sleep(100)
    ;Send("#^{Left}")
}
Win_2(*)
{
    ;Send("#^{Left}")
    ;Sleep(50)
    ;Send("#^{Left}")
    ;Sleep(100)
    Send("#^{Right}")
}
Win_3(*)
{
    Send("#^{Right}")
    Sleep(50)
    Send("#^{Right}")
}

Win_Tab(*)
{
    Send("#{Tab}")
}

/* END */

Autohotkeyで 自分好みの ポモドーロタイマー 作成

仕事効率を上げるために、25分周期で動作するポモドーロタイマーというものがあります。

スマートフォンアプリなど、さまざまなものがありますが、自分好みになるようにAutohotkeyGUIで作ってみました。

keyword: autohotkey, Version2, V2, Pomodoro Timer, ポモドーロタイマー, Obsidian

WindowsでのPomodoro Timer

autohotkeyの機能を調べていた時に、このページから AutoHotKey で、Windows アプリの表示位置を指定して開く – リボン日記

lifeworknext.com

の「SPTimer.exe」は、とても使いやすく重宝していたのですが、OSのUpdateのいくつかののち、左下にタイトルバーの分身が表示されるようなったりして、使わなくなりました。

ObsidianでのPomodoro Timer

メインで使っているソフトである Obsidian において、プラグインで ポモドーロタイマーがあります。 でも、結構場所を取ることや、Obsidian以外のアプリを使う時には、困っていました。 (右ペイン、カレンダー下にポモドーロタイマーを設置)

Autohotkeyでの実装

時間設定だけして、時間経過後にポップアップや音が出るというAutohotkeyでのポモドーロタイマーの実装は、ググればいくつか出ますが、 Autohotkey V1での実装であったりしました。

よく探すと、

Reddit - ソーシャルの新たな中心地

に、Autohotkey V2で作られた出来の良いものがあります。

下図には、このahkのもの、Obsidianのプラグイン、SPTimer.exe、そして自作の4つを起動しているものです。 サイズ感の違いを見てください。

作ったもの

  • 上記 Reddit でのGroggyOtter さんのを参考に Autohotkey V2で Classではなく普通のGUI部品として、わかりやすさ・修正しやすさ優先で作成
  • SPTimerのように、小さくて画面にカウントダウンが出る
  • タイムアップしたらポップアップ表示や通知を行う+音も出る
  • Obsidianのdailyファイルに記録がされる

などの要求要件を考えて作ったものです。

画面右上の最前面に表示

起動すると、画面右上にタイマー表示します。

11/15でのUpdateで、上記の4つのタイマーの比較の図のものよりさらに小さくして、右上隅に配置するようにして、 ウィンドウのX(Closeボタン)を押せなくするように配置しました。

(最大化しているウィンドウをミスして消してしまうのを止める方法として、 Xボタンを無効化するようにその最前面にウィンドウを置いておくというテクニック 閉じるボタン隠すくん のダウンロード・使い方 - フリーソフト100 を知って、タイマーの置き場所にしました)

ツールバー上に常駐

また、画面右下のツールバーに赤の丸い時計アイコンが常駐します。

上記アイコンを右クリックするとメニューがでて、「Show/Hide」「Stop&Hide」「ReStart」させることができます。

  • 「Show/Hide」は画面上でのタイマー表示を表示/隠すだけで、タイマーは動いたままです。再度選択すると、消えた表示も戻り、タイマーは消えていた時間も減った形で表示されます。
  • 「Stop&Hide」は、時間計測を止めて表示も消えます。(停止)
  • 「ReStart」は、25分間を最初からスタートします。
  • 「Reload」は、このahkを読み直します(Reload)。結果として25分間のタイマーがスタートします。
  • 「Exit」は、常駐しているタイマーのAutohotkeyそのものを終了します。(ツールバーから消えます)

移動させるには

タイトルバーも表示しない形になっていますが、タイマーのウィンドウの上で左ボタンを押してドラッグすれば、移動させることができます。

タイマーの上で右ボタンを押す

表示を隠します。

ホットキー「F8」

プログラムの最初の方に、ホットキーとして「F8」を指定しています。

  • hotkey(F8)を押すことでも、Show/Hideや隠すボタンと同じように、画面から消えます。表示を隠すだけでを終了するだけでカウントダウンは続いたままで、時間がくるとポップアップや音が出ます。

  • 再度Hotkey(F8)を押すと、表示が戻ります。

継続のポップアップウィンドウ

時間が00:00になると、音が鳴って、画面中央に、以下のような継続するか終了するかのポップアップが表示されます。

「はい」を押すと、休憩タイマー(5分)がスタートします。

また、Autohotkeyでのポップアップ以外にも、システムトレイの通知のところに、通知を出すことで、時間が来たことを知らせています。

休憩が終わったときのメッセージ。

Windowsで通知で画面を点滅させる設定

Windowsで警告時に画面を点滅させるか設定する方法 | エンジニアの備忘録 などを参考にして、Windowsの設定を行っておくと、タイマーの時間が00:00になり通知が発行されると画面が反転するので、音を消していたとしても気が付くことができます。

ahkスクリプト

autohotkeyそのもののインストールやその説明については、ググるなりしてください。

以下がahkでのプログラムです。 リスト表示の右上のほうに、クリップボードに保存ボタンがありますので、それでコピーして、エディタなどに貼り付けて、拡張子を .ahkとしたファイル名で保存してください。(例えば pomodorotimer.ahk)

autohotkeyがインストールされているなら、保存したファイルをダブルクリックすれば、タイマーが常駐し、カウントダウンがスタートします。

Update

  • 2025-11-17 関数化して、流れも整理しました。
  • 2025-11-19 MsgBoxもAlwaysOnTopしました。右ボタンでメニューをやめて、Trayでのメニューのみにしました。
; 2025-10-31 Pomodoro Timer by torazemon2016
; 2025-11-15 Update (Color表示、SystemTrayアイコン)
; 2025-11-17 関数化で作り直す
; 2025-11-19 AlwaysOnTopに

; 参考 "🍅 Pomodoro 🍅" By GroggyOtter
; https://www.reddit.com/r/AutoHotkey/comments/145kxh1/made_a_deal_with_a_user_on_the_sub_that_id_code/

#Requires AutoHotkey v2.0
#SingleInstance Force

;-------------------------------------------------------------------------
pomodoro_timer(25)  ; 25min 起動時に作成 左ボタンでドラッグで移動、右ボタン押すと終了メニュー出る
;-------------------------------------------------------------------------
pomodoro_timer(min)
{
    ;***ユーザによる設定値****************************
    toggle_hotkey := 'F8'   ; *** Hotキーの定義 ***

    pomodoro := min     ; 25(min)
    rest := 5           ;  5(min)
    interval := 1       ; 更新間隔(sec) (1秒毎に更新) 5秒毎とか10秒毎なら 5,10 (1.3とかしてデバッグも)
    ;pomodoro := 0.2    ; (for Debug) 0.2 = 12sec
    ;rest := 0.1        ; (for Debug) 0.1 =  6sec

    ; すごく小さく 右上に表示 (PCの画面の解像度により -100, w, hとも要修正)
    startx := A_ScreenWidth - 100   ;表示位置X (右)
    starty := 0                     ;表示位置Y (上)
    timer_w := 50   ; 表示の幅
    timer_h := 22   ; 表示の高さ

    ; サイズ12が小さくて大きくするなら、timer_w,timer_hやmake_gui()での値も大きく
    work_font := "s12 cBlack Norm"  ; フォントサイズs(12)、色c(Black)、書式(ノーマル)に設定 
    rest_font := "s12 cRed   Bold"  ; フォントサイズs(12)、色c(Red)、書式(Bold)に設定 

    work_color := "White"   ;Background Color
    rest_color := "Aqua"    ;

    work_end_message := '🍅 休憩時間になりました。`n🍅 休みましょう。`n🍅 タイマーを続けますか?'        ; 改行は `n
    rest_end_message := 'さあ時間です。頑張りましょう!'
    
    work_end_sound := "C:\Windows\Media\Ring06.wav"
    rest_end_sound := "C:\Windows\Media\Ring01.wav"
    
    logsavepath   := A_MyDocuments
    ;logsavepath   := "C:\Users\Username\Obsidian\daily"  ; Obsidian Daily file 

    log_work_start := "💻 Work start" ; 💻 🖥️ ⌨
    log_work_end   := "💻 Work end"
    log_rest_start := "🍵 Rest start" ; 🍵 (紅茶) ☕ (コーヒー)
    log_rest_end   := "🍵 Rest end"   ; ㊡ (まるの中に休の文字): Unicode U+32A1 もありかも
    
    ;***システム変数************************************
    static working := true                  ; 仕事中 true , 休暇中 false
    static timer_left := pomodoro * 60000   ; 分を秒*1000に
    static denominator := pomodoro * 60000  ; 分を秒*1000に
    static last_check := A_TickCount        ; 最後にチェックした時刻

    ;***main部*********************************************************************************
    static title := "AHK Timer"
    static timerGui := Gui('+AlwaysOnTop -SysMenu -ToolWindow -Caption +Border +E0x08000000' , title)   ; -Caption(タイトルバーなどなし)
    make_gui()
    show_gui()

    set_tray_and_hotkey()
    set_left_click()
    set_right_click()

    start_work()    ; プログラム起動と同時に開始
    
    ;***GUI部品作成と表示*******************************************************************
    make_gui()
    {
        timerGui.BackColor := work_color  ; 背景
        timerGui.SetFont(work_font)
        
        ; w40, w45 h5 なども、もとの大きさ(timer_w,timer_h)を変えたら好みでサイズ変更してください
        timerGui.timerText := timerGui.Add("Text", "x4 y1 w40 " , min . ":00")  ; w40 とすごく小さな表示に
        timerGui.timerText.SetFont(work_font)
        timerGui.timerBar := timerGui.Add("Progress", "x1 y+0 w45 h5 cGreen Vbar", 100) ; w45, h5 とすごく小さく。最後の100は100%とわかりやすいので
    }

    show_gui()
    {
        timerGui.Show("x" startx " y" starty " w" timer_w  " h" timer_h)    ; 表示
    }

    ;***Windowsシステム側への設定***************************************************************
    set_tray_and_hotkey()
    {
        ; 2025-11-12 常駐アイコン
        TraySetIcon("%SystemRoot%\System32\mmcndmgr.dll",15)    ; 赤い時計

        ;2025-11-12 システムトレイでのメニュー表示
        A_TrayMenu.Delete
        A_TrayMenu.Add "Show/&Hide", (*) => HideShowWindows()        ; &H キーボードのHをショートカット指定
        A_TrayMenu.Add "&Stop&&Hide", (*) => CloseWindow()       ; &S
        A_TrayMenu.Add "&ReStart", (*) => start_work()           ; &R
        A_TrayMenu.Add                                      ; -----
        A_TrayMenu.Add "Re&load", (*) => Reload()            ; &l
        A_TrayMenu.Add "&Exit", (*) => ExitApp()         ; 
        A_TrayMenu.Default := "Show/&Hide"

        Hotkey("*" toggle_hotkey, HideShowWindows)      ; Hotkeyで表示と隠す
        OnExit(stop_timer)                      ; アプリを終了するときに一応タイマー止めて終了へ
    }
    
    HideShowWindows(*)
    {
        if WinExist('ahk_id ' timerGui.hwnd)
            timerGui.Hide()
        else
            timerGui.Show()
    }

    CloseWindow(*)  ;   タイマーカウントダウンをやめて、表示も消す
    {
        stop_timer()
        timerGui.Hide()
    }

    set_left_click()
    {
        ; 左クリックダウン(0x201)メッセージを検知するコールバック関数を登録
        ; WM_LBUTTONDOWN が発生したとき、OnMessage_LButton関数が呼び出される
        OnMessage(0x201, OnMessage_LButton)
        OnMessage_LButton(wParam, lParam, msg, hwnd)        ; コールバック関数
        {
            ; ウィンドウをドラッグするためのメッセージを送信
            PostMessage(0xA1, 2, , , hwnd) ; WM_NCLBUTTONDOWN, HTCAPTION
        }
    }

    set_right_click()
    {
        ; 右クリックダウン(0x204)メッセージを検知するコールバック関数を登録
        ; WM_RBUTTONDOWN が発生したとき、OnMessage_RButton関数が呼び出される
        OnMessage(0x204, OnMessage_RButton)
        OnMessage_RButton(wParam, lParam, msg, hwnd)        ; コールバック関数
        {
            HideShowWindows()
        }
    }

    ;***タイマー関係************************************************************************
    start_work(*)           ; 起動最初やメニューからのスタートに対応するため用意
    {
        set_work()          ; 仕事モード
        start_timer()       ; ★タイマー開始★
    }

    start_rest(*)           ; 使ってない (TimeUp()で★タイマー停止★との対応がわかるようにするためそっちで直接書いている)
    {
        set_rest()          ; 仕事モード
        start_timer()       ; ★タイマー開始★
    }

    start_timer(*)
    {
        SetTimer(UpdateTime, interval * 1000)   ; タイマーを設定して、1秒毎に処理する関数(UpdateTime)を設定
    }

    stop_timer(*)
    {
        SetTimer(UpdateTime, 0)
    }

    ;***UpdateTime()が真のmain部*******************************
    UpdateTime()    ; タイマーカウントダウン(1秒ごと 呼び出されて、処理を繰り返す)
    {
        timerGui.timerText.Text := Format("{:02d}:{:02d}", Floor(timer_left / 60000), Floor(Mod(timer_left, 60000)) * 0.001)
        timerGui.timerBar.Value := (timer_left / denominator) * 100

        now := A_TickCount
        timer_left := timer_left - (now - last_check)
        ;MsgBox(now " " last_check " " now - last_check " " timer_left) ; for Debug
        
        if(timer_left < 0)
            TimeUp()

        last_check := A_TickCount   ; 次回更新に向けて時刻を記憶しておく
    }

    TimeUp()
    {
        timerGui.timerText.Text := "00:00"  ; 00:00 (timer_leftがマイナスになった場合でも00:01とか変に残るのを防ぐ)
        timerGui.timerBar.Value := 0        ; 0     (timer_leftがマイナスになってもバーが少し残る場合を防ぐ)

        stop_timer()    ; ★★★タイマー停止★★★

        msg := ""
        sound := ""
        log := ""
        if(working == true) ; 仕事中であったので仕事終わり
        {
            msg := work_end_message
            sound := work_end_sound
            log := log_work_end
        }
        else                ; 休憩終わり
        {
            msg := rest_end_message
            sound := rest_end_sound
            log := log_rest_end
        }
        user_notify(msg,sound,log)  ; ログ書いてから音鳴らして、(MsgBoxの)ポップアップ出すほうが良い

        ; Yes(Y)、No(N)    0x4 + Icon Question 0x20 + AlwaysOnTop(スタイル WS_EX_TOPMOST) 0x40000
        if (MsgBox(msg, title, 0x40024) = 'Yes')
        {
            if(working == true) ; 仕事中であったので仕事終わり
            {
                set_rest()      ;休暇モードへ start_rest()でもいいが★★★対応を見せるため
                start_timer()   ; ★★★タイマー開始★★★
            }
            else                ; 休憩終わり
            {
                set_work()      ; 仕事モードへ    start_work()でもいいが★★★対応を見せるため
                start_timer()   ; ★★★タイマー開始★★★
            }
        }
        else
        {
            CloseWindow()   ; タイマー表示終了
        }
    }

    set_work()          ; 仕事モード
    {
        working := true     ; 仕事モードに入る

        timerGui.BackColor := work_color  ; 背景
        timerGui.timerText.SetFont(work_font)
        timerGui.timerBar.Opt("+cGreen")

        timer_left := pomodoro * 60000
        denominator := pomodoro * 60000 
        last_check := A_TickCount

        write_log(log_work_start)   
    }

    set_rest()          ; 休憩モード
    {
        working := false    ; 休憩モードに

        timerGui.BackColor := rest_color  ; 背景
        timerGui.timerText.SetFont(rest_font)
        timerGui.timerBar.Opt("+cFuchsia")

        timer_left := rest * 60000
        ;denominator := rest * 60000
        denominator := pomodoro * 60000 ; Barの長さを25分のときと同じ量とする(バーの物理長さを同じ時間感覚に)
        last_check := A_TickCount

        write_log(log_rest_start)   
    }

    ;***ユーザへのメッセージ部************************************************
    ; 音を消している場合に対応 TrayTipで Windowsで警告時に画面を点滅させるか設定する方法
    ; https://engrmemo.jp/win/surface-notice-blink/
    user_notify(msg,sound,log)
    {
        TrayTip(msg,title,16)   ; SoundPlayで音出すので、TrayTipでは16:無音 (上のURL設定で->画面点滅)
        write_log(log)      ; ログ書いてから音鳴らして、(MsgBoxの)ポップアップ出すほうが良い
        SoundPlay(sound)
    }

    write_log(str)  ; 🍅 Unicode Character “🍅” (U+1F345)
    {           
        If WinExist("ahk_exe Obsidian.exe")
            WinActivate()

        Date := FormatTime(, "yyyy-MM-dd")
        Now := FormatTime(, "HH:mm")    ; Now := FormatTime(, "HH:mm:ss")
        FileAppend("`n" "- " Now " 🍅 Pomodoro " str "`n", logsavepath "\" Date ".md", "UTF-8")   
    }

}
;* END *

ahkファイルの最初の方に、個人の環境等で設定の変更が必要な変数などが固めてあります。

pomodoro_timer(min)
{
    ;***ユーザによる設定値****************************
    toggle_hotkey := 'F8'   ; *** Hotキーの定義 ***

    pomodoro := min     ; 25(min)
    rest := 5           ;  5(min)
    interval := 1       ; 更新間隔(sec) (1秒毎に更新) 5秒毎とか10秒毎なら 5,10 (1.3とかしてデバッグも)
    ;pomodoro := 0.2    ; (for Debug) 0.2 = 12sec
    ;rest := 0.1        ; (for Debug) 0.1 =  6sec

    ; すごく小さく 右上に表示 (PCの画面の解像度により -100, w, hとも要修正)
    startx := A_ScreenWidth - 100   ;表示位置X (右)
    starty := 0                     ;表示位置Y (上)
    timer_w := 50   ; 表示の幅
    timer_h := 22   ; 表示の高さ

    ; サイズ12が小さくて大きくするなら、timer_w,timer_hやmake_gui()での値も大きく
    work_font := "s12 cBlack Norm"  ; フォントサイズs(12)、色c(Black)、書式(ノーマル)に設定 
    rest_font := "s12 cRed   Bold"  ; フォントサイズs(12)、色c(Red)、書式(Bold)に設定 

    work_color := "White"   ;Background Color
    rest_color := "Aqua"    ;

    work_end_message := '🍅 休憩時間になりました。`n🍅 休みましょう。`n🍅 タイマーを続けますか?'        ; 改行は `n
    rest_end_message := 'さあ時間です。頑張りましょう!'
    
    work_end_sound := "C:\Windows\Media\Ring06.wav"
    rest_end_sound := "C:\Windows\Media\Ring01.wav"
    
    logsavepath   := A_MyDocuments
    ;logsavepath   := "C:\Users\Username\Obsidian\daily"  ; Obsidian Daily file 

    log_work_start := "💻 Work start" ; 💻 🖥️ ⌨
    log_work_end   := "💻 Work end"
    log_rest_start := "🍵 Rest start" ; 🍵 (紅茶) ☕ (コーヒー)
    log_rest_end   := "🍵 Rest end"   ; ㊡ (まるの中に休の文字): Unicode U+32A1 もありかも

起動キー(Hotley)、起動したときの表示位置、タイマー時間、表示メッセージ、再生する音、ログを記録するファイルのフォルダ、記録文字列などを変更してください。

ログファイルについて

仕事開始、仕事終了、休憩開始、休憩終了の記録を残すことができます。

ahkファイルの最初の設定部分に、保存するフォルダの指定があります。

logsavepath   := A_MyDocuments
;logsavepath   := "C:\Users\Username\Obsidian\daily"

初期は、マイドキュメントの下に、YYYY-mm-dd.md の名前のMarkdown形式で保存するようになっています。 Obsidianを想定しているので、Obsidianのdailyフォルダの場所などを指定してください。

ファイルには、以下のように記録されていきます。

- 20:16 🍅 Pomodoro 💻 Work start

- 20:41 🍅 Pomodoro 💻 Work end
  
- 20:41 🍅 Pomodoro 🍵 Rest start

- 20:46 🍅 Pomodoro 🍵 Rest end

おわりに

音とかメッセージなど、自分好みに変更して、やる気が出るように修正して、効率を上げてください。

Autohotkey Ver2のGUIによる40% QWERTYスクリーンタッチキーボード

AutohotkeyGUIを使って、物理的なキーボードのないPC (Surface)を、ペンや指だけで操作するを目指すシリーズです。

40%キーボードのタッチタイプキーボードです。

Keyword: Autohotkey, V2, ahk, タッチタイプキーボード, スクリーンキーボード, 仮想キーボード, 40%キーボード、レイヤー切替

この前の記事

torazaemon2016.hatenablog.jp

40% QWERTYキーボード

いわゆる40%キーボードとかと呼ばれている、キーの数が少ないキーボードを参考にして、画面が狭くならないスクリーンキーボードを目指したものです。

40%キーボードは「沼」ですね。

note.com

Autohotkeyで作っているので、キートップに表示する文字をレイヤー毎に切り替えられるので、物理キーボードよりも 使いやすい?ものになっています。

標準レイヤー

標準レイヤー(Spaceキー左右のIME ON/OFF ボタンと背景緑色に変化でローマ字かな変換の切り替えが容易)

左Shiftを押したレイヤー(大文字 右Spaceは全角空白になります)

右Shiftを押したレイヤー(記号中心)

左下Numberを押した数字レイヤー

Funcを押した機能レイヤー

けいならべレイヤー

小さいサイズにしたときのスクリーンキーボード

Autohotkeyでのプログラム

なお、Autohotkeyの詳しいことやインストールなどについては、他の詳しいページを参考にしてください。

Update

  • 2025-11-12 アイコンの追加やサウンド機能を付けました。特にサウンドは、タッチキーボードの押したかどうかわからないという問題にちょっとだけ解決になるものです。音が不要ならば、ツールバーのアイコンのメニューからサウンドON/offで切り替えてください。
  • 2025-11-21 40%や60%キーボード、分離と分離タイプでないもの、さらにオーソリニア(格子配列)も、TrayMenuから選べるようにしました。

  • 音が低遅延のものになりました。だいぶ印象が変わります。

  • キーボードの定義を別ファイルとして、GUI系とわけました。

最初に、40%キーボードの定義、次に60%キーボードの定義、最後に本体の3つに分かれていますので、 それぞれダウンロードしてファイルにしてください。

また IME_GET()などを利用していますので、(#Include "IMEv2.ahk" )

qiita.com

ダウンロードして、3つのファイルと同じところに置いてください。

40% キーボード定義 (ファイル名:virtualkeyboard-keymap-37.ahk)

; 2025-11-20 実行プログラムから分離
; 40%QWERTY 12+12+12+11=37key

;***************************************************************************************************************
;キーボードの並びの配列 
; 個々のキーは キー名、wの倍率、hの倍率、offsetの倍率 'keyname, w_magnification, h_magnification, offset_magnification'
/**************************************************************************/
; 分離
; オーソリニア(格子配列)(Ortholinear)QWERTY
key_table_ortho37_left := [
     [ 'bnTab,1,1,0', 'bnq,1,1,0', 'bnw,1,1,0', 'bne,1,1,0', 'bnr,1,1,0', 'bnt,1,1,0' ]
    ,[ 'bnLCtrl,1,1,0', 'bna,1,1,0', 'bns,1,1,0', 'bnd,1,1,0', 'bnf,1,1,0', 'bng,1,1,0' ]
    ,[ 'bnLShift,1,1,0', 'bnz,1,1,0', 'bnx,1,1,0', 'bnc,1,1,0', 'bnv,1,1,0', 'bnb,1,1,0' ]
    ,[ 'bnNumber,1,1,0', 'bnLWin,1,1,0', 'bnLAlt,1,1,0', 'bnvk1D,1,1,0', 'bnSpace,2,1,0' ]
]

key_table_ortho37_right := [
     ['bny,1,1,0', 'bnu,1,1,0', 'bni,1,1,0', 'bno,1,1,0', 'bnp,1,1,0','bnBackspace,1,1,0']
    ,['bnh,1,1,0', 'bnj,1,1,0', 'bnk,1,1,0', 'bnl,1,1,0', 'bnMinus,1,1,0', 'bnEnter,1,1,0' ]
    ,['bnn,1,1,0', 'bnm,1,1,0', 'bnComma,1,1,0', 'bnPeriod,1,1,0', 'bnUp,1,1,0',  'bnRShift,1,1,0' ]
    ,['bnRSpace,1,1,0', 'bnvk1C,1,1,0', 'bnFunc,1,1,0', 'bnLeft,1,1,0', 'bnDown,1,1,0', 'bnRight,1,1,0' ]
]

/**************************************************************************/
; 分離ロウスタッガード(Row-Staggered)QWERTY
key_table_row37_left := [
     [ 'bnTab,1,1,0', 'bnq,1,1,0', 'bnw,1,1,0', 'bne,1,1,0', 'bnr,1,1,0', 'bnt,1,1,0' ]
    ,[ 'bnLCtrl,1.2,1,0', 'bna,1,1,0', 'bns,1,1,0', 'bnd,1,1,0', 'bnf,1,1,0', 'bng,1,1,0' ]
    ,[ 'bnLShift,1.4,1,0', 'bnz,1,1,0', 'bnx,1,1,0', 'bnc,1,1,0', 'bnv,1,1,0', 'bnb,1,1,0' ]
    ,[ 'bnNumber,1,1,0', 'bnLWin,1,1,0', 'bnLAlt,1,1,0', 'bnvk1D,1,1,0', 'bnSpace,2.4,1,0' ]
]

key_table_row37_right := [
     ['bny,1,1,0', 'bnu,1,1,0', 'bni,1,1,0', 'bno,1,1,0', 'bnp,1,1,0','bnBackspace,1.4,1,0']
    ,['bnh,1,1,0.2', 'bnj,1,1,0', 'bnk,1,1,0', 'bnl,1,1,0', 'bnMinus,1,1,0', 'bnEnter,1.2,1,0' ]
    ,['bnn,1,1,0.4', 'bnm,1,1,0', 'bnComma,1,1,0', 'bnPeriod,1,1,0', 'bnUp,1,1,0',  'bnRShift,1,1,0' ]
    ,['bnRSpace,1.4,1,0', 'bnvk1C,1,1,0', 'bnFunc,1,1,0', 'bnLeft,1,1,0', 'bnDown,1,1,0', 'bnRight,1,1,0' ]
]

/**************************************************************************/
; オーソリニア(格子配列)(Ortholinear)QWERTY
key_table_ortho37 := [
     [ 'bnTab,1,1,0', 'bnq,1,1,0', 'bnw,1,1,0', 'bne,1,1,0', 'bnr,1,1,0', 'bnt,1,1,0', 
        'bny,1,1,0', 'bnu,1,1,0', 'bni,1,1,0', 'bno,1,1,0', 'bnp,1,1,0','bnBackspace,1,1,0']
    ,[ 'bnLCtrl,1,1,0', 'bna,1,1,0', 'bns,1,1,0', 'bnd,1,1,0', 'bnf,1,1,0', 'bng,1,1,0',
        'bnh,1,1,0', 'bnj,1,1,0', 'bnk,1,1,0', 'bnl,1,1,0', 'bnMinus,1,1,0', 'bnEnter,1,1,0' ]
    ,[ 'bnLShift,1,1,0', 'bnz,1,1,0', 'bnx,1,1,0', 'bnc,1,1,0', 'bnv,1,1,0', 'bnb,1,1,0',
        'bnn,1,1,0', 'bnm,1,1,0', 'bnComma,1,1,0', 'bnPeriod,1,1,0', 'bnUp,1,1,0',  'bnRShift,1,1,0' ]
    ,[ 'bnNumber,1,1,0', 'bnLWin,1,1,0', 'bnLAlt,1,1,0', 'bnvk1D,1,1,0', 'bnSpace,2,1,0',
        'bnRSpace,1,1,0', 'bnvk1C,1,1,0', 'bnFunc,1,1,0', 'bnLeft,1,1,0', 'bnDown,1,1,0', 'bnRight,1,1,0' ]
]

/**************************************************************************/
; ロウスタッガード(Row-Staggered)QWERTY
key_table_row37 := [
     [ 'bnTab,1,1,0', 'bnq,1,1,0', 'bnw,1,1,0', 'bne,1,1,0', 'bnr,1,1,0', 'bnt,1,1,0',
        'bny,1,1,0', 'bnu,1,1,0', 'bni,1,1,0', 'bno,1,1,0', 'bnp,1,1,0','bnBackspace,1.4,1,0']
    ,[ 'bnLCtrl,1.2,1,0', 'bna,1,1,0', 'bns,1,1,0', 'bnd,1,1,0', 'bnf,1,1,0', 'bng,1,1,0',
        'bnh,1,1,0', 'bnj,1,1,0', 'bnk,1,1,0', 'bnl,1,1,0', 'bnMinus,1,1,0', 'bnEnter,1.2,1,0' ]
    ,[ 'bnLShift,1.4,1,0', 'bnz,1,1,0', 'bnx,1,1,0', 'bnc,1,1,0', 'bnv,1,1,0', 'bnb,1,1,0',
        'bnn,1,1,0', 'bnm,1,1,0', 'bnComma,1,1,0', 'bnPeriod,1,1,0', 'bnUp,1,1,0',  'bnRShift,1,1,0' ]
    ,[ 'bnNumber,1.2,1,0', 'bnLWin,1,1,0', 'bnLAlt,1,1,0', 'bnvk1D,1,1,0', 'bnSpace,1.6,1,0',
        'bnRSpace,1.6,1,0', 'bnvk1C,1,1,0', 'bnFunc,1,1,0', 'bnLeft,1,1,0', 'bnDown,1,1,0', 'bnRight,1,1,0' ]
]

;--------------------------------------------------------------------------------------------------------------
;**キーボード1個1個のボタンのCSVデータ
;  ( ... ) は 行継続 (continuation section)
; AutoHotkey では、( で始めて ) で終わる部分を「行継続セクション」でこの中はそのまま文字列リテラルとして扱われる
; csv := と 次の行の ( と 次の行の " は、このとおりにしておかないとエラー。同様に、一番最後の " と ) も独立行。また途中に"を書くときにハマるので注意 `" で書く
keymap_csv37 :=
(
"
; 記述の ;から書き始まっているのはMakeMapsFromCSV()で読み飛ばす
; 1:button名, 2:normal, 3:左shift, 4:右シフト, 5:Number, 6:Func, 7:けいならべ, (8:コメント) の必須7項目と、任意のコメントの8項目の並び  

; 2025-09-23 分離QWERTY
; qwerty 1段   40%キーボードでは使用せず
bnEsc,Esc,Esc,Esc,Esc,SIZE,,,   ;comment test Escape key size変更
bn1,1,!,!,!,F1,F1,,
bn2,2,`",\,`",F2,F2,,           ;ダブルコーテーションは`でエスケープすることでエラーを回避
bn3,3,#,#,F3,F3,,
bn4,4,$,$,F4,F4,,
bn5,5,%,%,F5,F5,,

bn6,6,&&,&&,F6,F6,,             ;Buttonコントロールに&文字を表示させるには、&&と記述する。send_key関数でも case '&&' としてやる必要がある
bn7,7,',',F7,F7,,
bn8,8,(,(,F8,F8,,
bn9,9,),),F9,F9,,
bn0,0,|,|,F10,F10,,             ; | を 0のshiftにする

bnHat,^,~,~,~,F12,,             ;分離では未使用
bnEnmark,\,\,|,|,|,,            ;分離では未使用 円マークと|のキーはキー削除のため | を 0のshiftにする
bnBackspace,BS,}+{BS,BS,BS,BS,BS,


; qwerty 2段
bnTab,Tab,}+{Tab,Esc,Tab,Esc,Esc,   HIDEは両windowを隠す
bnq,q,Q,!,1,F1,f,               ; q->f               
bnw,w,W,`",2,F2,w,              ;ダブルコーテーションは`でエスケープすることでエラーを回避
bne,e,E,#,3,F3,m,
bnr,r,R,$,4,F4,r,
bnt,t,T,%,5,F5,p,

bny,y,Y,&&,6,F6,}ye{,           ; &&にしなければならないことに注意 
bnu,u,U,',7,F7,}yo{,            ; 'は特別な処理せずともいける
bni,i,I,(,8,F8,u,
bno,o,O,),9,F9,o,
bnp,p,P,?,0,F10,x,
bnAtmark,@,``,``,[,F11,,        ;分離では未使用 backquoteは`でエスケープすることで`の1文字になる
bnLBracket,[,{,{,{,F12,,        ;分離では未使用


; qwerty 3段 CaspはLCtrl
bnLCtrl,LCtrl,LCtrl,LCtrl,LCtrl,LCtrl,LCtrl,
bna,a,A,+,+,}^a{,n,
bns,s,S,-,-,}^s{,s,
bnd,d,D,=,=,}^d{,t,
bnf,f,F,~,(,}^f{,k,
bng,g,G,^,),}^g{,h,

bnh,h,H,;,{,}^h{,}yu{,
bnj,j,J,:,},TIME,a,             ; Time }!;{
bnk,k,K,[,[,DATE,i,             ; Date }!:{
bnl,l,L,],],F11,e,
bnSemiColon,;,+,+,PgDn,,,       ;分離では未使用
bnColon,:,*,*,],PgUp,,          ;分離では未使用
bnRBracket,],},},},PgDn,,       ;分離では未使用      

bnMinus,-,=,@,``,F12,-,         ; 1段から3段へ backquoteは`でエスケープすることで`の1文字になる

bnEnter,Enter,}+{Enter,Enter,Enter,Enter,Enter,Enter,


; qwerty 4段
bnLShift,LShift,LLL,LShift,LShift,LShift,LShift,
bnz,z,Z,*,*,}^z{,v,             ;けいならべでは、vaのヴぁは出せないのでv
bnx,x,X,/,/,}^x{,z,
bnc,c,C,\,\,}^c{,d,
bnv,v,V,|,$,}^v{,g,
bnb,b,B,_,%,}^b{,b,

bnn,n,N,<,<,}^n{,}ya{,
bnm,m,M,>,>,}^m{,}nn{,
bnComma,COMMA,<,{,COMMA,HOME,COMMA,,     ;コンマ(,)はCSVで文字として扱うのは面倒なのでMakeMapsFromCSV関数でCOMMAを,に置き換えて代入することで処理する
bnPeriod,.,>,},.,End,.,          
bnSlash,/,/,?,?,,,              ;分離では未使用
bnUnder,\,_,PgUp,,,,            ;分離では未使用
bnUp,Up,Up,}+{Up,Up,}+{Up,Up,
bnRShift,RShift,RShift,RRR,RShift,RShift,RShift,    ;RShift は領域選択ができる(+をつけて送出する処理)


; 5段目
bnNumber,Number,Number,Number,NNN,Number,Number,    ; 記号キー(数字キー)(実キーボードにはない創作キー) send_key()関数で処理
bnLWin,LWin,LWin,LWin,LWin,LWin,LWin,
bnLAlt,LAlt,LAlt,LAlt,LAlt,LAlt,LAlt,
bnvk1D,無変換,無変換,無変換,無変換,無変換,無変換,     ;無変換キー vk1D
bnSpace,Space,Space,Space,Space,Space,Space,    ;左スペースキー すべてノーマル

bnRSpace,Space, ,}+{Space, ,Space,Enter,    ;右スペースキー Number時全角空白、Funcのときサイズ変更に                  
bnvk1C,変換,変換,変換,変換,変換,変換,                   ;変換キー vk1C  
bnFunc,Func,Func,Func,Func,FFF,Func,            ;特殊キー(実キーボードにはない創作キー) send_key()関数で処理

bnLeft,Left,Left,}+{Left,Left,}+{Left,Left,
bnDown,Down,Down,}+{Down,Down,}+{Down,Down,
bnRight,Right,Right,}+{Right,Right,}+{Right,Right,


; 物理キーには定義がある特殊キー---------------
bnRWin,RWin,RWin,RWin,RWin,RWin,RWin,           ;分離では未使用
bnRCtrl,RCtrl,RCtrl,RCtrl,RCtrl,RCtrl,RCtrl,    ;分離では未使用
bnRAlt,RAlt,RAlt,RAlt,RAlt,RAlt,RAlt,           ;分離では未使用

bnF11,F11,F11,F11,F11,F11,F11,                  ;分離では未使用
bnF12,F12,F12,F12,F12,F12,F12,                  ;分離では未使用
bnF13,F13,F13,F13,F13,F13,F13,                  ;分離では未使用
bnF14,F14,F14,F14,F14,F14,F14,                  ;分離では未使用
bnF15,F15,F15,F15,F15,F15,F15,                  ;分離では未使用
bnF16,F16,F16,F16,F16,F16,F16,                  ;分離では未使用

"
)
; ) で 継続行終わり
;--------------------------------------------------------------------------------------------------------------
;* End *

60%キーボード定義 (ファイル名:virtualkeyboard-keymap-66.ahk)

; 2025-11-20 実行プログラムから分離
; 60%+cursor QWERTY 14+14+13+14+11=66key (!= 67key (Enterが2つ分))

;***************************************************************************************************************
;キーボードの並びの配列 
; 個々のキーは キー名、wの倍率、hの倍率、offsetの倍率 'keyname, w_magnification, h_magnification, offset_magnification'
/**************************************************************************/
; 分離オーソリニア(格子配列)(Ortholinear)QWERTY
key_table_ortho66_left := [
 [ 'bnEsc,1,1,0', 'bn1,1,1,0', 'bn2,1,1,0', 'bn3,1,1,0', 'bn4,1,1,0', 'bn5,1,1,0' ]
,[ 'bnTab,1,1,0', 'bnq,1,1,0', 'bnw,1,1,0', 'bne,1,1,0', 'bnr,1,1,0', 'bnt,1,1,0' ] 
,[ 'bnLCtrl,1,1,0', 'bna,1,1,0', 'bns,1,1,0', 'bnd,1,1,0', 'bnf,1,1,0', 'bng,1,1,0' ]
,[ 'bnLShift,1,1,0', 'bnz,1,1,0', 'bnx,1,1,0', 'bnc,1,1,0', 'bnv,1,1,0', 'bnb,1,1,0' ] 
,[ 'bnNumber,1,1,0', 'bnLWin,1,1,0', 'bnLAlt,1,1,0', 'bnvk1D,1,1,0', 'bnSpace,2,1,0' ]
]

key_table_ortho66_right := [
 ['bn6,1,1,0', 'bn7,1,1,0', 'bn8,1,1,0', 'bn9,1,1,0', 'bn0,1,1,0', 'bnMinus,1,1,0', 'bnHat,1,1,0', 'bnBackspace,1,1,0']
,['bny,1,1,0', 'bnu,1,1,0', 'bni,1,1,0', 'bno,1,1,0', 'bnp,1,1,0','bnAtmark,1,1,0', 'bnLBracket,1,1,0' , 'bnEnter,1,2,0' ]
,['bnh,1,1,0', 'bnj,1,1,0', 'bnk,1,1,0', 'bnl,1,1,0',  'bnSemiColon,1,1,0', 'bnColon,1,1,0', 'bnRBracket,1,1,0' ]
,['bnn,1,1,0', 'bnm,1,1,0', 'bnComma,1,1,0', 'bnPeriod,1,1,0',  'bnSlash,1,1,0',  'bnUnder,1,1,0', 'bnUp,1,1,0',  'bnRShift,1,1,0' ]
,['bnRSpace,3,1,0', 'bnvk1C,1,1,0', 'bnFunc,1,1,0', 'bnLeft,1,1,0', 'bnDown,1,1,0', 'bnRight,1,1,0' ]
]

/**************************************************************************/
; 分離ロウスタッガード(Row-Staggered)QWERTY
key_table_row66_left := [
 [ 'bnEsc,0.8,1,0', 'bn1,1,1,0', 'bn2,1,1,0', 'bn3,1,1,0', 'bn4,1,1,0', 'bn5,1,1,0' ]
,[ 'bnTab,1,1,0', 'bnq,1,1,0', 'bnw,1,1,0', 'bne,1,1,0', 'bnr,1,1,0', 'bnt,1,1,0' ]
,[ 'bnLCtrl,1.2,1,0', 'bna,1,1,0', 'bns,1,1,0', 'bnd,1,1,0', 'bnf,1,1,0', 'bng,1,1,0' ]
,[ 'bnLShift,1.4,1,0', 'bnz,1,1,0', 'bnx,1,1,0', 'bnc,1,1,0', 'bnv,1,1,0', 'bnb,1,1,0' ]
,[ 'bnNumber,1.4,1,0', 'bnLWin,1,1,0', 'bnLAlt,1,1,0', 'bnvk1D,1,1,0', 'bnSpace,2,1,0' ]
]

key_table_row66_right := [
 ['bn6,1,1,0', 'bn7,1,1,0', 'bn8,1,1,0', 'bn9,1,1,0', 'bn0,1,1,0', 'bnMinus,1,1,0', 'bnHat,1,1,0', 'bnBackspace,1.6,1,0']
,['bny,1,1,0.2', 'bnu,1,1,0', 'bni,1,1,0', 'bno,1,1,0', 'bnp,1,1,0','bnAtmark,1,1,0', 'bnLBracket,1,1,0' , 'bnEnter,1.2,2,0.2' ]
,['bnh,1,1,0.4', 'bnj,1,1,0', 'bnk,1,1,0', 'bnl,1,1,0',  'bnSemiColon,1,1,0', 'bnColon,1,1,0', 'bnRBracket,1,1,0' ]
,['bnn,1,1,0.6', 'bnm,1,1,0', 'bnComma,1,1,0', 'bnPeriod,1,1,0',  'bnSlash,1,1,0',  'bnUnder,1,1,0', 'bnUp,1,1,0',  'bnRShift,1,1,0' ]
,['bnRSpace,3.6,1,0', 'bnvk1C,1,1,0', 'bnFunc,1,1,0', 'bnLeft,1,1,0', 'bnDown,1,1,0', 'bnRight,1,1,0' ]
]

/**************************************************************************/
; オーソリニア(格子配列)(Ortholinear)QWERTY
key_table_ortho66 := [
 [ 'bnEsc,1,1,0', 'bn1,1,1,0', 'bn2,1,1,0', 'bn3,1,1,0', 'bn4,1,1,0', 'bn5,1,1,0', 
    'bn6,1,1,0', 'bn7,1,1,0', 'bn8,1,1,0', 'bn9,1,1,0', 'bn0,1,1,0', 'bnMinus,1,1,0', 'bnHat,1,1,0', 'bnBackspace,1,1,0']
,[ 'bnTab,1,1,0', 'bnq,1,1,0', 'bnw,1,1,0', 'bne,1,1,0', 'bnr,1,1,0', 'bnt,1,1,0', 
    'bny,1,1,0', 'bnu,1,1,0', 'bni,1,1,0', 'bno,1,1,0', 'bnp,1,1,0','bnAtmark,1,1,0', 'bnLBracket,1,1,0' , 'bnEnter,1,2,0' ]
,[ 'bnLCtrl,1,1,0', 'bna,1,1,0', 'bns,1,1,0', 'bnd,1,1,0', 'bnf,1,1,0', 'bng,1,1,0',
    'bnh,1,1,0', 'bnj,1,1,0', 'bnk,1,1,0', 'bnl,1,1,0',  'bnSemiColon,1,1,0', 'bnColon,1,1,0', 'bnRBracket,1,1,0' ]
,[ 'bnLShift,1,1,0', 'bnz,1,1,0', 'bnx,1,1,0', 'bnc,1,1,0', 'bnv,1,1,0', 'bnb,1,1,0', 
    'bnn,1,1,0', 'bnm,1,1,0', 'bnComma,1,1,0', 'bnPeriod,1,1,0',  'bnSlash,1,1,0',  'bnUnder,1,1,0', 'bnUp,1,1,0',  'bnRShift,1,1,0' ]
,[ 'bnNumber,1,1,0', 'bnLWin,1,1,0', 'bnLAlt,1,1,0', 'bnvk1D,1,1,0', 'bnSpace,3,1,0',
    'bnRSpace,2,1,0', 'bnvk1C,1,1,0', 'bnFunc,1,1,0', 'bnLeft,1,1,0', 'bnDown,1,1,0', 'bnRight,1,1,0' ]
]

; ロウスタッガード(Row-Staggered)QWERTY
key_table_row66 := [
 [ 'bnEsc,0.8,1,0', 'bn1,1,1,0', 'bn2,1,1,0', 'bn3,1,1,0', 'bn4,1,1,0', 'bn5,1,1,0', 
    'bn6,1,1,0', 'bn7,1,1,0', 'bn8,1,1,0', 'bn9,1,1,0', 'bn0,1,1,0', 'bnMinus,1,1,0', 'bnHat,1,1,0', 'bnBackspace,1.6,1,0']
,[ 'bnTab,1,1,0', 'bnq,1,1,0', 'bnw,1,1,0', 'bne,1,1,0', 'bnr,1,1,0', 'bnt,1,1,0', 
    'bny,1,1,0', 'bnu,1,1,0', 'bni,1,1,0', 'bno,1,1,0', 'bnp,1,1,0','bnAtmark,1,1,0', 'bnLBracket,1,1,0' , 'bnEnter,1.2,2,0.2' ]
,[ 'bnLCtrl,1.2,1,0', 'bna,1,1,0', 'bns,1,1,0', 'bnd,1,1,0', 'bnf,1,1,0', 'bng,1,1,0',
    'bnh,1,1,0', 'bnj,1,1,0', 'bnk,1,1,0', 'bnl,1,1,0',  'bnSemiColon,1,1,0', 'bnColon,1,1,0', 'bnRBracket,1,1,0' ]
,[ 'bnLShift,1.4,1,0', 'bnz,1,1,0', 'bnx,1,1,0', 'bnc,1,1,0', 'bnv,1,1,0', 'bnb,1,1,0', 
    'bnn,1,1,0', 'bnm,1,1,0', 'bnComma,1,1,0', 'bnPeriod,1,1,0',  'bnSlash,1,1,0',  'bnUnder,1,1,0', 'bnUp,1,1,0',  'bnRShift,1,1,0' ]
,[ 'bnNumber,1.4,1,0', 'bnLWin,1,1,0', 'bnLAlt,1,1,0', 'bnvk1D,1,1,0', 'bnSpace,2.5,1,0',
    'bnRSpace,2.5,1,0.026', 'bnvk1C,1,1,0', 'bnFunc,1,1,0', 'bnLeft,1,1,0', 'bnDown,1,1,0', 'bnRight,1,1,0' ]
]   ; Spaceを 2.5 2.5 とすると微妙に1pixelほどずれるので3,2と整数値。もしくはサイズ大で決めて2.5,2.5のRのOffsetを0.026程度に微妙な隙間入れる


;--------------------------------------------------------------------------------------------------------------
;**キーボード1個1個のボタンのCSVデータ
;  ( ... ) は 行継続 (continuation section)
; AutoHotkey では、( で始めて ) で終わる部分を「行継続セクション」でこの中はそのまま文字列リテラルとして扱われる
; csv := と 次の行の ( と 次の行の " は、このとおりにしておかないとエラー。同様に、一番最後の " と ) も独立行。また途中に"を書くときにハマるので注意 `" で書く
keymap_csv66 :=
(
"
; 記述の ;から書き始まっているのはMakeMapsFromCSV()で読み飛ばす
; 1:button名, 2:normal, 3:左shift, 4:右シフト, 5:Number, 6:Func, 7:けいならべ, (8:コメント) の必須7項目と、任意のコメントの8項目の並び  

; 2025-09-23 分離QWERTY
; qwerty 1段   40%キーボードでは使用せず
bnEsc,Esc,Esc,Esc,Esc,Esc,Esc,, ;comment test Escape key size変更
bn1,1,!,!,F1,F1,1,
bn2,2,`",`",F2,F2,2,            ;ダブルコーテーションは`でエスケープすることでエラーを回避 
bn3,3,#,#,F3,F3,3,
bn4,4,$,$,F4,F4,4,
bn5,5,%,%,F5,F5,5,

bn6,6,&&,&&,F6,F6,6,                ;Buttonコントロールに&文字を表示させるには、&&と記述する。send_key関数でも case '&&' としてやる必要がある
bn7,7,',',F7,F7,7,
bn8,8,(,(,F8,F8,8,
bn9,9,),),F9,F9,9,
bn0,0,|,|,F10,F10,0,                ; | を 0のshiftにする

bnMinus,-,=,``,F11,F11,-,           ; 1段から3段へ backquoteは`でエスケープすることで`の1文字になる
bnHat,^,~,~,F12,F12,^,              ;分離では未使用
bnEnmark,\,\,|,|,|,\,           ;分離では未使用 円マークと|のキーはキー削除のため | を 0のshiftにする
bnBackspace,BS,}+{BS,BS,BS,BS,BS,

; qwerty 2段
bnTab,Tab,}+{Tab,Tab,Tab,Tab,Tab,
bnq,q,Q,!,1,}^q{,f,             ; q->f               
bnw,w,W,`",2,}^w{,w,                ;ダブルコーテーションは`でエスケープすることでエラーを回避
bne,e,E,#,3,}^e{,m,
bnr,r,R,$,4,}^r{,r,
bnt,t,T,%,5,}^t{,p,

bny,y,Y,&&,6,}^y{,}ye{,         ; &&にしなければならないことに注意 
bnu,u,U,',7,}^u{,}yo{,          ; 'は特別な処理せずともいける
bni,i,I,(,8,}^i{,u,
bno,o,O,),9,}^o{,o,
bnp,p,P,?,0,}^p{,x,
bnAtmark,@,``,``,[,F13,,        ;分離では未使用 backquoteは`でエスケープすることで`の1文字になる
bnLBracket,[,{,{,{,F14,,        ;分離では未使用
bnEnter,Enter,}+{Enter,Enter,Enter,Enter,Enter,Enter,


; qwerty 3段 CaspはLCtrl
bnLCtrl,LCtrl,LCtrl,LCtrl,LCtrl,LCtrl,LCtrl,
bna,a,A,+,+,}^a{,n,
bns,s,S,-,-,}^s{,s,
bnd,d,D,=,=,}^d{,t,
bnf,f,F,~,(,}^f{,k,
bng,g,G,^,),}^g{,h,

bnh,h,H,;,{,}^h{,}yu{,
bnj,j,J,:,},}^j{,a,             ; Time }!;{
bnk,k,K,[,[,}^k{,i,             ; Date }!:{
bnl,l,L,],],}^l{,e,
bnSemiColon,;,+,+,``,TIME,,,        ;分離では未使用
bnColon,:,*,*,],DATE,,          ;分離では未使用
bnRBracket,],},},},,,       ;分離では未使用      

; qwerty 4段
bnLShift,LShift,LLL,LShift,LShift,LShift,LShift,
bnz,z,Z,*,*,}^z{,v,             ;けいならべでは、vaのヴぁは出せないのでv
bnx,x,X,/,/,}^x{,z,
bnc,c,C,\,\,}^c{,d,
bnv,v,V,|,$,}^v{,g,
bnb,b,B,_,%,}^b{,b,

bnn,n,N,<,<,}^n{,}ya{,
bnm,m,M,>,>,}^m{,}nn{,
bnComma,COMMA,<,{,COMMA,HOME,COMMA,,     ;コンマ(,)はCSVで文字として扱うのは面倒なのでMakeMapsFromCSV関数でCOMMAを,に置き換えて代入することで処理する
bnPeriod,.,>,},.,End,.,          
bnSlash,/,?,PgUp,?,?,/,             ;分離では未使用
bnUnder,\,_,PgDn,\,\,\,         ;分離では未使用
bnUp,Up,Up,}+{Up,Up,}+{Up,Up,
bnRShift,RShift,RShift,RRR,RShift,RShift,RShift,    ;RShift は領域選択ができる(+をつけて送出する処理)


; 5段目
bnNumber,Number,Number,Number,NNN,Number,Number,    ; 記号キー(数字キー)(実キーボードにはない創作キー) send_key()関数で処理
bnLWin,LWin,LWin,LWin,LWin,LWin,LWin,
bnLAlt,LAlt,LAlt,LAlt,LAlt,LAlt,LAlt,
bnvk1D,無変換,無変換,無変換,無変換,無変換,無変換,     ;無変換キー vk1D
bnSpace,Space,Space,Space,Space,Space,Space,    ;左スペースキー すべてノーマル

bnRSpace,Space, ,}+{Space, ,Space,Enter,    ;右スペースキー Number時全角空白、Funcのときサイズ変更に                  
bnvk1C,変換,変換,変換,変換,変換,変換,                   ;変換キー vk1C  
bnFunc,Func,Func,Func,Func,FFF,Func,            ;特殊キー(実キーボードにはない創作キー) send_key()関数で処理

bnRAlt,RAlt,RAlt,RAlt,RAlt,RAlt,RAlt,           ;
bnLeft,Left,Left,}+{Left,Left,}+{Left,Left,
bnDown,Down,Down,}+{Down,Down,}+{Down,Down,
bnRight,Right,Right,}+{Right,Right,}+{Right,Right,

; 物理キーには定義がある特殊キー---------------
bnRWin,RWin,RWin,RWin,RWin,RWin,RWin,           ;未使用
bnRCtrl,RCtrl,RCtrl,RCtrl,RCtrl,RCtrl,RCtrl,    ;未使用
bnRAlt,RAlt,RAlt,RAlt,RAlt,RAlt,RAlt,           ;未使用
bnF11,F11,F11,F11,F11,F11,F11,                  ;未使用
bnF12,F12,F12,F12,F12,F12,F12,                  ;未使用
bnF13,F13,F13,F13,F13,F13,F13,                  ;未使用
bnF14,F14,F14,F14,F14,F14,F14,                  ;未使用
bnF15,F15,F15,F15,F15,F15,F15,                  ;未使用
bnF16,F16,F16,F16,F16,F16,F16,                  ;未使用
"
)
; ) で 継続行終わり
;--------------------------------------------------------------------------------------------------------------
;* End *

本体(virtualkeyboard.ahk)

; 2025-10-08 40% QWERTY by torazemon2016
; 2025-10-30 関数化
; 2025-11-12 ツールバーアイコンやサウンドの追加 
; 2025-11-19 流れの整理
; 2025-11-20 key_table,keymapを別ファイルに、分離キーボード版もコードマージ
; 2025-11-21 音を低遅延の方法で(Wavをメモリ読み込み、再生)

; 参考
; title: "Can buttons in GUI act like keyboard keys?"
; https://www.reddit.com/r/AutoHotkey/comments/13gawje/can_buttons_in_gui_act_like_keyboard_keys/

#Requires AutoHotkey 2.0+       ; Version 2以上

#SingleInstance Force

;IME_SET,GET() IME制御ライブラリを使用
; https://qiita.com/kenichiro_ayaki/items/d55005df2787da725c6f
#Include "IMEv2.ahk"    

; キーレイアウトおよびキーボードボタン個々のCSVデータ(key_table,keymap_csv)
#Include "virtualkeyboard-keymap-66.ahk"        ; 60%keyboard 66keys
#Include "virtualkeyboard-keymap-37.ahk"        ; 40%keyboard 37keys

/*******************************************************/
Virtual_Keyboard(2,"Separate 37keys Row")   
                    ; KeySize : keyboardの大きさ (SIZEキー)   ; 1: small size, 2: large size
                    ; KeyShape: キーボード形状 (TrayMenu)
/*******************************************************/
Virtual_Keyboard(KeySize,KeyShape,*) 
{
    /* ユーザーがいろいろと定義 ***************************/
    ; Hotキーの定義
    toggle_hotkey := 'F4'   ; Hotkey

    ; キーサイズ指定
    ; 小さいタイプ
    S_margin := 1       ; キーとキーの間
    S_btn_w := 46       ; キーボタンの幅
    S_btn_h := 46       ; キーボタンの高さ
    S_font_size := "s14"  ; フォントサイズ(ポイント)
    ; 大きめ
    L_margin := 1       ; キーとキーの間
    L_btn_w := 76       ; キーボタンの幅
    L_btn_h := 80       ; キーボタンの高さ
    L_font_size := "s18 Bold"  ; フォントサイズ(ポイント)
    ;L_Margin := 30     ; (for Debug)
    ;L_Btn_w := 100     ; (for Debug)

    ; 初期表示場所
    Startx := 1         
    Starty := 800

    ; クリック音(Wavfile)
    ;ClickSound := "C:\Windows\Media\ding.wav"
    ClickSound := "C:\Windows\Media\Windows Navigation Start.wav"
    isSound := true     ; 音を出す true / 出さない false

    /* ユーザー定義おわり ***************************/

    /* システム側 ********************************/
    ; TrayMenu
    TrayMenu_Hide := "Hide &Keyboard"   ; &K キーボードのKをショートカット指定
    TrayMenu_Show := "Show &Keyboard"
    TrayMenu_SoundON := "&Sound ON"     ; &S キーボードのSをショートカット指定
    TrayMenu_SoundOff := "&Sound Off"

    wavPtr := LoadWavToMemory(ClickSound)   ; キークリック音

    ;***ここからが本体(main) キーボードデータを作成し、GUIオブジェクトを作成して表示*******************************
    ; キーボードの種類決定
    Keymap_csv := keymap_csv37
    Key_table := key_table_row37
    Key_table_r := ""
    switch KeyShape 
    {
        case "66keys Row":
            Keymap_csv := keymap_csv66
            Key_table := key_table_row66
        case "66keys Ortho":
            Keymap_csv := keymap_csv66
            Key_table := key_table_ortho66  ;オーソリニア(格子配列)(Ortholinear)QWERTY
        case "37keys Row":
            Keymap_csv := keymap_csv37
            Key_table := key_table_row37
        case "37keys Ortho":
            Keymap_csv := keymap_csv37
            Key_table := key_table_ortho37  ;オーソリニア(格子配列)(Ortholinear)QWERTY
        ;分離
        case "Separate 66keys Row":
            Keymap_csv := keymap_csv66
            Key_table := key_table_row66_left
            Key_table_r := key_table_row66_right
        case "Separate 66keys Ortho":
            Keymap_csv := keymap_csv66
            Key_table := key_table_ortho66_left ;オーソリニア(格子配列)(Ortholinear)QWERTY
            Key_table_r := key_table_ortho66_right
        case "Separate 37keys Row":
            Keymap_csv := keymap_csv37
            Key_table := key_table_row37_left
            Key_table_r := key_table_row37_right
        case "Separate 37keys Ortho":
            Keymap_csv := keymap_csv37
            Key_table := key_table_ortho37_left ;オーソリニア(格子配列)(Ortholinear)QWERTY
            Key_table_r := key_table_ortho37_right
    }
    
    ; サイズ指定
    if(KeySize == 1)    ; 小さめ
    {
        Margin := S_margin
        Btn_w := S_btn_w
        Btn_h := S_btn_h
        Font_size := S_font_size
    }
    else                ; 大きめ
    {
        Margin := L_margin
        Btn_w := L_btn_w
        Btn_h := L_btn_h
        Font_size := L_font_size
    }

    ;*** Keymap部 **********************************************
    maps := MakeMapsFromCSV(Keymap_csv) ; Includeファイルの中に定義

    ; --- それぞれのモードのときのキーの文字 ---
    Bn2Asc_map := maps[1]   ; MakeMapsFromCSV()のとは1つずれているのに注意
    Bn2LShift_map := maps[2]
    Bn2RShift_map := maps[3]
    Bn2Number_map := maps[4]
    Bn2Func_map := maps[5]
    Bn2Kei_map := maps[6]

    ;システム変数
    ; --- 同時にON (押されているがあるので、(Shiftの)CurrentModeとは違う扱い) ---
    AltState := 0   ; ALtキーが押されてるか1 ない 0
    CtrlState := 0  ; Ctrlキーが押されてるか1 ない 0
    WinState := 0   ; Winキーが押されてるか1 ない 0
    ; --- モード切替用変数
    CurrentMode := "normal"  ; 初期値: normal(ノーマルキー) LShift(シフトキー) RShift(右シフト) Number(記号) Func(F1など拡張) Keinarabe(けいならべ)    
    
    ;*** GUI部 ***********************************************
    ;キーボードGUIオブジェクトを作成
    ;guikeyboard := Gui('+AlwaysOnTop', "AHK Keyboard") ; これだと文字がエディタなどに入力されない
    gui1 := Gui('+AlwaysOnTop +E0x08000000', "AHK Keyboard 1")  ; これだと文字を送れる ; "WS_DISABLED"
    make_keyboard(gui1,Key_table)
    gui1.Show('x' Startx ' y' Starty ' NoActivate')     ; 画面表示
    gui1.OnEvent("Close", ShowHideWindow)                   ; X で閉じるとき

    ; ★分離キーボード
    if InStr(KeyShape, "Separate")
    {
        ;右キーボードGUIオブジェクトを作成
        gui2 := Gui('+AlwaysOnTop +E0x08000000', "AHK Keyboard 2")  ; これだと文字を送れる ; "WS_DISABLED"
        make_keyboard(gui2,Key_table_r)
        gui2.Show('x' Startx+1100 ' y' Starty ' NoActivate')    ; 画面右上
        gui2.OnEvent("Close", ShowHideWindow)                   ; 分離キーボード
    }
    
    set_Tray()
    Hotkey("*" toggle_hotkey, ShowHideWindow)               ; keyboard on/off
    OnExit(ExitWindow)                                  ; 終了時

    ;***ここまでがmain********************************************************************

    ;以下 関数群 ***********************************************************************
    ; --- CSVからMap化する関数 ---
    MakeMapsFromCSV(csvText) 
    {
        ;bm1 := Map()  ; reverse map (normal を button name にマップ (asc2bn))不要に
        bm2 := Map()  ; 英字表示マップ   (normal)
        bm3 := Map()  ; LShift表示マップ(LShift)
        bm4 := Map()  ; RShift表示マップ(RShift)
        bm5 := Map()  ; Number表示マップ(Number)
        bm6 := Map()  ; Func表示マップ  (Func)
        bm7 := Map()  ; けいならべ表示マップ(Keinarabe)

        Loop Parse, csvText, "`n", "`r"
        {
            line := Trim(A_LoopField)
            
            if ( (line = "") || (InStr(line, ";", 0, 1) = 1) )  ; 読み飛ばし定義: 空行 または 1文字目が「;」
                continue

            ;MsgBox("line=" line)
            parts := StrSplit(line, ",")    ; , で分ける
            if (parts.Length >= 6) 
            {
                For k, v in parts
                {
                    if(v == "COMMA")    ; , (COMMA)の処理
                        v := ","

                    switch k
                    {
                    case 1: ; btnname
                        btnname := parts[1]
                        ;bm1[normal] := v   ; 不要に
                    case 2: ; normal
                        bm2[btnname] := v
                    case 3: ; LShift
                        bm3[btnname] := v
                    case 4: ; RShift 
                        bm4[btnname] := v
                    case 5: ; Number
                        bm5[btnname] := v
                    case 6: ; Func
                        bm6[btnname] := v
                    case 7: ; Keinarabe ; けいならべ
                        bm7[btnname] := v
                    default:
                        comment := parts[8]     ; 読み捨てるだけ
                    }
                }
            } 
            else
            {
                MsgBox("error: " line)  ; 定義ミスを表示 (要素が足らない)
                exit                    ; 終了する
            }
        }

        ;return [bm1, bm2, bm3, bm4, bm5, bm6, bm7]  ; 配列で返す
        return [bm2, bm3, bm4, bm5, bm6, bm7]  ; 配列で返す
    }

    ; --- キーラベル(ボタン名bn???)から文字を取得関数 ---
    GetKey2Name(key) 
    {
        ;MsgBox(key)
        switch CurrentMode 
        {
            case "normal":
                return Bn2Asc_map.Has(key) ? Bn2Asc_map[key] : ""
            case "LShift":
                return Bn2LShift_map.Has(key) ? Bn2LShift_map[key] : ""
            case "RShift":
                return Bn2RShift_map.Has(key) ? Bn2RShift_map[key] : ""
            case "Number":
                return Bn2Number_map.Has(key) ? Bn2Number_map[key] : ""
            case "Func":
                return Bn2Func_map.Has(key) ? Bn2Func_map[key] : ""
            case "Keinarabe":
                return Bn2Kei_map.Has(key) ? Bn2Kei_map[key] : ""
            default:
                return Bn2Asc_map.Has(key) ? Bn2Asc_map[key] : ""
        }
    }

    ;*GUI部****************************************************************
    ; --- keyboard作成 --- 隙間(offset)やエンタースペースのような大きさが違う具合(wやmargin)
    make_keyboard(guio,keytable,*)
    {
        guio.SetFont(Font_size)  ; フォントサイズ(ポイント)を設定
        guio.MarginX := guio.MarginY := Margin    
        guio.BackColor := "Silver"  ; 背景色 
        
        for _, row in keytable {               ; 段毎
            y := Margin + (A_Index-1) * (Margin + Btn_h) ; 各段のY座標
            for _, btn in row {                 ; 段での各文字毎
                w := 0          ; キーの幅
                h := 0          ; キーの高さ
                offset := 0     ; 右の最初やEnterキーのずれの位置合わせ
                
                parts := StrSplit(btn, ",") ; , で分ける 'keyname,w,h,offset'
                bnkey := parts[1]           ; btnのキー名
                bw := parts[2]              ; 基本のサイズへの倍率
                bh := parts[3]              ; 基本のサイズへの倍率
                boffset := parts[4]         ; 前のキーとのずれの倍率

                if(bw > 1)
                    w := (Btn_w * bw) + (Margin * ((bw)-1))
                else
                    w := (Btn_w * bw)

                if(bh > 1)
                    h := (Btn_h * bh) + (Margin * ((bh)-1))
                else
                    h := (Btn_h * bh)
                
                offset := Btn_w * boffset

                ;if(bw > 1)
                ;   MsgBox(y " " w " " h " " bnkey " " offset " " (btn_w * bw) + (margin * ((bw)-1)))

                if (A_Index = 1)                ; 各段の最初のキー(xm)
                    con := guio.AddButton('xm+'         offset ' y' y ' w' w ' h' h ' v' bnkey, GetKey2Name(bnkey))
                else                            ; 最初以外のキー
                    con := guio.AddButton('x+' margin + offset ' yp ' ' w' w ' h' h ' v' bnkey , GetKey2Name(bnkey))

                con.OnEvent('Click', send_key.Bind(bnkey))  ; bn??? ボタンの名前(bn???で登録) 
            }
        }
    }
    
    ;***Gui Window関係**************************************************
    set_Tray()
    {
        ; 常駐アイコン
        TraySetIcon("%SystemRoot%\System32\msctf.dll",20)   ; 緑のキーボード

        ; システムトレイでのメニュー表示
        ; https://www.autohotkey.com/boards/viewtopic.php?t=115272
        A_TrayMenu.Delete
        A_TrayMenu.Add TrayMenu_Hide, (*) => ShowHideWindow()    ;
        if isSound
            A_TrayMenu.Add TrayMenu_SoundOff, SoundOnOff
        else
            A_TrayMenu.Add TrayMenu_SoundOn, SoundOnOff
        A_TrayMenu.Add "Change Size", (*) => Change_size()   
        Submenu1 := Menu()
        Submenu1.Add("66keys Row", KeyShapeHandler)
        Submenu1.Add("37keys Row", KeyShapeHandler)
        Submenu1.Add("66keys Ortho", KeyShapeHandler)
        Submenu1.Add("37keys Ortho", KeyShapeHandler)
        Submenu1.Add("Separate 66keys Row", KeyShapeHandler)
        Submenu1.Add("Separate 37keys Row", KeyShapeHandler)
        Submenu1.Add("Separate 66keys Ortho", KeyShapeHandler)
        Submenu1.Add("Separate 37keys Ortho", KeyShapeHandler)
        A_TrayMenu.Add "Keyboard Shape",Submenu1
        A_TrayMenu.Add                                      ; -----
        A_TrayMenu.Add "&Edit", (*) => Edit()                ; &E キーボードのEをショートカット指定
        A_TrayMenu.Add "&Reload", (*) => Reload()            ; &R キーボードのRをショートカット指定
        A_TrayMenu.Add "Exit", (*) => ExitWindow()           ;
        A_TrayMenu.Default := TrayMenu_Hide
    }

    /* *** WindowやTrayの内部関数 *** */
    ; キーボードをShow/Hideする-----
    ShowHideWindow(*)
    {
        Clear_mods()    ; 万が一、装飾キーがONになっているのをクリアするため
        
        if WinExist('ahk_id ' gui1.hwnd)
        {
            gui1.Hide()
            A_TrayMenu.Rename TrayMenu_Hide, TrayMenu_Show
            CurrentMode := "normal" ; 次に表示するときにはnormal表示に
            change_keyboard()
        }
        else
        {
            gui1.Show('NoActivate')
            A_TrayMenu.Rename TrayMenu_Show, TrayMenu_Hide
        }

        ; ★分離キーボード
        if InStr(KeyShape, "Separate")
        {
            if WinExist('ahk_id ' gui2.hwnd)
                gui2.Hide()
            else
                gui2.Show('NoActivate')
        }
    }
    
    ; --- 万が一、Ctrlキーなどが押されたままになっていたりするのを、それを外すため ---
    Clear_mods(*) 
    {
        for _, key in ['lshift', 'rshift', 'lctrl', 'rctrl', 'lalt', 'ralt', 'lwin', 'rwin']
            if GetKeyState(key)             ; もし押されたままの状態だったら
                SendInput('{' key ' Up}')   ; 上げる(押されてない)にする
    }

    ; 終了時 後処理
    ExitWindow(*)
    {
        Clear_mods()
        FreeWavMemory(wavPtr)
        ExitApp()
    }

    ; -------------------------------------------
    ; KeySize: キーボードサイズ (1,2)
    Change_size()
    {
        if(KeySize == 1)    ; 1: small size, 2: large size
            rebuild_keyboard(2,KeyShape)    ; 2:largeサイズで起動
        else
            rebuild_keyboard(1,KeyShape)    ; 1:smallサイズで起動
    }

    ; KeyShape: キーボード形状
    KeyShapeHandler(Item, *)
    {
        ;MsgBox("You selected " Item)
        rebuild_keyboard(KeySize,Item)
    }

    rebuild_keyboard(size,shape)
    {
        if WinExist('ahk_id ' gui1.hwnd)
            gui1.Destroy()

        ; ★分離キーボード
        if InStr(KeyShape, "Separate")
        {
            if WinExist('ahk_id ' gui2.hwnd)
                gui2.Destroy()
        }

        Virtual_Keyboard(size,shape)
    }
    
    ; -------------------------------------------
    ; クリック音
    SoundOnOff(*)
    {
        if isSound
        {
            isSound := false
            A_TrayMenu.Rename TrayMenu_SoundOff, TrayMenu_SoundOn
            FreeWavMemory(wavPtr)                   ; メモリから解放
        }
        else
        {
            isSound := true
            A_TrayMenu.Rename TrayMenu_SoundOn, TrayMenu_SoundOff
            wavPtr := LoadWavToMemory(ClickSound)   ; メモリへ読み込み
            ;SoundPlay(ClickSound)  ; 音出す
            PlayWavFromMemory(wavPtr)   ; 超低遅延ですぐ鳴る
        }
    }

    ; ===========================================
    ; 超低遅延ですぐ鳴るWavfile音再生
    ; WAV をメモリに読み込む
    LoadWavToMemory(filePath) {
        f := FileOpen(filePath, "r")
        size := f.Length

        ptr := DllCall("GlobalAlloc", "uint", 0x0040, "uptr", size, "ptr")
        f.RawRead(ptr, size)
        f.Close()

        return ptr
    }

    ; メモリWAV を再生(超低遅延)
    PlayWavFromMemory(ptr) {
        flags := 0x00020005  ; SND_MEMORY | SND_ASYNC | SND_NODEFAULT
        DllCall("winmm\PlaySound", "ptr", ptr, "ptr", 0, "uint", flags)
    }

    ; 再生を停止
    StopWav() {
        DllCall("winmm\PlaySound", "ptr", 0, "ptr", 0, "uint", 0)
    }

    ; メモリ解放
    FreeWavMemory(ptr) {
        DllCall("GlobalFree", "ptr", ptr)
    }
    ; ===========================================

    ;***キーボード実行部**************************************
    ; --- 表示を切り替える ---
    change_keyboard(*)
    {
        for _, btn in gui1 {
            ;MsgBox("btn.Text = " btn.Text) ; 
            btn.Text := GetKey2Name(btn.Name)
        }
        set_BackgroundColor(gui1)

        guix := gui1    ; 分離でなかった場合はgui1にbnvk1Cがあるので
        ; ★分離キーボード
        if InStr(KeyShape, "Separate")
        {
            for _, btn in gui2 {
                ;MsgBox("btn.Text = " btn.Text) ; 
                btn.Text := GetKey2Name(btn.Name)
            }
            guix := gui2    ; 分離なのでgui2にbnvk1Cがある
            set_BackgroundColor(guix)           ; 分離キーボード
        }

        ; 上のforでキーを全部入れ替えてから、以下の部分的な変更する
        ; Toggle IME
        if(IME_GET() == 1)
        {
            gui1["bnvk1D"].Text := "あ->A"
            guix["bnvk1C"].Text := "あ"            ; 分離キーボード
        }
        else
        {
            gui1["bnvk1D"].Text := "A"
            guix["bnvk1C"].Text := "A->あ"      ; 分離キーボード
        }
        
        if(CurrentMode == "Func")
        {
            guix["bnvk1C"].Text := "けいならべ"        ; 分離キーボード
        }
        
        if(CtrlState == 1)
        {
            gui1["bnLCtrl"].Text := "CCC"
        }
        if(AltState == 1)
        {
            gui1["bnLAlt"].Text := "AAA"
        }
        if(WinState == 1)
        {
            gui1["bnLWin"].Text := "WWW"
        }
    }

    ; --- 背景色をセットする ---
    set_BackgroundColor(guix)
    {
        ; 上から優先のためif文で
        if(CtrlState == 1)
        {
            guix.BackColor := "Yellow"
        }
        else if(AltState == 1)
        {
            guix.BackColor := "Olive"
        }
        else if(WinState == 1)
        {
            guix.BackColor := "Blue"
        }
        else if(CurrentMode == "Func")
        {
            guix.BackColor := "Maroon"
        }
        else if(CurrentMode == "Number")
        {
            guix.BackColor := "Lime"
        }
        else if(CurrentMode == "LShift")
        {
            guix.BackColor := "Fuchsia"
        }
        else if(CurrentMode == "RShift")
        {
            guix.BackColor := "Red" 
        }
        else if(IME_GET() == 1) ; IME ON
        {
            guix.BackColor := "Green"
        }
        else
        {
            guix.BackColor := "Silver"
        }
    }
    
    ; --- Timeボタン ---
    send_time()
    {
        IME_state := IME_get()
        IME_set(0)
        ; FormatTime, now,, HH:mm:ss
        now := FormatTime(, "HH:mm")
        Send(now)
        Send(A_Space)
        if(IME_state == 1)
        {
            Sleep(500)
            IME_set(1)
        }
    }
    
    ; --- Dateボタン ---
    send_date()
    {
        IME_state := IME_get()
        IME_set(0)
        ; FormatTime, now,, HH:mm:ss
        ; FormatTime, now,, yyyy/MM/dd HH:mm:ss
        now := FormatTime(, "yyyy-MM-dd")
        Send(now)
        Send(A_Space)
        if(IME_state == 1)
        {
            Sleep(500)
            IME_set(1)
        }
    }
    
    ; --- 変換ボタン ---
    XFER()
    {
        if(CurrentMode == "Keinarabe")  ; けいならべ
        {
            return
        }

        ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)  
        IME_SET(1)      ; IMEをON
        sleep(100)      ; PC性能によりスピード調整が必要かも
        if(IME_GET() == 1)
        {
            if(CurrentMode == "Func")
            {
                CurrentMode := "Keinarabe"  ; けいならべ
            }
            else
            {
                CurrentMode := "normal"
            }
            change_keyboard()
        }
        else
        {
            ; もう一度チャレンジ
            IME_SET(1)  ; IMEをON
            sleep(100)      ; PC性能によりスピード調整が必要かも
            if(IME_GET() == 1)
            {
                if(CurrentMode == "Func")
                {
                    CurrentMode := "Keinarabe"  ; けいならべ
                }
                else
                {
                    CurrentMode := "normal"
                }
                change_keyboard()
            }
            else
            {
                ; AlwaysOnTop(スタイル WS_EX_TOPMOST) 0x40000
                MsgBox("IME_ONに変わりませんでした 右下のIMEの状態を確認して再度押してみてください",,0x40000)
            }
        }
    }
    
    ; --- 無変換ボタン ---
    NFER()
    {
        ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)  
        IME_SET(0)      ; IMEをoff
        sleep(100)
        if(IME_GET() == 1)
        {
            ; AlwaysOnTop(スタイル WS_EX_TOPMOST) 0x40000
            MsgBox("IME_offに切り替わりませんでした 申し訳ありませんが再度押してみてください",,0x40000)
        }
        else
        {
            CurrentMode := "normal"
            change_keyboard()
        }
    }

    ;*** キー送出 ********************************************************
    modifykey := "" ; 複数入力を覚えておく必要があるので関数の外に変数
                    ; ^!# が入る (また+のshiftはまた別 選択とかモードとかで扱いを別にしている)
    send_key(key2,*) 
    {
        if isSound  ; 音を出す
        {
            ;SoundBeep(523,100) ; Blockingするため、ここで止まる
            ;SoundPlay(ClickSound)  ; non blocking でも毎回ファイル読み込むため遅延あり
            PlayWavFromMemory(wavPtr)   ; メモリに読み込み済みのものを再生するので超低遅延ですぐ鳴る

        }

        key := Getkey2Name(key2)    ;bn???->文字
        ;MsgBox("send_key =" key2 " GetKey2Name=" key)  
        
        switch key {
            /*** 特殊機能キー ***/
            case 'SIZE':    ;   ; 2025-11-12 キー設定はなしにした
                change_size()   ; SystemTrayでの対応
            
            case 'HIDE':        ; 2025-11-12 キー設定はなし 
                ShowHideWindow()    ; SystemTrayでの対応 

            case 'TIME':        ; 2025-11-12 
                send_time()     ; hh:mm 表示 
                
            case 'DATE':        ; 2025-11-12 
                send_date()     ; YYYY/MM/DD 表示     
                
            case '変換':      ; vk1C  
                XFER()          ; IME ON
                
            case '無変換':       ; vk1D
                NFER()          ; IME off
            
            /***装飾キー 押したかどうかのキー****************/
            case 'Alt':  goto ALT
            case 'LAlt': goto ALT
            case 'RAlt': 
                ALT:
                AltState := (AltState = 0) ? 1 : 0
                ;MsgBox("Alt =" AltState)
                change_keyboard()   ; 色も関数でやる

            case 'Ctrl':  goto CTRL
            case 'LCtrl': goto CTRL
            case 'RCtrl': 
                CTRL:
                CtrlState := (CtrlState = 0) ? 1 : 0
                ;MsgBox("Ctrl =" CtrlState)
                change_keyboard()   ; 色も関数でやる

            case 'LWin': goto wIN
            case 'RWin':
                WIN:
                if(WinState == 1)   ; Win のあと再度Winを押すと Winキーを押したのと同じにしてスタートメニューを開く
                {
                    SendInput('{Blind}' modifykey '{' key '}')
                }
                WinState := (WinState = 0) ? 1 : 0
                ;MsgBox(""in =" WinState)
                change_keyboard()   ; 色も関数でやる

            /***Mode(状態)のキー****************/
            case 'LLL':  goto LSFT
            case 'Shift':  goto LSFT
            case 'LShift': 
                LSFT:
                if((AltState == 1) || (CtrlState == 1 ) || (WinState == 1)) ; 修飾キーONのときはモード変えない
                    goto END
                    
                CurrentMode := (CurrentMode != "LShift") ? "LShift" :   "normal"
                ;MsgBox("Lshift =" CurrentMode " Key=" key " key2=" key2)
                change_keyboard()   ; 色も関数でやる
                
            case 'RRR':  goto RSFT
            case 'RShift':
                RSFT:
                if((AltState == 1) || (CtrlState == 1 ) || (WinState == 1)) ; 修飾キーONのときはモード変えない
                    goto END
                    
                CurrentMode := (CurrentMode != "RShift") ? "RShift" :   "normal"
                ;MsgBox("Rshift =" CurrentMode " Key=" key " key2=" key2)
                change_keyboard()   ; 色も関数でやる
                
            case 'FFF': goto FUNC
            case 'Func':
                FUNC:
                if((AltState == 1) || (CtrlState == 1 ) || (WinState == 1)) ; 修飾キーONのときはモード変えない
                    goto END

                CurrentMode := (CurrentMode != "Func") ? "Func" : "normal"
                ;MsgBox("Func =" CurrentMode " Key=" key " key2=" key2)
                change_keyboard()

            case 'NNN': goto NUMBER
            case 'Number':
                NUMBER:
                if((AltState == 1) || (CtrlState == 1 ) || (WinState == 1)) ; 修飾キーONのときはモード変えない
                    goto END

                CurrentMode := (CurrentMode != "Number") ? "Number" : "normal"
                ;MsgBox("Number =" CurrentMode " Key=" key " key2=" key2)
                change_keyboard()

            /***実際に文字出力*****************************/
            case '&&':  ; &はautohotkeyでのbutton表示で特別扱いが必要なため 
                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)
                key := "&"
                SendInput('{Blind}{' key '}')

            default:    ; 
                if(CtrlState == 1)
                {
                    modifykey := modifykey . "^"
                    CtrlState := 0
                    change_keyboard()   ; 色も変える
                }
                if(AltState == 1)
                {
                    modifykey := modifykey . "!"
                    AltState := 0
                    change_keyboard()   ; 色も変える
                }
                if(WinState == 1)
                {
                    modifykey := modifykey . "#"
                    WinState := 0
                    change_keyboard()   ; 色も変える
                }

                ; 実際に文字出力 '{' と '}' で挟んで出すので、Enterとかそのまま書ける
                ; F13での }^x とかの } は これの最初の '{' を打ち消すだめ (正確には }^x{ となるけど動いている)
                ;MsgBox("SendInput(" modifykey "{" key "}")
                SendInput('{Blind}' modifykey '{' key '}')
                                
                END:
                modifykey := "" ; 
        }
    }
}
/* END */

物理キーボードがないSurfaceタブレットとして使う生活をエンジョイしてください。

Autohotkey Ver2のGUIによる分離QWERTYスクリーンタッチキーボード に「けいならべ」のような配置を追加

2025-11-21 全部集約したものになりました。 torazaemon2016.hatenablog.jp

以下は、上ができる前までの内容です。


AutohotkeyGUIを使って、物理的なキーボードのないPC (Surface)を、ペンや指だけで操作するを目指すシリーズで、実用的なタッチタイプキーボードの実装に、「けいならべ」のような配置を追加したものです。

この前の記事 torazaemon2016.hatenablog.jp

Keyword: Autohotkey, ahk, タッチタイプキーボード, スクリーンキーボード, 仮想キーボード, Virtual keyboard, けいならべ

なお、Autohotkeyの詳しいことやインストールなどについては、他の詳しいページを参考にしてください。

40%キーボード+けいならべ もどきスクリーンタッチキーボード

自分でキー配置を自由に配置できる、分離QWERTYスクリーンタッチキーボードです。

いわゆる40%キーボードとかと呼ばれている、キーの数が少ないキーボードを参考にして、画面が狭くならないスクリーンキーボードを目指したものです。

通常のQWERTY配列のものを、左Shiftキー、右Shiftキー、Numberキー、Funcキーという切り替えキーで、キーの種類を切り替えます。

これに、日本語IMEのON/offキーをわかりやすく、スペースキーの左右に配置して、状態も表示しています。

「A->あ」ボタンを押して、日本語IMEをONにした

サイズを小さいものに

左Shiftキー(大文字)

Number(数字中心配置)

Func(機能中心)

今回は、QWERTY配列だけではなく、ローマ字日本語入力として「けいならべ」を参考にした配列を追加しています。

けいならべもどき配置

「けいならべ」とは左手側に子音、右手側に母音がある並びです。

web1.nazca.co.jp

分離ではなく普通の一体型40%QWERTYも作りましたので追加しておきます(2025/10/13)

torazaemon2016.hatenablog.jp

分離QWERTY+けいならべもどき配置

Autohotkeyでのプログラムです

; 2025-10-05 分離QWERTY+けいならべもどき by torazemon2016

; 参考
; title: "Can buttons in GUI act like keyboard keys?"
; https://www.reddit.com/r/AutoHotkey/comments/13gawje/can_buttons_in_gui_act_like_keyboard_keys/

#Requires AutoHotkey 2.0+       ; Version 2以上

;IME_SET,GET() IME制御ライブラリを使用
; https://qiita.com/kenichiro_ayaki/items/d55005df2787da725c6f
#Include "IMEv2.ahk"    

; --- キーボードボタンのCSVデータ ---
;  ( ... ) は 行継続 (continuation section)
; AutoHotkey では、( で始めて ) で終わる部分を「行継続セクション」でこの中はそのまま文字列リテラルとして扱われる
; csv := と 次の行の ( と 次の行の " は、このとおりにしておかないとエラー。同様に、一番最後の " と ) も独立行
csv :=
(
"
; 記述の ;から書き始まっているのは読み飛ばす
; 1:button名, 2:normal, 3:左shift, 4:右シフト, 5:Number, 6:Func, 7:けいならべ, (8:コメント) の必須7項目と、任意のコメントの8項目の並び  

; 2025-09-23 分離QWERTY
; qwerty 1段   40%キーボードでは使用せず
bnEsc,Esc,Esc,Esc,Esc,SIZE,,,       comment test Escape key size変更
bn1,1,~,!,!,F1,F1,,
bn2,2,`",\,`",F2,F2,,               ; ダブルコーテーションは`でエスケープすることでエラーを回避
bn3,3,#,#,F3,F3,,
bn4,4,$,$,F4,F4,,
bn5,5,%,%,F5,F5,,

bn6,6,&&,&&,F6,F6,,             ;Buttonコントロールに&文字を表示させるには、&&と記述する。send_key関数でも case '&&' としてやる必要がある
bn7,7,',',F7,F7,,
bn8,8,(,(,F8,F8,,
bn9,9,),),F9,F9,,
bn0,0,|,|,F10,F10,,             ; | を 0のshiftにする
bnHat,^,~,~,~,End,,             ;分離では未使用
bnEnmark,\,\,|,|,|,,            ;分離では未使用 円マークと|のキーはキー削除のため | を 0のshiftにする
bnBackspace,BS,BS,}+{BS,BS,BS,BS,


; qwerty 2段
bnTab,Tab,Tab,}+{Tab,Esc,HIDE,Esc,  HIDEは両windowを隠す
bnq,q,Q,!,1,F1,f,               ; q->f               
bnw,w,W,`",2,F2,w,              ;ダブルコーテーションは`でエスケープすることでエラーを回避
bne,e,E,#,3,F3,m,
bnr,r,R,$,4,F4,r,
bnt,t,T,%,5,F5,p,

bny,y,Y,&&,6,F6,}ye{,       ; &&にしなければならないことに注意 
bnu,u,U,',7,F7,}yo{,            ; 'は特別な処理せずともいける
bni,i,I,(,8,F8,u,
bno,o,O,),9,F9,o,
bnp,p,P,|,0,F10,x,
bnAtmark,@,A.``,[,{,,,          ;分離では未使用 backquoteは`でエスケープすることで`の1文字になる
bnLBracket,[,[,{,{,{,,          ;分離では未使用


; qwerty 3段 CaspはLCtrl
bnLCtrl,LCtrl,LCtrl,LCtrl,LCtrl,LCtrl,LCtrl,
bna,a,A,+,+,}^a{,n,
bns,s,S,-,-,}^s{,s,
bnd,d,D,*,*,}^d{,t,
bnf,f,F,/,/,}^f{,k,
bng,g,G,=,=,}^g{,h,

bnh,h,H,;,;,}^a{,}yu{,
bnj,j,J,:,:,}^s{,a,
bnk,k,K,[,[,,i,              
bnl,l,L,],],Home,e,
bnSemiColon,;,,+,PgDn,,,        ;分離では未使用
bnColon,:,,*,],,,               ;分離では未使用
bnRBracket,],,},},},,           ;分離では未使用      

bnMinus,-,=,@,``,End,-,         ; 1段から3段へ backquoteは`でエスケープすることで`の1文字になる

bnEnter,Enter,Enter,}+{Enter,Enter,Enter,Enter,Enter,


; qwerty 4段
bnLShift,LShift,LLL,LShift,LShift,LShift,LShift,
bnz,z,Z,~,$,}^z{,v,             ;けいならべでは、vaのヴぁは出せないのでv
bnx,x,X,^,%,}^x{,z,
bnc,c,C,\,\,}^c{,d,
bnv,v,V,|,<,}^v{,g,
bnb,b,B,_,>,}^b{,b,

bnn,n,N,!,!,}^z{,}ya{,
bnm,m,M,?,?,}^x{,}nn{,
bnComma,,,,,,,,                 ;コンマはCSVで文字として扱うのは面倒なのでMakeMapsFromCSV関数で直接代入することで処理する
bnPeriod,.,>,},.,}^v{,.,         
bnSlash,/,/,?,?,?,,             ;分離では未使用
bnUnder,\,_,PgUp,,,,            ;分離では未使用
bnUp,Up,Up,}+{Up,Up,}+{Up,Up,
bnRShift,RShift,RShift,RRR,RShift,RShift,RShift,    ;RShift は領域選択ができる(+をつけて送出する処理)


; 5段目
bnNumber,Number,Number,Number,NNN,Number,Number,    ; 記号キー(数字キー)(実キーボードにはない創作キー) send_key()関数で処理
bnLWin,LWin,LWin,LWin,LWin,LWin,LWin,
bnLAlt,LAlt,LAlt,LAlt,LAlt,LAlt,LAlt,
bnvk1D,無変換,無変換,無変換,無変換,無変換,無変換,     ;無変換キー vk1D
bnSpace,Space,Space,Space,Space,Space,Space,    ;左スペースキー すべてノーマル

bnRSpace,Space,Space,}+{Space, ,SIZE,Enter,   ;右スペースキー Number時全角空白、Funcのときサイズ変更に                  
bnvk1C,変換,変換,変換,変換,変換,変換,                   ;変換キー vk1C  
bnFunc,Func,Func,Func,Func,FFF,Func,            ;特殊キー(実キーボードにはない創作キー) send_key()関数で処理

bnLeft,Left,Left,}+{Left,Left,}+{Left,Left,
bnDown,Down,Down,}+{Down,Down,}+{Down,Down,
bnRight,Right,Right,}+{Right,Right,}+{Right,Right,


; 物理キーには定義がある特殊キー---------------
bnRWin,RWin,RWin,RWin,RWin,RWin,RWin,           ;分離では未使用
bnRCtrl,RCtrl,RCtrl,RCtrl,RCtrl,RCtrl,RCtrl,    ;分離では未使用
bnRAlt,RAlt,RAlt,RAlt,RAlt,RAlt,RAlt,           ;分離では未使用

bnF11,F11,F11,F11,F11,F11,F11,                  ;分離では未使用
bnF12,F12,F12,F12,F12,F12,F12,                  ;分離では未使用
bnF13,F13,F13,F13,F13,F13,F13,                  ;分離では未使用
bnF14,F14,F14,F14,F14,F14,F14,                  ;分離では未使用
bnF15,F15,F15,F15,F15,F15,F15,                  ;分離では未使用
bnF16,F16,F16,F16,F16,F16,F16,                  ;分離では未使用
"
)
; ) で 継続行終わり

; --- CSVからMap化する関数 ---
MakeMapsFromCSV(csvText) {
    ;bm1 := Map()  ; reverse map (normal を button name にマップ (asc2bn))不要に
    bm2 := Map()  ; 英字表示マップ   (normal)
    bm3 := Map()  ; LShift表示マップ(LShift)
    bm4 := Map()  ; RShift表示マップ(RShift)
    bm5 := Map()  ; Number表示マップ(Number)
    bm6 := Map()  ; Func表示マップ  (Func)
    bm7 := Map()  ; けいならべ表示マップ(Keinarabe)

    Loop Parse, csvText, "`n", "`r"
    {
        line := Trim(A_LoopField)
        
        if ( (line = "") || (InStr(line, ";", 0, 1) = 1) )  ; 読み飛ばし定義: 空行 または 1文字目が「;」
            continue

        ;MsgBox("line=" line)
        parts := StrSplit(line, ",")    ; , で分ける
        if (parts.Length >= 6) {     ; 7項目以上
            Try {
                btnname := parts[1]
                normal := parts[2]
                LShift := parts[3]
                RShift := parts[4]
                Number := parts[5]
                Func   := parts[6]
                Keinarabe := parts[7]   ; けいならべ
                comment := parts[8]     ; 読み捨てるだけだけど、7項目(,が6つ)がなければエラーでcatchへ

                ;bm1[normal] := btnname ; 不要に
                bm2[btnname] := normal
                bm3[btnname] := LShift
                bm4[btnname] := RShift
                bm5[btnname] := Number
                bm6[btnname] := Func
                bm7[btnname] := Keinarabe

            } Catch as e {
                MsgBox("error: " btnname)   ; 定義ミスボタン名を表示 (,の間違いなどのミス)
                exit                        ; 終了する
            }
        }
    }

    ; 文字Comma「,」 は特別扱い csvロード関数(MakeMapsFromCSV)でやるには例外処理面倒いので、以下のように直接代入する
    ;bm1[","] := "bnComma"  ; 不要に
    bm2["bnComma"] := ","   ; normal
    bm3["bnComma"] := "<"    ; LShift
    bm4["bnComma"] := "{"   ; RShift
    bm5["bnComma"] := ","   ; Number
    bm6["bnComma"] := "}^c{"    ; Func
    bm7["bnComma"] := ","   ; Keinarabe
    
    ;return [bm1, bm2, bm3, bm4, bm5, bm6]  ; 配列で返す
    return [bm2, bm3, bm4, bm5, bm6, bm7]  ; 配列で返す
}

; --- CSVからMap作成 ---
maps := MakeMapsFromCSV(csv)

bn2asc_map := maps[1]
bn2LShift_map := maps[2]
bn2RShift_map := maps[3]
bn2Number_map := maps[4]
bn2Func_map := maps[5]
bn2Kei_map := maps[6]


; --- キーラベル(ボタン名bn???)から文字を取得関数 ---
; モード切替用大域変数
global CurrentMode := "normal"  ; 初期値: normal(ノーマルキー) LShift(シフトキー) RShift(右シフト) Number(記号) Func(F1など拡張) Keinarabe(けいならべ)

GetKey2Name(key) {
    global CurrentMode, bn2asc_map, bn2LShift_map, bn2RShift_map, bn2Number_map, bn2Func_map, bn2Kei_map
    ;MsgBox(key)
    if (CurrentMode = "normal")
        return bn2asc_map.Has(key) ? bn2asc_map[key] : ""
    else if (CurrentMode = "LShift")
        return bn2LShift_map.Has(key) ? bn2LShift_map[key] : "" ; lshift
    else if (CurrentMode = "RShift")
        return bn2RShift_map.Has(key) ? bn2RShift_map[key] : ""
    else if (CurrentMode = "Number")
        return bn2Number_map.Has(key) ? bn2Number_map[key] : ""
    else if (CurrentMode = "Func")
        return bn2Func_map.Has(key) ? bn2Func_map[key] : ""
    else 
        return bn2Kei_map.Has(key) ? bn2Kei_map[key] : ""
}

/**************************************************************************/
; キーボードの並びの配列 
; QWERTY
/*
key_table := [
     [ 'bnEsc', 'bn1', 'bn2', 'bn3', 'bn4', 'bn5', 'bn6', 'bn7', 'bn8', 'bn9', 'bn0','bnMinus', 'bnHat', 'bnBackspace' ]
    ,[ 'bnTab', 'bnq', 'bnw', 'bne', 'bnr', 'bnt', 'bny', 'bnu', 'bni', 'bno', 'bnp','bnAtmark', 'bnLBracket' , 'bnEnter' ]
    ,[ 'bnLCtrl', 'bna', 'bns', 'bnd', 'bnf', 'bng', 'bnh', 'bnj', 'bnk', 'bnl', 'bnSemiColon', 'bnColon', 'bnRBracket' ]
    ,[ 'bnLShift', 'bnz', 'bnx', 'bnc', 'bnv', 'bnb', 'bnn', 'bnm', 'bnComma', 'bnPeriod', 'bnSlash',  'bnUnder', 'bnUp', 'bnRShift' ]
    ,[ 'bnFunc', 'bnLWin', 'bnLAlt', 'bnvk1D', 'bnSpace', 'bnvk1C', 'bnF14', 'bnF15', 'bnRAlt', 'bnLeft', 'bnDown', 'bnRight' ]
    ]
*/

; 分離QWERTY
key_table_left := [
     [ 'bnTab',     'bnq',      'bnw',      'bne',      'bnr',      'bnt']
    ,[ 'bnLCtrl',   'bna',      'bns',      'bnd',      'bnf',      'bng']
    ,[ 'bnLShift',  'bnz',      'bnx',      'bnc',      'bnv',      'bnb']
    ,[ 'bnNumber',  'bnLWin',   'bnLAlt',   'bnvk1D',   'bnSpace'       ]
    ]

key_table_right := [
     [ 'bny',       'bnu',      'bni',      'bno',      'bnp',      'bnBackspace']
    ,[ 'bnh',       'bnj',      'bnk',      'bnl',      'bnMinus',  'bnEnter' ]
    ,[ 'bnn',       'bnm',      'bnComma',  'bnPeriod', 'bnUp',     'bnRShift']
    ,[ 'bnRSpace',  'bnvk1C',   'bnFunc',   'bnLeft',   'bnDown',   'bnRight']
    ]

/********************************************************/
; キーボード起動 (Autohotkey起動時に直接起動。Hotkey()で起動Keyを定義)
virtual_keyboard_katanakai(2)   ; 1:small 2:large サイズで起動

/********************************************************/
; ここからが本体
virtual_keyboard_katanakai(size,*)
{
    /******************/
    ; 以下は同時にON (押されているがあるので、(Shiftの)Global CurrentModeとは違う扱い)
    AltState := 0   ; ALtキーが押されてるか1 ない 0
    CtrlState := 0  ; Ctrlキーが押されてるか1 ない 0
    WinState := 0   ; Winキーが押されてるか1 ない 0
    
    keysize := size ; keyboardの大きさ (SIZEキー)     ; 1: small size, 2: large size
    /******************/

    ;左キーボードGUIオブジェクトを作成
    ;guikeyboard := Gui('+AlwaysOnTop', "AHK Keyboard") ; これだと文字がエディタなどに入力されない
    gui1 := Gui('+AlwaysOnTop +E0x08000000', "AHK Keyboard")    ; これだと文字を送れる ; "WS_DISABLED"
    
    key_table := key_table_left
    make_keyboard(gui1,key_table,size)

    gui1.Show('x0 y1000 NoActivate')        ; 画面左上

    ;右キーボードGUIオブジェクトを作成
    gui2 := Gui('+AlwaysOnTop +E0x08000000', "AHK Keyboard 2")  ; これだと文字を送れる ; "WS_DISABLED"

    key_table := key_table_right 
    make_keyboard(gui2,key_table,size)  ; 1: small size, 2: large size

    gui2.Show('x1600 y1000 NoActivate') ; 画面右上

    ; Hotキーの定義
    toggle_hotkey := 'F5'                               ; Hotkey
    Hotkey("*" toggle_hotkey, CloseWindows)             ; keyboard on/off
    gui1.OnEvent("Close", CloseWindows1)                ; X で閉じるとき
    gui2.OnEvent("Close", CloseWindows2)                ; X で閉じるとき
    OnExit(clear_mods)                                  ; 終了時 実行するもの

    /***** 内部関数 **********************************************/
    ; 両方のWindowを同時に閉じる(Hideする)-----
    CloseWindows(*)
    {
        clear_mods()    ; 万が一、装飾キーがONになっているのをクリアするため
        
        if WinExist('ahk_id ' gui1.hwnd)
        {
            gui1.Hide()
        }
        else
        {
            gui1.Show('NoActivate')
        }
        if WinExist('ahk_id ' gui2.hwnd)
        {
            gui2.Hide()
        }
        else
        {
            gui2.Show('NoActivate')
        }   
    }
    
    ; 1のWindowを閉じる(Hideする)-----
    CloseWindows1(*)
    {
        clear_mods()    ; 万が一、装飾キーがONになっているのをクリアするため
        
        if WinExist('ahk_id ' gui1.hwnd)
            gui1.Hide()
        ;else
            ;gui1.Show('NoActivate')
    }
    ; 2のWindowを閉じる(Hideする)-----
    CloseWindows2(*)
    {
        clear_mods()    ; 万が一、装飾キーがONになっているのをクリアするため
        
        if WinExist('ahk_id ' gui2.hwnd)
            gui2.Hide()
        ;else
            ;gui2.Show('NoActivate')
    }

    ; 万が一、Ctrlキーなどが押されたままになっていたりするのを、それを外すため
    clear_mods(*) {
        for _, key in ['lshift', 'rshift', 'lctrl', 'rctrl', 'lalt', 'ralt', 'lwin', 'rwin']
            if GetKeyState(key)             ; もし押されたままの状態だったら
                SendInput('{' key ' Up}')   ; 上げる(押されてない)にする
    }

    ;keyboard作成------------------
    make_keyboard(guio,key_table,size,*)
    {
        ; サイズ指定
        ;MsgBox("size = " size) ; 
        if(size == 1)
        {
            ; 小さいタイプ
            margin := 0     ; キーとキーの間
            btn_w := 60     ; 70    
            btn_h := 50     ; 70
            guio.SetFont("s14")  ; フォントサイズ(ポイント)
        }
        else
        {
            ; 大きめ
            margin := 1     ; キーとキーの間
            btn_w := 90     ; ボタンの幅
            btn_h := 90     ; ボタンの高さ
            guio.SetFont("s18")  ; フォントサイズ(ポイント)を設定
        }
        
        guio.MarginX := guio.MarginY := margin    
        guio.BackColor := "Silver"  ; 背景色 
        
        for _, row in key_table {               ; 段毎
            y := margin + (A_Index-1) * (margin + btn_h) ; 各段のY座標
            for _, bnkey in row {                 ; 段での各文字毎
                w := 0
                h := 0
                offset := 0     ; 右の最初やEnterキーのずれの位置合わせのため
                switch bnkey 
                {
                    case 'bnEsc':                   ; 1段目左端
                        w := btn_w * 0.8            ; 少し小さく
                        h := btn_h
                    
                    case 'bnTab':                   ; 2段目左端
                        w := btn_w * 1.0
                        h := btn_h
                    
                    case 'bnLCtrl':                 ; 3段目左端
                        w := btn_w * 1.2                
                        h := btn_h
                    
                    case 'bnLShift':                ; 4段目左端
                        w := btn_w * 1.4
                        h := btn_h
                    
                    case 'bnF13':                   ; 5段目左端
                        w := btn_w * 1      
                        h := btn_h
                    
                    case 'bnSpace':                 ; スペースキー
                        w := btn_w * 1.4 + btn_w * 1 + margin * 1  ; LShiftの幅分 + 1キー分 + 1つ分の隙間
                        h := btn_h

                    case 'bn6':                     ; 分離右 1段目左
                        w := btn_w * 1
                        h := btn_h
                        offset := 0

                    case 'bnBackspace':             ; 分離1段目右端
                        ;w := btn_w * 0.6 + btn_w * 1
                        w := btn_w * 0.4 + btn_w * 1
                        h := btn_h

                    case 'bny':                     ; 分離右 2段目左
                        w := btn_w * 1      
                        h := btn_h
                        ;offset := btn_w * 0.2

                    case 'bnEnter':                 ; Enterキー
                        ;w := btn_w * 1.2           ; Enterの幅               
                        ;h := btn_h * 2.0 + margin  ; Enterの高さ ; 2段分+1つ分の隙間
                        ;w := btn_w * 2.2 + margin  ; Enterの幅 2個分+marginの分              
                        w := btn_w * 1.2  margin    ; Enterの幅 2個分+marginの分              
                        h := btn_h                  ; Enterの高さ ; 1段分
                        ;offset := btn_w * 0.2      ; p と Enterとの間 3段目に横長になった

                    case 'bnh':                     ; 分離右 3段目左
                        w := btn_w * 1              
                        h := btn_h
                        ;offset := btn_w * 0.4
                        offset := btn_w * 0.2

                    case 'bnn':                     ; 分離右 4段目左
                        w := btn_w * 1          
                        h := btn_h
                        ;offset := btn_w * 0.6
                        offset := btn_w * 0.4

                    case 'bnRSpace':                 ; 分離右 5段目 右スペースキー
                        ;w := btn_w * 0.6 + btn_w * 2 + margin * 1  ; bnnずれ幅分 + 2キー分 + 1つ分の隙間
                        ;w := btn_w * 0.6 + btn_w * 1  ; bnnずれ幅分 + 1キー分 (RCtrlありのとき)
                        w := btn_w * 0.4 + btn_w * 1  ; bnnずれ幅分 + 1キー分 (RCtrlありのとき)
                        h := btn_h
                        offset := 0

                    default:
                        w := btn_w                  ; 普通のキーwidth
                        h := btn_h                  ; 普通のキーheight
                }
                
                if (A_Index = 1)                ; 各段の最初のキー
                    con := guio.AddButton('xm+'         offset ' y' y ' w' w ' h' h ' v' bnkey, GetKey2Name(bnkey))
                else                            ; 最初以外のキー
                    con := guio.AddButton('x+' margin + offset ' yp ' ' w' w ' h' h ' v' bnkey, GetKey2Name(bnkey))

                con.OnEvent('Click', send_key.Bind(bnkey))  ; bn??? ボタンの名前(bn???で登録) 
            }
        }
    }
    
    ; 表示を切り替える----------
    change_keyboard(*)
    {
        global CurrentState

        for _, btn in gui1 {
            ;MsgBox("btn.Text = " btn.Text) ; 
            btn.Text := GetKey2Name(btn.Name)
        }
        for _, btn in gui2 {
            ;MsgBox("btn.Text = " btn.Text) ; 
            btn.Text := GetKey2Name(btn.Name)
        }

        ; Toggle IME
        if(IME_GET() == 1)
        {
            gui1["bnvk1D"].Text := "あ->A"
            gui2["bnvk1C"].Text := "あ"
        }
        else
        {
            gui1["bnvk1D"].Text := "A"
            gui2["bnvk1C"].Text := "A->あ"
        }
        if(CurrentMode == "Func")
        {
            gui2["bnvk1C"].Text := "けいならべ"
        }
        
        if(CtrlState == 1)
        {
            gui1["bnLCtrl"].Text := "CCC"
            ;gui2["bnRCtrl"].Text := "CCC"  ; 今回未使用
        }
        if(AltState == 1)
        {
            gui1["bnLAlt"].Text := "AAA"
            ;gui2["bnRAlt"].Text := "AAA"   ; 今回未使用
        }
        if(WinState == 1)
        {
            gui1["bnLWin"].Text := "WWW"
            ;gui2["bnRWin"].Text := "WWW"   ; 今回未使用
        }

        set_BackgroundColor()
    }

    ; 背景色をセットする
    set_BackgroundColor(*)  ; 
    {
        global CurrentMode
        ;MsgBox("Ctrl " CtrlState " Alt " AltState " Win " WinState)

        ; 上から優先
        if(CtrlState == 1)
        {
            gui1.BackColor := "Yellow"
            gui2.BackColor := "Yellow"
        }
        else if(AltState == 1)
        {
            gui1.BackColor := "Olive"
            gui2.BackColor := "Olive"
        }
        else if(WinState == 1)
        {
            gui1.BackColor := "Blue"
            gui2.BackColor := "Blue"
        }
        else if(CurrentMode == "Func")
        {
            gui1.BackColor := "Maroon"
            gui2.BackColor := "Maroon"
        }
        else if(CurrentMode == "Number")
        {
            gui1.BackColor := "Lime"
            gui2.BackColor := "Lime"
        }
        else if(CurrentMode == "LShift")
        {
            gui1.BackColor := "Fuchsia"
            gui2.BackColor := "Fuchsia"
        }
        else if(CurrentMode == "RShift")
        {
            gui1.BackColor := "Red"
            gui2.BackColor := "Red"
        }
        else if(IME_GET() == 1) ; IME ON
        {
            ;MsgBox("IME_GET()" IME_GET())  
            gui1.BackColor := "Green"
            gui2.BackColor := "Green"
        }
        else
        {
            ;MsgBox("IME_GET()" IME_GET())  
            gui1.BackColor := "Silver"
            gui2.BackColor := "Silver"
        }
    }

    ; キー送出---------
    modifykey := "" ; 複数入力を覚えておく必要があるので関数の外に変数
                    ; ^!# が入る (また+のshiftはまた別 選択とかモードとかで扱いを別にしている)
    send_key(key2,*) 
    {
        global CurrentMode

        key := Getkey2Name(key2)    ;bn???->文字
        ;MsgBox("send_key =" key2 " GetKey2Name=" key)  

        switch key {
            case '&&':  ; &はautootkeyでのbutton表示で特別扱いが必要なため 
                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)
                key := "&"
                SendInput('{Blind}{' key '}')

            case 'SIZE':    ;
                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)
                if WinExist('ahk_id ' gui1.hwnd)
                    gui1.Destroy()
                if WinExist('ahk_id ' gui2.hwnd)
                    gui2.Destroy()
                if(keysize == 1)    ; 1: small size, 2: large size
                {
                    virtual_keyboard_katanakai(2)   ; 2:largeサイズで起動
                }
                else
                {
                    virtual_keyboard_katanakai(1)   ; 1:smallサイズで起動
                }
                
            case 'HIDE':
                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)
                CurrentMode := "normal" ; 次に表示するときにはnormal表示に (そのままだとFuncのまま)
                change_keyboard()
                CloseWindows()
                
            case '変換':  ; vk1C
                if(CurrentMode == "Keinarabe")  ; けいならべ
                {
                    return
                }

                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)  
                IME_SET(1)      ; IMEをON
                sleep(100)      ; PC性能によりスピード調整が必要かも
                if(IME_GET() == 1)
                {
                    if(CurrentMode == "Func")
                    {
                        CurrentMode := "Keinarabe"  ; けいならべ
                    }
                    else
                    {
                        CurrentMode := "normal"
                    }
                    change_keyboard()
                }
                else
                {
                    ; もう一度チャレンジ
                    IME_SET(1)  ; IMEをON
                    sleep(100)      ; PC性能によりスピード調整が必要かも
                    if(IME_GET() == 1)
                    {
                        if(CurrentMode == "Func")
                        {
                            CurrentMode := "Keinarabe"  ; けいならべ
                        }
                        else
                        {
                            CurrentMode := "normal"
                        }
                        change_keyboard()
                    }
                    else
                    {
                        MsgBox("IME_ONに変わりませんでした 右下のIMEの状態を確認して再度押してみてください")
                    }
                }

            case '無変換':       ; vk1D
                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)  
                IME_SET(0)      ; IMEをoff
                sleep(100)
                if(IME_GET() == 1)
                {
                    MsgBox("IME_offに切り替わりませんでした 申し訳ありませんが再度押してみてください")
                }
                else
                {
                    CurrentMode := "normal"
                    change_keyboard()
                }
            
            /***装飾キー 押したかどうかのところ****************/
            case 'Alt':  goto ALT
            case 'LAlt': goto ALT
            case 'RAlt': 
                ALT:
                AltState := (AltState = 0) ? 1 : 0
                ;MsgBox("Alt =" AltState)
                change_keyboard()   ; 色も関数でやる

            case 'Ctrl':  goto CTRL
            case 'LCtrl': goto CTRL
            case 'RCtrl': 
                CTRL:
                CtrlState := (CtrlState = 0) ? 1 : 0
                ;MsgBox("Ctrl =" CtrlState)
                change_keyboard()   ; 色も関数でやる

            case 'LWin': goto wIN
            case 'RWin':
                WIN:
                if(WinState == 1)   ; Win のあと再度Winを押すと Winキーを押したのと同じにしてスタートメニューを開く
                {
                    SendInput('{Blind}' modifykey '{' key '}')
                }
                WinState := (WinState = 0) ? 1 : 0
                ;MsgBox(""in =" WinState)
                change_keyboard()   ; 色も関数でやる

            /***Mode(状態)のところ****************/
            case 'LLL':  goto LSFT
            case 'Shift':  goto LSFT
            case 'LShift': 
                LSFT:
                gui2.Show('NoActivate')
                if((AltState == 1) || (CtrlState == 1 ) || (WinState == 1)) ; 修飾キーONのときはモード変えない
                    goto END
                    
                CurrentMode := (CurrentMode != "LShift") ? "LShift" :   "normal"
                ;MsgBox("Lshift =" CurrentMode " Key=" key " key2=" key2)
                change_keyboard()   ; 色も関数でやる
                
            case 'RRR':  goto RSFT
            case 'RShift':
                RSFT:
                gui1.Show('NoActivate')
                if((AltState == 1) || (CtrlState == 1 ) || (WinState == 1)) ; 修飾キーONのときはモード変えない
                    goto END
                    
                CurrentMode := (CurrentMode != "RShift") ? "RShift" :   "normal"
                ;MsgBox("Rshift =" CurrentMode " Key=" key " key2=" key2)
                change_keyboard()   ; 色も関数でやる
                
            case 'FFF': goto FUNC
            case 'Func':
                FUNC:
                if((AltState == 1) || (CtrlState == 1 ) || (WinState == 1)) ; 修飾キーONのときはモード変えない
                    goto END

                CurrentMode := (CurrentMode != "Func") ? "Func" : "normal"
                ;MsgBox("Func =" CurrentMode " Key=" key " key2=" key2)
                change_keyboard()

            case 'NNN': goto NUMBER
            case 'Number':
                NUMBER:
                if((AltState == 1) || (CtrlState == 1 ) || (WinState == 1)) ; 修飾キーONのときはモード変えない
                    goto END

                CurrentMode := (CurrentMode != "Number") ? "Number" : "normal"
                ;MsgBox("Number =" CurrentMode " Key=" key " key2=" key2)
                change_keyboard()

            /***実際に文字出力*****************************/
            default:    ; cvbb
                if(CtrlState == 1)
                {
                    modifykey := modifykey . "^"
                    CtrlState := 0
                    change_keyboard()   ; 色も関数でやる
                }
                if(AltState == 1)
                {
                    modifykey := modifykey . "!"
                    AltState := 0
                    change_keyboard()   ; 色も関数でやる
                }
                if(WinState == 1)
                {
                    modifykey := modifykey . "#"
                    WinState := 0
                    change_keyboard()   ; 色も関数でやる
                }

                ; 実際に文字出力 '{' と '}' で挟んで出すので、Enterとかそのまま書ける
                ; F13での }^x とかの } は これの最初の '{' を打ち消すだめ (正確には }^x{ となるけど動いている)
                ;MsgBox("SendInput(" modifykey "{" key "}")
                SendInput('{Blind}' modifykey '{' key '}')

                END:
                modifykey := "" ; 
        }
    }
    
}

/* END */

プログラムの簡単な説明(前回から変わったところなど)

前回の記事から、無駄な部分などをかなり整理しました。

最初の部分でのキーボタンの定義のcsv形式は、前回使っていなかった第2項目が消えて、また、ExtraだったものをNumberとかFuncと機能と一致するような名前にし、さらに今回の「けいならべ」配列を追加しています。

; --- キーボードボタンのCSVデータ ---
csv :=
(
"
; 記述の ;から書き始まっているのは読み飛ばす
; 1:button名, 2:normal, 3:左shift, 4:右シフト, 5:Number, 6:Func, 7:けいならべ, (8:コメント) の必須7項目と、任意のコメントの8項目の並び  

bnTab,Tab,Tab,}+{Tab,Esc,HIDE,Esc,  HIDEは両windowを隠す
bnq,q,Q,!,1,F1,f,               ; q->f               
bnw,w,W,`",2,F2,w,              ;ダブルコーテーションは`でエスケープすることでエラーを回避
bne,e,E,#,3,F3,m,
bnr,r,R,$,4,F4,r,
bnt,t,T,%,5,F5,p,

Funcキーでの画面などで、AutohotkeyでのModifyキー(Ctrl,Alt,Winを^,!,#)扱いになるようにして、それにAutohotkeyでのキー名を送り込むように{}で囲まれるようになっています。 プログラムの最後に送り出すSendInput()がありますので、それを見てもらえれば「 }^c{ 」とかになっている理由もわかるかと思います。

けいならべと同じ配置にするのも難しくはなく、自分が使いやすいように、キー定義やキー配置も、いろいろと改造して、自分が使いやすいキーボードにしてみてください。

TouchPanelGestures

物理キーボードがないので、TouchPanelGesturesアプリは必須です。

suwa.pupu.jp

プログラム中でホットキーとして「F5」を指定しています。(前のF2からF5に変更)

ジェスチャーとしては「指3本の→↓」に落ち着いてきました。 Surfaceのキーボードのタッチパッドでの、3本指や4本指の操作とTouchPanelGesturesのものが衝突して、意図しない動きが出ることが多く、 三本指で→↓あたりが混乱少なく、キーボードを出したり、隠したりできます。

torazaemon2016.hatenablog.jp

物理キーボードがないSurfaceタブレットとして使う生活をエンジョイしてください。

Autohotkey Ver2のGUIによる分離QWERTYスクリーンタッチキーボード

2025-11-21 全部集約したものになりました。 torazaemon2016.hatenablog.jp

以下は、上ができる前までの内容です。


AutohotkeyGUIを使って、物理的なキーボードのないPC (Surface)を、ペンや指だけで操作するを目指すシリーズ 2回目です。

前回は、GUIを出す習作みたいなメニューでしたが、今回は、もう少し実用的なタッチタイプキーボードの実装です。

torazaemon2016.hatenablog.jp

Keyword: Autohotkey, ahk, タッチタイプキーボード, スクリーンキーボード, 仮想キーボード, Virtual keyboard

Windowsでのタッチタイプキーボード

Surface ProなどのWindowsタブレット型PCでのWindows標準のスクリーンタッチタイプキーボードは、ちょっとイマイチな感があるので、 WWWを探すと、

hotvirtualkeyboard.com

とかがありますが、自分で配置やキー割り当てができて、Surfaceで物理キーボードがなくても使えるものをと、作ってみました。

Autohotkeyでスクリーンタッチキーボード

そこで、AutohotkeyGUIを使って作るにはと思い、探すと、

ahkscript.github.io がありましたが、v1からのものをv2にしたものらしく、実行すると、表示はされるけど、キー操作できないみたいでした。

さらに探すと、v2で動く

https://www.reddit.com/r/AutoHotkey/comments/13gawje/can_buttons_in_gui_act_like_keyboard_keys/

が見つかりました。

これを参考にして、自分でキー配置を自由に配置してみたいとして、分離QWERTYスクリーンタッチキーボードを作成しました。

(このほかにも、カタナ式などやってみました。自由にタッチキーボードが作れて面白いです)

なお、Autohotkeyの詳しいことやインストールなどについては、他の詳しいページを参考にしてください。

分離QWERTY配置

2つのWindowで、分離キーボードを実現しています。

小さいサイズ

大きいサイズで、IME ON状態

Windows標準の分割タイプと違って、2つのWindowになっているので、間が見えますし、左右ばらばらに自由に配置できるので、都合の良いところに置けます。(基本的に左右の高さが一致してないと使いづらいっぽいですが)

標準的なテンキーなし日本語QWERTY配列より、右側の記号部分を削っていますので、それらは左下の「Extra」キーを押すことで出てきます。

詳しくは以下のスクリプトや、実際に動かしてみてみてください。 キーボードの大きさなど、プログラム中で値を書いていますので、それを修正すれば、好みのサイズにすることができるかと思います。

あと、物理キーボードがないので、TouchPanelGesturesアプリは必須です。

suwa.pupu.jp

プログラム中でホットキーとして「F2」を指定しているので、 「指3本のSingleTap」とかを F2 と設定しておくと、画面を指3本でタップすることでキーボードを出したり、隠したりできるので、すごく楽になります。

torazaemon2016.hatenablog.jp

物理キーボードがないSurfaceタブレットとして使う生活をエンジョイしてください。


QWERTY2.ahk スクリプト

; 2025-09-23 分離QWERTY by torazemon2016

; 参考
; title: "Can buttons in GUI act like keyboard keys?"
; https://www.reddit.com/r/AutoHotkey/comments/13gawje/can_buttons_in_gui_act_like_keyboard_keys/

#Requires AutoHotkey 2.0+       ; Version 2以上

;IME_SET,GET() IME制御ライブラリを使用
; https://qiita.com/kenichiro_ayaki/items/d55005df2787da725c6f
#Include "IMEv2.ahk"    

; --- キーボードボタンのCSVデータ ---
; 定義の記述の ;から書き始まっているのは読み飛ばす
; csv := と 次の行の ( と 次の行の " は、このとおりにしておかないとエラー。同様に、一番最後の " と ) も独立行
; 2025-09-23 分離QWERTY
csv :=
(
"
; 1:button名, 2:文字, 3:英字時, 4:shift時, 5:extra時(, 6:コメント) の必須5項目か、任意のコメントの6項目の並び  

; qwerty 1段
bnEsc,Esc,Esc,Esc,Esc,      comment test Escape key
bn1,1,1,!,F1
bn2,2,2,`",F2,              ダブルコーテーションは`でエスケープすることでダブルコーテーション1文字になる
bn3,3,3,#,F3
bn4,4,4,$,F4
bn5,5,5,%,F5

bn6,6,6,&&,F6,              Buttonコントロールに&文字を表示させるには、&&と記述する。send_key関数での判断の際にも case '&&' としてやる必要がある
bn7,7,7,',F7
bn8,8,8,(,F8
bn9,9,9,),F9
bn0,0,0,|,F10,              | を 0のshiftにする
bnMinus,-,-,=,]
bnHat,^,^,~,End,            分離では未使用
bnEnmark,\,\,|,|,           分離では未使用 円マークと|のキーはキー削除のため | を 0のshiftにする
bnBackspace,Backspace,BS,}+{BS,BS


; qwerty 2段
bnTab,Tab,Tab,}+{Tab},Tab
bnq,q,q,Q,F11
bnw,w,w,W,F12
bne,e,e,E,+
bnr,r,r,R,*
bnt,t,t,T,|

bny,y,y,Y,^
bnu,u,u,U,~
bni,i,i,I,@
bno,o,o,O,{
bnp,p,p,P,[
bnAtmark,@,@,``,[,          分離では未使用 backquoteは`でエスケープすることで`の1文字になる
bnLBracket,[,[,{,{,         分離では未使用
bnEnter,Enter,Enter,Enter,Enter


; qwerty 3段 CaspはLCtrl
bnLCtrl,LCtrl,Ctrl,Ctrl,Ctrl
bna,a,a,A,}^{a
bns,s,s,S,}^{s
bnd,d,d,D,}^{d
bnf,f,f,F,}^{f
bng,g,g,G,\

bnh,h,h,H,;
bnj,j,j,J,:
bnk,k,k,K,``,                backquoteは`でエスケープすることで`の1文字になる
bnl,l,l,L,}
bnSemiColon,;,;,+,],        分離では未使用
bnColon,:,:,*,],            分離では未使用
bnRBracket,],],},},         分離では未使用       


; qwerty 4段
bnLShift,LShift,LShift,LShift,LShift
bnz,z,z,Z,}^{z
bnx,x,x,X,}^{x
bnc,c,c,C,}^{c
bnv,v,v,V,}^{v
bnb,b,b,B,_

bnn,n,n,N,/
bnm,m,m,M,?
bnComma,,,,,                コンマはCSVで文字として扱うのは面倒なのでMakeMapsFromCSV関数で直接代入することで処理する
bnPeriod,.,.,>,End,          
bnSlash,/,/,?,?,            分離では未使用
bnUnder,\,\,_,PgUp,         分離では未使用
bnUp,Up,Up,Up,}+{Up}
bnRShift,RShift,RShift,RShift,RShift,   RShift は領域選択ができる(+をつけて送出する処理)


; 5段目
bnExtra,Extra,Extra,Extra,Extra,        特殊キー(実キーボードにはない創作キー) send_key()関数で処理
bnLWin,LWin,LWin,LWin,LWin
bnLAlt,LAlt,LAlt,LAlt,LAlt
bnvk1D,vk1D,無変換,無変換,無変換,          無変換キー vk1D
bnSpace,Space,Space,}+{Space},Space,    左スペースキー

bnRSpace,Space,Space,Space,Space,       右スペースキー
bnvk1C,vk1C,変換,変換,変換,               変換キー vk1C
bnLeft,Left,Left,Left,}+{Left}
bnDown,Down,Down,Down,}+{Down}
bnRight,Right,Right,Right,}+{Right}


; 物理キーには定義がある特殊キー---------------
bnRWin,RWin,RWin,RWin,RWin,             分離では未使用
bnRCtrl,RCtrl,RCtrl,RCtrl,RCtrl,        分離では未使用
bnRAlt,RAlt,RAlt,RAlt,RAlt,             分離では未使用

bnF11,F11,F11,F11,F11,                  分離では未使用
bnF12,F12,F12,F12,F12,                  分離では未使用
bnF13,F13,F13,F13,F13                   分離では未使用
bnF14,F14,F14,F14,F14,                  分離では未使用
bnF15,F15,F15,F15,F15,                  分離では未使用
bnF16,F16,F16,F16,F16,                  分離では未使用
"
)

; --- CSVからMap化する関数 ---
MakeMapsFromCSV(csvText) {
    bm := Map()   ; send文字マップ
    bm2 := Map()  ; reverse map (英字 2 button name マップ (asc2bn))
    bm3 := Map()  ; 英字表示マップ   normal
    bm4 := Map()  ; shift表示マップ (shift)
    bm5 := Map()  ; extra表示マップ (F13)

    Loop Parse, csvText, "`n", "`r"
    {
        line := Trim(A_LoopField)
        
        if ( (line = "") || (InStr(line, ";", 0, 1) = 1) )  ; 読み飛ばし定義: 空行 または 1文字目が「;」
            continue

        ;MsgBox("line=" line)

        parts := StrSplit(line, ",")    ; , で分ける
        if (parts.Length >= 5) {     ; 5項目以上
            /* 
            ; 6番目の要素をコメント扱い
            if (parts.Length >= 6) {
                key := parts[1]
                comment := parts[6] ; 読み捨てるだけ
                MsgBox(key " : " comment)
            }
            */
            key := parts[1]
            sendmoji := parts[2]
            normal := parts[3]
            shift := parts[4]
            extra := parts[5]

            bm[key] := sendmoji
            bm2[sendmoji] := key
            bm3[key] := normal
            bm4[key] := shift
            bm5[key] := extra
        }
    }

    ; 文字Comma「,」 は特別扱い csvロード関数(MakeMapsFromCSV)でやるには例外処理面倒いので、以下のように直接代入する
    bm["bnComma"] := ","
    bm2[","] := "bnComma"
    bm3["bnComma"] := ","   ; normal
    bm4["bnComma"] := "<"    ; shift
    bm5["bnComma"] := "Home"    ; extra
    
    ; これと、「&」に対する処理だけはどうしようもない cvsでの代入はできるが、GUIで表示には「&&」としなければならないため、send_key()で特殊処理やる
    ;bm4["bn6"] := "&&"     ; shift

    return [bm, bm2, bm3, bm4, bm5]  ; 配列で返す
}

; --- CSVからMap作成 ---
maps := MakeMapsFromCSV(csv)

bn2send_map := maps[1]
asc2bn_map := maps[2]
bn2asc_map := maps[3]
bn2shift_map := maps[4]
bn2extra_map := maps[5]

; --- キーラベル(ボタン名bn???)から文字を取得関数 ---
GetKey2Name(key) {
    global CurrentMode, bn2send_map, asc2bn_map, bn2asc_map, bn2shift_map
    ;MsgBox(key)
    if (CurrentMode = "normal")
        return bn2send_map.Has(key) ? bn2asc_map[key] : ""
    else if (CurrentMode = "shift")
        return bn2shift_map.Has(key) ? bn2shift_map[key] : ""
    else
        return bn2extra_map.Has(key) ? bn2extra_map[key] : ""
}

; --- 文字からキーラベル(bn???)を取得関数 ---
GetName2Key(name) {
    global CurrentMode, bn2send_map, bn2asc_map, bn2shift_map, asc2bn_map
    ;MsgBox(name)
    ; keyには、最初のもの(asc)しか入っていない
    ;if (CurrentMode = "normal")
        return asc2bn_map.Has(name) ? asc2bn_map[name] : ""
    ;else
    ;   return asc2bn_map.Has(name) ? shift2bn_map[name] : ""
}

/**************************************************************************/
; たまに(万が一)、Ctrlキーなどが押されたままになっていたりするので、それを外すため
clear_mods(*)
{
    for _, key in ['LShift', 'RShift', 'LCtrl', 'RCtrl', 'LAlt','RAlt', 'LWin', 'RWin' ]
    {
        ;MsgBox("clear_mods() " key " -> " GetName2Key(key))

        if GetKeyState(key)                 ; 万が一、キーが押された状態になっていたら
            SendInput('{' key ' Up}')       ; ノーマルな状態にする
    }
}
    
/**************************************************************************/
; キーボードの並びの配列 
; QWERTY
/*
key_table := [
     [ 'bnEsc', 'bn1', 'bn2', 'bn3', 'bn4', 'bn5', 'bn6', 'bn7', 'bn8', 'bn9', 'bn0','bnMinus', 'bnHat', 'bnBackspace' ]
    ,[ 'bnTab', 'bnq', 'bnw', 'bne', 'bnr', 'bnt', 'bny', 'bnu', 'bni', 'bno', 'bnp','bnAtmark', 'bnLBracket' , 'bnEnter' ]
    ,[ 'bnLCtrl', 'bna', 'bns', 'bnd', 'bnf', 'bng', 'bnh', 'bnj', 'bnk', 'bnl', 'bnSemiColon', 'bnColon', 'bnRBracket' ]
    ,[ 'bnLShift', 'bnz', 'bnx', 'bnc', 'bnv', 'bnb', 'bnn', 'bnm', 'bnComma', 'bnPeriod', 'bnSlash',  'bnUnder', 'bnUp', 'bnRShift' ]
    ,[ 'bnExtra', 'bnLWin', 'bnLAlt', 'bnvk1D', 'bnSpace', 'bnvk1C', 'bnF14', 'bnF15', 'bnRAlt', 'bnLeft', 'bnDown', 'bnRight' ]
    ]
*/

; 分離QWERTY
key_table_left := [
     [ 'bnEsc', 'bn1', 'bn2', 'bn3', 'bn4', 'bn5' ]
    ,[ 'bnTab', 'bnq', 'bnw', 'bne', 'bnr', 'bnt']
    ,[ 'bnLCtrl', 'bna', 'bns', 'bnd', 'bnf', 'bng']
    ,[ 'bnLShift', 'bnz', 'bnx', 'bnc', 'bnv', 'bnb']
    ,[ 'bnExtra', 'bnLWin', 'bnLAlt', 'bnvk1D', 'bnSpace']
    ]
key_table_right := [
     [ 'bn6', 'bn7', 'bn8', 'bn9', 'bn0', 'bnBackspace' ]
    ,[ 'bny', 'bnu', 'bni', 'bno', 'bnp', 'bnEnter' ]
    ,[ 'bnh', 'bnj', 'bnk', 'bnl', 'bnMinus' ]
    ,[ 'bnn', 'bnm', 'bnComma', 'bnPeriod', 'bnUp', 'bnRShift' ]
    ,[ 'bnRSpace',   'bnvk1C',  'bnLeft', 'bnDown', 'bnRight' ]
    ]

/******************/
; モード切替用グローバル変数
global CurrentMode := "normal"  ; 初期値: normal(ノーマルキー) shift(シフトキー) extra(F1など拡張)

; 以下4つは同時にON (押されているがあるので、上記Modeとは違う扱い)
global AltState := 0    ; ALtキーが押されてるか1 ない 0
global CtrlState := 0   ; Ctrlキーが押されてるか1 ない 0
global WinState := 0    ; Winキーが押されてるか1 ない 0
global RShiftState := 0 ; 右シフト(RShift)キーが押されてるか1 ない 0  (F1のシフト(+F1)を実現するためなど)

/********************************************************/
; キーボード起動 (Autohotkey起動時に直接起動)
;virtual_keyboard_katanakai(1)  ; 1:small 2:large サイズで起動
virtual_keyboard_katanakai(2)   ; 1:small 2:large サイズで起動

/********************************************************/
; ここからが本体
virtual_keyboard_katanakai(size,*)
{
    ;左キーボードGUIオブジェクトを作成
    ;guikeyboard := Gui('+AlwaysOnTop', "AHK Keyboard") ; これだと文字がエディタなどに入力されない
    gui1 := Gui('+AlwaysOnTop +E0x08000000', "AHK Keyboard")    ; これだと文字を送れる ; "WS_DISABLED"
    
    key_table := key_table_left
    make_keyboard(gui1,key_table,size)  ; 1: small size, 2: large size

    gui1.Show('x0 y200 NoActivate')     ; 画面左上

    ;右キーボードGUIオブジェクトを作成
    gui2 := Gui('+AlwaysOnTop +E0x08000000', "AHK Keyboard 2")  ; これだと文字を送れる ; "WS_DISABLED"

    key_table := key_table_right 
    make_keyboard(gui2,key_table,size)  ; 1: small size, 2: large size

    gui2.Show('x1600 y200 NoActivate')  ; 画面右上

    ; Hotキーの定義
    toggle_hotkey := 'F2'                               ; Hotkey
    Hotkey("*" toggle_hotkey, CloseWindows)             ; keyboard on/off
    gui1.OnEvent("Close", CloseWindows)                 ; X で閉じるとき
    gui2.OnEvent("Close", CloseWindows)                 ; X で閉じるとき
    OnExit(clear_mods)                                  ; 終了時 実行するもの

    /***** 内部関数 **********************************************/
    ; 両方のWindowを同時に閉じる(Hideする)-----
    CloseWindows(*)
    {
        clear_mods()    ; 万が一、装飾キーがONになっているのをクリアするため
        
        if WinExist('ahk_id ' gui1.hwnd)
            gui1.Hide()
        else
            gui1.Show('NoActivate')
        
        if WinExist('ahk_id ' gui2.hwnd)
            gui2.Hide()
        else
            gui2.Show('NoActivate')
    }

    ;keyboard作成------------------
    make_keyboard(guio,key_table,size,*)
    {
        ; サイズ指定
        ;MsgBox("size = " size) ; 
        if(size == 1)
        {
            ; 小さいタイプ
            margin := 0     ; キーとキーの間
            btn_w := 60     ; 70    
            btn_h := 50     ; 70
            guio.SetFont("s14")  ; フォントサイズ(ポイント)
        }
        else
        {
            ; 大きめ
            margin := 5     ; キーとキーの間
            btn_w := 80     ; ボタンの幅
            btn_h := 70     ; ボタンの高さ
            guio.SetFont("s18")  ; フォントサイズ(ポイント)を設定
        }
        
        guio.MarginX := guio.MarginY := margin    
        guio.BackColor := "Silver"  ; 背景色 
        
        for _, row in key_table {               ; 段毎
            y := margin + (A_Index-1) * (margin + btn_h) ; 各段のY座標
            for _, bnkey in row {                 ; 段での各文字毎
                w := 0
                h := 0
                offset := 0     ; 右の最初やEnterキーのずれの位置合わせのため
                switch bnkey 
                {
                    case 'bnEsc':                   ; 1段目左端
                        w := btn_w * 0.8            ; 少し小さく
                        h := btn_h
                    
                    case 'bnTab':                   ; 2段目左端
                        w := btn_w * 1.0
                        h := btn_h
                    
                    case 'bnLCtrl':                 ; 3段目左端
                        w := btn_w * 1.2                
                        h := btn_h
                    
                    case 'bnLShift':                ; 4段目左端
                        w := btn_w * 1.4
                        h := btn_h
                    
                    case 'bnF13':                   ; 5段目左端
                        w := btn_w * 1      
                        h := btn_h
                    
                    case 'bnSpace':                 ; スペースキー
                        w := btn_w * 1.4 + btn_w * 1 + margin * 1  ; LShiftの幅分 + 1キー分 + 1つ分の隙間
                        h := btn_h

                    case 'bn6':                     ; 分離右 1段目左
                        w := btn_w * 1
                        h := btn_h
                        offset := 0

                    case 'bnBackspace':             ; 分離1段目右端
                        w := btn_w * 0.6 + btn_w * 1
                        h := btn_h

                    case 'bny':                     ; 分離右 2段目左
                        w := btn_w * 1      
                        h := btn_h
                        offset := btn_w * 0.2

                    case 'bnEnter':                 ; Enterキー
                        w := btn_w * 1.2            ; Enterの幅               
                        h := btn_h * 2.0 + margin   ; Enterの高さ ; 2段分+1つ分の隙間
                        offset := btn_w * 0.2       ; p と Enterとの間

                    case 'bnh':                     ; 分離右 3段目左
                        w := btn_w * 1              
                        h := btn_h
                        offset := btn_w * 0.4

                    case 'bnn':                     ; 分離右 4段目左
                        w := btn_w * 1          
                        h := btn_h
                        offset := btn_w * 0.6

                    case 'bnRSpace':                 ; 分離右 5段目 右スペースキー
                        w := btn_w * 0.6 + btn_w * 2 + margin * 1  ; bnnずれ幅分 + 2キー分 + 1つ分の隙間
                        h := btn_h
                        offset := 0

                    default:
                        w := btn_w                  ; 普通のキーwidth
                        h := btn_h                  ; 普通のキーheight
                }
                
                if (A_Index = 1)                ; 各段の最初のキー
                    con := guio.AddButton('xm+'         offset ' y' y ' w' w ' h' h ' v' bnkey, GetKey2Name(bnkey))
                else                            ; 最初以外のキー
                    con := guio.AddButton('x+' margin + offset ' yp ' ' w' w ' h' h ' v' bnkey, GetKey2Name(bnkey))

                con.OnEvent('Click', send_key.Bind(bnkey))  ; bn??? ボタンの名前(bn???で登録) 
            }
        }
    }
    
    ; 表示を切り替える----------
    ;change_keyboard(guio,*)
    change_keyboard(*)
    {
        global CtrlState
        global AltState
        global WinState
        global RShiftState

        for _, btn in gui1 {
            ;MsgBox("btn.Text = " btn.Text) ; 
            btn.Text := GetKey2Name(btn.Name)
        }
        for _, btn in gui2 {
            ;MsgBox("btn.Text = " btn.Text) ; 
            btn.Text := GetKey2Name(btn.Name)
        }

        ; Toggle IME
        if(IME_GET() == 1)
        {
            gui1["bnvk1D"].Text := "A->あ"
            gui2["bnvk1C"].Text := "あ"
        }
        else
        {
            gui1["bnvk1D"].Text := "A"
            gui2["bnvk1C"].Text := "A->あ"
        }

        ; Current Mode
        if(CurrentMode == "shift")
        {
            ;gui1["bnLShift"].Text := "SSS"
            ;gui2["bnRShift"].Text := "SSS"
            gui1["bnLShift"].Text := "SSS"
        }
        else if(CurrentMode == "extra")
        {
            gui1["bnExtra"].Text := "EEE"
        }
        
        if(CtrlState == 1)
        {
            gui1["bnLCtrl"].Text := "CCC"
            ;gui2["bnRCtrl"].Text := "CCC"  ; 今回未使用
        }
        if(AltState == 1)
        {
            gui1["bnLAlt"].Text := "AAA"
            ;gui2["bnRAlt"].Text := "AAA"   ; 今回未使用
        }
        if(WinState == 1)
        {
            gui1["bnLWin"].Text := "WWW"
            ;gui2["bnRWin"].Text := "WWW"   ; 今回未使用
        }
        if(RShiftState == 1)
        {
            gui2["bnRShift"].Text := "RRR"
        }

        set_BackgroundColor()
    }

    ; 背景色をセットする
    set_BackgroundColor(*)  ; 
    {
        global AltState
        global WinState
        global CtrlState
        global RShiftState
        global CurrentMode
        ;MsgBox("Ctrl " CtrlState " Alt " AltState " Win " WinState)

        ; 上から優先
        if(RShiftState == 1)
        {
            gui1.BackColor := "Red"
            gui2.BackColor := "Red"
        }
        else if(CtrlState == 1)
        {
            gui1.BackColor := "Yellow"
            gui2.BackColor := "Yellow"
        }
        else if(AltState == 1)
        {
            gui1.BackColor := "Olive"
            gui2.BackColor := "Olive"
        }
        else if(WinState == 1)
        {
            gui1.BackColor := "Blue"
            gui2.BackColor := "Blue"
        }
        else if(CurrentMode == "extra")
        {
            gui1.BackColor := "Maroon"
            gui2.BackColor := "Maroon"
        }
        else if(CurrentMode == "shift")
        {
            gui1.BackColor := "Fuchsia"
            gui2.BackColor := "Fuchsia"
        }
        else if(IME_GET() == 1) ; IME ON
        {
            ;MsgBox("IME_GET()" IME_GET())  
            gui1.BackColor := "Green"
            gui2.BackColor := "Green"
        }
        else
        {
            ;MsgBox("IME_GET()" IME_GET())  
            gui1.BackColor := "Silver"
            gui2.BackColor := "Silver"
        }
    }

    ; キー送出---------
    modifykey := "" ; 複数入力を覚えておく必要があるので関数の外に変数
                    ; ^!# が入る (また+のshiftはまた別 選択とかモードとかで扱いを別にしている)
    send_key(key2,*) 
    {
        global CurrentMode
        global AltState
        global WinState
        global CtrlState
        global RShiftState  ; 右shiftを特別扱い

        key := Getkey2Name(key2)    ;bn???->文字
        ;MsgBox("send_key =" key2 " GetKey2Name=" key)  

        switch key {
            case '&&':  ; &はautootkeyでのbutton表示で特別扱いが必要なため 
                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)
                key := "&"
                SendInput('{Blind}{' key '}')

            case '変換':  ; vk1C
                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)  
                IME_SET(1)      ; IMEをON
                sleep(100)      ; PC性能によりスピード調整が必要かも
                if(IME_GET() == 1)
                {
                    change_keyboard()
                }
                else
                {
                    ; もう一度チャレンジ
                    IME_SET(1)  ; IMEをON
                    sleep(100)      ; PC性能によりスピード調整が必要かも
                    if(IME_GET() == 1)
                    {
                        change_keyboard()
                    }
                    else
                    {
                        MsgBox("IME_ONに変わりませんでした 右下のIMEの状態を確認して再度押してみてください")
                    }
                }

            case '無変換':       ; vk1D
                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)  
                IME_SET(0)      ; IMEをoff
                sleep(100)
                if(IME_GET() == 1)
                {
                    MsgBox("IME_offに切り替わりませんでした 申し訳ありませんが再度押してみてください")
                }
                else
                {
                    change_keyboard()
                }
            
            /***装飾キー 押したかどうかのところ****************/
            case 'Alt':  goto ALT
            case 'LAlt': goto ALT
            case 'RAlt': 
                ALT:
                AltState := (AltState = 0) ? 1 : 0
                ;MsgBox("Alt =" AltState)
                change_keyboard()   ; 色も関数でやる

            case 'Ctrl':  goto CTRL
            case 'LCtrl': goto CTRL
            case 'RCtrl': 
                CTRL:
                CtrlState := (CtrlState = 0) ? 1 : 0
                ;MsgBox("Ctrl =" CtrlState)
                change_keyboard()   ; 色も関数でやる

            case 'LWin': goto wIN
            case 'RWin':
                WIN:
                if(WinState == 1)   ; Win のあと再度Winを押すと Winキーを押したのと同じにしてスタートメニューを開く
                {
                    SendInput('{Blind}' modifykey '{' key '}')
                }
                WinState := (WinState = 0) ? 1 : 0
                ;MsgBox(""in =" WinState)
                change_keyboard()   ; 色も関数でやる

            /***Mode(状態)のところ****************/
            case 'Shift':  goto SFT
            case 'LShift': goto SFT
                SFT:
                if((AltState == 1) || (CtrlState == 1 ) || (WinState == 1)) ; 修飾キーONのときはモード変えない
                    goto END
                    
                CurrentMode := (CurrentMode != "shift") ? "shift" : "normal"
                RShiftState := 0    ; 解除する
                ;MsgBox("Shift =" CurrentMode " Key=" key " key2=" key2)
                change_keyboard()   ; 色も関数でやる

            case 'RShift':  ; 選択するため&F13でのF?をShiftつけたいとき (+F1 になる)
                ;MsgBox("RS =" RShiftState)
                ; Shift状態にはしない(F13モードのままで) 
                ;;CurrentMode := (CurrentMode != "shift") ? "shift" :   "normal"    
                RShiftState := (RShiftState = 0) ? 1 : 0
                change_keyboard()   ; 色も関数でやる

            case 'Extra':
                if((AltState == 1) || (CtrlState == 1 ) || (WinState == 1)) ; 修飾キーONのときはモード変えない
                    goto END

                CurrentMode := (CurrentMode != "extra") ? "extra" : "normal"
                RShiftState := 0    ; 解除する
                ;MsgBox("F13 =" CurrentMode " Key=" key " key2=" key2)
                change_keyboard()
        

            /***実際に文字出力*****************************/
            default:    ; cvbb
                ;MsgBox("default send_key = '{Blind}' " modifykey "'{'" key "'}'")
                ;SendInput('{Blind}{' key '}')
                ;Send('{' key '}')  ; {Blind}なくても変化なし!?

                if(CtrlState == 1)
                {
                    modifykey := modifykey . "^"
                    CtrlState := 0
                    change_keyboard()   ; 色も関数でやる
                }
                if(AltState == 1)
                {
                    modifykey := modifykey . "!"
                    AltState := 0
                    change_keyboard()   ; 色も関数でやる
                }
                if(WinState == 1)
                {
                    modifykey := modifykey . "#"
                    WinState := 0
                    change_keyboard()   ; 色も関数でやる
                }

                if(RShiftState == 1)
                {
                    ;MsgBox("RS == 1 " RShiftState)
                    modifykey := "+"
                    SendInput('{Blind}' modifykey '{' key '}')
                    goto END
                }

                ; 実際に文字出力 '{' と '}' で挟んで出すので、Enterとかそのまま書ける
                ; F13での }^x とかの } は これの最初の '{' を打ち消すだめ (正確には }^x{ となるけど動いている)
                SendInput('{Blind}' modifykey '{' key '}')
                
                END:
                modifykey := "" ; 
        }
    }
    
}

/* END */

けいならべ配置やカタナ式配置など自分で作れますので、いろいろと改造してみてください。

torazaemon2016.hatenablog.jp

(2025-10-06追加) 今のものをupdateして、けいならべ もどき配置を追加したバージョン torazaemon2016.hatenablog.jp