Skip to content

Latest commit

 

History

History
1650 lines (1202 loc) · 114 KB

File metadata and controls

1650 lines (1202 loc) · 114 KB

Emacs Virtual Keyboard TODO List

Inbox

押しながら移動して離した場合に何か有用な働きをさせたい

  • 離した場所のキーだけが入力される(普通)
  • 経路のキーが全て入力される
  • 押した場所のキーと離した場所のキーが入力される
  • グライド入力? みたいなのってどういうの?
  • フリック入力

switch-frameイベントをできるだけ妨害したらどうなるか

Virtual Keyboardへのswitch-frameやその逆をinput-decode-mapで妨害できないものか。 また、できたとして実際に妨害したらどうなるのだろうか。実害は無いのか。 フォーカス移動が他に悟られないなどのメリットはあるだろうか。

レイアウトを切り替えるボタンを通常のキーとして置けるようにする

要するにFnキー。 正直タイトルバーに切り替えボタンが置けるからもう要らない気がする。

カナキーロックボタンを追加する

キーボードで打てるキーはASCIIに限らない。

かなを入力するとして全角文字を入力すべきだろうか。それとも半角カタカナ?

カナロックで打てるキーをkey-spec上でどう表現すれば良いのだろう。 おそらく他にも特殊な入力状態が欲しくなるケースはあると思う。 あー、レイアウト全体を切り替えてしまうと言うのは一つの手か……。

カナ入力自体が出来たところで何に使えるのか。Emacs付属のinput-methodと連携できないだろうか。いや、そもそもATOKのフリック入力と併用するためにこれを作ったわけで……。

ただ、シフト状態を増やす目的の例としては意味があると思う。

レイアウトを切り替えるボタンを通常のキーとして置けるようにするので済ますか悩むところ。

SVGキーボードスタイルを追加する

テキストスタイルが思いのほか綺麗に出来てしまったので、正直SVGで作りたい気持ちはほとんど無くなってしまった。

任意のバッファ内に直接キーボードを生成できるようにする

バッファローカルキーボードを作りたい。 SVG画像じゃないと実用的じゃないと思う (←サイズを小さくして部分的にread-onlyにすれば十分使えると思う。ひょっとしたらoverlayのdisplayプロパティでテキストとして表示してしまうという手もある?(正確な位置判定が難しいそう))。 これをやるならキーボードの状態はバッファに持たせない方が良い (←現状ですでにバッファには持たせないようになった)。 一つのバッファに複数のキーボードを配置する可能性があるので。

キーボードオブジェクトをキーボードバッファから取得している次の関数を修正しなければならないはず。

  • vkbd-translate-keyboard-buffer-event
  • vkbd-text-keyboard-translate-event
  • vkbd-text-keyboard-key-object-at-event
  • vkbd-event-to-keyboard
  • vkbd-event-to-keyboard-buffer
  • vkbd-event-in-keyboard-buffer-p

他にも(buffer-list)で全キーボードオブジェクトを列挙している箇所も問題。

バッファに vkbd-keyboard-buffer-keyboard-list という変数を追加してはどうか。もしくは現在の vkbd-keyboard-buffer-keyboard にリストを指定出来るようにするか。もしくはグローバル変数に全キーボードオブジェクトをリストアップするか。全キーボードオブジェクトを巡回する必要がある処理はあるので、素直にグローバルにした方が良いかもしれない。

最低限全部のキーボードオブジェクトを列挙する関数は用意した方が良い。 vkbd-all-keyboard-objectsを追加。

最大の問題は、現状私はこの機能を必要としていないということ。

key-idは要らない

keyboardが全てのキーを保持していれば、オブジェクトのeqで判別できる。 とは言え、それが本当に良いのだろうか。 Hacker’s KeyboardのようにFnキーを押したらレイアウト毎全部書き換わるような場合、どのみちキーオブジェクトも全部更新しなければならない。 それなら全更新関数でキーオブジェクト毎作り直した方が早いのではないか。 キーオブジェクトをキーボードオブジェクトが一貫して管理する必要はあるのだろうか。 キーオブジェクトはスタイルの物なのか、それともキーボードオブジェクトの物なのか。

Someday

キーの高さはline-heightで設定する方が良い

1行にキーが一つしか無い場合、column-separatorは挿入されないから。

コミット 7dc23fe で実際にやってみたが、フレームで表示したときになぜかクリック/タッチ位置がずれる問題があった。下の行になるほどクリック/タッチした位置よりもより下が反応してしまう。10x7においてRETを押しても何も反応せず、BSを押すとRETが反応するといった具合。ウィンドウ表示時には問題が起きない。line-heightテキストプロパティを使ったときに何か内部的な計算がおかしいのだと思う。

コミットをリバートして保留とする。

キーボードバッファでスクロール出来てしまうのを抑制する

今現在はほぼ出来なくなっているけれど、それでもまだ M-: (scroll-up) とかやれば出来る。スクロールバーが出ているのなら下ボタンを押せばスクロール出来てしまう。また、水平スクロールは制限されていないから C-x <C-x > はできてしまう。極端な話、 M-: (set-window-start (vkbd-keyboard-container-window vkbd-global-keyboard) (point-max)) とかやれば末尾にスクロールしてキーボードを消してしまえる。

スクロール位置を固定して変えさせないなんて事できるの? 調べた限り直接的に禁止する方法は無い。

検出して戻す? 何かをトリガーにして検出して戻すくらいしか出来ない。window-scroll-functionsは事後的に通知が来る。そこで直接修正するのはやめた方が良さそうなので、タイマーを仕掛けて何とかするくらいか。post-command-hookで毎回チェックするのは悪影響が大きいのでやめた方が良い。

ポイントの移動を禁止するという方法もあり得るが、禁止する方法もそれほど良い方法があるわけでは無い。

現実的には、キーボードバッファの生成・全体更新後に確実にポイントを(point-min)へ移動するくらいだろう。

通常のウィンドウで表示できるようにする

正直必要ないと思う。 やるならcontainer-typeにnormal-windowを追加するか、side-window-sideとしてno-sideみたいなのを追加するか、もしくは独立したオプションにするか。

少なくともread-event系では物理キーに対する修飾キーロックできる

read-eventを置き換えているので、全てのイベントに対して常時修飾キーを適用出来るはず。 ただ、read-event系は通常の入力には使われないので、あまり旨味がない気もする。

それと他のキーボートの入力に修飾キーを適用する機能は問題もある。例えば複数のvkbd-keyboardを作成したらどうなるだろうか。第一にどのキーボードの修飾キー状態を採用すべきだろうか。第二に他のvkbd-keyboardの入力に対して別のvkbd-keyboardの修飾キー状態を適用すべきだろうか(そもそもできるのだろうか)。基本的にvkbd-keyboard間で修飾キーを適用する必要は全く無い。そして物理キーボードに適用する修飾キーは、どれか一つのもので十分だし、全ての修飾キーの和集合であればより親切だろう。

その上で、正直それほどやる意味があるとは思えない。

フレーム使用時でもキーリピートする

フォーカスが当たっていないフレーム上でmouse upイベントが発生しないのでどうしようもないと思う(touchscreen-endがどうなのかは未検証)。

修飾キーのロックが2回目以降の非仮想キーに対して適用されない

修飾キーと通常のキー入力を組み合わせられるようにするで解決できなかった問題。

modifer-bar-modeと同じ原理で処理しているが、修飾キー適用は read-event => 修飾キー適用 => それを(input-decode-mapに設定されている関数が)返す という一連の流れが必要なので、返してしまったらもうread-eventで後続のキーを取得することが出来ない。

全てのイベントをトラップできれば良いのだけど、そのような目的で使える良い仕組みは見当たらない。文字部分だけならinput-method-functionが使えるかもしれないけど。

C-gはシグナルを発するべき?

C-gで脱出できなくて困ることがある。

少なくともread-event中に物理キーでC-gを押すと中断されるのに対して、vkbdでC-gを押しても中断されず7が返ってくる。

;; VKBDからC-gを押したとき
(read-event "Prompt: ") ;; => 7
(read-char "Prompt: ") ;; => 7
(read-char-exclusive "Prompt: ") ;; => 7
(read-quoted-char "Prompt: ") ;; => 7
(read-key-sequence "Prompt: ") ;; => "\^G"
(read-key "Prompt: ") ;; => 7
(read-minibuffer "Prompt: ") ;; Quit
;; 物理キーボードからC-gを押したとき
(read-event "Prompt: ") ;; Quit
(read-char "Prompt: ") ;; Quit
(read-char-exclusive "Prompt: ") ;; Quit
(read-quoted-char "Prompt: ") ;; => 7  (inhibit-quitが使われているから)
(read-key-sequence "Prompt: ") ;; Quit
(read-key-sequence-vector "Prompt: ") ;; Quit
(read-key "Prompt: ") ;; => 7 (echo-keystrokesが使われているから)
(read-minibuffer "Prompt: ") ;; Quit
(let ((echo-keystrokes 0))
  (read-key-sequence "Prompt: ")) ;; => "\^G"
(let ((inhibit-quit t))
  (read-key-sequence "Prompt: ")) ;; => "\^G"
(let ((echo-keystrokes 0))
  (read-event "Prompt: ")) ;; Quit
(let ((inhibit-quit t))
  (read-event "Prompt: ")) ;; => 7

先に仕様を理解する必要がある。

Quitting (日本語訳)

変数quit-flagをtにするとquitできるらしい。

(setq quit-flag t)

マニュアルにはread-key-sequenceはQuitを抑制するようなことが書いてあるが、実際にはそうなっていない。明示的なinhibit-quitが必要。

echo-keystrokesが0だとquitが起きない。keyboard.cのecho_now関数でquit_throw_to_read_charを呼んでいるから?

(let ((echo-keystrokes 0))
  (read-key-sequence "Prompt: ")) ;; => "\^G"

テスト:

コード物理C-gvkbd以前read-eventだけ対応後
(read-event “Prompt: “)Quit7Quit
(read-char “Prompt: “)Quit7Quit
(read-char-exclusive “Prompt: “)Quit7Quit
(let ((echo-keystrokes 0)) (read-event “Prompt: “))Quit7Quit
(let ((inhibit-quit t)) (read-event “Prompt: “))777
(read-quoted-char “Prompt: “)7 (inhibit-quitが使われているから)77
(read-key-sequence “Prompt: “)Quit”\^G””\^G”×
(read-key-sequence-vector “Prompt: “)Quit[7][7]×
(read-key “Prompt: “)7 (echo-keystrokesが使われているから)77
(read-minibuffer “Prompt: “)QuitQuitQuit
(let ((echo-keystrokes 0)) (read-key-sequence “Prompt: “))”\^G””\^G””\^G”
(let ((inhibit-quit t)) (read-key-sequence “Prompt: “))”\^G””\^G””\^G”

read-eventだけ対応した。vkbd-emulate-quit変数の所を見よ。

read-key-sequenceの方は難しすぎる。ドキュメントを素直に読めば通常のread-key-sequenceはQuitしないべきだと思う。でも現実はQuitする。でもecho-keystrokesを0にするとQuitが起きないのであった。また、read-minibufferでは変換後にquitすると正しく動作しない。ミニバッファのコマンドループの中なのだから、素直に7を返してキーマップのコマンドを実行させるべきなのだろう。それらの判定のための関数や変数がよくわからないし、分かったところで他にも別の状況があるかもしれない。この辺りが手の打ち所だと思う。

フレームパラメータalphaを指定してみる(半透明にする)

狭い画面で使うので後ろが見えた方が良い。 うーん、Windowsでは効くけどAndroidでは効かないみたい。 それならいいや。そのうち対応したら。

タイトルバーにヘッダーラインを使う

Child Frames (GNU Emacs Lisp Reference Manual)にも書いてあるとおりheader-lineをタイトルバー代わりに使える。

  • フレームパラメータのdrag-with-header-lineをt
  • バッファーローカル変数のheader-line-formatに閉じるボタンとキャプション
  • 閉じるボタンのイベントは[header-line mouse-1]等に登録する

これでマウスで移動することも閉じることもできる。

しかしAndroidでタッチによる移動は出来なかった。 実装されていない? それじゃ意味が無いので元に戻した。

ポイント(カーソル)の位置を自動的に避けてくれるモード

もはやフレームは使わないので要らないかな。

(サイズ調整)フレームサイズをドラッグで変えられるようにする

user-dataのスケールをドラッグで調整できれば良いわけだが、面倒。 メニューで許して。

(サイズ調整)自動的に小さくなったり大きくなってくれたりするモード

もはやフレームは使わないので要らないかな。

(サイズ調整)押したらサイズが大きくなるというのはどうだろう

で、正確なキーの位置へ移動してから離すと入力される。 領域の小ささを多少カバーできるのではないか。 まぁ、無茶かな。

シフト用のキー文字を別faceにする

シフトキーを押したときに入力される文字を表示するテキストの色を変えたい場合に有用。 現在はvkbd-text-keyが使われているが、これをvkbd-text-key-for-shiftとかなんとかにする。しかしpressedやlockedのことを考えると気が進まない。 正直私は要らないと思う。

Finished

子フレームにキーボードを表示する

キーマップ変換の仕組みを理解する

マニュアルや既存ソースコードの例

Translation Keymaps (GNU Emacs Lisp Reference Manual)に説明がある。そこにはlocal-function-key-mapを使用してC-c hで後続のイベントにH-を付加する例がある。

elisp-function:modifier-bar-modeはelisp-variable:input-decode-mapを使用している。elisp-function:tool-bar-event-apply-control-modifier関数等を通じてelisp-function:modifier-bar-button関数が呼ばれる。

touch-screen.elはfunction-key-mapを使用している。elisp-function:touch-screen-translate-touch関数が呼ばれる。

とりわけelisp-function:touch-screen-translate-touch関数は大いに参考になる。呼ばれた時点でのイベントをelisp-variable:current-key-remap-sequence変数から取得している。

単純な変換関数を作って挙動を探る

ひとまず何も変換しない変換関数を作ってログを出力させてみる。

(defun my-test-translate-event (prompt)
  (message "Translate curr=%s prompt=%s" current-key-remap-sequence prompt)
  current-key-remap-sequence
  ;; nil
  ;; []
  ;; [?a]
  ;; [?a ?b]
  )

(define-key input-decode-map [down-mouse-1] #'my-test-translate-event)
;; (define-key input-decode-map [down-mouse-1] nil t)

上のコードを評価した後、このバッファ内をマウスで押してみると次のようなメッセージが得られた。

Translate curr=[(down-mouse-1 (#<window 947 on todo.org<el-vkbd>> 1186 (508 . 256) 87241859 nil 1186 (63 . 12) nil (508 . 16) (8 . 20)))] prompt=nil

それと同時にポイント(テキストカーソル)は押した位置に移動する。

my-test-translate-eventの戻り値を変えてみる。試しにnilを返すように変更してもそれは変わらない。しかし [] を返すようにしたら押した時点では移動しなかった(離したときに移動する)。

戻り値挙動
current-key-remap-sequence押し下げ時移動
nil押し下げ時移動
[]押し下げ時移動しない
[?a]aが挿入
[?a ?b]bが挿入(複数は返せない!)

入力関数はどのような結果を返すだろうか。my-test-translate-eventが [?a] を返すようにした上で試してみる。

(read-event "Prompt: ") ;; => (down-mouse-1 (#<window 947 on todo.org<el-vkbd>> 2044 (249 . 473) 88984375 nil 2044 (31 . 23) nil (57 . 13) (8 . 20)))
(read-char "Prompt: ") ;; ERROR: (error "Non-character input-event")
(read-char-choice "Prompt: " '(?a ?b)) ;; => 97
(read-key "Prompt: ") ;; 押し下げ時に返ってこず、解放時にmouse-1
(read-key-sequence "Prompt: ") ;; => "a"

疑問点・アイデア:

  • 複数の入力を返せないなら、イベントを新たに追加してしまってはどうか。
  • read-eventにも効かせたいならinput-method-functionを使ってはどうか。
  • バッファローカルなlocal-function-key-mapで十分かもしれない。
  • フレーム跨ぎ(変換前と後のフレームが異なる)問題をどうするか。

イベント追加技

わざわざ変換するのではなく、直接elisp-variable:unread-command-eventsに追加してしまえば良い。

(defun my-test-translate-event (prompt)
  (message "Translate curr=%s prompt=%s" current-key-remap-sequence prompt)
  (setq unread-command-events (list ?a ?b))
  [])

こうすると実際に押し下げ時点でaとbが両方バッファに挿入される。

input-method-function

elisp-function:read-eventの説明を読むとinput-methodについて書かれている。

Reading Input (GNU Emacs Lisp Reference Manual)を見るとread-eventが値を返す前にelisp-variable:input-method-functionが処理を行うことが分かる。

Invoking the Input Method (GNU Emacs Lisp Reference Manual)

22.8.4 入力メソッドの呼び出し

イベント読み取り関数は、現在の入力メソッドがある場合はそれを呼び出します (「入力メソッド」を参照)。input-method-function の値が nil 以外の場合、関数である必要があります。read-event は、修飾ビットのない印刷文字 (SPC を含む) を読み取ると、その関数を呼び出して、その文字を引数として渡します。

(defun my-test-input-method-func (char)
  (message "char=%s" char)
  (list char))
(setq-local input-method-function #'my-test-input-method-func)
;; (setq-local input-method-function 'list)

残念ながらマウスイベント発生時は呼ばれない。 修飾ビットのない印刷文字 (SPC を含む) を読み取ると と書かれている通り。残念。

local-function-key-mapを使う?

local-function-key-mapはマイナーモード、ローカルマップ、グローバルマップ等で使われなかったイベントに対して効く物で、バッファローカルという意味ではない。なので使う意味が無い。

フレーム跨ぎ問題

子フレームで仮想キーボードを表示する場合、マウスイベントが発生するフレームと入力反映させるフレームが異なるという問題がある。

次のコードはマウスイベント発生位置にあるテキストプロパティ my-test-input の値をunread-command-eventsに追加する。

(defun my-test-translate-event (prompt)
  (message "Translate curr=%s prompt=%s" current-key-remap-sequence prompt)
  (when (= (length current-key-remap-sequence) 1)
    (let ((event (aref current-key-remap-sequence 0)))
      (when (mouse-event-p event)
        (let* ((posn (event-start event))
               (window (posn-window posn))
               (point (posn-point posn)))
          (with-current-buffer (window-buffer window)
            (when (< point (point-max))
              (let ((input (get-text-property point 'my-test-input)))
                (when input
                  (setq unread-command-events
                        (append unread-command-events
                                input
                                nil))
                  [])))))))))
(define-key input-decode-map [down-mouse-1] #'my-test-translate-event)
;; (define-key input-decode-map [down-mouse-1] nil t)

つまりそのようなテキストプロパティ付きのテキストをバッファに挿入すると、入力を行うボタンとして機能する。

(defun my-test-create-input-button ()
  (insert (propertize "[Input a b]" 'my-test-input '(?a ?b))))
;; (my-test-create-input-button) [Input a b]

my-test-create-input-buttonを評価するとabを入力するボタンがバッファに挿入される。

現在のバッファにあるボタンを押した場合はバッファの遷移が無いので問題なくabが入力される。

しかし現在のバッファ以外にあるボタンを押した場合はどうなるだろうか。

別のウィンドウにあるボタンを押した場合、まだウィンドウが遷移する前なので元のウィンドウのバッファにabが入力される。

別のフレームにあるボタンを押した場合、フレームが遷移してからマウスイベントが発生するため別のフレームで現在選択中のウィンドウにあるバッファ(ボタンがあるバッファではない)にabが入力される。

別フレームをマウスを押してもフレームの遷移が発生しないようにする方法はおそらくない。

一番手っ取り早いのはselect-frame(もしくはselect-frame-set-input-focus)で戻してしまうこと。

(defvar my-test-main-frame (selected-frame)) ;; 仮の戻し先

(defun my-test-translate-event (prompt)
  (message "Translate curr=%s prompt=%s" current-key-remap-sequence prompt)
  (when (= (length current-key-remap-sequence) 1)
    (let ((event (aref current-key-remap-sequence 0)))
      (when (mouse-event-p event)
        (let* ((posn (event-start event))
               (window (posn-window posn))
               (point (posn-point posn)))
          (with-current-buffer (window-buffer window)
            (when (< point (point-max))
              (let ((input (get-text-property point 'my-test-input)))
                (when input
                  (select-frame my-test-main-frame)
                  (setq unread-command-events
                        (append unread-command-events
                                input
                                nil))
                  [])))))))))

子フレームであれば親フレームに戻してしまえば良い。

結論

  • フィルタ関数は自分の処理すべきイベントが来たときに、unread-command-eventsにキーを追加して[]を返すべし。そうでなければnilを返せば良い。
  • 子フレームを選択させたくなければselect-frameやselect-frame-set-input-focusを使って無理矢理親フレームを選択してしまえば良い。

Emacsの(マウス・タッチ)イベントからキーを入力する仕組みを作る

子フレームをドラッグで移動できるようにする

子フレームを閉じるボタンを追加する

Androidでのドラッグ移動がカクつくのを直す

タッチイベント時にタッチでフレームを移動すると、フレームが振動して発散しウィンドウ範囲外に飛び出して見えなくなってしまうことがある。 フレーム移動直後におかしな座標を持つtouchscreen-updateイベントが発生することが原因。 touchscreen-updateイベントは指を動かさなくてもフレームを移動しただけで発生する。 おそらくフレーム位置を変更した直後に変更前のタッチ座標を元にイベントが送られてきているのではないか。 いずれにせよEmacs Lispからではそれが正しい座標なのか不正な座標なのか判断できない。 フレーム移動直後のイベントは信用できないので、移動した時刻を記録しておいて、一定時間(vkbd-frame-move-debounce-time変数)経った後でなければ処理しないようにした。

シフトキーで入力する文字を表示できるようにする

レイアウトとスタイルを分離する

キーを半分ずらせるようにする

JPキーボードレイアウトを追加する

USキーボードレイアウトを追加する

シフトキー併用で記号を入力できるようにする

キーを表すのにkey-typeではなくkey-dataという構造を使うようにする。 key-dataは一つのキーを表し、key-typeは実際に入力されるものを表す。

よりコンパクトなレイアウトを追加する

  • vkbd-layout-10x7 : 記号の1文字入力にこだわらないレイアウト
  • vkbd-layout-10x6 : ほとんどの記号にShiftが必要、InsとDelを削除

グローバルキーボードのopen・closeコマンドを作る

M-x vkbd-open-global-keyboardでキーボードを開き、M-x vkbd-close-global-keyboardで閉じるというだけのものがあれば便利。

ツールバーにグローバルキーボードトグルボタンを追加する

row-separatorをもう少し狭くする

狭くならない原因はline-spacingだった。frame-parameterのline-spacingパラメータは無視されるみたい。私はデフォルト値(グローバル値)を4にしているから、それが使われて行間が大きくなっていた。 vkbd-keyboard-buffer-line-spacing変数を追加。

シフト時キーがある場合のraise値をカスタマイズできるようにする

line-spacingを小さくしたら下に寄りがちになってしまった。 シフトキーが無いときも調整できた方が良いかも。

vkbd-text-key-raise変数を追加。

ついでにvkbd-text-keyboard-key-data-string関数を全体的に修正する。

11x7レイアウトを追加する

Pixel7での私のフォント設定だと、あと1列分くらいは入る。 もう少し記号をシフト無しで入力したい。 特に ( と ) はLispのコードを書くなら頻出するので、必ずシフト無しにしたい。 また、diredで上のディレクトリに移動するのに ^ を使っているので、これもシフト無しに為たい。 () [] <> は一貫して同じ列に配置。 +-*=といった算術に使う物も全てシフトキー無しで近くに配置。 _もプログラミング言語によってはよく使うのでシフト無し。 !#$%はUSでもJPでも数字キーの同じ場所にある。これらはそのままの並びで上の方に並べる。 全体的に、右上のShiftの近くに寄せる。

2ストロークキーが入力できない問題を修正する

例えば C-c C- と複数ストロークのキーを押したときに、<mouse-1> undefinedと表示される。

二つ目のC-を押したときになぜか down-mouse-1 に対する変換関数は呼ばれないのにその後の mouse-1 は呼ばれる。そして C-c mouse-1 を実行しようとして、そんな組み合わせはキーマップにないのでエラーになる。

modifier-bar-modeはなぜかこれが問題なく動作する。modifier-barでC-を押した後キーでcを押し、その後にmodifier-barでC-を押すと C-c tool-bar control- と表示されるが、その時にちゃんと変換関数は呼ばれており、続いてcなどと押せば[tool-bar control-]+?cが[3]になってC-c C-cを押したことになる。

(read-key-sequence “Prompt: “) のレベルでそのような挙動になる。

(defun my-mouse-translator (prompt)
  (message "Enter translator %s" current-key-remap-sequence)
  [?q])

;; (define-key input-decode-map (kbd "<down-mouse-1>") nil t)
(define-key input-decode-map (kbd "<down-mouse-1>") #'my-mouse-translator)

上を評価してからマウスボタンを押すと押し下げた瞬間にメッセージが表示される。

C-cをキーボードから直接した後、マウスボタンを押し下げると「C-c (C-h for help)」または「C-c down-mouse-1-(C-h for help)」と表示される(C-cを押してからメッセージが出てからマウスを押すと後者になるようだ)。そしてマウスボタンを解放すると C-c <mouse-1> is undefinedと表示される。

;; (define-key input-decode-map (kbd "<down-mouse-1>") nil t)
(define-key input-decode-map (kbd "<down-mouse-1>") #'my-mouse-translator)
;; (define-key input-decode-map (kbd "C-c <down-mouse-1>") nil t)
(define-key input-decode-map (kbd "C-c <down-mouse-1>") #'my-mouse-translator)

としても無駄。キーでC-cを押してからマウスを押し下げてもC-c <down-mouse-1>の変換関数は呼ばれない。これはC-x、ESCでも同じだった。

;; (define-key input-decode-map (kbd "<mouse-1>") nil t)
(define-key input-decode-map (kbd "<mouse-1>") #'my-mouse-translator)

しかしこれは呼ばれる。C-c の後に押し下げても何も呼ばれないのは同じ。しかしその後解放するとmouse-1に対する変換関数は呼ばれる。ちなみにこれはmouse-1をdrag-mouse-1にしても同じ。解放イベントだけ変換される。down-mouse-1がどこへ消えたのか分からない。

まとめると、C-cやC-x、ESCのようなプレフィックスの直後ではdown-mouse-1は変換されず、その後のmouse-1やdrag-mouse-1は変換される。

ただしmouse-2はdown-mouse-2だけ登録してもプレフィックス無しでも変換関数は呼ばれない。down-mouse-3は大丈夫。

もう何が何だか分からない。

今できること:

  • down-mouse-1とmouse-1の両方を監視する。
  • down-mouse-1が来たらmouse-1かdrag-mouse-1を待ってから終了。
  • いきなりmouse-1が来たらすぐに終了。

↑はダメだった。down-mouse-1の変換中にread-eventを呼ぶとdown-mouse-1の変換が再度呼ばれて無限ループに陥った。

結局最後は unread-command-events を使うのを止めた。つまり変換結果を完璧に処理すれば良い。

  • キーボードバッファ内
    • キーがある場所
      • イベントに変換できた [ イベント … ]
      • イベントに変換できなかった(修飾キー等) [ ] (握りつぶす)
    • キーが無い場所 nil (閉じるボタンを押したりドラッグによるフレーム移動が出来るように)
  • キーボードバッファ外 nil (他での利用を邪魔しない)

10x7レイアウトを修正する

(と)をシフト無しで押したい! InsとEndは諦める。HomeとEndは左に移動して残す。 できるだけ数字とアルファベット部分に記号を混ぜない。 数字の所にあった記号を最上段のシフトに移動する。並び順はUSに近づける。 ただし`’”の並びは特殊なのでそこは仕方ない。 上矢印の左右に@と?を移動する。 ^がシフトになってしまうのは諦める。

SPCは何も表示しない方が良いかもしれない

やっぱり真っ白なことを判別して押しているみたいなので、文字があるとすぐに分からない。

アルファベット部分に記号をシフトで入れるのを止める

当たり前だけどそれだと大文字が打てなくなる。なんてこった。全く気が付いていなかった! 10x6レイアウトは削除する。維持不可能なので。

キー押し下げ中にキーの色を変更する

修飾キー押し下げ状態時にキーの色を変更する

普通に押し下げの色にする。これはロックとは違う。

シフトキーの状態で大文字小文字の表示を切り替える

更新メカニズムを整備する。

key-dataを何か他の用語に変える。

key-type、key-data、key-objectと似たようなのが複数あって分かりづらい。
key-type
Emacsのキーシーケンス(イベントリスト)に名前を付けた物。
key-data
キーボードの中の一つのキーがどんなものなのか、静的な性質を記述する。シフトキーの有無によって複数のkey-typeが指定できる。任意の指定を追加できるプロパティリストも持てる(現状では:wでキーの幅が指定出来る)。
key-object
キーの動的な側面を含めて一つのオブジェクトとして操作できるようにする。

key-descriptorが一番しっくりくる。次いでkey-spec。key-attributeなんてのも。 vkbd-key-descriptor-*という関数名は長すぎる。key-descくらいでも悪くない。 いや、でもdescriptorという用語もそれほど分かりやすいものでは無いし、specの方が静的なイメージは上かも。予め指定されたキーの性質、という。ここは素直にspecにしよう。

2回修飾キーを押したときに修飾キーをロックし3回目で全て解除する

CANCELLED Caps Lockを追加する

2回押しでロックできるようになったので不要。

フレームの移動範囲を限定する

タイトルバーが半分……いや、1/3残っていれば良い。

vkbd-keyboard-frame-keep-visible-margins変数を追加した。

ユーザーデータ(永続データ)の保存機構を作る

フレームの位置、レイアウトが想定されている。

保存する場所はファイル、変数、カスタマイズ変数、関数呼び出しから選べるようにしたい。項目毎に個別に。

  • :user-data-store (<user-data-store-function> . <user-data-store-parameters>) | nil
  • <user-data-store-function> : function (action parameters user-data-alist):void
  • default-user-data-store
    • :file <filename>
    • :file-key <symbol>
    • :item-destination : ( (<item-name> . <destination> ).. )
    • :default-destination : <destination>
  • <destination> :
    • (file <filename> <file-key>)
    • (variable <variable-name>)
    • (custom-variable <variable-name>)
    • (function <function>)

うーん複雑だ。こんな複雑な物は必要ないんじゃないの? まぁ、柔軟性はかなりあるけど。作っちゃったからいいや。詳細はvkbd.elのvkbd-data-storage-*の周辺を参照。

デフォルトの保存先 (locate-user-emacs-file “vkbd”) 。もちろんいくらでも保存先を変えられる。ファイル以外にもカスタマイズ変数に保存したり(customファイルが保存できるならセッションを跨いでも有効)、単に普通の変数に保存するだけにもできる(セッション限り有効)。

optionsには:user-data-storageを追加。それを元にユーザーデータを読み書きする。

keyboardオブジェクトには:user-dataプロパティを持たせた。

frame-positionを保存できるようにした。

フレームの位置を記憶する・オプションでも指定出来るようにする

optionsに:user-data-storageが指定されている場合は、その保存先に保存するようにした。

optionsの:frame-positionの指定を優先。

余計なフレームサイズ変更を無くす

最初のフレーム作成時には位置とサイズのフレームパラメータは指定しないようにした。 本当は最初から正確なものを指定出来れば良いのだけど、なかなかそうもいかないので。 サイズ計測は実際のフレーム・ウィンドウに割り当てて表示しないと正確さは保証されない。

タッチでキーを押したときに2回押されることがあるのを直す

read-eventループでtouchscreen-updateを考慮していないせい。 タッチして動かすと離したことになる。そして実際に離すともう一回キーが押される。

この現象を利用すると1回のタッチ→移動→解放で2文字打てるというメリットもある。 とはいえ同じキーで移動して2回打たれるのは本意では無い。

有用な使い方については後に回す。 押しながら移動して離した場合に何か有用な働きをさせたい

ボタンの押し下げ色を黒にする

白だと分かりづらい。ロック色も派手すぎるので黒にする。

修飾キーと通常のキー入力を組み合わせられるようにする

現在は修飾キーを押しても、その次にIMEまたは物理キーボードから文字を入力してもその文字は修飾されない。修飾キー押し下げ状態はvkbd-keyboardの中にしかないのだから当たり前。しかしmodifier-bar-modeはそれができる。修飾キーが押されたときにread-eventループに入り、次のキーまで取得して、そのキーを修飾したものを変換結果として返すから。

これを実現することで、IMEと連携するのが前提なレイアウトを作成できるようになる。

ロック機能で複数の非仮想キー入力に修飾キーを適用し続けるのは困難。1回read-eventしたキーに修飾キーを適用したらinput-decode-map関数は結果を返さなければならないし、その後の非仮想キーをトラップする良い方法はおそらく無い。input-decode-mapの全部のキーに関数を仕込むのはさすがに嫌。input-method-functionを使えば文字に関しては何とかなるかもしれない(マウスイベントにS-とかC-とかを付け続けるのは無理)。

既存のIMEと併用することを前提としたコンパクトレイアウトを追加する

BS、RET、SPCは要らない! といってもアルファベットを入れるならあまり小さくならないなぁ。 ATOKでもモードさえ切り替えればアルファベットや記号を入力できるわけだし(確定操作が必要だけど)、どうやっても入力できない文字に限定したレイアウトがあれば良いかも。

修飾キーとの兼ね合いを考える必要がある。 現状では、C-を押した後IMEのアルファベットを押してもそれにC-は適応されない。これを実現するにはC-が押されて解放された後にさらに次のキー入力までread-eventで待つ必要がある(modifier-bar-modeはこれをやっている)。

vkbd-layout-special-keys-onlyを追加した。

text-conversion-styleを制御する?

Androidで他のキーボード(IM)と併用したときに修飾キーが効かないことがある。

原因は text-conversion-style (もしくはoverriding-text-conversion-style)。 バッファのtext-conversionが有効(tもしくはaction)だとIMからの入力は直接バッファに挿入され、その挿入の詳細はtext-conversion-*変数に格納された上でtext-conversionイベントが発生する。input-decode-mapが介在する余地はない。

修飾キーを適用するキーを入力するread-eventの前後でset-text-conversion-styleを呼び出してみたが、ほとんどの場合うまくいかなかった。

set-text-conversion-styleが効かない原因はフレームの選択と入力フォーカスにあった。

実はAndroidのEmacsでは子フレームをタッチスクリーンでタップしても子フレームに入力フォーカスが移動しない。選択フレームは変わるようなのだが、その状態で何かを入力すると親フレームに文字が入力される。select-frame-set-input-focusで明示的に入力フォーカスを与えてやると、その子フレームで入力できるようになる。タッチイベントが発生する前にswitch-frameイベントが発生し、そのときのselected-frameは親フレームで、イベント内の移動先フレームは子フレームを指す。続くtouchscreen-beginイベントでの選択フレームとカレントバッファは子フレームとkeyboard-bufferとなる。しかしその時の入力フォーカスはやはり親フレームのままなのである。

その状態でtouchscreen-beginが起きたときに次のキーをread-eventで読み込むとどうなるか。親フレームでIMが有効だと親フレームの選択ウィンドウ内のバッファにテキストが直接挿入されるのである。

read-eventの前に(set-text-conversion-style nil)を呼び出しても、virtual keyboardバッファのtext-conversion-styleがnilになるだけ。これは元々nilなので何の変化もない。入力フォーカスは親フレームにあり、そこにあるバッファのtext-conversion-styleがtのままであれば、やはり親フレームのバッファに直接テキストが挿入されてしまう。

(window-buffer (selected-window parent-frame))をカレントバッファにして(set-text-conversion-style nil)すればうまく行った。

よく見たらmodifier-bar-modeにも似たようなコードが入っていた。

メニューボタンを配置する

想定されるメニュー項目:
  • レイアウト切り替え
  • ウィンドウ・子フレーム・独立フレーム切り替え
  • ボトムバーの有無

タッチ操作でレイアウトを変更できるようにしたい

先にメニューボタンが必要。

redirect-frame-focusを使うかどうか

使った方が安定する気がする。

vkbd-make-frameの後(vkbd-make-keyboard-frame内)で設定する。

親フレームへのフォーカス移動は次の場所で行う:

  • ドラッグ移動の後
  • メニュー表示の後
  • 引き続きキー変換終了時

CANCELLED フレームの入力フォーカスを制御する

  • できるだけキーにフォーカスして、フォーカスが外れたら閉じるモード
  • できるだけ親フレームにフォーカスするモード
  • touchscreen-updateやendでフォーカスが戻ってしまっている?

基本的にできるだけ常に親フレームにフォーカスを当てた方が安定して動作するので、これは難しいと思う。

  • キーの変換後に、そのキーが狙った場所に入力されるには親フレームが選択されている必要がある。
  • text-conversionはフォーカスが当たっているフレームのウィンドウがターゲットになる。
  • Androidではタップによるフォーカス移動はデフォルトではできない。(選択はできるがフォーカスが移動しない)

タイトルバー以外でも移動できるのはやめよう

キーの間の隙間で移動できてしまうのはさすがに良くないと思う。

ついでに移動できるのは frame-parameter undecoratedが非nilのとき限定にした。container-type = window を導入したときに、分割ウィンドウ(サイドバー)表示なのにタイトルバー部分のドラッグでフレーム全体が移動できてしまっていたので。

フレームの種類を変更できるようにする

Keyboard Containerという概念を導入する。

コンテナタイプ:

  • child-frame
  • independent-frame
  • window

FrameにせよWindowにせよEmacsの概念と被るのでContainerという呼び方にした。

ただし、ユーザー向けには(メニュー等に)Container Typeと書いても伝わらないと思うので、Frame Typeと書くことにする。

内部的にもframe-typeと呼ぶことも考えたが、vkbd-make-keyboard-frameという関数はすでにあるので、vkbd-make-keyboard-containerとした。

(子フレームではなく)独立フレームで使えるようにする

(フレームではなく)ウィンドウで表示できるようにする

正直分割ウィンドウの下半分もしくは上半分に表示してくれた方が使いやすいのでは? また、フォーカススイッチによって生じる問題も低減できるはず。

フレームやウィンドウを閉じたときにバッファも消えるようにする

たまたまフォーカスが当たっているときにC-x 5 0で閉じるとバッファが残る。

バッファを削除したときもおそらく問題があると思う。ウィンドウやフレームは自動的に消えるけど、内部的にはキーボードオブジェクトはまだ生きている。global keyboardのトグルがおかしくなる(?)。 バッファを削除したときは専用ウィンドウは一緒に消える。逆はない。

ウィンドウやフレームが削除されたことを知るには、 window-configuration-change-hook や (after-)delete-frame-functions にグローバルフックを登録する。ローカルフックでは削除を完全に捉えきれないし、window-configuration-change-hookだけでは例えウィンドウ表示(container-type=window)時でもフレームが削除されたときに一緒にウィンドウが削除されたことを検出できない。

(buffer-list)で全バッファを見て、ウィンドウが生きているか確認するくらいしか手は無さそう。

container-type切り替え中の削除でもフックが呼ばれるので、その時にkeyboardを削除しないように注意する必要がある。

windowをどのサイドに表示するかを選べるようにする

container-type=window時にだけメニューに表示する。

Emacsのイベント読み取り関数の構造を詳しく調べる

Reading One Event (GNU Emacs Lisp Reference Manual)

コアはlread.cで定義されているread-event、read-char、read-char-exclusiveの三つ。そこからread_filtered_eventが呼ばれ(呼ぶ関数はその三つだけ)、さらにkeyboard.cのread_charに繋がっている。

  • lread.c
    • (lread.c)elisp-function:read-event
      • (minibuf.c)barf_if_interaction_inhibited read-eventで一番最初に呼ばれる関数。
      • (lread.c)read_filtered_event(0,0,0,!inherit_input_method,seconds)(lread.c)
    • (lread.c)elisp-function:read-char
      • (minibuf.c)barf_if_interaction_inhibited
      • (lread.c)read_filtered_event(1,1,1,!inherit_input_method,seconds)(lread.c)
      • (character.c)char_resolve_modifier_mask
    • (lread.c)elisp-function:read-char-exclusive
      • (minibuf.c)barf_if_interaction_inhibited
      • (lread.c)read_filtered_event(1,1,0,!inherit_input_method,seconds)(lread.c)
      • (character.c)char_resolve_modifier_mask
    • (lread.c)read_filtered_event(bool no_switch_frame, bool ascii_required, bool error_nonascii, bool input_method, Lisp_Object seconds)
      • (keyboard.c)read_char

細かい関数:

(minibuf.c)barf_if_interaction_inhibited
elisp-variable:inhibit-interactionが非nilの時にシグナルを出すだけ。
(character.c)char_resolve_modifier_mask
read-char系で戻り値に適用される。(elisp-function:char-resolve-modifiersと同等)

read_filtered_event系の次に重要なのが read_key_sequence 系関数。

  • (keyboard.c)elisp-function:read-key-sequence => read_key_sequence_vs => read_key_sequence => read_char
  • (keyboard.c)elisp-function:read-key-sequence-vector => read_key_sequence_vs => read_key_sequence => read_char

Emacsにおけるキー入力の大半はこれが受け持っている。興味深いことにこれはread_filtered_eventを使用せず直接read_charを呼び出す。

input-decode-mapの適用はread_key_sequenceで行われ、read_filtered_eventでは行われない。これが現在のvkbdが一部の状況で効かない最大の原因(他にもフレームスイッチ問題などもあるが)。

その他の大半はread_key_sequenceに行き着く。

  • (subr.el)elisp-function:read-key => read-key-sequence-vector
  • (subr.el)elisp-function:read-char-choice => read-key, read-from-minibuffer
  • (minibuf.c)read-from-minibuffer => read_minibuf => (keyboard.c)recursive_edit_1 => command_loop => command_loop_2 => command_loop_1 => read_key_sequence
  • (minibuf.c)completing-read

まれにread-eventやread-char、read-char-exclusiveを呼び出す関数もある。

■主な入力関数とその依存先、現状でのvkbd使用可否(input-decode-mapの効果)

ファイル名関数名vkbd使用可否依存先
lread.celisp-function:read-event×read_filtered_event => read_char
lread.celisp-function:read-char×read_filtered_event => read_char
lread.celisp-function:read-char-exclusive×read_filtered_event => read_char
subr.elelisp-function:read-keyread-key-sequence-vector
subr.elelisp-function:read-char-choiceread-char-choice-use-read-key, read-char-choice-with-read-key変数read-char-choice-use-read-keyによる
subr.el`elisp-function:read-char-from-minibufferread-from-minibuffer
subr.el`elisp-function:read-char-choice-with-read-keyread-key
rmc.elelisp-function:read-multiple-choiceread-event, x-popup-dialog, completing-read引数long-formによる
simple.elread-quoted-char×read-event
keyboard.celisp-function:read-key-sequenceread_key_sequence_vs => read_key_sequence => read_charread_key_sequence内でinput-decode-map対応
keyboard.celisp-function:read-key-sequence-vectorread_key_sequence_vs => read_key_sequence => read_charread_key_sequence内でinput-decode-map対応
minibuf.cread-from-minibuffercommand_loop => read_key_sequence
minibuf.ccompleting-read

■read-event、read-char、read-char-exclusiveの違い

違いはread_filtered_eventの引数の違いとその戻り値の処理。

read_filtered_eventの引数:

  • bool no_switch_frame
  • bool ascii_required
  • bool error_nonascii
  • bool input_method
  • Lisp_Object seconds

最初の三つが違う。

関数no_switch_frameascii_requirederror_nonascii
read-event000
read-char111
read-char-exclusive110

read-charとread-char-exclusiveは文字コードしか返さない。文字以外のイベントが発生したときに排除して無視するか、無視せずエラーを出すかがread-char-exclusiveとread-charの違い。

read-eventは全てありのままに返す。

問題はswitch-frameイベントの取扱。read-charとread-char-exclusiveはno_switch_frameを1にしてswitch-frameイベントを遅延させる。遅延のさせかたに少し癖がある。no_switch_frameのとき、read_filtered_eventはswitch-frameを読み取ったらそれを関数ローカルな変数に保存しておく。そして他のイベントが正常に読み取れて正常に結果を返す直前に、ローカルな変数に保存していたswitch-frameイベントをグローバル変数unread_switch_frameに格納する。unread_switch_frameはkeyboard.cで定義されている変数。read_char内で参照する。

read-charとread-char-exclusiveはdisable_text-conversionを呼んで変換を無効にする。

■課題

  • (keyboard.c)read_charが何をするのか細かく知りたい。
  • マウス・タッチイベントがどのようにread_charに届くのか。

read-event等にadviceを仕込めないか

read-eventにadviceを仕込んで仮想キーボードへのマウスイベントを無理矢理キーイベントに変換してしまう。

先にEmacsのイベント読み取り関数がどのような構造になっているのか調べるべき。 (Emacsのイベント読み取り関数の構造を詳しく調べる)

問題はread-charやread-char-exclusiveの挙動をread-eventでエミュレートできるか。read-charやread-char-exclusiveが呼び出されたときに仮想キーボードを有効にするには、内部ではread-eventを呼んでマウスイベントを取得する必要がある。つまり、内部ではread-charやread-char-exclusiveは使えない。となると、read-eventにはないread-charやread-char-exclusiveの挙動を再現する必要が出てくる。

  • text-conversionの無効化と復元
  • switch-frameイベントの遅延
  • 非文字イベント発生時のエラーまたはリトライ
  • シンボルの文字コード化
  • 戻り値に対するchar_resolve_modifier_maskの適用

switch-frameが一番心配だが、出来なくは無さそう。

修飾キーの適用順を統一する必要はあるのか

例えばS-C-dとC-S-dの結果が変わる。S-C-dの結果は#x2000044になってDが挿入されるが、C-S-dは#x2000004になってdelete-charになる。 S-C-pとC-S-pも同様にPが打たれるかShiftを押しながらprevious-lineになるかが違う。 物理キーボードで打つと基本的にC-S-になる。 M-についてはどうか。

慣例ではアルファベット順で並べるものらしい(Function Keys (GNU Emacs Lisp Reference Manual))。つまりA-C-H-M-S-s-の順。これはビット順とは異なる。

実際に現在処理を行っている関数:

  • elisp-function:vkbd-apply-modifiers
  • elisp-function:vkbd-apply-modifier-key
  • elisp-function:event-apply-modifier
(vkbd-apply-modifiers ?d '(control shift)) ;;=> 68
(vkbd-apply-modifiers ?d '(shift control)) ;;=> 33554436

(vkbd-apply-modifier-key ?d 'control) ;; => 4  ( ?\C-d )
(vkbd-apply-modifier-key 4 'shift) ;; => 68  (?D)
?\S-\C-d ;; => 33554436
?\C-\S-d ;; => 33554436

event-apply-modifierの適用順序で結果が変わる。 要するに、 d => +control => 4 => +shift => D の過程でcontrolが抜け落ちてしまう。

modifier-bar-modeはどうしているか。tool-bar-apply-modifiers関数はなんとevent-apply-modifierを適用する順番をA-s-H-S-C-M-の順で固定している。これはビット順(22~27)。結果のプレフィックスもこの順番で付く。で、これが正しいのか正直よく分からない。

event-apply-modifierを元に独自に複数修飾子に対応するバージョンを作成した方が早そう。

event-apply-modifierの仕様を詳しく見て行く。

■event-apply-modifierの仕様

(event-apply-modifier EVENT SYMBOL LSHIFTBY PREFIX)

  • (numberp event)
    • (eq symbol ‘control)
      • base-eventが64(@)~95(_)の範囲 => (base-event - 64) に元の修飾子と、?A~?Zのときは ?\S-\0 も適用する。
      • その他 => 単にeventにLSHIFTBYを適用する。
    • (eq symbol ‘shift)
      • ?a~?z => (upcase base-event) に元の修飾子を適用する。
      • その他 => 単にeventにLSHIFTBYを適用する。
    • その他 => 単にeventにLSHIFTBYを適用するだけ。
  • (memq symbol (event-modifiers event)) => eventのまま(すでに含まれている)。
  • その他 => symbol または (symbol …) の symbol の部分に PREFIX をくっつける。

これだとcontrolの適用で元の情報が失われる。従って適用順序が重要になる。その他、shift、controlの順で適用するべき。

■char-resolve-modifiers関数(char_resolve_modifier_mask in character.c)の仕様

ちなみにelisp-function:char-resolve-modifiersも似たようなことをする関数。

(char-resolve-modifiers CHAR)

※対象はASCII文字(0<=(CHAR&~MODMASK)<128)のみ。それ以外はそのまま返す。

  • CHARのshiftビットが立っている
    • (CHAR&255)が ?A ~ ?Z => shiftビットを消す
    • (CHAR&255)が ?a ~ ?z => shiftビットを消して、upcase
    • (CHAR&~MODMASK)が #x20以下 => shiftビットを消す
  • CHARのcontrolビットが立っている
    • (CHAR&255)が’ ’ => 文字部分とcontrolビットを消す(つまりcontrol以外の修飾ビットのみ残る)
    • (CHAR&255)が’?’ => #x7f | control以外の修飾ビット
    • (CHAR&#x5f)が ?A ~ ?Z (つまり小文字も含むアルファベット) => CHAR & (#x1f | control以外の修飾ビット)
    • (CHAR&#x7f)が ?@ ~ ?_ (大文字とその前後の記号) => CHAR & (#x1f | control以外の修飾ビット)

(read-event)でC-SPCを押してみると#x4000020が返ってくる所を見るとresolveされた値が返ってくるわけでは無さそう(read-charやread-char-exclusiveはわざわざchar_resolve_modifier_maskを戻り値に適用しているのだから当然か)。

■実装

vkbd-apply-modifiers関数を書き直した。vkbd-apply-modifier-keyは廃止。

Shift+シフトありキーを押したときにshiftで修飾しない

例えば一つのキーに(?a del)を割り当てたときに、Shiftとこのキーを押すとS-<delete>を押したことになってしまう。S-<delete>の入力は諦めて<delete>だけが入力されるべき。普通の記号でも同じ問題は起きないのだろうか。

結構大がかりな修正が必要になってしまった。

サイドバーのサイズをできるだけ維持する

elisp-function:window-preserve-sizeで出来るっぽい。window-size-fixedだと限界を越えて小さくなった後に、手動で元に戻すことも出来なくなる。

長押しでキーリピートする

まず第一に vkbd-wait-for-mouse-up-event を一定時間で切り上げる。そしてキーを入力、つまり変換関数は押されたキーのイベントを返す。これは簡単。

問題はリピートされる次の入力をどうやって生成するか。

タイマーイベントしかあるまい。mouse up(mouse-1, drag-mouse-1)が発生したら終了。いや、タイマー以外のいかなるイベント(switch-frameは無視すべきかもしれない)が発生しても終了するべきだろう。

本来であれば通常の1回目の押し下げ時もread-eventを内部で使うべきでは無いのかもしれない。

ここで衝撃の事実を発見。私が入れているAndroidのソフトキーボードは全て、通常文字はリピートしなかった。リピートするのはSpaceやBackSpace、Del、PageUp、PageDown、矢印キーなどのリピートすることに意味が大きそうなキーだけ。Returnがどれもリピートしないのは意外。

難しいのはやっぱり修飾キーの問題。Shiftをロックして矢印キーをリピートなんてのは一番やりたいこと。でもそれだとタイマーで戻ってきた後にread-eventで待たざるを得ない。別にいいのかな?

要するに、押し下げ→一定時間→最初のキー入力を返す→0秒タイマーですぐに戻す(この間にマウスイベントが起きたら?)→read-eventでリピート時間経過もしくは解放を待つ→解放された→修飾キーが押しっぱなし→次のイベントをread-eventで待つという感じにするしかない。

実装中に遭遇した問題:

  • Messageバッファでリピートが止まらなくなる。visible-bellのせいか分からないけれど、リピートのたびにエラーが出てどうやっても止められなくなる。up待ちのread-eventの待ち時間が0にならないようにしたら治った。
  • フレーム使用時にマウスupしてもリピートが止まらない。フレームからフォーカスが外れているのでmouse upイベントが発生しない。これはしょうがない。Emacsではフォーカスが当たっていないところのマウスイベントは発生しない。ホイールなんかでもそうだった。downだけはその瞬間にフォーカスが当たるから発生するけど。これは諦めるしかない。キーリピートはcontainer-type=window時のみとする。

Emacs終了時にユーザーデータを保存すべき

現在はキーボードオブジェクトを削除するときしかユーザーデータを保存しないので、キーボードを出しっぱなしでEmacsを終了すると次回起動時に状態を復元できない。

そもそも設定を変更した後にすぐに保存すべき。ただし、フレーム位置のドラッグ時は除く。

フレーム位置のドラッグ時が残るなら、結局Emacs終了時に保存しなければならない。

  • 設定変更と保存に関する修正
    • vkbd-set-keyboard-user-dataは値の変更を検出して:user-data-modifiedプロパティをセットする。
    • vkbd-save-keyboard-user-dataは:user-data-modifiedプロパティがセットされているときだけ保存する。
    • vkbd-set-keyboard-user-data-save関数を追加してvkbd-set-keyboard-user-dataとvkbd-save-keyboard-user-dataの両方を呼び出す。
    • フレーム位置変更以外の部分ではvkbd-set-keyboard-user-data-saveを使う。
  • Emacs終了時の保存
    • vkbd-save-all-keyboard-user-dataを追加する。
    • vkbd-global-setup-for-user-data-saveとvkbd-global-teardown-for-user-data-saveを追加し、kill-emacs-hookにvkbd-save-all-keyboard-user-dataを登録したり解除したりする。

タッチでキーリピートをするときに指が動くとリピートが中断される

touchscreen-updateが発生するのが原因だが、どうすれば良いのか。

ああ、(vkbd-wait-for-mouse-up-event seconds)の中でread-eventループするときにsecondsで指定した秒数だけ毎回待ってしまうからだ。終わる時間を計算しておいて、そこをめがけて待つようにしないといけない。

キーボードからのボタン押し下げに対応する

container-type=windowのときに閉じるボタンをRETで押したくなる。

それよりも(set-window-parameter window ‘no-other-window t)にしてウィンドウを選択出来ないようにした方が良い気がする。

とはいえ一応RETとSPCでボタンを押せるようにしておいた。

ウィンドウにno-other-windowを設定する

C-x oで仮想キーボードに移りたくない。

キーボードバッファでの入力イベントを抑制する

キーボードバッファにフォーカスを持たせて何か入力したときにエラーが発生して鬱陶しいので。

self-insert-commandを対象バッファへ切り替え後のself-insertにリマップするのはどうだろう。 それでもreturnキーを押したときのnewlineはエラーになる。 そもそもその入力対象バッファにおいて通常のキーがself-insert-commandである保証はない。ましてやreturnはshell-modeなんかだと送信機能に割り当てられている。

単に入力対象バッファへ移動するだけでもよい?

いや、仮想キーボードバッファ内での物理キー入力に対してすべきことは、それを入力対象バッファへ転送することじゃないだろうか。

つまり入力対象バッファへ切り替えた上で、 unread-command-events にイベントを追加する。

……切りがないから止めよう。単に入力対象バッファへ移動するだけで十分だ。

害になりそうなコマンドを vkbd-select-input-target へリマップしておく。

window表示時でもカーソルを消す

vkbd-keyboard-buffer-local-variablesを追加してcursor-typeにnilを指定した。

タイトルバーに並べるボタンをカスタマイズできるようにする

なんか header-line-format でカスタマイズすれば良いような気もしてきたけど、一応独自のバーをカスタマイズできる方が良いだろう。
  • format-mode-lineでフォーマットできるようにしてみる。
  • 試しにレイアウトを切り替えるボタンを配置しておく。10x7と特殊キーのみ。
  • おまけでOn-Screenキーボードをトグルするボタンも追加してみる。これはAndroidでのみ有効にしておく。本当は(将来的には)他のプラットフォームでもあり得るのかもしれないけど。

タイトルバーにレイアウトを切り替えるボタンを置けるようにする

タイトルバーを消せるようにする

ネイティブのオンスクリーンキーボードをトグルするボタンを付ける

タイトルバーに付ける。押すとオンスクリーンキーボードが無効化されて消えたり、元の設定に戻って表示されたりする。

[2025-12-02 Tue]追記:

その後色々と問題が分かった。

elisp-function:frame-toggle-on-screen-keyboard(frame.el)は色々なところから呼び出されている:

  • touch-screen.el
  • elisp-function:help–help-screen (help-macro.el)
  • elisp-function:read-multiple-choice–short-answers (rmc.el)
  • elisp-function:isearch-mode (isearch.el)
  • elisp-function:minibuffer-setup-on-screen-keyboard (minibuffer.el)
  • elisp-function:minibuffer-exit-on-screen-keyboard (minibuffer.el)
  • elisp-function:read-char-choice-with-read-key (subr.el)
  • elisp-function:touch-screen-handle-point-up (touch-screen.el) ※3箇所
  • elisp-function:modifier-bar-button (tool-bar.el)

これらを全て個別にブロックするのは手間がかかるし変化にも弱い。

となると、elisp-function:frame-toggle-on-screen-keyboard(frame.el)自体を無効化する方が良い。

ちなみにelisp-function:frame-toggle-on-screen-keyboardの中身はandroidの時にandroid-toggle-on-screen-keyboardを呼び出すだけ。android以外では何もしないでnilを返す。

なのでadviceで関数を置き換える。ただし、従来の動作、つまり本来のオンスクリーンキーボードも明示的に制御したいので、around adviceにして、元の関数を呼び出すかどうかの変数を用意しておく。

Emacsのオンスクリーンキーボードとして使えるようにするも一緒に対処した。

メニューに「カスタマイズ」を追加する

(customize-group ‘vkbd)を起動する。

レイアウトを切り替えるとwindow-preserve-sizeが解けてしまう

効果が解けてサイズが変化しやすくなってしまう。 fit-window-to-buffer呼び出し時は必ずwindow-preserve-sizeもしなければならない。

Emacsのオンスクリーンキーボードとして使えるようにする

elisp-function:frame-toggle-on-screen-keyboard(frame.el)をオーバーライドしてしまえば、ネイティブのオンスクリーンキーボードの代わりにvkbdを表示するようなことも可能なはず。

つまり、hide引数がnilのときはvkbd-open-global-keyboardを呼び出し、tなら-open-global-keyboardを呼び出せば良い。

frame引数がselected-frameではないことはあるのだろうか。grepした限り少なくとも現在のlispディレクトリの中にはない(一つだけnilを指定している所がある)。もしそれに備えるならvkbd-open-global-keyboardにもparent-frame引数を入れた方が良い。その上でselected-frameを使っているところをそのparent-frameにしなければならない。

ネイティブのオンスクリーンキーボードをトグルするボタンを付けると一緒に対処した。

Magitを使ったときの問題を少し改善

Magitはデフォルトではbottomのside windowを使用する。 elisp-variable:transient-display-buffer-actionのデフォルト値がそうなっているから。

そうするとvkbdがbottomのサイドバーで表示していた場合、vkbdが消えてしまい操作不能になる。

状況が分かるようにslotを指定してはどうか。これで最低限何が起きているのか分かる。

複数のフレームで使用したときの問題を調査して解決する

Androidの場合、フレームは画面丸丸別のものに切り替わる。同時に二つのフレームを見ることはできない。なので、別のフレームにある仮想キーボードは操作できない。 トグルするボタンを押したときに即座に作り直す必要があるのではないか。

問題点:

条件を満たすフレームの内最後に選択したフレームを探す関数を作成した。 get-mru-windowにヒントを得て作成。 window-use-timeという関数は分かりにくいが、ウィンドウ切り替え毎にカウントアップする値があって、ウィンドウ毎に最後に選択した時のカウント値が記憶されている。それが大きいものが最近使われたウィンドウとなる。ウィンドウはwindow-list-1もしくはget-window-with-predicateで巡回できるので、当然そのフレームも列挙することができる。 そのようにして作ったのが vkbd-last-non-keyboard-frame と vkbd-previous-non-keyboard-frame 。二つあるのはselected-frameを含めるかどうかの違い。

結局の所、入力対象フレームは最も最近選択した非キーボードフレームだから、全ウィンドウを列挙して、そのフレームがキーボードフレームではないものを列挙し、その中でwindow-use-time値が一番高い物を選べば良い。

vkbd-frame-toggle-osk-using-vkbdを複数のフレームに対応させる

elisp-function:frame-toggle-on-screen-keyboardのFRAME引数を使用していないので形式上は問題。 ただ、現在のlispディレクトリの中に(selected-frame)以外を引き渡しているところは無い(一箇所のnilを除く)。 本来であれば、vkbd-global-keyboardが別のフレームの子フレームやウインドウだったら作り直す必要がある。
container-type作り直す必要は無い
independent-frame気にしない
child-frame親フレームと異なるなら作り直す
windowウィンドウがあるフレームと異なるなら作り直す

入力対象フレームの割り出し処理を改善する

vkbd-keyboard-target-frameが典型的。 キーボードフレーム上がクリックされたとき、発生したキーをどこのフレームに送信すべきだろうか。つまり、入力対象フレームはどこだろうか。

container-typeがchild-frameだけの時は親フレームを選択すれば良かった。windowも同一フレームなので問題は無いだろう。independent-frameのときはこれが問題になる。

普段フレームを一つしか使っていない人ならそれほど問題にはならない。キーボードフレーム以外の普通のフレームを選択すれば良いから。しかし複数のフレームを使っている人はどうだろうか。

理想的にはswitch-frameを監視して、キーボードフレームへスイッチする前のフレームを入力対象にするのが一番良さそう。それも完璧かと言われればよく分からない部分は残るが。例えば何らかの一時的なフレームを経由してキーボードを押した場合とか。まぁ、あまり考えられないし、そのときは間違ったところへ送っても仕方が無いと思う。

↑get-window-with-predicateとwindow-use-timeの組み合わせで最近選択したフレームの中で条件に合うフレームを探すことが出来た。

フレームのドラッグ移動後にフォーカスを外すべき?

これまではparent-frameがあれば選択するようにしていたが、確実に非キーボードフレームにフォーカスするようにした。

ドキュメントのカスタマイズ変数一覧を更新する

customizeからカスタムキーレイアウトを定義したい

ユーザー定義のキーレイアウトをcustomize画面から作成したい。 詳細なキー配置はsexpをいじるのでも構わないが、そこまで行く道筋は用意する必要がある。

案:

  • vkbd-default-keyboard-layoutに直接リストを指定させる。
  • vkbd-layout-user-1のような変数を作成して、それをカスタマイズさせる。
  • vkbd-layout-listをカスタマイズできるようにし、そこに変数名では無く生のレイアウトオブジェクトをsexpで書けるようにする。

vkbd-layout-listをカスタマイズ可能にする。そうすれば使わないレイアウトをリストから除外できるようにもなる。

それに先だってlayoutオブジェクトにlayout-idが必要。vkbd-layout-id、vkbd-layout-rows、ついでにvkbd-layout-properties関数を追加。map関数とレイアウト選択メニューもそれに対応させる。

レイアウト定義内の1文字のシンボルを文字として扱う

vkbd-layout-listのカスタマイズ時に文字コードよりも分かりやすいので。

window-toggle-side-windows(C-x w s)で削除されてしまう

ウィンドウが削除されたらキーボードも削除するようにしたので当然。 残すオプションも追加する?

ウィンドウで表示しているとキー操作でvkbdを閉じる方法が無いので、C-x w sで消えるのは案外悪いことではない気もする。

一応追加しておく。

options :prevent-auto-deletion

  • nil : 自動削除する
  • t : 自動削除しない
  • window : container-typeがwindowのときだけ自動削除する
(setq vkbd-global-keyboard-options '(:prevent-auto-deletion window))

frameの時で自動削除しない理由はないと思う。

大きいレイアウトと小さいレイアウトをカスタマイズ

vkbd-title-button-extrasに追加したのはあくまで例のつもりだったんだけど、小さいレイアウトと大きいレイアウトを明示的に変更できるようにすべきだろうか。

最初は「1」とか「2」とかいうボタンを追加しようと思っていた。レイアウトボタンを好きなように設定して欲しかったから。

今でもvkbd-title-button-extrasにコマンドとして

(lambda () (interactive) (vkbd-set-keyboard-layout (vkbd-guess-current-keyboard) 'user-layout-01))

を指定すれば機能する。……うーん、ちょっと酷いような気もしてきたな(笑)

ボタンのコマンドの所に(:eval <S式>)や(:layout <layout-id>)を指定出来るようにした。ここまでやれば十分だろう。

キーの幅に任意の数のcolumn-separatorの幅を追加できるようにする

配列を整えるのに必要。

キーテキストのセンタリングを正確にする

displayプロパティを使用して正確に中央に寄せる。

ただしこれには欠点がある。

本質的に、空白の幅がピクセル単位で変わる可能性があるので上下左右のキー同士が揃わない可能性が高くなる。

text-scale-adjustを使うと明らかにレイアウトが崩れる。displayプロパティの(space :width num)指定はフレームの文字幅を基準にするのでfaceをtext-scale-adjustでfaceをリマップしても変化しない。フレームの文字幅を基準にしてくれているおかげで安定した幅指定が出来るというメリットもあるので仕方ないところ。

もちろん古い方式を前提にレイアウト指定(:w指定)を書いた場合は文字数幅に丸められないのでレイアウトがズレる。

もし将来テキスト端末でも使いたいのであれば、古い方式の方が良い。

ということで古い方式と選べるようにしておく。

[2025-12-03 Wed 18:25]うーん、ダメだ。Android上で位置が揃わない。デフォルトは古い方式にしよう。

pauseキーを追加する

キー幅調整を改善したついでにusやjpの右上に入れたくなったので。

vkbd-global-keyboard-optionsの:typeを真面目に書く

これは全てのoptionsを調べてまとめることを意味する。

いくつか付随する問題も修正した:

  • :layout(:default-layoutではない)と:container-type(:default-container-typeではない)が指定されているときはユーザーが変更できないようにメニューから項目を削除した。メニュー以外の変更も防止するか迷うが今のところ保留。
  • nilを有効な指定値として扱うべきもの(plist-memberを使う):
    • :frame-parameters
    • :buffer-local-variables
    • :title-bar-format
    • :title-extra-buttons

シフトキーを押したときに大文字が表示されないのを直す

面白いのは後の段階で結局シフト修飾が適用されるので、大文字は打てるところ。 レイアウトに1文字シンボルを指定出来るようにしたときの対応漏れ。 1文字シンボルは可能な限りその文字コードとして扱わなければならない。

スクリーンショットを更新する

使用できないシチュエーションについて調べる

=> read系関数については「Emacsのイベント読み取り関数の構造を詳しく調べる」で調べた。 その上で「read-event等にadviceを仕込めないか」で解決した。 transient.el等についてはもう少し調べる必要がある。

Reading Input (GNU Emacs Lisp Reference Manual)

  • read-key-sequenceは機能しているっぽい。
  • read-event、read-char、read-char-exclusiveには効かない。
  • read-key、read-char-choiceは機能する。
  • read-multiple-choiceは正しく機能しない。 (read-event使用)(long-formの時は効く)
  • read-quoted-charは正しく機能しない。 (read-event使用)

他にも:

  • transient.elやset-transient-mapが機能しない(フレーム表示時。container-type=window時は機能する) switch-frameが起きるから?

こういったことについてREADME.orgに書くべき。 その前に解決を試みる。

現時点で分かることはREADME.orgに書いておいた。

displayプロパティを使ったセンタリングを改善する

(num . width)で現在のfaceの文字幅を基準にした幅を指定出来る。 今はフレームのデフォルト文字幅が基準になっているからtext-scale-adjustすると絶対に崩れる。 widthを使ってみたが、text-scale-adjustで2段階に1回特定の行がずれる。4文字のキーがある行がダメみたい。4文字だと1文字あまりで0.5文字幅の空白を挿入することになるけど、それがフォントサイズによってはちゃんと(整数倍で)再現できないと言うことか?

まぁ、これをデフォルトで有効にするのはやはり難しそうだ。

一部のカスタマイズ変数のカスタマイズでBad Formatエラーがでる

よく発生する問題。defcustomの:typeに指定するwidgetは:formatに "%{%t%}:" が含まれていなければならない。 コロンが無いと”Bad Format”エラーが出る。そして%tがないとカスタマイズバッファ上で項目名が表示されない。さらに%{ %}で囲まれていないとカスタマイズ変数用のfaceで色づかない。そこまで限定するならなんでわざわざ:typeで指定したwidgetの:formatから書式を抽出するのだろうか。意味が分からない。%tを使わずに直接タグ文字列を埋め込んでいたらそれを抽出したい? なぜ? 同じ物を二回表示したいの?

いくつか解決方法を試した。

一つはdefconstで :format に見えないコロンを入れる方法。

(defconst vkbd-keyboard-buffer-line-spacing-cus-type
  `(choice :tag ""
           :format ,(concat "%{%t%}"
                            (propertize ":" 'display "") ": %[Value Menu%] %v")
           (const :tag "Global value" global)
           (const :tag "No extra space" nil)
           (integer :tag "In pixels")
           (float :tag "Relative to the default frame line height")))

(defcustom vkbd-keyboard-buffer-line-spacing nil
  "`line-spacing' value in keyboard buffers."
  :group 'vkbd-text-style
  :type vkbd-keyboard-buffer-line-spacing-cus-type)

これは vkbd-keyboard-buffer-line-spacing のカスタマイズだけならうまく行くが、 vkbd-global-keyboard-options に含まれる部分ではうまくいかない。コロンが二重に表示されてしまう。

二つ目はこのchoiceを派生したwidgetを作って、必要に応じて:formatを変える方法。わざわざwidgetを作ったのにchoiceのボタンを含む複雑な:formatをタグのありなしを分けて何度も書かなければならない。

(define-widget 'vkbd-line-spacing 'choice
  ""
  :tag ""
  :format (concat "%{%t%}"
                  (propertize ":" 'display "") ": %[Value Menu%] %v") ;;←見えないコロンと通常のコロンあり(変数毎)
  :args '((const :tag "Global value" global)
          (const :tag "No extra space" nil)
          (integer :tag "In pixels")
          (float :tag "Relative to the default frame line height")))

(defcustom vkbd-keyboard-buffer-line-spacing nil
  "`line-spacing' value in keyboard buffers."
  :group 'vkbd-text-style
  :type 'vkbd-line-spacing)

(defconst vkbd-keyboard-options-cus-type
  `(set
...略...
    (list :inline t :tag "Line Spacing" :format "%{%t%}: %v"
          (const :format "" :line-spacing)
          (vkbd-line-spacing :format "%[Value Menu] %v")) ;;←コロンを消したVer(変数毎)

三つ目はdefconstは選択項目リストだけにして、choice部分は毎回書く方法。これもchoice派生のwidgetを作るのと同じで:formatを書き分けなければならない。

四つ目は一つの値のみを持つwidgetを作り、それで包むこと。

(define-widget 'vkbd-cus-item 'lazy
  "A widget to avoid \"Bad Format\" errors emitted from the
`custom-variable-value-create' function in cus-edit.el. It adds a tagged
`:format' to the type specified by the `:type' argument. The tag
contains an invisible colon."
  :tag ""
  :format (concat "%{%t%}" (propertize ":" 'display "") ": %v")) ;;←複雑な見えないコロンと見えるコロンを含む指定はここだけ

(defconst vkbd-keyboard-buffer-line-spacing-cus-type
  '(choice :format "%[Value Menu%] %v" ;;←ここで消すのは変数毎に必要(気になるならchoice-without-tagみたいなwidgetを作れば良い)
           (const :tag "Global value" global)
           (const :tag "No extra space" nil)
           (integer :tag "In pixels")
           (float :tag "Relative to the default frame line height")))

(defcustom vkbd-keyboard-buffer-line-spacing nil
  "`line-spacing' value in keyboard buffers."
  :group 'vkbd-text-style
  :type `(vkbd-cus-item :type ,vkbd-keyboard-buffer-line-spacing-cus-type))

こうすれば:typeとしてすぐにchoiceを指定するところではvkbd-cus-itemで包めば良いし、vkbd-global-keyboard-optionsの中のような場所ではそのまま使えばよい。line-spacingに限らず同様のケースは他にもいくつかあるので、それにもそのまま適用出来る。

そもそもこれは 「Choice: [Value Menu]」という無駄な表記を「[Value Menu]」にしたかったから生じている問題。無駄な「Choice: 」を諦めるという方法もあるが、vkbd-global-keyboard-optionsのset内では普通に問題なく消せるのだし、customizeバッファの直下でだけできないというのはおかしな話。何よりスペースの無駄。

line-heightテキストプロパティでキーの高さを変える

Line Height (GNU Emacs Lisp Reference Manual)に書いてあるように、line-heightテキストプロパティで行の高さをコントロールできる。
(insert "ABCDEFG" (propertize "\n" 'line-height 3.0))

任意のfaceの高さを基準にすることもできるようだ。

セパレータ用のfaceの:heightを変えるよりマシな方法な気がする。

変数vkbd-text-key-line-heightと:text-key-line-heightオプションを追加した。

line-heightテキストプロパティの (height total) の指定は意味不明。試してみると、heightはテキスト下端から上方向の高さで、totalは不明な場所から下方向の高さになる。totalは行に普通の文字しかない場合はテキスト上端から下方向の高さになっているように見える。しかし大きな文字を混在させると、正直どこから下端までの高さなのか分からない。大きな文字の中心くらい?

(insert "ABCDE" (propertize "\n" 'line-height '(40 60)))

(insert (propertize "ABCDE" 'font-lock-face '(:height 2.0)) "ABCDE" (propertize "\n" 'line-height '(40 60))) ;; いや、デフォルトサイズのテキストの上端から下方向かな? 大きさを変えても関係ない?

(insert (propertize "\nABCDEFG\n" 'line-height '(40 60) 'font-lock-face '(:height 300))) ;;いやいや、そうでもない。\nに適用されているフェイスが基準か? でもこれだと今度はheight指定の方がおかしいぞ。

意味不明。

defcustomで設定を変更したときにキーの外観を更新すべきだろうか

faceで自動的に変わる部分は良いが、セパレータの幅はじめ他のものは自動的には変わらない。調整するのに繰り返し閉じて開き直さないといけない。

対象となるカスタマイズ変数を列挙する:

vkbd-keyboard-frame-parameters保留フレームプールクリア付きコンテナ再作成
vkbd-keyboard-frame-keep-visible-margins保留位置制限適用
vkbd-keyboard-buffer-local-variables保留バッファ作り直し
vkbd-keyboard-buffer-line-spacing nilバッファ内容作り直しvkbd-make-keyboard-buffer-contentsに入れてしまう。
vkbd-title-bar-formatバッファ内容作り直し
vkbd-title-button-close-captionバッファ内容作り直し
vkbd-title-button-close-formatバッファ内容作り直し
vkbd-title-button-menu-captionバッファ内容作り直し
vkbd-title-button-menu-formatバッファ内容作り直し
vkbd-title-button-extrasバッファ内容作り直し
vkbd-text-title-button-separator-widthバッファ内容作り直し
vkbd-text-key-widthバッファ内容作り直し
vkbd-text-key-raiseバッファ内容作り直し
vkbd-text-key-centering-methodバッファ内容作り直し
vkbd-text-column-separator-widthバッファ内容作り直し
vkbd-text-row-separator-heightバッファ内容作り直し
vkbd-text-key-line-heightバッファ内容作り直し
vkbd-layout-list
vkbd-default-keyboard-layout
vkbd-default-keyboard-style
vkbd-global-keyboard-user-data-storageグローバルキーボード作り直し
vkbd-global-keyboard-optionsグローバルキーボード作り直し
vkbd-recycle-framesフレームプールクリア
vkbd-replace-read-functions保留advice状態更新
vkbd-key-repeat-delay関係ない
vkbd-key-repeat-interval関係ない
vkbd-key-repeat-enabled関係ない
vkbd-log-generate関係ない
vkbd-log-output関係ない

全キーボードオブジェクトを列挙する関数を追加する:

  • vkbd-all-keyboard-objects

とりあえずバッファ内容の作り直しだけは対応する。

  • vkbd-recreate-all-keyboards-buffer-contents
  • vkbd-cus-set-with-recreate-buffer-contents

続きはdefcustomの:setを改善するへ。

defcustomの:setを改善する

バッファ内容の作り直しが必要な部分についてはdefcustomで設定を変更したときにキーの外観を更新すべきだろうかで修正した。それ以外の部分についてどうするか。

残りの部分:

変数名対処値変更時に何をすべきか
vkbd-keyboard-frame-parameters対処するフレームプールクリア付きコンテナ再作成
vkbd-keyboard-frame-keep-visible-margins保留位置制限適用
vkbd-keyboard-buffer-local-variables対処する内容再作成、メジャーモード再設定、ローカル変数、フック再設定
vkbd-layout-list対処するレイアウトIDが分かるならそれでレイアウトを再設定する
vkbd-default-keyboard-layout何もしない何もしない(デフォルトレイアウトは起動してしまえば意味が無い。ユーザーの選択が優先されるので)
vkbd-default-keyboard-style何もしない何もしない(デフォルトスタイルは起動してしまえば意味が無い。ユーザーの選択が優先されるので)
vkbd-global-keyboard-user-data-storage保留グローバルキーボード作り直し? 少し不安がある。手動でやってよ
vkbd-global-keyboard-options対処するグローバルキーボード作り直し。ただし:frame-parametersは反映されない可能性あり
vkbd-recycle-frames対処するフレームプールクリア
vkbd-replace-read-functions対処するadvice状態更新
vkbd-key-repeat-delay何もしないびみょー
vkbd-key-repeat-interval何もしないびみょー
vkbd-key-repeat-enabled何もしないびみょー
vkbd-log-generate何もしない何もしない
vkbd-log-output何もしない何もしない

個人的にはそこまでちゃんとやらなくても良いんじゃないかなぁと。

user-data-storageに関しては弊害も考えられる。現在の設定が失われたり。

vkbd-replace-read-functionsは簡単だからやろうかな。 elisp-function:vkbd-cus-set-with-update-read-functions

vkbd-recycle-framesはunused-framesを消せば良いだろう。もう常に消してしまって良いと思う。 elisp-function:vkbd-cus-set-with-delete-all-unused-frames

グローバルキーボードの作り直しなんかは自分でやって欲しいような気もする。「グローバルキーボードを作り直しますか?」みたいに聞いてくれれば一番親切だろうけど。 elisp-function:vkbd-cus-set-with-recreate-global-keyboard

レイアウト関連はカスタムレイアウトを作る時に即時適用して欲しい気はする。 elisp-function:vkbd-cus-set-with-reload-keyboard-layout

default-layoutやdefault-styleは基本的にオブジェクトを作った時点にのみ影響があるので、変更しても再作成する必要が無いと思う。 基本的にoptionsによる強制値やユーザーが選択したレイアウトやスタイルが優先されるので。 唯一、はじめて起動してまだユーザーがレイアウトやスタイルを変更していないときには、切り替えることが正しい可能性はある。 とはいえ、一度起動してしまったら後は例え選択を変えていなくても、変えないのがユーザーの選択だという解釈はあり得る。

一応フレームパラメータも対応しておくか。 elisp-function:vkbd-cus-set-with-recreate-keyboard-containers

vkbd-global-keyboard-optionsも :frame-parameters があるので同様の処理をしないといけない。

バッファローカル変数も一応何とかする。面倒くさいけど。 vkbd-cus-set-with-reset-keyboard-buffers

キータイプをカスタマイズできるようにする

キーテキストを置き換える仕組みは欲しい。矢印キーや修飾キーは変えたいかもしれない。特に英語圏では矢印記号が使えるとは限らない。日本でもEmacsでちゃんと表示できるように設定してあるかは分からない。

elisp-variable:vkbd-key-type-alistで決まっているのだが、これをどうやってカスタマイズさせるか。

素直にelisp-variable:vkbd-user-key-text-alistを追加。

vkbd-key-type-alist自体に何かを追加できるようにしたい。ユーザー独自のキータイプを追加したいだろう。

elisp-variable:vkbd-user-key-type-alistを追加。

unread-command-eventsをうまく使えないか

やっぱりunread-command-eventsはread_charで処理しているので、read-eventに作用する。
(let ((unread-command-events (list ?b)))
  (read-event))

もちろんvkbdは最初unread-command-eventsを使うやり方で実装を開始したが、今はinput-decode-mapで返すやり方に変わっている。

それはうまくいかないケースがあったからなのだが、それはなんであったかもう忘れた。おそらく修飾キーがらみだと思うけど……。

本当にうまく行かないのだろうか。

少なくとも複数のキーを生成するのに使えないか。

一つのキータイプで複数のキーを生成できるようにする

一つの時はこれまで通りにし、複数の時はunread-command-eventsを使うようにする。

ロック中に一つのキータイプで複数のキーを生成するとき

修飾キーロック中に一つのキータイプで複数のキーを生成するとき、最初のキーだけにしか修飾キーが適用されない。この挙動は問題だろうか。 それが良い場合もあるだろうけど、それはロックじゃないときにそうなれば良いのであって、ロックした以上は全部に効かないとダメだろう。

vkbd-tool-bar-modeを作る

マイナーモードのキーマップにツールバーボタンを定義することって出来るんだっけ? それが出来るならその方が良いと思うけど。要調査。→やはりマイナーモードマップにツールバーを仕込む方が良いみたい。

vkbd-tool-bar-modeを作成。従来のvkbd-add-to-tool-barとvkbd-remove-from-tool-barはvkbd-tool-bar-modeを呼び出すだけにする。

デフォルトのフォントは固定幅にする

つまりfixed-pitchから継承する。

mode-lineやheader-lineにtitle-bar-formatを指定出来るようにする

下にもバーが欲しいかも?での問題。

同じフォーマットなので使えると思いきや、実際に指定してみるとボタンが押せない。

原因は二つ:

  • キーシーケンスとして [mode-line mouse-1] のようにプレフィックスが付く。
  • キーマップを節約するためにクリックした位置のテキストプロパティから実行するコマンドを割り出すようにしている。

そのあたりを直した。

title-bar-formatの直前に見えない空白文字を挿入する

(vkbd-keyboard-buffer-local-variablesを通じてmode-line-formatを指定して)モードラインを表示したとき、モードラインをクリックするとなぜかバッファの左上にあるキーマップが使われてタイトルバーの最初のボタン(通常は閉じるボタン)が押されてしまう問題がある。

原因はよく分からない。モードラインの右側の空白を押しただけなのに、なぜかバッファの先頭にボタンがあるとそれが反応してしまう。先頭に何か普通の(キーマップがない)文字を入れると反応しない。

しかたないので、タイトルバーの前に見えない空白を挿入する。そうすれば反応しない。

タイトルバーを表示する位置を指定出来るようにする

vkbd-text-title-bar-position top bottom both auto nil
  • タイトルバーを非表示にしてヘッダーラインやモードラインを使いたいときに便利。
  • 上側のサイドウィンドウで表示しているときに下にあると便利そう。
  • 両方に表示できると、フレームを上にはみ出させて下のバーで操作ができる。

他の有力な方法は、上と下のバーを別にするというもの。top-barとbottom-barみたいにする。まぁ、どうかなぁ。今やらなくてもおそらく将来的にそのように拡張できると思う。現状でもheader-lineとmode-lineを使えばだいたい似たようなことは出来る。

下にもバーが欲しいかも?

(子フレーム表示の時に) 画面の上の方に避けておきたいときに下にバーがあるとありがたいのだけど。 その分キーボードが大きくなってしまうから本末転倒かな? vkbdを下の方に配置して、かつ、モードラインやミニバッファが見えるように空けたい場合、上下両方にバーがあるとスペースが無駄になる。それ以外の時は、画面外に一方のバーを出してしまうことで無駄を回避できる。

(Window表示の時に) 上側にサイドウィンドウで表示したときに、ウィンドウの下にタイトルバーが表示できると良いかも? さらにそのタイトルバーのドラッグでウィンドウサイズを調整できると良い。

モードラインを下のバーにしてはどうだろうか。vkbd-keyboard-buffer-local-variablesを通じてmode-line-formatに(:eval title-bar-format)を設定すれば良い。……と思ったらボタンをクリックしても反応しない。調査の結果、キーマップを統一するためにクリックした位置から実行するコマンドを割り出すようにしていたのが原因だった。モードラインやヘッダーラインでは当然ポイントからテキストプロパティを取り出すようなことは出来ない。ボタン毎にキーマップを作る方法に直す。また、モードラインやヘッダーライン上ではキーシーケンスとして mode-line や header-line がプレフィックスとして付くのでそれにも対応する。mode-lineやheader-lineにtitle-bar-formatを指定出来るようにするで修正。

ただ、これだとおそらくモードラインをタッチしてフレームの移動は出来ないと思う。drag-with-mode-lineを有効にしても、タッチイベントでの子フレーム移動が実装されていなかったと思う。

タイトルバーを表示する位置を指定出来るようにするにて下にも上下にも表示できるようになった。

キー行の改行にfaceが適用されていないのを直す

(insert "\n") してしまっているので vkbd-text-keyboard フェイスが適用されていない。vkbd-text-keyboardのbackgroundに色を付けてextendedをtにすれば一目瞭然。

CANCELLED (サイズ調整)サイズをoptionsで変更できるようにしたい

今はvkbd-text-keyboardを変更しなければならない。

あくまでユーザーサイドで手軽に調整する方法があれば良い。optionsや専用の変数で指定するくらいなら匿名faceでやれば十分。optionsにはfaceを置き換える仕組みすらある。

CANCELLED (サイズ調整)text-scale-modeと似た原理でサイズを調整する

現状でもバッファ内でelisp-function:text-scale-adjustを使えばサイズを変えられる。 タイトルバーの部分のサイズも調整するかは迷うところ。 まぁ、細かく変更したいならM-x customize-gruop vkbd-text-styleで調整すべきではある。

追記: キーテキストのセンタリングを正確にするの修正によってelisp-function:text-scale-adjustで調整すると確実にレイアウトが崩れるようになってしまった。何か回避策はあるだろうか。

追記: displayプロパティを使ったセンタリングを改善するで崩れにくくなった。それでもやはりわずかにズレていることはある。text-scale-modeを使うと倍率によってはズレる。

調べてみたところface-remapping-alistという仕組みがあって、text-scale-modeはこれを使っている。そしてface-remap-add-relativeを使えば簡単にfaceのサイズを相対的に変えることが出来る。

Face Remapping (GNU Emacs Lisp Reference Manual)

これを使うのも一つの手だが、バッファ全体で変化してしまうのは少し問題。将来的にバッファの中に自由にキーボードを挿入できるようにするかもしれない。

propertize処理は全て管理下にあるのだから、全てのテキストを匿名faceで包み込めば済む話ではある。その方が良い。

(サイズ調整)サイズに関するオプションをメニューに追加する

うーん、faceのカスタマイズでやってくれないかな?

propertize時に匿名faceで包む。 (:inherit 指定のface :height scale )

user-dataにdisplay-scaleを追加。150% 125% 112.5% 106.25% 100% 93.75% 87.5% 75% 50%

optionsやカスタマイズ変数は追加しない。 あくまでユーザーがメニューから素早くサイズ調整するためのもの。