torazaemon2016’s blog

手書き文字認識メモ開発

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タブレットとして使う生活をエンジョイしてください。