ときさんブログ

ヘルニアン・ラプソディ

Surface Go 3 に CachyOS Handheld をインストールして仮想キーボードをカスタマイズした話

背景

長男が高校を卒業し、GIGAスクールPCのSurface Go 3を貰い受けたので、元から付属していたJISキーボードを廃棄してUSキーボードを買ったら認識してくれず、Omarchyをインストールするもキーボードが使えないと死ぬので改めてインストールできそうなOSはないか?と探したらCachyOSのHandheld版というものがあることを知ってインストール。

しかし、仮想キーボードでターミナルを叩いた時に異変に気付いた。なんとデフォルトで有効になっている仮想キーボードの plasma-keyboard には Esc, Tab, Ctrl, Alt キーが無いのだ…

plasma-keyboard
写真はFirefoxだけど…

かつて男塾塾長の江田島平八はこう言っていた。

男なら死ねい
男なら死ねい

諦めの悪いワシはClaudeに聞きながら対応したのだった…(ちなみに👇️のターミナルの作業は面倒くさいので初期で Tailscale と openssh を入れてSSHで作業してます)

目的

仮想キーボードにデフォルトでない Esc, Tab, Ctrl, Alt を追加してVim等のターミナル操作を可能にする。


最終構成

項目 内容
OS CachyOS Handheld (KDE Plasma / Wayland)
仮想キーボード maliit-keyboard (AUR)
カスタムレイアウト /usr/lib/maliit/keyboard2/languages/en/Keyboard_en.qml
アップデート保護 pacman hook

キーボードレイアウト

[ Esc ][ Tab ][ Ctrl ][ Alt ][ - ][ / ][ ' ][ : ][ [ ][ ] ]
[  q  ][  w  ][  e  ][  r  ][  t  ][  y  ][  u  ][  i  ][  o  ][  p  ]
[  a  ][  s  ][  d  ][  f  ][  g  ][  h  ][  j  ][  k  ][  l  ]
[  ⇧  ][  z  ][  x  ][  c  ][  v  ][  b  ][  n  ][  m  ][  ⌫  ]
[ ?123 ][  ,  ][        Space        ][  .  ][  ↵  ]

手順

1. maliit-keyboard のインストール

paru -S maliit-framework maliit-keyboard

2. kwin の InputMethod を maliit に設定

kwriteconfig6 --file kwinrc --group Wayland --key InputMethod \
    /usr/share/applications/com.github.maliit.keyboard.desktop

設定確認:

cat ~/.config/kwinrc | grep -i inputmethod
# → InputMethod=/usr/share/applications/com.github.maliit.keyboard.desktop

再起動して反映:

sudo reboot

3. レイアウトファイルのバックアップ

sudo cp /usr/lib/maliit/keyboard2/languages/en/Keyboard_en.qml \
        /usr/lib/maliit/keyboard2/languages/en/Keyboard_en.qml.bak

4. カスタムレイアウトの適用

usr/lib/maliit/keyboard2/languages/en/Keyboard_en.qml

import QtQuick 2.4
import MaliitKeyboard 2.0
import keys 1.0

KeyPad {
    anchors.fill: parent
    content: c1
    symbols: "languages/Keyboard_symbols.qml"

    Column {
        id: c1
        anchors.fill: parent
        spacing: 0

        // 追加行: ファンクションキー
        Row {
            anchors.horizontalCenter: parent.horizontalCenter
            spacing: 0
            ActionKey { label: "Esc";  shifted: "Esc";  action: "keysequence"; valueToSubmit: "Escape"; noMagnifier: true; skipAutoCaps: true }
            ActionKey { label: "Tab";  shifted: "Tab";  action: "keysequence"; valueToSubmit: "Tab";    noMagnifier: true; skipAutoCaps: true }
            ActionKey { label: "Ctrl"; shifted: "Ctrl"; action: "keysequence"; valueToSubmit: "Ctrl";   noMagnifier: true; skipAutoCaps: true }
            ActionKey { label: "Alt";  shifted: "Alt";  action: "keysequence"; valueToSubmit: "Alt";    noMagnifier: true; skipAutoCaps: true }
            CharKey { label: "-"; shifted: "_"; extended: ["|","\\","~","`"]; extendedShifted: ["|","\\","~","`"] }
            CharKey { label: "/"; shifted: "?"; }
            CharKey { label: "'"; shifted: "\""; extended: ["`","'"]; extendedShifted: ["`","\""] }
            CharKey { label: ":"; shifted: ";"; }
            CharKey { label: "["; shifted: "{"; }
            CharKey { label: "]"; shifted: "}"; }
        }

        Row {
            anchors.horizontalCenter: parent.horizontalCenter
            spacing: 0
            CharKey { label: "q"; shifted: "Q"; extended: ["1"]; extendedShifted: ["1"]; leftSide: true }
            CharKey { label: "w"; shifted: "W"; extended: ["2"]; extendedShifted: ["2"] }
            CharKey { label: "e"; shifted: "E"; extended: ["3","è","é","ê","ë","€"]; extendedShifted: ["3","È","É","Ê","Ë","€"] }
            CharKey { label: "r"; shifted: "R"; extended: ["4"]; extendedShifted: ["4"] }
            CharKey { label: "t"; shifted: "T"; extended: ["5","þ"]; extendedShifted: ["5","Þ"] }
            CharKey { label: "y"; shifted: "Y"; extended: ["6","ý","¥"]; extendedShifted: ["6","Ý","¥"] }
            CharKey { label: "u"; shifted: "U"; extended: ["7","û","ù","ú","ü"]; extendedShifted: ["7","Û","Ù","Ú","Ü"] }
            CharKey { label: "i"; shifted: "I"; extended: ["8","î","ï","ì","í"]; extendedShifted: ["8","Î","Ï","Ì","Í"] }
            CharKey { label: "o"; shifted: "O"; extended: ["9","ö","ô","ò","ó"]; extendedShifted: ["9","Ö","Ô","Ò","Ó"] }
            CharKey { label: "p"; shifted: "P"; extended: ["0"]; extendedShifted: ["0"]; rightSide: true }
        }

        Row {
            anchors.horizontalCenter: parent.horizontalCenter
            spacing: 0
            CharKey { label: "a"; shifted: "A"; extended: ["ä","à","â","ª","á","å","æ"]; extendedShifted: ["Ä","À","Â","ª","Á","Å","Æ"]; leftSide: true }
            CharKey { label: "s"; shifted: "S"; extended: ["ß","$"]; extendedShifted: ["$"] }
            CharKey { label: "d"; shifted: "D"; extended: ["ð"]; extendedShifted: ["Ð"] }
            CharKey { label: "f"; shifted: "F" }
            CharKey { label: "g"; shifted: "G" }
            CharKey { label: "h"; shifted: "H" }
            CharKey { label: "j"; shifted: "J" }
            CharKey { label: "k"; shifted: "K" }
            CharKey { label: "l"; shifted: "L"; rightSide: true }
        }

        Row {
            anchors.horizontalCenter: parent.horizontalCenter
            spacing: 0
            ShiftKey {}
            CharKey { label: "z"; shifted: "Z" }
            CharKey { label: "x"; shifted: "X" }
            CharKey { label: "c"; shifted: "C"; extended: ["ç"]; extendedShifted: ["Ç"] }
            CharKey { label: "v"; shifted: "V" }
            CharKey { label: "b"; shifted: "B" }
            CharKey { label: "n"; shifted: "N"; extended: ["ñ"]; extendedShifted: ["Ñ"] }
            CharKey { label: "m"; shifted: "M" }
            ActionKey { label: "⌫"; shifted: "⌫"; action: "backspace"; noMagnifier: true; skipAutoCaps: true }
        }

        Item {
            anchors.left: parent.left
            anchors.right: parent.right
            height: panel.keyHeight + Device.row_margin

            SymbolShiftKey { id: symShiftKey; anchors.left: parent.left; height: parent.height }
            LanguageKey    { id: languageMenuButton; anchors.left: symShiftKey.right; height: parent.height }
            CharKey        { id: commaKey; label: ","; shifted: ","; extended: ["'","\"",";",":","@","&","(",")"];  extendedShifted: ["'","\"",";",":","@","&","(",")"];  anchors.left: languageMenuButton.right; height: parent.height }
            SpaceKey       { id: spaceKey; anchors.left: commaKey.right; anchors.right: dotKey.left; noMagnifier: true; height: parent.height }
            CharKey        { id: dotKey; label: "."; shifted: "."; extended: ["?","-","_","!","+","%","#","/"]; extendedShifted: ["?","-","_","!","+","%","#","/"]; anchors.right: enterKey.left; height: parent.height }
            ActionKey { id: enterKey; label: "↵"; shifted: "↵"; action: "return"; noMagnifier: true; skipAutoCaps: true; anchors.right: parent.right; height: parent.height }
        }
    }
}

maliit再起動:

pkill maliit-keyboard

5. pacman hook でアップデート保護

カスタムファイルをバックアップ:

sudo mkdir -p /etc/maliit-keyboard-custom
sudo cp /usr/lib/maliit/keyboard2/languages/en/Keyboard_en.qml \
        /etc/maliit-keyboard-custom/Keyboard_en.qml

hook作成:

sudo mkdir -p /etc/pacman.d/hooks
sudo tee /etc/pacman.d/hooks/maliit-keyboard-layout.hook << 'EOF'
[Trigger]
Operation = Upgrade
Type = Package
Target = maliit-keyboard

[Action]
Description = Restoring custom maliit keyboard layout...
When = PostTransaction
Exec = /bin/cp /etc/maliit-keyboard-custom/Keyboard_en.qml /usr/lib/maliit/keyboard2/languages/en/Keyboard_en.qml
EOF

トラブルシューティング

キーボードが出てこない

タスクバーのキーボードアイコンがONになっているか確認。テキストフィールドをタップして呼び出す。

maliit-keyboard が起動しない

ps aux | grep maliit

起動していなければ👇️

pkill maliit-keyboard  # 念のため

kwin が自動起動するはずなのでログアウト→ログインで解決

kwin の InputMethod がリセットされた

cat ~/.config/kwinrc | grep -i inputmethod

maliit になっていなければ再設定👇️

kwriteconfig6 --file kwinrc --group Wayland --key InputMethod \
    /usr/share/applications/com.github.maliit.keyboard.desktop
sudo reboot

システムアップデート後にレイアウトが戻った

hook が動作しているか確認:

cat /etc/pacman.d/hooks/maliit-keyboard-layout.hook
ls /etc/maliit-keyboard-custom/

手動で復元:

sudo cp /etc/maliit-keyboard-custom/Keyboard_en.qml \
        /usr/lib/maliit/keyboard2/languages/en/Keyboard_en.qml
pkill maliit-keyboard

できあがり🎉

AndroidのSSHアプリ風になったけどOK🎉
AndroidのSSHアプリ風になったけどOK🎉


注意事項

  • plasma-keyboard (qt6-virtualkeyboard ベース) は独自レイアウトシステムを持ち QT_VIRTUALKEYBOARD_LAYOUT_PATH無効
  • maliit-keyboard は AUR パッケージ。CachyOS 公式リポジトリには含まれない
  • 日本語入力 (ja_JP) は /usr/lib/maliit/keyboard2/languages/ja/ に別レイアウトあり。同様に編集可能
  • ターミナル起動したらSurface Go 3って書いてあったので修正しました(´・ω・`)
    Surface Go 2じゃなくて3でした(´・ω・`)
    Surface Go 2じゃなくて3でした(´・ω・`)
© 2020 時藤屋 all rights reserved.