torazaemon2016’s blog

手書き文字認識メモ開発

Autohotkeyによるカタナ改タッチキーボードのスクリプト

カタナ配置をスクリーンキーボード用に改造したもの

カタナ式

https://oookaworks.up.seesaa.net/image/E382ABE382BFE3838AE5BC8Fv8.5E3839EE3838BE383A5E382A2E383AB.pdf

を参考に、 Autohotkeyでのタッチタイプでは、物理キーでのシフトキーのような「同時押し」が難しいことや、 Surface Proを左右の手で挟んで持つと、使えるのは親指だけになるので、それで打ちやすいようにとして、カタナ配列を参考に 5x3 で配置したものです。

ローマ字かな漢字変換を、子音と変換のSpaceを左手側、母音と確定のEnterを(、。も確定扱い)を右手側に配置し、変換切り直しのEscと矢印キー、RShiftキーを、機能キー表示Windowに配置しています。

元記事:分離QWERTY torazaemon2016.hatenablog.jp


katanakai.ahk スクリプト

; 2025-09-21 katana配列へ 親指一本で入力
; http://oookaworks.seesaa.net/article/455391788.html#gsc.tab=0

; 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()
; https://qiita.com/kenichiro_ayaki/items/d55005df2787da725c6f
#Include "IMEv2.ahk"    

/*******************************/
; --- CSVデータ ---
; 定義の記述の ;から書き始まっているのは読み飛ばす
; csv := と 次の行の ( と 次の行の " は、このとおりにしておかないとエラー。同様に、一番最後の " と ) も独立行

csv :=
(
"
; 1:button名, 2:文字, 3:英字時, 4:shift時, 5:extra時(, 6:コメント) の必須5項目か、任意のコメントの6項目の並び  

; qwerty 1段
bnEsc,Esc,Esc,3,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,^,^,~,F12
bnEnmark,\,\,|,,                    円マークと|のキーはキー削除のため | を 0のshiftにする
bnBackspace,Backspace,BS,BS,BS

; qwerty 2段
bnTab,Tab,Tab,1,Tab
bnq,q,q,Q,
bnw,w,w,:,
bne,e,e,=,
bnr,r,r,!,}^a
bnt,t,t,),}^z
bny,y,y,\,
bnu,u,u,$,
bni,i,i,/,
bno,o,o,;,
bnp,p,p,[,
bnAtmark,@,@,``,Home,               backquoteは`でエスケープすることで`の1文字になる
bnLBracket,[,[,0,End
bnEnter,Enter,Enter,Enter,Enter

; qwerty 3段 CaspはLCtrl
bnLCtrl,LCtrl,Ctrl,Ctrl,Ctrl
bna,a,a,%,F9
bns,s,s,},}^w
bnd,d,d,],}^y
bnf,f,f,F,}^f
bng,g,g,#,}^f
bnh,h,h,`",}^c
bnj,j,j,J,
bnk,k,k,?,}^s
bnl,l,l,L,Right
bnSemiColon,;,;,*,
bnColon,:,:,+,}^{Home
bnRBracket,],],),}^{End

; qwerty 4段
bnLShift,LShift,LShift,LShift,LShift
bnz,z,z,{,
bnx,x,x,+,}+!h{
bnc,c,c,,
bnv,v,v,V,
bnb,b,b,(,
bnn,n,n,@,}^v
bnm,m,m,',}^x
bnPeriod,.,.,>,
bnComma,,,,,                        コンマはCSVで扱うのは無理(関数で直接代入することで回避)
bnSlash,/,/,?,PgUp
bnUnder,\,\,_,PgDn
bnUp,Up,Up,4,}+{Up}
bnRShift,RShift,RShift,5,RShift,    RShift は領域選択ができる R Shift

; qwerty 5段目
bnF13,F13,F13,F13,F13
bnLWin,LWin,LWin,6,LWin
bnLAlt,LAlt,LAlt,LAlt,LAlt
bnSpace,Space,Space,}+{Space},} ,     extraでは   全角スペースに設定 }の記載は必要
bnF14,F14,F14,F14,F14
bnF15,F15,F15,F15,F15
bnRAlt,RAlt,RAlt,RAlt,RAlt
bnLeft,Left,Left,8,}+{Left}
bnDown,Down,Down,9,}+{Down}
bnRight,Right,Right,0,}+{Right}

; 特殊キー-67keyで未使用---------------
bnHome,Home,Home,2,}^{Home
bnEnd,End,End,7,}^{End

bnRWin,RWin,RWin,RWin,RWin
bnRCtrl,RCtrl,RCtrl,RCtrl,RCtrl
bnRSpace,Space,Space,Space,Space

bnF11,F11,F11,F11,F11
bnF12,F12,F12,F12,F12
bnF16,F16,F16,F16,F16

bnvk1D,vk1D,vk1D,vk1D,vk1D,         無変換キー vk1D
bnvk1C,vk1C,A->あ,A->あ,,               変換キー vk1C (別途Toggleキー扱いで処理、 初期表示 A->あ)

bnnn,}nn,}nn,*,}nn,                 ん(nnキー)の設置(物理キーにはないものを作る)

"
)

; --- 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 >= 3) {
            /* 
            ; 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
        }
    }

    ; 文字 , は特別扱いが必要 
    ; csvロード関数(MakeMapsFromCSV)でやるには例外処理面倒いので、以下のように直接代入する
    bm["bnComma"] := ","
    bm2[","] := "bnComma"
    bm3["bnComma"] := ","   ; normal
    bm4["bnComma"] := "<"    ; shift
    bm5["bnComma"] := ","   ; extra
    
    ; " ` に対する処理 csvの定義のところでも以下のように ` を使って書けばOK
    ;bm4["bn2"] := "`""
    ;bm4["bnAtmark"] := "``"
    ; & に対する処理 だけはどうしようもない send_key()でも特殊処理やる
    ;bm4["bn6"] := "&&"

    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]

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

; 以下3つは同時に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  (文字選択の機能実現のため、右シフトを特別扱い)

; --- キーラベル(ボタン名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] : ""
}

/**************************************************************************/
;; キーボードの並びの配列 
/*
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' ]
    ,[ 'bnF13', 'bnLWin', 'bnLAlt', 'bnvk1D', 'bnSpace', 'bnvk1C', 'bnF14', 'bnF15', 'bnRAlt', 'bnLeft', 'bnDown', 'bnRight' ]
    ]
*/

;2025-09-21 ミニマム追及 3つWindowで
key_table_left := [
     [ 'bnb',   'bnz',  'bnp']
    ,[ 'bnt',   'bns',  'bnd']
    ,[ 'bnr',   'bnk',  'bng']
    ,[ 'bnm',   'bnh',  'bnn']
    ,[ 'bnLShift',  'bnSpace']
    ]
key_table_right := [
     [ 'bnx',       'bnMinus',  'bnBackspace']
    ,[ 'bnnn',      'bni',      'bne']
    ,[ 'bna',       'bnu',      'bny']
    ,[ 'bno',       'bnw',      'bnEnter']
    ,[ 'bnComma',   'bnPeriod']
    ]
    
key_table_function := [
     [ 'bnF13',     'bnTab',    'bnHome',   'bnEsc',    'bnUp',     'bnRShift']
    ,[ 'bnvk1C',    'bnLWin',   'bnEnd',    'bnLeft',   'bnDown',   'bnRight' ]
    ]


/****************************************************/
; たまに(万が一)、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}')       ; ノーマルな状態にする
    }
}
    
/**************************************************************************/
; ここからが本体

virtual_keyboard_katanakai(1)   ; 大きいサイズ(2)で起動

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

    gui1.Show('x0 y150 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('x2400 y150 NoActivate')  ; 画面右上

    ; GUIオブジェクトを作成(function)
    gui3 := Gui('+AlwaysOnTop +E0x08000000', "AHK Keyboard 2")  ; これだと文字を送れる ; "WS_DISABLED"
    key_table := key_table_function 

    make_keyboard(gui3,key_table,size)  ; 1: small size, 2: large size

    gui3.Show('x1970 y1000 NoActivate') ; 画面右上

    ; Hotキーの定義
    toggle_hotkey := 'F2'                               ; Hotkey
    Hotkey("*" toggle_hotkey, CloseWindows)             ; keyboard on/off
    gui1.OnEvent("Close", CloseWindows)                 ; X で閉じるとき
    gui2.OnEvent("Close", CloseWindows)                 ; X で閉じるとき
    ;gui3.OnEvent("Close", CloseWindows)                ; X で閉じるとき functionは別動作に (一緒に消さない)
    OnExit(clear_mods)                                  ; 終了時 実行するもの

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

        ; OnEventに入れてないので、3だけ残すことが可能だけど、手順は煩雑かな
        if WinExist('ahk_id ' gui3.hwnd)
            gui3.Hide()
        else
            gui3.Show('NoActivate')
    }

    ;keyboard作成------------------
    make_keyboard(guio,key_table,size,*)
    {
        ; サイズ指定
        ;MsgBox("size = " size) ; 
        if(size == 1)
        {
            ; 小さいタイプ(手はちょっと無理だけど、ペンで押せる)
            margin := 1     ; キーとキーの間
            btn_w := 70     ; 
            btn_h := 70     ;
            offset := 0     ; Enterキーでの特殊な位置合わせのため
            guio.SetFont("s18")  ; フォントサイズ(ポイント)
        }
        else
        {
            ; Surface Pro8 で両手打ち
            margin := 10        ; キーとキーの間
            btn_w := 80     ; ボタンの幅
            btn_h := 70     ; ボタンの高さ
            offset := 0     ; Enterキーでの特殊な位置合わせのため
            guio.SetFont("s18")  ; フォントサイズ(ポイント)を設定
        }
        
        guio.MarginX := guio.MarginY := margin    
        guio.BackColor := "green"  ; 背景色 
        
        for _, row in key_table {               ; 段毎
            y := margin + (A_Index-1) * (margin + btn_h) ; 各段のY座標
            for _, key in row {                 ; 段での各文字毎
                if (key = 'bnSpace')            ; スペースキー
                {
                    ;w := btn_w * 1.2 + btn_w * 1 + margin * 1  ; Shiftの幅分 + 1キー分 + 1つ分の隙間
                    w := btn_w * 2 + margin * 1  ;  2キー分 + 1つ分の隙間
                    h := btn_h
                }
                else if (key = 'bnRSpace')            ; スペースキー
                {
                    ;w := btn_w * 2 +             margin * 1  ; Shiftの幅分 + 1キー分 + 1つ分の隙間
                    w := btn_w                  ;
                    h := btn_h
                }
                else if (key = 'bnEnter')       ; Enterキー
                {
                    w := btn_w * 1          ; Enterの幅               
                    h := btn_h * 2.0 + margin   ; Enterの高さ ; 2段分+1つ分の隙間
                    ;offset := btn_w * 0.6 + margin     ; 2段目 [ と Enterとの間
                    offset := 0     ; 
                }
                /*
                else if (key = 'bnBackSpace')   ; 1段目右端
                {
                    w := btn_w * 1      
                    h := btn_h
                }
                else if (key = 'bnTab') ;       ; 2段目左端
                {
                    w := btn_w * 1              
                    h := btn_h
                }
                else if (key = 'bnLCtrl')   ;   ; 3段目左端
                {
                    w := btn_w * 1.2                
                    h := btn_h
                }
                else if (key = 'bna')       ;   ; 3段目 右 左端
                {
                    w := btn_w * 1.4                
                    h := btn_h
                }
                else if (key = 'bnRCtrl')   ;   ; 3段目 左端2
                {
                    w := btn_w * 1.4                
                    h := btn_h
                }
                else if (key = 'bnLShift')  ;   ; 4段目左端
                {
                    w := btn_w * 1.6                
                    h := btn_h
                }
                else if (key = 'bnRShift')  ;   ; 4段目 左端2
                {
                    w := btn_w * 1.6                
                    h := btn_h
                }
                else if (key = 'bnComma')   ;   ; 4段目 左端2
                {
                    w := btn_w              
                    h := btn_h
                }
                else if (key = 'bnvk1C')    ;   ; 4段目 左端2
                {
                    w := btn_w              
                    h := btn_h
                }
                else if (key = 'bnF13')         ; 5段目左端
                {
                    w := btn_w * 1      
                    h := btn_h
                }
                */
                else
                {
                    w := btn_w                  ; 普通のキーwidth
                    h := btn_h                  ; 普通のキーheight
                }
                
                if (A_Index = 1)                ; 各段の最初のキー
                    con := guio.AddButton('xm '                ' y' y ' w' w ' h' h ' v' key, GetKey2Name(key))
                else                            ; 最初以外のキー
                    con := guio.AddButton('x+' margin + offset ' yp ' ' w' w ' h' h ' v' key,GetKey2Name(key))

                con.OnEvent('Click', send_key.Bind(key))    ; bn??? ボタンの名前(bn???で登録) 
                
                offset := 0     ; Enterでサイズ変更しているので戻すため
            }
        }
    }

    ; 表示を切り替える----------
    ;change_keyboard(guio,*)
    change_keyboard(*)
    {
        global AltState
        global WinState
        global CtrlState

        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)
        }
        for _, btn in gui3 {
            ;MsgBox("btn.Text = " btn.Text) ; 
            btn.Text := GetKey2Name(btn.Name)
        }

        ; Shift Mode
        if(CurrentMode == "shift")
        {
            ;gui1["bnLShift"].Text := "SSS"
            ;gui2["bnRShift"].Text := "SSS"
            gui1["bnLShift"].Text := "SSS"
        }
        else
        {
            ; Toggle IME
            if(IME_GET() == 1)
            {
                gui3["bnvk1C"].Text := "あ->A"
            }
            else
            {
                gui3["bnvk1C"].Text := "A->あ"
            }
        }

        if(CurrentMode == "extra")
        {
            ;gui1["bnF13"].Text := "FFF"
            gui3["bnF13"].Text := "FFF"
        }

        if(CtrlState == 1)
        {
            ;gui1["bnLCtrl"].Text := "CCC"
            ;gui2["bnRCtrl"].Text := "CCC"  ; 今回未使用
            gui3["bnLCtrl"].Text := "CCC"
        }
        if(AltState == 1)
        {
            ;gui1["bnLAlt"].Text := "AAA"
            ;gui2["bnRAlt"].Text := "AAA"
            gui3["bnLAlt"].Text := "AAA"
        }
        if(WinState == 1)
        {
            ;gui1["bnLWin"].Text := "WWW"
            ;gui2["bnRWin"].Text := "WWW"   ; 今回未使用
            gui3["bnLWin"].Text := "WWW"
        }

        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"
            gui3.BackColor := "Red"
        }
        else if(CtrlState == 1)
        {
            gui1.BackColor := "Yellow"
            gui2.BackColor := "Yellow"
            gui3.BackColor := "Yellow"
        }
        else if(AltState == 1)
        {
            gui1.BackColor := "Olive"
            gui2.BackColor := "Olive"
            gui3.BackColor := "Olive"
        }
        else if(WinState == 1)
        {
            gui1.BackColor := "Blue"
            gui2.BackColor := "Blue"
            gui3.BackColor := "Blue"
        }
        else if(CurrentMode == "extra")
        {
            gui1.BackColor := "Maroon"
            gui2.BackColor := "Maroon"
            gui3.BackColor := "Maroon"
        }
        else if(CurrentMode == "shift")
        {
            gui1.BackColor := "Fuchsia"
            gui2.BackColor := "Fuchsia"
            gui3.BackColor := "Fuchsia"
        }
        else
        {
            gui1.BackColor := "Green"
            gui2.BackColor := "Green"
            gui3.BackColor := "Green"
        }
    }

    ; キー送出(大事な関数)---------
    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)  

        ; 2025-09-21 IME Toggle switch にする
        if(key2 == "bnvk1C")
        {
            str := gui3["bnvk1C"].Text
            ;MsgBox("send_key =" key2 " GetKey2Name=" key " Text=" str)
            if( str == "A->あ")
            {
                ;MsgBox("if あいう send_key =" key2 " GetKey2Name=" key " Text=" str)
                IME_SET(1)  ; IMEをON
                sleep(100)      ; PC性能によりスピード調整が必要かも
                if(IME_GET() == 1)
                {
                    gui3["bnvk1C"].Text := "あ->A"
                }
                else
                {
                    ; もう一度チャレンジ
                    IME_SET(1)  ; IMEをON
                    sleep(100)      ; PC性能によりスピード調整が必要かも
                    if(IME_GET() == 1)
                    {
                        gui3["bnvk1C"].Text := "あ->A"
                    }
                    else
                    {
                        MsgBox("IME_ONに変わりませんでした 右下のIMEの状態を確認して再度押してみてください")
                    }
                }
            }
            else if(str == "あ->A")
            { 
                ;MsgBox("else if ABC  send_key =" key2 " GetKey2Name=" key " Text=" str)
                IME_SET(0)  ; IMEをoff
                sleep(100)
                if(IME_GET() == 1)
                {
                    MsgBox("IME_offに切り替わりませんでした 申し訳ありませんが再度押してみてください")
                }
                else
                {
                    gui3["bnvk1C"].Text := "A->あ"
                }
            }
        }
                
        switch key {
            case '&&':  ; &はautootkeyでのbutton表示で特別扱いが必要なため 
                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)
                key := "&"
                SendInput('{Blind}{' key '}')

            case '.':   ; IME ONで.のときは 。 なので確定にする
                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)
                SendInput('{Blind}{' key '}')
                if(IME_GET() == 1)
                {
                    SendInput('{Blind}{Enter}')
                }

            case ',':   ; IME ONで.のときは 、 なので確定にする
                ;MsgBox("case && send_key =" key2 " GetKey2Name=" key)
                SendInput('{Blind}{' key '}')
                if(IME_GET() == 1)
                {
                    SendInput('{Blind}{Enter}')
                }
                
            /***装飾キー 押したかどうかのところ****************/
            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':  ; 選択するため
                ;MsgBox("RS =" RShiftState)
                ; Shift状態にはしない(F13モードのままで) 
                ;;CurrentMode := (CurrentMode != "shift") ? "shift" :   "normal"    
                RShiftState := (RShiftState = 0) ? 1 : 0
                change_keyboard()   ; 色も関数でやる

            case 'F13':
                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 */

Syncthing と SyncTrayzor が Ver.2 にUpdate

Version 2になりました

Syncthing v2.0.0 (2025-08-12)(https://forum.syncthing.net/t/syncthing-v2-0-0-2025-08-12/24758)

Version2でデータベースがSQLiteになったり、APIが整理されたりと、大きく変わったようです。

また、SyncTrayzorも元のものからForkして、Syncthing Version2にも対応しました。 https://github.com/GermanCoding/SyncTrayzor?tab=readme-ov-file

Note This is SyncTrayzor v2, a continuation of the (discontinued) original SyncTrayzor by @canton7 (Antony Male)

Ver2で正式にARM版も公開されました。 (ただし、32bit版はもうなくなりましたが、もうほとんど問題ないでしょう。)

これまでどおり、競合(Conflict)を表示してくれるので、安心して使えます。

keyword: Sync, Windows, iOS, iPad, iPhone, Android, Obsidian, 同期, ファイルコピー, Conflict

Windows環境update

WIndowsノートPCのSynctrayzor(Ver1)環境をVer2にしてみました。

バックアップ

まず、念のため、現環境のバックアップ(やらなくても大丈夫なところだけど、念のためと思う方は)

新規にインストールする人は、以下の場所にファイルが存在することを知っておいてください。

C:\Users\自分のID\AppData\Local\Syncthing

の中にある、config.xml を別な場所や名前を変えてコピー取っておくこと。

また、そこにあるindex-v0.14.0.db がデータベースになっています。 (数字はバージョンによって変わるかも)

また、

C:\Users\自分のID\AppData\Roaming\SyncTrayzor

に、syncthing.exe 本体が存在するので、このフォルダ自体を名前変えておく(C:\Users\自分のID\AppData\Roaming\SyncTrayzor.ver1)とかして、新しいsyncthing.exeが導入されるようにする。

(なにもしてないと、syncthing.exeがVer1の古いままで更新されなかったり、トラブったりしました。 フォルダがなければ、次のインストールで、新しいsyncthing.exeがコピーされてくる)

インストール

https://github.com/GermanCoding/SyncTrayzor/releases/tag/v2.0.1

今回はARM PCなのでarm64版をインストールしてみます。 (IntelAMDの方はx64版で)

ダウンロードしてきたSyncTrayzorSetup-arm64.exeをダブルクリックして実行してインストール。

Windowsの保護画面が出るので、詳細情報を押す。

詳細情報を押して、実行。

Install for all users をクリック

インストールパス(デフォルトで) など、基本的に「右下のNext」でインストール

デスクトップにアイコンを作るをチェック入れる

インストールを開始する。

インストールできたので、Finishする

起動編

Synctrayzorを起動すると、新しいsyncthing.exeをコピーしてくるとか、初回はSQLiteのDBを作るなど、思ったより時間が掛かります。 下のような白い画面でも、しばらく待ってみてください。

無事起動されると、いつものようなsyncthingの画面になります。 (今回からDefaultフォルダもなくなりました)

syncthingのバージョンを確認すると、

2.0.3 となっていることで、新しいVersion2になったことがわかります。

また、左上のsynctrayzorのメニューのヘルプから、バージョンを出すと、 以下のようなポップアップがでて、SyncTrayzorが2.0.1、Syncthingが2.0.3になっていることがわかります。

なお、synctrayzorのver1からのupdateの場合、先に書いたように、AppData\Roamingフォルダ以下が残っていたりしたときに、syncthing.exeのバージョンが1.30のままになる場合もあります。

C:\Users\自分のID\AppData\Roaming\SyncTrayzor を確認して、 一度、そこにあるsyncthing.exeを削除して、そこにsyncthing.exeをない形にしてから、再度synctrayzorを起動すると、自動で最新版を取ってきてコピーしてくれるようなので、この場所を確認してみてください。

なお、削除の際には、syncthingのプロセスが動いたままだと消せないので、 (しっかりSyncthingもSyncTrayzorも arm64bit版になっています)

以下のようにsynctrayzorから停止させて、synctrayzorも止めてから行ってください。

設定編

Ver1からのupdateで、これまでの設定が残っている場合、 新しいsyncthing.exe(Ver.2)でデータベースをSQLiteの新しい形に作り直すため、起動してくるまで時間が掛かります。

うまく起動してくると、前の設定でのデバイスやフォルダーが見えてくると思います。

設定で、いつものようにネットワークなどの許可をoffにして、LAN環境のみの設定にする。

バイスの追加やフォルダの追加は、これまでと同じなので、 syncthingのページを参考にしてみてください。

競合

PC,iPhoneで同時に双方で編集して、競合(Conflict)起こすようにして、Syncthingで同期させたら しっかり、競合が表示された!

「今すぐ解決する」のところを押すと、

「フォルダーを表示」を押して

該当する2つのファイルを選択したのち、WinMergeがインストールされているPCであれば、右ボタンで

編集後、残す方を選択

SynctrainのVer.2

iPhone(iPad)での無料のsyncthingである「Synctrain」は、すでに 2.0になっていました。

Synctrain

Synctrain

  • T-Shaped
  • 仕事効率化
  • 無料
apps.apple.com

Androidのほうは、Forkを。

play.google.com

これまでのsyncthing関連ページの一覧

インストールして使ってみたい方は、以下のページを見ていただけると、理解しやすいかと思います。

torazaemon2016.hatenablog.jp

torazaemon2016.hatenablog.jp

torazaemon2016.hatenablog.jp

MöbiusSyncでiOSのPhotoの共有

Möbius SyncでiOSの写真を共有

iPadiPhoneの写真をノートPCなどとsyncthingで簡単にすぐに共有可能にする方法です。

共有となっていますが、基本は、片方向(iPhoneからWindowsへ)だけです。

すでに、2つのマシンでのSyncthing的な接続ができている前提として、 今回は、2ndのiPadminiから1stのノートPCという形になるので、 手順は

  1. 2ndのiPadmini にて、Photoフォルダを共有設定
  2. 2ndのiPadmini にて、相手先デバイスに☑を入れて許可を出す
  3. 1stのノートPCにて、受け入れて写真を保存するフォルダを指定

になります。

1. 2ndのiPadminiで 「フォルダーを追加」画面にて「Pick Photo Collection」を選択

左下のボタン「Pick Photo Collection」を選択

警告が出るけど、「Proceed」押す

写真全部「All Photos」を選択して「Select」押す

フォルダ名を決める。今回は「ALl Photos」で「保存」

2. 相手に許可を出す

iPadminiの(共有する)フォルダーとなる(ファイル数やサイズに注意) 「編集」ボタンを押して

「共有」で、どこと共有するかを指定する 相手のデバイス名に☑を入れる

どんな写真・ビデオをコピー可能とするかを指定

  • 適宜コピーしたいものに☑
  • ビデオは大きいですが、自動でWindowsにコピーされるのは大変便利

3. 1stのノートPC側

ノートPC側で「追加」

フォルダーを追加で、右下の「ブラウザ」ボタンを押して、

保存先フォルダを決める(今回はデスクトップ)

フォルダーパスが設定される(デスクトップにAll Photosができる)

Windowsにコピー(同期)された

日付ごとのフォルダに、ファイル名としては、

  • カメラで撮ったものは、2025-04-26T073526_IMG_2079.heic など
  • スクリーンキャプチャは、2025-04-26T114201_IMG_2082.png

となる

iPhoneで撮った写真・ビデオが、すぐにWindowsと同期できてPC側でアクセスできるのは大変便利ですので、 ぜひSyncthingアプリをインストールして活用してください。

SyncTrayzorでのConflict

Conflictした場合

Syncthingでは、2つのマシンの間でファイルの同時編集等で衝突(Conflict)が発生した場合、ファイル名にsync-conflict-などをつけて保存していきます。 (例:2025-04-26.mdと2025-04-26.sync-conflict-20250426-144056-32IIFSB.md)

WindowsのSyncTrayzorでは、その時に、Conflictが発生していることを知らせてくれるので、大変便利です。 そして、そのファイルがどれかをポップアップウィンドウが表示され、衝突状況を表示し、さらに、どちらを残すのかをボタンクリックで選択できます。

「今すぐ解決します。」をクリックすると、

ウィンドウが表示されるので、残す方を「選択」ボタンを押します。

ファイルが見てみたい場合は、「フォルダーを表示」を押すと

エクスプローラー画面で、該当するファイルが選択されています。

WinMergeがインストールされていれば

winmerge.org

もし、WinMergeがインストールされていれば、 Ctrlキーを押しながら、Conflictしている2つめのファイルを選択して

右ボタンでコンテキストメニューから「比較」すると

WinMergeが起動して、テキストの違いを編集可能になる

編集して、内容を統合して完成したら、ファイルを保存してWinMergeを終了します。

の画面のうち、編集して残したいファイル名の右側に出ている「選択」ボタンを押すことで、 自動で残したいほうのみが残り、他のファイルは自動的に削除されます。

さらに、AutohotkeyがインストールされているPCであれば、

note.com

以下のようにAutohotkeyWinMergeが起動するようにしておくと、エクスプローラーで2つを選んだら「無変換(vk1D) + d」キーを押せばすぐにWinMerge起動と、さらに簡単です。

vk1D & d::   ; WinMerge (diff) for Syncthing Diff
{
    IME_set(0)
    Send("^+c") ; Explolerでのパスのコピー
    sleep(100)
    files := A_Clipboard
    Run("WinMergeU.exe " files )    ; C:\Program Files\WinMerge
    return
}

https://www.autohotkey.com/

SyncTrayzorとMöbiusSyncのフォルダ同期編

ノートPC(1stマシン)、iPadmini(2ndマシン)の間が接続されたので、次に同期したいフォルダーを指定します。

iPadminiのMöbius Syncにおいて、保存する場所によるパターンA(無料で利用可)とパターンB(有料版で可能)を紹介します。

フォルダ同期編:

  1. ノートPC(1stマシン)において、同期するフォルダの指定
  2. ノートPC(1stマシン)にて、同期するフォルダのiPadmini(2ndマシン)への許可を出す
  3. iPadmini(2ndマシン)において、ノートPCの同期するフォルダが見えてくるので、iPadminiのどこに保存するかを設定
  4. 同期するフォルダの共有が開始(1stから2ndへのコピー)

1. ノートPC(1stマシン)において、同期するフォルダの指定

ノートPCのGUIにおいて、左側のフォルダー欄の「フォルダーの追加」を押す

フォルダーを追加の設定において、下部の「フォルダーパス」欄の右側の「ブラウザ」ボタンを押す (Syncthingと比較して、SyncTrayzorの便利な点)

選択画面で共有したいフォルダーを選択する

「フォルダー名」をキーボードより入力して、保存する。 ここでは「sample」という名前にしています (フォルダー名と同じである必要はありませんが、混乱しないように同じにしています)

公開するフォルダーが追加された

2. ノートPC(1stマシン)にて、同期フォルダのiPadmini(2ndマシン)への許可を出す

追加された「sample」欄をクリックして、内容を開いて、下部の「編集」ボタンを押す

フォルダーの編集

「共有」の設定

iPadmini(2ndマシン)に☑を入れ、共有マシンとして選択

3(A). iPadmini(2ndマシン)において、ノートPCの同期フォルダが見えてくるので、iPadminiのどこに保存するかを設定(パターンA)

iPadmini(2ndマシン)に、新規フォルダーの追加が表示されるので「追加」する

フォルダーを追加の画面になるので、どこに保存するかを設定する。

パターンAとして、そのまま「保存」する例から。 この場合、Mobuis Syncアプリのフォルダの中にパスが設定される。

4(A). 同期するフォルダの共有が開始(1stから2ndへのコピー)

コピーが行われる

ノートPC(1stマシン)のsampleフォルダが

iPadmini(2ndマシン)の ローカルストレージの「Mobius Sync」フォルダ内の「sample」フォルダに、1stマシンのファイルがコピー(同期)された。

3(B). iPadmini(2ndマシン)において、ノートPCの同期フォルダが見えてくるので、iPadminiのどこに保存するかを設定(パターンB)

パターンBでは、Möbius Syncの有料版の機能である、iOSのどこの場所でも共有フォルダ設定にできる方法を紹介します。

パターンAと同様に、ノートPC(1stマシン)で共有設定したあと、iPadmini(2nd)において、新規フォルダーを「追加」

「フォルダーを追加」画面において、左下部の「Pick External Folder」を押す

フォルダー選択画面になる(Möbius Syncフォルダ内でスタート)ので、 左側の「このiPad内」を選択して、自分の希望する場所を選択する

「Obsidian」を選択 (ObsidianアプリでのVaultを選択することができる)

右上の「開く」で決定

警告としていろいろと出るけど「Proceed at my own risk」を選択

フォルダーパスが設定される(bookmark:フォルダID)になってわかりにくいけど、フォルダが設定されたと考える。

「フォルダー名」も入力する(この場合、sampleからDocumentsに書き換えた)

4(B). 同期するフォルダの共有が開始(1stから2ndへのコピー)

コピーが行われる


同期が完了して「最新」になれば、これで2つのマシン間で同期できるので、 どちらかのマシンでファイルを編集して、それが同期で更新されていく様子を確認してみてください。

なお、同時に両方のマシンでファイルを更新すると「衝突(Conflict)」が発生するので、 その場合のことも、経験しておいてください。


そのほかに

SyncTrayzorでのConflictの話

torazaemon2016.hatenablog.jp

MöbiusSyncでPhotoの共有の話

torazaemon2016.hatenablog.jp

SyncTrayzorとMöbius Syncの接続編

接続編

  1. ノートPC(1stマシン)において、IDのQRコードで表示
  2. iPadmini(2ndマシン)において、iPadのカメラでノートPCのQRコードを読み込むことで、ノートPC(1stマシン)のデバイスIDを登録
  3. ノートPCにiPadminiの接続が表示されてくるので、それを許可する

1. ノートPC(1stマシン)において、IDのQRコードで表示

右上の「メニュー」から「IDを表示」を選択

QRコード表示

2. iPadmini(2ndマシン)において、iPadのカメラでノートPCのQRコードを読み込むことで、ノートPC(1stマシン)のデバイスIDを登録

右下の「接続先デバイスを追加」を押す

バイスIDのところにある「Scan QR code」を押す

カメラのアクセス許可を求められるので「許可」する

カメラが起動してQRコードを探すので、ノートPCで表示しているQRコードを読み取る。

バイスIDが読み取られ、入力される

バイス名を手で入力(空欄だとノートPCでの名前が入る) して保存

最初のGUI画面に戻り、接続先デバイス(今回は「IdeaPad5x」)が表示

バイス名「IdeaPad5x」をクリックすると、情報が表示されるので、押しておく。

暫くすると、相手(のIPアドレスへ)接続しようと始める

相手(ノートPC)の接続待ち

3. ノートPC(1stマシン)にiPadminiの接続が表示されてくるので、それを許可する

「デバイスを追加」を押す

「保存」する

接続先デバイスが追加(今回は「iPadmini」)が表示

同様に押して、情報を表示

ノートPC(1stマシン)、iPadmini(2ndマシン)の間が接続されたら、次に進んで、同期したいフォルダーを指定します。

もし、2つのマシン間が接続されていないようなら、もう少し時間を待ったりしてみてください。

どうもうまくいかないようなら、1stマシンのIPアドレスと2ndマシンのIPアドレスを確認して、両方とも「LAN内」に居ることなどや、 無線LAN装置の設定(FIREWALL機能やセキュリティ)なども確認してみてください。

次は 3. フォルダ同期編:

1,2,3,4. SyncTrayzorとMöbiusSyncのフォルダ同期編 - torazaemon2016’s blog

iPadmini(2ndマシン)にMöbius Syncをインストール

3. Apple Storeから「Möbius Sync」をインストールして起動してください。

Möbius Sync

Möbius Sync

Möbius Sync

  • Pickup Infinity Limited
  • 仕事効率化
  • 無料
apps.apple.com

アプリをインストールして起動

ノートPCにSynctrayzorをインストールしたように、 右上のボタンで言語として「Japanese」

右上の「メニュー」ボタンでプルダウン

「メニュー」から「設定」を選択

4. 「接続」でネットワーク設定

ここも、ノートPCのときと同様に、「LAN内で探索」のみ☑を入れることで、ローカルオンリーな設定にして「保存」おきます。

iOSに関する設定

iOSにかかわる設定タブがあります。

Background sync (バックグラウンドでの動作)についてです。 なにもしなければ、自分でMöbius Syncをアクティブにしたときに同期が動きます。 同期の初期設定が完全に終わるまでは手動で同期したほうが、確認しやすいです。

動作が安定してきて常用運用するときは、quickを2、powerを2などにしておくと、iPadがONになっていると、同期が動くことになります。 Notifyも☑しておくと便利かもしれません。

iOS Permissionsで、「OpenSettings」を押すと、iOSの設定画面が開かれます。 写真や、カメラ(次のQRコード読み込みに必要)へのアクセスや、ローカルネットワークの許可を出しておく必要があります。

(以下はiPadではなくiPhoneでのMöbius Syncの画面。外部ネットワークを使わないので、モバイルデータ通信はoff)

次は 2. 接続編

1,2,3. SyncTrayzorとMöbius Syncの接続編 - torazaemon2016’s blog