====== PocketTerm35 ====== ===== 概要 ===== {{::pocketterm35:pocketterm35.jpg?400|}} [[https://docs.waveshare.com/PocketTerm35|WaveShare]]からリリースされた、Raspberry Pi4/5を組み込む形で動作させる小型PCである 実測で重量**432g** uConsoleが579gなので、75%程の重量となっている Raspberry Pi4 2GBまたは Raspberry Pi5 1GBを組み込んだモデルと、Raspberry Piを含まないベアボーン的なキットがある ベアボーン的なキットについてもリチウムイオンバッテリーパックを含むものと含まないものとがある リチウムイオン電池が含まれる場合、輸入の際に空路が使えないので、時間が余分にかかることは考慮すべきであろう ==== 電池パックについて ==== {{ ::pocketterm35:battery.jpg?400|}} 電池パックは、3.7Vのリチウムイオン電池のセルにPH2.0ヘッダをつければ((ケースに収まるサイズに限るが))なんでもいいようである 但し、極性が一般的なものとは逆になっていることが[[https://ton-technotes.com/blog/2026-04-14-pocketterm35-order-story/|こちら]]で触れられている もちろん、PH2.0ヘッダなので、引っこ抜いて逆にして圧着しなおせばいいのだけれど、最初から極性を逆にしているものも売られているので、容量も含め適切なものを用意したい 写真のように、左が赤(+)で、右が黒(-)になるように配線されていれば大丈夫である 電池としては3.7Vのリチウムイオン電池であればいいが、容量的にはキットに入っているという5000mAhのものでいいだろう デバイスの種別や、SSDの有無などで変わってくるとは思うが、わたしの場合で2h程度は余裕で運用できる感じだ ==== 電源オフ ==== OSをシャットダウンしても、完全に電源が切れるわけではなく、システムはスタンバイ状態になる この状態では本体上部の電池残量のLEDは点灯しており、Raspberry Pi5 もスタンバイ状態の赤LEDが点灯している この状態から復帰するには、Raspberry Pi5を内蔵しているモデルなら、左側のシリコンボタンを押せばいい ただ、スタンバイ状態からだと復帰がすごい速いかというとそうでもないうえに、そこそこ電池を食うのであまりこのモードの意義は感じない この状態から完全に電源を切るには、本体上部の電源ボタンをダブルクリックすればいい 電池残量表示のLEDが消え、Raspnerry Pi5のインジケータも消える ===== このデバイスが向いている人・向かない人 ===== ==== 向いている人 ==== * 80x24程度の端末内ですべてを完結できる人 * オールドPCのエミュレータで遊びたい人 * 小さなデバイスならどんな苦労もいとわない人 Linuxのターミナルとviなどのシンプルなエディタで生活できる人、オールドPCのエミュレータで遊びたい人には向いているといえる これは電卓といえば電卓アプリを立ち上げたりせず、bc -l と入力したり irb と入力したりする、端末に閉じて生きられる人のためのデバイスである その場合でも、Raspberry Piを含むキット((Paspberry Pi4 2GBまたはRaspberry Pi5 1GB))はメモリが少なすぎてLinuxといえどデスクトップ環境を運用するのには向いていないので、Raspberry Piは自分で適切なモデルを買って入れるべきだろう ==== 向かない人 ==== * 本格的なデスクトップ端末を期待している人 * 変態キーボード配列は受け入れられない人、JIS配列以外は気持ち悪くて使えない人 * サーバとして運用したい人 * 老眼が進行著しい人 * ガンガンゲームを楽しみたい人 * ARM64とx64の違いがイマイチ判ってない人 * DTBOがなんなのかわかってない人 画面サイズ640x480しかないので、汎用的なポータブルデバイスを欲しい人には向いていない 画面サイズの制約が大きく、ウェブブラウジングやメールの読み書きも実用的でないし、VSCodeのようなエディタすら運用するのが難しい しかもキーボードが、サイズ的な制約で仕方ないこととはいえ、きわめて、標準的ではない配列となっている ベースはUS配列なので、普段からUS配列を使っている人であればそれでもまだなじめる可能性がなくはないが、JIS配列でないとダメな人にはかなり厳しいだろう いくつかクセつよポイントがあるが、非常に致命的なのはシフトキーが左にしかないことだろう 右親指でシフトキーを押して、左親指で他のキーを全部カバーすることになるかと思うが、ちょっと癖が強すぎて泣けてくる またサーバとして使用することを考えている場合も全く向いていない サーバならば、リモートからアクセスできるようにしてあるのが普通で、ローカルにキーボードとモニターを常備しておく必要はないし、であるならば、もっと廉価なケースがあるのだ なのでサーバにしたいなら、シンプルなケースに入れて、モニターもキーボードもなしで運用する方がいいだろう ゲームはできなくはないが、Raspberry Pi4/5のGPUは決してパワフルなものではないので、3Dでごりごり動くようなゲームコンソールとして使いたいというのであれば、それは無理 Steam は動くのでカジュアルなゲームであれば十分に遊べるのだが 一応、Ubuntu なりの Linux ディストリビューションはおおむね ARM64だろうが x64だろうが動くは動く 物好きな向きなら Windows11さえインストールすること自体は可能だろう OSとそれに付随するパッケージだけで暮らすのであれば問題はないが、サードパーティ製のパッケージやアプリを期待していると ARM64であることがデメリットとなりうる たとえば、Google Chromeや Slackなどのアプリは x64版は存在しているが AMR64版は存在しない Chromiumやブラウザ版の Slackで代替するしかない また、Hazkey などのオープンソースのものでさえ、公式が提供しているのは x64版だけで ARM64版が欲しければ自力でビルドする以外に方法はない 少なくとも自力でこういったものをビルドできる程度の知識と経験を持っているか、それらを今から習得したいという熱意のあるユーザでないと、この端末、というか Raspberry Piを使うのには向いていない そいうのがいやだなあ、と思うなら x64のSBCなり小型PCなりを検討するべきだ 何なら値段もうっかりするとそっちの方が安かったりもするし ===== 設定など ===== ==== 組立て ==== {{ ::pocketterm35:build.jpg?400|}} 組立てはそれほど難しくはない 液晶、キーボード、そしてシステム基板などはすべて組付けられた状態になっており、やることは基板と組み込む Raspberry Piとを接続し、バッテリーを接続するだけのことである [[https://docs.waveshare.com/PocketTerm35/Assembly-Guide|動画付きの解説]]があるので、その通りにやればいい ねじは付属のドライバーで開け閉めできるし、プラグもフレキ以外は刺さる向きにしか刺せない USB用の内部配線は解説ページとケーブル色が違う場合もあるが、刺さるように刺せばいいので色は気にしなくていい スピーカーケーブルも刺さる向きで刺せばいい 逆に電池だけは色をしっかり確認すること フレキは端子面(金色の側)がコネクタに対して下を向くように差し込めばいい 裏ブタは結構タイトで一番の難易度だと思う 特にPi5の場合はシリコンゴムのボタンを側面につけるのだが、これは本体に固定されるわけではなく、単にそこにはまっているだけで少し傾けただけでずれる、落ちるなので、裏ブタを取り付けるときにはボタンのある側面を下にして横向きで作業するといいだろう このボタンは見た目にちゃんとついているようでもわずかにずれていて、Pi5側のボタンを押し込んだ状態になっていたりするので、裏ブタを閉めた後で、押し込んできちんとボタンの押下、解放ができているか確認すること なお、写真にあるように、Pi5に純正ヒートシンクをマウントして、その上に M2 HAT+を乗せてM.2 2230のNVMe SSDを乗せた状態でもきちんとケースに収まるので、SSD運用を考えている場合はこの組み合わせなら大丈夫ということを記しておく((もちろんほかの組み合わせでもできるものもあるだろうが、試してないので何とも言えない)) ==== ソフトウェア設定 ==== 基本的にほとんどそのままのRaspberry Pi向けのOSが動くが、タッチパネル周りだけは DTBOを持ってきて入れたうえで、config.txtに追記する必要がある ダウンロードリンクは[[https://docs.waveshare.com/PocketTerm35/Software-Guide|PocketTerm35のWiki]]にある ここで、config.txtの修正やDTBOファイルの配置について書かれているが、すべてのディストリビューションで当てはまるわけでもないようなので、適宜自分の環境に合わせて適用してほしい 以下は Ubuntu26.04LTSの場合である config.txt は /boot の下、またはWindowsなどでTFカードをマウントして編集する場合には、TFカードのルートフォルダにある 内容はOSによって微妙に異なるが、要はpi4には waveshare-35dpi-4bを pi5には waveshare-35dpi-5bをそれぞれdtoverlayに指定してやればOKである waveshare-35dpi-[345]b.dtbo は /boot/firmware/current/overlay の下においてやればいい **カーネルなどの更新を行った場合、新しく /boot/firmware/new/overlayが作られ、その下には waveshare-35dpi-[345]b.dtbo はないので、改めてこれらのファイルをコピーする必要がある** リブートする前なら /boot/firmware/new/overlay の下に、リブート後なら /boot/firmware/current/overlayの下にそれぞれコピーする((いずれも Ubuntu26.04LTSの場合)) [all] os_prefix=current/ [tryboot] os_prefix=new/ [all] arm_64bit=1 kernel=vmlinuz cmdline=cmdline.txt initramfs initrd.img followkernel # Enable the audio output, I2C and SPI interfaces on the GPIO header. As these # parameters related to the base device-tree they must appear *before* any # other dtoverlay= specification dtparam=audio=on dtparam=i2c_arm=on dtparam=spi=on dtparam=watchdog=on # Comment out the following line if the edges of the desktop appear outside # the edges of your display disable_overscan=1 # If you have issues with audio, you may try uncommenting the following line # which forces the HDMI output into HDMI mode instead of DVI (which doesn''t # support audio output) #hdmi_drive=2 # Enable the KMS ("full" KMS) graphics overlay, leaving GPU memory as the # default (the kernel is in control of graphics memory with full KMS) dtoverlay=vc4-kms-v3d disable_fw_kms_setup=1 # Autoload overlays for any recognized cameras or displays that are attached # to the CSI/DSI ports. Please note this is for libcamera support, *not* for # the legacy camera stack camera_auto_detect=1 display_auto_detect=1 # Config settings specific to arm64 dtoverlay=dwc2 [pi5] max_framebuffers=2 arm_boost=1 dtoverlay=waveshare-35dpi-5b dtoverlay=dwc2,dr_mode=host [pi4] max_framebuffers=2 arm_boost=1 dtoverlay=wavewshare-35dpi-4b dtoverlay=dwc2,dr_mode=host [pi3+] # Use a smaller contiguous memory area, specifically on the 3A+ to avoid an # OOM oops on boot. The 3B+ is also affected by this section, but it shouldn''t # cause any issues on that board dtoverlay=vc4-kms-v3d,cma-128 [pi02] # The Zero 2W is another 512MB board which is occasionally affected by the same # OOM oops on boot. dtoverlay=vc4-kms-v3d,cma-128 [cm4] # Enable the USB2 outputs on the IO board (assuming your CM4 is plugged into # such a board) dtoverlay=dwc2,dr_mode=host [all] ==== 電流不足といわれたら ==== {{ ::pocketterm35:psu_max_current_5000.png?400|}} 5V 5Aが供給できない、というような警告が出る場合がある そんな場合は、[[https://docs.waveshare.com/PocketTerm35/FAQ|本家のFAQ]]にあるように、rpi-eeprom-configを使って、最大電流量に5000mAを指定してやればいい $ sudo rpi-eeprom-config --edit とすると、nanoが起動して、EEPROMの設定が表示される 既にPSU_MAX_CURRENTの項目があれば値を5000に、なければ末尾に PSU_MAX_CURRENT=5000 のように、追記して保存してnanoを終了する((Ctrl+X で変更内容を保存するか聞かれるので Y と答えて、ファイル名等はそのままENTERを押せば保存終了する)) あとは再起動すれば、電流量不足の警告は出なくなるはずである ===== アプリなど ===== ==== Ubuntu26.04LTS ==== OSとして Ubuntu 26.04LTSを選択した AIなどに聞けば「その画面サイズなら LUbuntu 使えば?」なんていわれるが、今、デスクトップ環境は X11から Waylandへの移行が進んでおり、Ubuntu 26.04LTS上の GNOMEは Waylandに完全移行しており、X11ベースのLUbuntuではパッケージの一部で問題が起きるかもしれないし起きないかもしれない そういうめんどくさい思いを抱えながら運用するくらいなら、Ubuntuでいいんじゃないか? ということで Ubuntuにした {{ ::pocketterm35:desktop_config.png?400|}} ドック((画面左端のタスクバー的なもの))は「自動的に隠す」設定にしておくことで邪魔にならない 必要に応じて [CMD] キーを押せば呼び出せるので実運用上は全く問題にならない 普段は隠れてて、必要な時にだけ出てくるなら、LUbuntuのLXDEよりGNOMEのドックの方がいい 何故なら、LXDEのバーは画面に占める割合が小さい分、ボタンやメニューも小さいのだ 指タップを前提にするなら、むしろGNOMEドックの大きさがちょうどいいくらいなのだ DevTerm を買った頃は Armbian ベースのUbuntuは設定アプリのUIが、縦480pxに全く収まっておらず、非常に不便だったのだが、純正の設定アプリのほとんどは縦が480pxでも収まるようにデザインが進化しており、Fcitx5など一部の設定以外では特に困ることはなかったことも追記しておく ==== GNOME Terminal ==== {{ ::pocketterm35:gnome-terminal.png?400|}} Ubuntuデスクトップを入れたら勝手に入ってくる端末ソフト 特にすごい機能もなければ、足りない機能もないというような、標準的なGNOMEの端末ソフト しかし、これが PocketTerm35を使う上で中心となるもの 古くからのX11ユーザはテキストを選択しただけでコピーバッファに入ることを想定しているかもしれないが、Ctrl+C を押さないと入らない((入っているときもあるのでこの辺の挙動がちょっと謎だが)) ==== vim ==== {{ ::pocketterm35:vim.png?400|}} いわずと知れた UNIX系システムの鉄板スクリーンエディタ コマンドモードとエディットモードとを行き来しながらファイルを編集する このエディタのキモは正規表現を使った強力な検索と編集の機能や一括大量削除などの繰り返し処理にあり、新規にファイルを書き起こすよりも、既存のファイルを修正したりするのに威力を発揮する 大体どのディストリビューションでも当たり前の顔して入っているので、使い方を覚えておけば、UNIX系システムでトラブル対応する際などに力を発揮すること間違いなし カーソルの移動をコマンドモード内で h, j, k, lで行うのに慣れるには、RogueやNetHackをプレイすればいいが、やりすぎて、u,i,n,m で斜めに移動しそうになったりすると末期的である ==== Microsoft Edit ==== {{ ::pocketterm35:microsoft-edit.png?400|}} マイクロソフト謹製の端末ソフト用スクリーンエディタ Rustで書かれていて、サクサク動いて、viはちょっととつきにくいなあという人にはお勧め [[https://github.com/microsoft/edit.git|GitHub]]にて公開されているソースを持ってきてビルドするだけ nanoでいいんじゃないのか? という声が聞こえてきそうではあるが、Windowsでも全く同じインターフェイスで使えるので、あっちこっちで微妙に違う、という問題に遭遇しないで済みそうという点でアドバンテージがある $ sudo apt install -y rustup $ rustup toolchain install stable $ git clone https://github.com/microsoft/edit.git $ cd edit $ cargo build --release ==== Codex ==== {{ ::pocketterm35:codex.png?400|}} 今どきの端末でAIなしではいられない とはいえ、GUIベースでは画面が小さすぎて死んでしまう ということで、OpenAI謹製の CLI である codexを使う 多くの情報があるので、今更感もあると思うが、tmuxをインストールしておくと、tmuxのセッションを経由して、codexが直接端末を操作してくれたりするので、その便利さたるや、GUIを凌駕しているといっても過言ではない インストールは次のコマンドで $ curl -fSSL https://chatgpt.com/codex/install.sh | sh $ . ~/.bashrc $ codex インストール後、初回起動時に、ChatGptのアカウントでのログインを求められる ブラウザが開くので、この初回の部分だけGUIを使うがあとは基本的に端末の中に閉じる なお、会話を続けたい場合は codex resumeで起動すれば、以前の会話のリストから選んで復帰できる ==== Claude Code ==== {{ ::pocketterm35:claudecode.png?400|}} AnthropicのAIである 使い方の点では codexとは大きな違いはない インストールも大体似たようなものである $ curl -fsSL https://claude.ai/install.sh | bash $ . ~/.bashrc $ claude 世間に大きな衝撃を与え、米政府によって禁止されてしまったFable5こそ現状では使えないが、Opus4.8なども使えるので、codexよりパワフルかもしれない なお、こちらのCLIは、Claude Pro以上のプランを購入しないと使えないので、無料でどうにかしたい勢は codex一択となる ==== Quasi88 ==== {{ ::pocketterm35:quasi88.png?400|}} NEC PC-8801のエミュレータ 入手は[[https://www.eonet.ne.jp/~showtime/quasi88/download.html|こちら]]から PC-8801のエミュレータは吐いて捨てるほど存在しているのだが、Quasi88が PocketTerm35 用としては一歩抜き出ている部分がある それは 0.7以降で追加されたツールバーの存在である タッチ操作を前提として考えたときに、このツールバーは非常に強力なのである フルスクリーンモードで 640x480の画面にぴったり収まるサイズも素晴らしい ==== PC6001VX ==== {{ ::pocketterm35:pc6001vx.png?400|}} NEC PC-6001シリーズのエミュレータ 入手は[[https://github.com/eighttails/PC6001VX|こちら]]から ビルドにはQt6 が必要であるが、Ubuntu 26.04LTSには Qt6.10.2のパッケージがあるので特に問題はないと思われる このエミュレータの秀逸なところは、キーマップが、キーボードに従うということである 何をいっているのかというと、多くの日本製PCのエミュレータはJISキーボードを前提としていて、キー配列がUS配列と合致しないのだ 具体的には Shift + 2を押すと '"'が入力されるというような話 まあ、単にずれているだけなら大きな問題ではないが、JIS配列の方がキーが多い関係で、マッピングが存在しない文字がエミュレータによって発生することがあるのだが、PC6001VXは刻印通りのキーが渡されるため、入力できない文字のようなものがない PocketTerm35は変態キー配列だがそのベースは当たり前のように英字配列だし、いくつかのキーはFnキーとのコンビネーションとなるため、エミュレータ側の対応いかんでは何が入力されるのか分かったものではないのだ なので、この実装は非常にありがたいのである $ git clone https://github.com/eighttails/pc6001vx $ cd pc6001vx $ qmake6 PC6001VX.pro $ make -j4 ==== OpenMSX ==== {{ ::pocketterm35:openmsx.png?400|}} MSXエミュレータ 入手は[[https://openmsx.org/|こちら]]から ビルドはできるし動作もするが、このエミュレータで遊ぶために必須となるのが「マウス」である 洗練されたメニューバーは細く、指でタップしようとしてもうまくタップできないのだ また、メニューを非活性化しないとMSX側を操作できないが、この操作はマウスの右クリックに割り当てられているので、指だけでやるのはなかなか難しい マウスでクリックすることで問題なく使えるので、マウスを接続する前提でならPocketTerm35でも遊ぶことができる 逆にマウスがないと画面を眺めるだけに近い状態になってしまう ==== NP2改 ==== {{ ::poketterm35:np21kai.png?400|}} PC-9801/9821エミュレータ 入手は[[https://github.com/AZO234/NP2kai|GitHub]]から BIOSやフォントは ~/.config/wxnp21kai の下に bios.rom, bios9821.rom, font.bmp のような名前で置く すべて小文字で置くこと bios9821.romは PC-9821から持ってくる場合のみ font.bmpはなければ font.tmpというファイルが生成されてそれを使うのでなければないでなんとかなる なんならBASICを使わないなら、NECチェックを外せるなら、bios.romも多分いらない 最新版はWX Widgetsを使用しているがα版の位置づけのようである とりあえず git で持ってきたらビルドする $ git clone https://github.com/AZO234/NP2kai $ cd NP2kai $ cmake -S . -B build_wx -DBUILD_WX=ON -DUSE_NETWORK=ON -G Ninja $ cmake --build build_wx -j4 ただし、このままビルドすると多分普通の人には使い物にならないものが出来上がる キーマップが正しくないのだ 最初US配列との食い合わせが悪いのかと思ったが、さにあらず wx/kbtrans.cpp の中にあるASCII文字列のテーブルが全くどのキーボードともあってないのだ なので、とりあえず、それっぽく動くようにマッピングを修正した パッチを下に置くので、適宜パッチをあてるなどして使ってもらえればいいと思う --- wx/kbtrans.cpp.orig 2026-06-12 12:02:45.278737976 +0900 +++ wx/kbtrans.cpp 2026-06-12 21:45:49.464169487 +0900 @@ -33,14 +33,14 @@ { WXK_F8, 0x69, 0x69 }, { WXK_F9, 0x6a, 0x6a }, { WXK_F10, 0x6b, 0x6b }, - { WXK_F11, 0x73, 0x73 }, /* VF1 */ - { WXK_F12, 0x74, 0x74 }, /* VF2 */ + { WXK_F11, 0x52, 0x52 }, /* VF1 */ + { WXK_F12, 0x53, 0x53 }, /* VF2 */ /* Special keys */ { WXK_ESCAPE, 0x00, 0x00 }, { WXK_RETURN, 0x1c, 0x1c }, { WXK_BACK, 0x0e, 0x0e }, { WXK_TAB, 0x0f, 0x0f }, - { WXK_SPACE, 0x35, 0x35 }, + { WXK_SPACE, 0x34, 0x34 }, /* Cursor keys */ { WXK_UP, 0x3a, 0x3a }, { WXK_DOWN, 0x3d, 0x3d }, @@ -64,10 +64,10 @@ { WXK_NUMPAD_DIVIDE, 0x3e, 0x3e }, { WXK_NUMPAD_DECIMAL, 0x3a, 0x3a }, /* Insert/Delete/Home/End/PageUp/PageDown */ - { WXK_INSERT, 0x3e, 0x3e }, - { WXK_DELETE, 0x46, 0x46 }, - { WXK_HOME, 0x3f, 0x3f }, - { WXK_END, 0x3d, 0x3d }, + { WXK_INSERT, 0x38, 0x38 }, + { WXK_DELETE, 0x39, 0x39 }, + { WXK_HOME, 0x3e, 0x3e }, + { WXK_END, 0x3f, 0x3f }, { WXK_PAGEUP, 0x36, 0x36 }, { WXK_PAGEDOWN, 0x37, 0x37 }, /* Modifiers */ @@ -80,18 +80,18 @@ /* ASCII key mappings (0x20-0x7e) for 106-key layout */ static const UINT8 ascii_to_pc98_106[0x60] = { /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ -/* 20 */ 0x35, NC, NC, NC, NC, NC, NC, 0x27, /* ' ( ) ... */ -/* 28 */ NC, NC, NC, NC, NC, 0x0b, NC, 0x0c, /* - . / */ +/* 20 */ 0x34, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* ' ( ) ... */ +/* 28 */ 0x08, 0x09, 0x0a, 0x0b, 0x30, 0x0b, 0x31, 0x32, /* - . / */ /* 30 */ 0x0a, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, -/* 38 */ 0x08, 0x09, 0x26, NC, NC, NC, NC, NC, -/* 40 */ 0x1a, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, -/* 48 */ 0x24, 0x17, 0x25, 0x24, 0x25, 0x23, 0x22, 0x18, -/* 50 */ 0x19, 0x10, 0x13, 0x1e, 0x14, 0x16, 0x2a, 0x11, -/* 58 */ 0x2b, 0x15, 0x29, 0x1b, 0x0d, 0x28, 0x0c, NC, -/* 60 */ NC, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, -/* 68 */ 0x24, 0x17, 0x25, 0x24, 0x25, 0x23, 0x22, 0x18, -/* 70 */ 0x19, 0x10, 0x13, 0x1e, 0x14, 0x16, 0x2a, 0x11, -/* 78 */ 0x2b, 0x15, 0x29, NC, NC, NC, NC, NC, +/* 38 */ 0x08, 0x09, 0x27, 0x26, 0x30, 0x0b, 0x31, 0x32, +/* 40 */ 0x1a, 0x1d, 0x2d, 0x2b, 0x1f, 0x12, 0x20, 0x21, +/* 48 */ 0x22, 0x17, 0x23, 0x24, 0x25, 0x2f, 0x2e, 0x18, +/* 50 */ 0x19, 0x10, 0x13, 0x1e, 0x14, 0x16, 0x2c, 0x11, +/* 58 */ 0x2a, 0x15, 0x29, 0x1b, 0x0d, 0x28, 0x0c, 0x33, +/* 60 */ 0x1a, 0x1d, 0x2d, 0x2b, 0x1f, 0x12, 0x20, 0x21, +/* 68 */ 0x22, 0x17, 0x23, 0x24, 0x25, 0x2f, 0x2e, 0x18, +/* 70 */ 0x19, 0x10, 0x13, 0x1e, 0x14, 0x16, 0x2c, 0x11, +/* 78 */ 0x2a, 0x15, 0x29, 0x1b, 0x0d, 0x28, 0x0c, NC, }; static bool key_states[0x100]; なお、このキーマップだと PocketTerm35のキーボードでは':'が入力できない 0x20 の行を以下のようにすればUS配列で一般的に";"の右隣にある "'"で":"が入力できるようになる /* 20 */ 0x34, 0x01, 0x27, 0x03, 0x04, 0x05, 0x06, 0x27, /* ' ( ) ... */ ==== MZ800 Emulator ==== {{ ::pocketterm35:ropoko.png?400|}} Linuxでも動くMZ800のエミュレータで、2.0.xよりMZ-1500/MZ-700のエミュレータもビルド可能となった 旧バージョンのようにMZ-700のROMにパッチあてしたものを与えなくても使用可能である ただし、2.0.4-previewの時点では、ビルドはできてもクラッシュする codexに調査させてパッチをあてたものならば動いた ソースは[[https://github.com/michalhucik/mz800emu|GitHub]]にて公開されている パッチをあててビルドすると、mz800emu, mz700emu-ntsc, mz700emu-pal, mz1500emuの四種類ができる mz700emu-ntsc だけ試してみてあるが、ほかも動くのではないかと思う なお、ROMは日本語版にしないとキーボードもフォントもおかしな状態になるので注意 $ git clone https://github.com/michalhucik/mz800emu.git $ cd mz800emu $ patch -p1 diff --git a/src/iface-video/sdlapp/sdlapp_imgui_video.cpp b/src/iface-video/sdlapp/sdlapp_imgui_video.cpp index 64f2e4e..5508c36 100644 --- a/src/iface-video/sdlapp/sdlapp_imgui_video.cpp +++ b/src/iface-video/sdlapp/sdlapp_imgui_video.cpp @@ -41,7 +41,7 @@ typedef struct SdlappMyImGuiVideo_t { SDL_Surface *surface; - SDL_TimerID update_title_timer; + guint update_title_timer; Uint64 lastPictureTime; int TARGET_FPS; int FRAME_TIME; @@ -153,14 +153,18 @@ static void sdlapp_myimgui_video_set_colormap(uint32_t *colormap) video_sdl3_set_surface_colormap(colormap, g_sdlapp_myimgui_video->surface); } -static Uint32 sdlapp_mygui_video_update_status_line(void *userdata, SDL_TimerID timerID, Uint32 interval) +static gboolean sdlapp_mygui_video_update_status_line(guint timer_id, gpointer user_data) { - (void)timerID; - SDL_Window *window = (SDL_Window *)userdata; + (void)timer_id; + SDL_Window *window = (SDL_Window *)user_data; + if (!window) + { + return FALSE; + } char *title_text = iface_video_create_window_title_text(); SDL_SetWindowTitle(window, title_text); g_free(title_text); - return interval; + return TRUE; } static gboolean sdlapp_myimgui_video_init(void) @@ -308,7 +312,7 @@ static gboolean sdlapp_myimgui_video_init(void) // protože SetForegroundWindow selže když foreground drží konzole) // Nastavení titulku okna - g_sdlapp_myimgui_video->update_title_timer = SDL_AddTimer(IFACE_VIDEO_UPDATE_WINDOW_TITLE_INTERVAL_MS, sdlapp_mygui_video_update_status_line, win->sdl_window); + g_sdlapp_myimgui_video->update_title_timer = sdlapp_add_timer(g_sdlapp, IFACE_VIDEO_UPDATE_WINDOW_TITLE_INTERVAL_MS, sdlapp_mygui_video_update_status_line, win->sdl_window); sdlapp_myimgui_video_reset_fps_timer(); @@ -326,7 +330,8 @@ static void sdlapp_myimgui_video_exit(void) { if (g_sdlapp_myimgui_video->update_title_timer) { - SDL_RemoveTimer(g_sdlapp_myimgui_video->update_title_timer); + sdlapp_remove_timer(g_sdlapp, g_sdlapp_myimgui_video->update_title_timer); + g_sdlapp_myimgui_video->update_title_timer = 0; }; if (g_sdlapp_myimgui_video->surface) @@ -337,6 +342,14 @@ static void sdlapp_myimgui_video_exit(void) g_free(g_sdlapp_myimgui_video); g_sdlapp_myimgui_video = NULL; }; + if (!headless) + { + if (g_gui && g_gui->win && g_gui->win->sdl_window && g_gui->win->gl_context) + { + SDL_GL_MakeCurrent(g_gui->win->sdl_window, g_gui->win->gl_context); + } + imgui_images_destroy_all(); + }; if (headless && g_gui) { /* Stub g_gui byl alokovaný v init headless větvi - free, ne destroy @@ -344,11 +357,6 @@ static void sdlapp_myimgui_video_exit(void) g_free(g_gui); }; g_gui = NULL; - sdl3_backend_video_quit(); - if (!headless) - { - imgui_images_destroy_all(); - }; } static SDL_Window *getSDL_Window_by_name(const char *name) diff --git a/src/iface/iface_video.c b/src/iface/iface_video.c index 278f66f..a040743 100644 --- a/src/iface/iface_video.c +++ b/src/iface/iface_video.c @@ -107,6 +107,15 @@ void iface_video_exit(void) if (!g_iface_video) return; + if (g_iface_video_callbacks->exit) + { + g_iface_video_callbacks->exit(); + } + else + { + WARN("Exit callback is not set!\n"); + }; + if (g_iface_video->redraw_full_screen_request_rwlock) { APP_RWLOCK_DESTROY(g_iface_video->redraw_full_screen_request_rwlock); @@ -129,15 +138,6 @@ void iface_video_exit(void) g_free(g_iface_video); g_iface_video = NULL; - - if (g_iface_video_callbacks->exit) - { - g_iface_video_callbacks->exit(); - } - else - { - WARN("Exit callback is not set!\n"); - }; } char *iface_video_create_window_title_text(void) diff --git a/src/ui-imgui/bootstrap/myimgui.cpp b/src/ui-imgui/bootstrap/myimgui.cpp index f1cbd30..9ae5b44 100644 --- a/src/ui-imgui/bootstrap/myimgui.cpp +++ b/src/ui-imgui/bootstrap/myimgui.cpp @@ -260,6 +260,11 @@ static gboolean myimgui_init_for_sdl3_opengl(SdlAppWindow *win, gpointer user_da SDLAPP_ERROR("Failed to get GL context"); return false; }; + if (!SDL_GL_MakeCurrent(sdl_window, gl_context)) + { + SDLAPP_ERROR("SDL_GL_MakeCurrent failed: %s", SDL_GetError()); + return false; + }; // Setup Dear ImGui context IMGUI_CHECKVERSION(); @@ -597,6 +602,11 @@ void myimgui_render_cb(SdlAppWindow *win, gpointer user_data) } else if (win->renderer_type == SDLAPP_RENDERER_OPENGL) { + if (!SDL_GL_MakeCurrent(win->sdl_window, win->gl_context)) + { + SDLAPP_ERROR("SDL_GL_MakeCurrent failed: %s", SDL_GetError()); + return; + }; ImGui_ImplOpenGL3_NewFrame(); } else @@ -665,6 +675,10 @@ void myimgui_destroy_cb(SdlAppWindow *win, gpointer user_data) } else if (win->renderer_type == SDLAPP_RENDERER_OPENGL) { + if (win->sdl_window && win->gl_context) + { + SDL_GL_MakeCurrent(win->sdl_window, win->gl_context); + } ImGui_ImplOpenGL3_Shutdown(); } else ==== Steam ==== {{ ::pocketterm35:steam.png?400|}} ARM64 UbuntuでSteamとか、って思っているかもしれないが、ゲームコンソールっぽいボタンもついているのだから、試してみる もちろん、っぽいだけで、現状、このボタンはただのカーソルキーと英字キーだ なので、すべてのゲームが快適に遊べるわけではない それでも、それなりのキーアサインのゲームなら問題なく遊べる インストールは snapから $ sudo snap install steam このスクリーンショットは日本語になっているが、設定をただ「日本語」にしてもフォントがないのか、□が表示されていた 原因をまじめに突き止めてもよかったのだが、codexを起動して「steamの設定を日本語にしたが、フォントがないのか□が表示されてしまう」とプロンプトしたらあっという間に設定をなおしてくれたので、どうやったのかはあまりちゃんと把握していないが、とにかく日本語が表示されるようになった なので、同じトラブルに見舞われた人がいたら、codex((claude(Opus4.8)は解決できなかったのでcodex一択である))に相談することをお勧めする {{ ::pocketterm35:xegraderplus_prod.png?400|}} なお、テストプレイのために XeGrader plusを導入して遊んでみた カーソルキーとスペースキーで操作できるので、この端末で遊ぶのにはもってこいだ 文字も十分可読サイズで、プレイするのには全く問題なかった ただし、アクションゲームは(も)うまくはないので、ガチプレイに耐えられるかどうかは保証できない 起動時に、x64をARM64に変換するからなのか、起動時にはちょっと時間がかかるし、ウィンドウが消えている時間がそこそこ長いので本当に動いているか不安になるかもしれないが、大体動いているので、気長に待って吉である ==== Wireguard(VPN) ==== UbuntuならばWireguard VPNで自宅内へもアクセスしたい [[https://tailscale.com/|Tailscale]]というフリーのVPNもあるので、自宅内にサーバおくとかめんどくさいな、という向きはそっちでもいいと思う ただ、わたしは、多少管理の手間があってもWireguardの方がよかったと感じている 自宅内でホスティングしているwireguardの方がスループットが出るし、接続も安定している Tailscaleだと、なんかしょっちゅうセッションが切れている印象 なので、設定してあるとめちゃ便利 設定については [[wireguard|WireGuard]] を参照のこと ===== あると便利な周辺機器 ===== ==== マウス ==== {{ ::pocketterm35:penmouse.jpg?400|}} 基本的には大半のアプリをフルスクリーンで運用するので、あまり細かい作業はないため、大体のことは指タップで済む が、一部のアプリ((OpenMSXなど))ではUIに細かいウィジェットが使われている関係で、指タップではうまく操作できなかったりする そんなときはやはりマウスがあると便利である 携行することを考えれば、携行性が高いものがよいだろう 写真は、[[https://direct.sanwa.co.jp/ItemPage/400-MAWB231BL|サンワサプライのペンマウス]]で、Bluetoothまたは 2.4GHzの無線でUSBドングル経由で接続できる サイズ的にコンパクトで、PocketTerm35にちょうどいいと思ったので選んでみた [[pocketterm35#OpenMSX|OpenMSX]]みたいなマウスがないと困るやつを操作するときにはもちろんだが、意外といいなと思ったのが、Terminal の上で文字列を選択してコピペする操作 X11などでおなじみの、左クリックしてマウスをぐりぐりっとドラッグして文字列を選択し、それを中ボタンでペーストするアレである [[pocketterm35#gnome_terminal|GNOME Terminal]]の場合は上でも書いたが、「コピー(Ctrl+C)」「ペースト(Ctrl+V)」になるが、選択は左クリックしながらのドラッグになる この操作が、このペンマウスの場合は、カーソルを文字列の開始点に合わせたら、ペン先を押し込んで、そのままラインマーカで線を引くように動かすことになる この動作がことのほか良い感じでちょっと感心した 選択したら右クリックして出てきたメニューから「コピー(Ctrl+C)」を選べばいい ただ、逆に、1ワードだけ選択するときに使うダブルクリックはこのペンマウスだとちょっとやりにくいということも併せて記しておく((単に私が下手なだけで、練習したらできるようになりそうな気もする)) どちらを多用するのかによっては使い勝手の評価が分かれるのではないかと思う ==== キーボード ==== キー配列が変態的なため本格的にタイプしたければ外付けのキーボードは選択肢としてあると思うが、実用性としては疑問である 理由は単純で、画面が640x480なうえ小さいため、外付けのキーボードを置いた状態で画面を見えるように置くのが難しいという物理的制約があるからである そんな使い方するくらいなら、sshdを動かしておいて、別の画面の大きなちゃんとしたキーボードのついた端末からログインして作業する方がいいだろう ===== 改善されるといい点 ===== ==== 画面サイズ ==== そもそものこのデバイスのサイズ(幅)がRaspberry Piのそれで決まっているように見えるが、横幅を拡大し、より大型のディスプレイを搭載すればより実用性の高いデバイスになると思う 具体的には uConsoleと同程度の5inch 1280x720 が載っていれば、より汎用的に利用できたのにと思う ==== アクションキー ==== {{ ::pocketterm35:actionkeys.jpg?400|}} uConsoleや DevTermでもおなじみゲーム用と思しきキーが PocketTerm35にもあるが、これ、左側はただのカーソルキーだし、右側の A/B/X/Y、さらにはいかにもマウスの左右クリックを想起させるような L/Rボタンに至ってはそれぞれ、'A','B','X','Y','L','R' が割り当てられたただの英字キーである ジョイパッドとして認識されているわけでもないため、ゲームやPCエミュレータからジョイパッドとしても使えなければ、ゲームで一般的に使われる英字キーでもないので、まったく利用価値がない これはジョイパッドとして認識されるようにするか、最低でも、単純な英字キーとは別のものに割り当てるべきだろう これらの制御は RP2040で行われているようなので、ファームウェアを書き換えることで改善できると思われるので、今後の研究対象のひとつだと思っている キーコードの割り当ては、RP2040側にインストールされている CircuitPythonにて行われている 背面のRP2040リセットのボタンをいい感じに連打していると、CIRCUITPYというUSBメモリデバイスが現れることがある((標準のboot.pyによって、CIRCUITPYが出ないようにされているので、いい感じでRESETを連打することで、boot.pyの実行がキャンセルされることがあるので、そのタイミングでCIRCUITPYが現れる。本当にいい感じで連打してとしかいいようがない))ので、それが出てきたら、中の boot.pyや code.pyを変更したり、追加のライブラリを libフォルダーに入れたりしてカスタムすることができる ==== 電池容量のOS側からの検出 ==== 電池容量や充電状態は画面上に搭載された四つの赤色LEDで表示されている だが、OS側からアクセスできるようになっておらず、結果OS側では電池残量に応じた制御を行うことはできない I2Cでもいいので、これが読み出せるようにしてあればいろいろできただろうにと、非常に残念 ==== 死にポートの存在 ==== Raspberry Pi5はHDMIポートを二つ持っているが、一つを本体のモニタにつないで、もう一つは何もつながない状態でケースに封印されてしまっている 外付けのモニターをつなげられるようにすれば、移動中は極小デバイスとして、移動先で外部モニターやキーボードをつないでまあまあな性能のPCとして運用できればより利用価値が高まるのに残念である ==== ポーチがでかすぎる ==== {{ ::pocketterm35:poach.jpg?400 | }}{{ ::pocketterm35:poach2.jpg?400 | }} ポーチ……でかすぎませんか? このポーチで持ち運ぶ気にはちょっとなれない ===== 諸々のこと ===== ==== 配送オプション ==== わたしは待ちきれない人なので、DHLの配送オプションにお金を支払った 6/8発送になって、当初6/11配送と出ていたが、6/9には国内に届き通関まですんで、結局は6/10に届いた 通常配送に比べて一週間程度早いようなので、この一週間に金を支払う価値をみいだせるなら悪いオプションではない なお、DHLは配送日時の変更をおこなえるが、国内に届くタイミングまで待った方がいい わたしの場合、最初変更が可能になった時点では6/11が選べる最速だったが、トラッキングで6/9に国内に入ったのを確認したところで改めて配送日時の変更をおこなったら、当初なかった6/10がオプションに現れた なので、国内に届いたタイミング次第で前倒しできる可能性があるので、慌てて、変更可能になった時点で指定しない方がいいかもしれない ==== SSD ==== {{ ::pocketterm35:ssd.jpg?400|}} SSDに関してはWaveshareでは[[https://www.waveshare.com/pcie-to-m.2-board-e.htm|PCIe TO M.2 Board]]を推奨してるが、写真のように、純正ヒートシンク + M.2 Hat+ でもケースに収まる ただし、純正ヒートシンクをマウントしたうえで、スペーサーをつけて M.2 HAT+を乗せる形になるので、GPIOピンへのアクセスはかなり悪くなる また2230しか乗せられないので、SSDの選択肢も少なくなる PCIe TO M.2 Boardは、ヒートシンク部分の高さが当然低く、SSDの真下にあたる部分の冷却((エアフロー))が、純正ヒートシンクと比べてどのくらい良いのかで、SSDの温度に影響しそうな気もする ただ、ケースとSSDとの間のスペースが大きいので、SSDの方に剣山状のヒートシンクを乗せればいいようにも思う 純正の場合は、写真のように、金属のプレート状のものを乗せるくらいしかできないのでどっちがSSD運用上有利なのかはわからない ==== キーマップ変更 ==== ファームウェアのソースを公開して、って Waveshareに頼んだら、ファームウェアいじらないでもキーマップなら変えられるよ? ……と、いわれた {{ :pocketterm35:code_py.png?400|}} そう、PocketTerm35のファームウェアは [[https://docs.circuitpython.org|Adafruit CircuitPython]]で構成されているようだ つまりは、できること・できないことは Adafruit CircuitPython次第ということだ なお、この部分は大変に長くなるので[[PocketTerm35のキーマップを変更する]]にまとめる まず、作業を始める前に、スクリーンロックをOFFにするかUSBキーボードを用意する スクリーンロックがかかると、CIRCUITPYドライブはアンマウントされてしまうので、万一不具合がある状態だとキー入力ができず、スクリーンロックを解除できず、結果CIRCUITPYを修正できなくなってしまう なので、スクリーンロックをオフにするか、万一に備えてUSBの外付けキーボードを用意しておくことを推奨する やりたいことによって、code.pyを修正すれば済むのか、あるいはboot.pyを修正したり、ほかのライブラリをたしたりしないといけないのかが変わってくる === CIRCUITPYをマウントする === 実はこの操作は運任せである まあ、気分はわかるのだが、Waveshareは、boot.pyの中で、disable_usb_drive()しているので、リセットを押してboot.pyが走る前にいい感じのところで再度リセットがかかれば、CIRCUITPYが現れる この状態を逃さずに、バックアップを作成し作業用のコピーを作成する なお、CIRCUITPYが出たりでなかったりするのは不便なので、ある程度の作業が終わるまでは、boot.pyから``storage.disable_usb_drive()``の行をコメントアウトしてしまうといいかもしれない === キーボードが返すことができるキーコードの範囲で我慢する === 例えば、左シフトであるべき一にTABキーがあって間違って押しちゃうし、大体、右の方のキーとSHIFTを同時押しするのもめんどくさくてやだなあと思っていたとする このTABを普通におしたらシフト、Fnと押したらTABになるようにしたら便利そうだと思ったら、KEY_MAPという配列を書き換えればいい 具体的には、KEY_MAPの定義の row 5を下のように書き換えればいい Keycode.TABをKeycode.SHIFTに、Keycode.SHIFTを Keycode.RIGHT_SHIFTにそれぞれ置き換えただけである 右シフトをRIGHT_SHIFTにする必要があるかどうかはどうでもいいかもしれないが、これで動作がそのようになる 改造も小さくて、typoでもしない限りは、問題は起きないだろう [Keycode.SHIFT, Keycode.CAPS_LOCK, Keycode.MINUS, Keycode.EQUALS, Keycode.SEMICOLON, Keycode.QUOTE, Keycode.COMMA, Keycode.PERIOD, Keycode.RIGHT_SHIFT, None], # row 5 === アクションキーはゲームパッドであるべきだ === ゲームパッドをHIDデバイスとして追加するのはそれほど難しくもないが、めんどくさくはある ゲームパッドは非常に多くの種類が存在してるのだが、PocketTerm35 にあるのは、四方向のデジタル方向キーと、A,B,X,Y,L,R, とSelect, Startの12ボタンになる 方向キーをD Hatとして実装するのか、アナログスティック((だが0か全開かだけ))として実装するのかは難しいところだが、手元にあったSNES風のパッドがアナログスティック実装していたのでそれに倣うことにする == ライブラリの準備 == [[https://github.com/adafruit/Adafruit_CircuitPython_HID/examples/hid_gamepad.py|hid_gamepad.py]]をCIRCUITPYのlibフォルダーに放り込む このとき、15行目あたりにある adafruit_hidを hidに変更する from hid import find_device 本来、CircuitPythonのhidのバンドルライブラリは adafruit_hidなのだが、PocketTerm35のものは hid になっているので、どちらかを変更しないといけないのだが、hidを adafruit_hidにすると code.pyの方に波及するので、ここでは追加ライブラリの hid_gamepad.pyの方を合わせてしまう == Gamepadデバイスの追加 == デバイスを追加する作業はboot.py に手を入れる必要がある ほとんど呪文みたいなものなので、このままコピペすればいいと思う import usb_hid # This is only one example of a gamepad report descriptor, # and may not suit your needs. GAMEPAD_REPORT_DESCRIPTOR = bytes(( 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) 0x09, 0x05, # Usage (Game Pad) 0xA1, 0x01, # Collection (Application) 0x85, 0x04, # Report ID (4) 0x05, 0x09, # Usage Page (Button) 0x19, 0x01, # Usage Minimum (Button 1) 0x29, 0x0A, # Usage Maximum (Button 10) ... サンプルは16ボタンだが参考にしたSNES風ゲームパッドが10ボタンだったので 0x15, 0x00, # Logical Minimum (0) 0x25, 0x01, # Logical Maximum (1) 0x75, 0x01, # Report Size (1) 0x95, 0x10, # Report Count (16) 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) 0x15, 0x81, # Logical Minimum (-127) 0x25, 0x7F, # Logical Maximum (127) 0x09, 0x30, # Usage (X) 0x09, 0x31, # Usage (Y) 0x09, 0x32, # Usage (Z) 0x09, 0x35, # Usage (Rz) 0x75, 0x08, # Report Size (8) 0x95, 0x04, # Report Count (4) 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0xC0, # End Collection )) gamepad = usb_hid.Device( report_descriptor=GAMEPAD_REPORT_DESCRIPTOR, usage_page=0x01, # Generic Desktop Control usage=0x05, # Gamepad report_ids=(4,), # Descriptor uses report ID 4. in_report_lengths=(6,), # This gamepad sends 6 bytes in its report. out_report_lengths=(0,), # It does not receive any reports. ) usb_hid.enable( (usb_hid.Device.KEYBOARD, usb_hid.Device.MOUSE, usb_hid.Device.CONSUMER_CONTROL, gamepad) ) == コードを変更する == PocketTerm35のキーマップは、標準とFnキー押下時の二種類があり、キーマトリックスの配列の形で定義されている 単純にキーボードのキーコードが返ればいいものであれば、単純にキーコードを書いておけばいいが、何か特別な処理をさせたい場合には、CUSTOM_KEYSに負数のコードを定義し、CUSTOM_KEYS_FUNCTION でマッピングされた関数がキーが押されたときに呼ばれるというような動きになっている もともとのマッピングではこの動作でも問題はなかったが、ゲームパッド動作を割り当てようと思うと、押しっぱなし(オートリピート)がきかなくなってしまったため、CUSTOM_KEYS_FUNCTIONに関数ではなく、makeとbreakの二つの手続きのペアのタプルを登録できるようにし、キーの処理部でも、タプルが登録されていたら、離す処理を呼び出すように変更した ここまでの準備をして、code.pyを書き換えた まず、ゲームパッドを code.pyで扱えるように宣言する from hid_gamepad import Gamepad と gp = Gamepad(usb_hid.devices) を追加するのがこの部分で必要なことである import usb_hid from hid.keyboard import Keyboard from hid.keycode import Keycode from hid.consumer_control import ConsumerControl from hid.consumer_control_code import ConsumerControlCode from hid.keyboard_layout_us import KeyboardLayoutUS from hid_gamepad import Gamepad import board import microcontroller import digitalio import time import pwmio #PWM模块 bl_pwm_value = 32767 #中间值 ad_pwm_value = 32767 #中间值 try: # Set up a keyboard device. kbd = Keyboard(usb_hid.devices) layout = KeyboardLayoutUS(kbd) consumer_control = ConsumerControl(usb_hid.devices) gp = Gamepad(usb_hid.devices) そして、カスタムのキーコード(負数)を追加する -112以降の "GAMEPAD_xxx"が追加されたシンボルである # Define custom function keys using a dictionary CUSTOM_KEYS = { # Add more custom keys as needed "FN_KEY" : -100, "FN_MUTE" : -101, "FN_VOLUME_DOWN" : -102, "FN_VOLUME_UP" : -103, "FN_LOCK_SCREEN" : -104, "FN_BL_CONTROL_SCREEN" : -105, "FN_BL_PWM_DOWN" : -106, "FN_BL_PWM_UP" : -107, "SHIFT_GRAVE_ACCENT" : -108, "SHIFT_BACKSLASH" : -109, "SHIFT_LEFT_BRACKET" : -110, "SHIFT_RIGHT_BRACKET" : -111, "GAMEPAD_THUMB" : -112, "GAMEPAD_THUMB2" : -113, "GAMEPAD_TRIGGER" : -114, "GAMEPAD_TOP" : -115, "GAMEPAD_BASE3" : -116, "GAMEPAD_BASE4" : -117, "GAMEPAD_TOP2" : -118, "GAMEPAD_PINKIE" : -119, "GAMEPAD_UP" : -120, "GAMEPAD_DOWN" : -121, "GAMEPAD_LEFT" : -122, "GAMEPAD_RIGHT" : -123 } そして、カスタムキーが操作されたときに呼び出される処理のマッピングが以下である マッピングが関数のタプルになっているものは、押されたとき、離されたときの処理で、単純に関数だけが書かれているものは押されたときだけ呼び出される SPECIAL_KEY_FUNCTIONS = { CUSTOM_KEYS["FN_MUTE"] : toggle_gp19, CUSTOM_KEYS["FN_VOLUME_DOWN"] : ad_pwm_down, CUSTOM_KEYS["FN_VOLUME_UP"] : ad_pwm_up, CUSTOM_KEYS["FN_LOCK_SCREEN"] : lock_screen, CUSTOM_KEYS["FN_BL_CONTROL_SCREEN"] : toggle_gp21, CUSTOM_KEYS["FN_BL_PWM_DOWN"] : bl_pwm_down, CUSTOM_KEYS["FN_BL_PWM_UP"] : bl_pwm_up, CUSTOM_KEYS["SHIFT_GRAVE_ACCENT"] : shift_grave_accent, CUSTOM_KEYS["SHIFT_BACKSLASH"] : shift_backslash, CUSTOM_KEYS["SHIFT_LEFT_BRACKET"] : shift_left_bracket, CUSTOM_KEYS["SHIFT_RIGHT_BRACKET"] : shift_right_bracket, Keycode.CAPS_LOCK : toggle_gp22, ConsumerControlCode.SCAN_PREVIOUS_TRACK : scan_previous_track, ConsumerControlCode.PLAY_PAUSE : play_pause, ConsumerControlCode.SCAN_NEXT_TRACK : scan_next_track, CUSTOM_KEYS["GAMEPAD_THUMB"] : (gamepad_press_thumb, gamepad_release_thumb), CUSTOM_KEYS["GAMEPAD_THUMB2"] : (gamepad_press_thumb2, gamepad_release_thumb2), CUSTOM_KEYS["GAMEPAD_TRIGGER"] : (gamepad_press_trigger, gamepad_release_trigger), CUSTOM_KEYS["GAMEPAD_TOP"] : (gamepad_press_top, gamepad_release_top), CUSTOM_KEYS["GAMEPAD_BASE3"] : gamepad_btn_base3, CUSTOM_KEYS["GAMEPAD_BASE4"] : gamepad_btn_base4, CUSTOM_KEYS["GAMEPAD_TOP2"] : (gamepad_press_top2, gamepad_release_top2), CUSTOM_KEYS["GAMEPAD_PINKIE"] : (gamepad_press_pinkie, gamepad_release_pinkie), CUSTOM_KEYS["GAMEPAD_UP"] : (gamepad_press_up, gamepad_release_up), CUSTOM_KEYS["GAMEPAD_DOWN"] : (gamepad_press_down, gamepad_release_down), CUSTOM_KEYS["GAMEPAD_LEFT"] : (gamepad_press_left, gamepad_release_left), CUSTOM_KEYS["GAMEPAD_RIGHT"] : (gamepad_press_right, gamepad_release_right), } この変更を行ったために、メインループにも手を入れた # Main loop try: while True: current_keys = set(scan_keyboard()) # Handle keys that were released for key in previous_keys - current_keys: if key is not None and key >= 0: kbd.release(key) elif key is not None and key in SPECIAL_KEY_FUNCTIONS: if isinstance(SPECIAL_KEY_FUNCTIONS[key], tuple): (fun_make, fun_break) = SPECIAL_KEY_FUNCTIONS[key] fun_break() # Handle keys that are pressed for key in current_keys - previous_keys: if key is not None: if key in SPECIAL_KEY_FUNCTIONS: if isinstance(SPECIAL_KEY_FUNCTIONS[key], tuple): (fun_make, fun_break) = SPECIAL_KEY_FUNCTIONS[key] fun_make() else: SPECIAL_KEY_FUNCTIONS[key]() elif key >= 0: # 过滤掉负值键码 kbd.press(key) キーマップは以下のようになる # Define the key map for the keyboard matrix KEY_MAP = [ #col0 col1 col2 col3 col4 col5 col6 col7 col8 col9 [CUSTOM_KEYS["GAMEPAD_UP"], CUSTOM_KEYS["GAMEPAD_LEFT"], CUSTOM_KEYS["GAMEPAD_DOWN"], CUSTOM_KEYS["GAMEPAD_RIGHT"], CUSTOM_KEYS["GAMEPAD_TOP2"], CUSTOM_KEYS["GAMEPAD_PINKIE"], CUSTOM_KEYS["GAMEPAD_TRIGGER"], CUSTOM_KEYS["GAMEPAD_TOP"], CUSTOM_KEYS["GAMEPAD_THUMB2"],CUSTOM_KEYS["GAMEPAD_THUMB"]], # row 0 [Keycode.ONE, Keycode.TWO, Keycode.THREE, Keycode.FOUR, Keycode.FIVE, Keycode.SIX, Keycode.SEVEN, Keycode.EIGHT, Keycode.NINE, Keycode.ZERO], # row 1 [Keycode.Q, Keycode.W, Keycode.E, Keycode.R, Keycode.T, Keycode.Y, Keycode.U, Keycode.I, Keycode.O, Keycode.P], # row 2 [Keycode.A, Keycode.S, Keycode.D, Keycode.F, Keycode.G, Keycode.H, Keycode.J, Keycode.K, Keycode.L, Keycode.BACKSPACE], # row 3 [Keycode.Z, Keycode.X, Keycode.C, Keycode.V, Keycode.B, Keycode.N, Keycode.M, Keycode.FORWARD_SLASH, Keycode.ENTER, None], # row 4 [Keycode.SHIFT, Keycode.CAPS_LOCK, Keycode.MINUS, Keycode.EQUALS, Keycode.SEMICOLON, Keycode.QUOTE, Keycode.COMMA, Keycode.PERIOD, Keycode.RIGHT_SHIFT, None], # row 5 [CUSTOM_KEYS["FN_KEY"], Keycode.CONTROL, Keycode.LEFT_ALT, CUSTOM_KEYS["GAMEPAD_BASE3"], Keycode.SPACE, CUSTOM_KEYS["GAMEPAD_BASE4"], Keycode.RIGHT_ALT, Keycode.WINDOWS, CUSTOM_KEYS["FN_KEY"], None] # row 6 ] あとは、キーを押したり離したりの処理を書けば完成である カーソルキーはゲームパッドでもあり、キーボード上のカーソルキーでもあるので、押すと両方に対してコードを送信している もしこれで何か問題が起きるようであれば、キーマップの方を Keycode.UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROWに戻してしまえばとりあえず不具合は出ないと思われる def gamepad_press_thumb(): # A button gp.press_buttons(2) def gamepad_release_thumb(): gp.release_buttons(2) def gamepad_press_thumb2(): # B button gp.press_buttons(3) def gamepad_release_thumb2(): gp.release_buttons(3) def gamepad_press_trigger(): # X button gp.press_buttons(1) def gamepad_release_trigger(): gp.release_buttons(1) def gamepad_press_top(): # Y button gp.press_buttons(4) def gamepad_release_top(): gp.release_buttons(4) def gamepad_press_top2(): # L button gp.press_buttons(5) def gamepad_release_top2(): gp.release_buttons(5) def gamepad_press_pinkie(): # R button gp.press_buttons(6) def gamepad_release_pinkie(): gp.release_buttons(6) def gamepad_btn_base3(): # Select button gp.click_buttons(9) def gamepad_btn_base4(): # Start button gp.click_buttons(10) def gamepad_press_up(): # Send both Gamepad / Keyboard gp.move_joysticks(x=0, y=-127) kbd.press(Keycode.UP_ARROW) def gamepad_release_up(): gp.move_joysticks(x=0,y=0) kbd.release(Keycode.UP_ARROW) def gamepad_press_down(): # Send both Gamepad / Keyboard gp.move_joysticks(x=0, y=127) kbd.press(Keycode.DOWN_ARROW) def gamepad_release_down(): gp.move_joysticks(x=0,y=0) kbd.release(Keycode.DOWN_ARROW) def gamepad_press_left(): # Send both Gamepad / Keyboard gp.move_joysticks(x=-127, y=0) kbd.press(Keycode.LEFT_ARROW) def gamepad_release_left(): gp.move_joysticks(x=0,y=0) kbd.release(Keycode.LEFT_ARROW) def gamepad_press_right(): # Send both Gamepad / Keyboard gp.move_joysticks(x=127, y=0) kbd.press(Keycode.RIGHT_ARROW) def gamepad_release_right(): gp.move_joysticks(x=0,y=0) kbd.release(Keycode.RIGHT_ARROW) boot.pyをいじれば、ゲームパッドなどもついかできそうだが、boot.pyを壊してしまったら CIRCUITPYドライブは現れない つまり結局ファームウェアを復元できる状態でないととてもじゃないが怖くていじれないというのが正解だ だから、code.pyの範囲でできることしかできない 背面のRESETボタンを押すと、CIRCUITPYというドライブが PocketTerm35上に出てくるので、その中にある code.pyを編集して、もう一回背面のリセットボタンを押すと、更新した code.pyを読み込んでキーボードが起動するので、新しいキーマップで使えるようになるよ とのこと が、この「RESETすればCIRCUITPYが見える」というのが曲者だった PiOSじゃなくてUbuntu26.04LTSだから悪いのか、そもそもそういうものなのか、RESETしたくらいでは何も出てこない ダブルクリックすると CIRCUITPYかRP2040のファームウェア更新用ドライブのどっちかが見えるので、CIRCUITPYが現れるまで繰り返すしかない そして、もう一つ問題が このCIRCUITPYがマウントされた状態では、内蔵キーボードが使えないのだ 外付けキーボード、またはマウスで操作しなければならない **なお、必ず、code.pyのオリジナルを保存しておくこと** 万一やらかしたときに回復できなくなってしまうので、絶対だ さて、code.pyだが、おおむねキーマトリックスがわかるようにキー定義が置かれている 但し、キーコードは adafruit hidでカバーされているキーコードしか返せない なるほど、だから L/R/A/B/X/Yに英字キーが割り当てられているわけだ adafruit hidで定義されているキーコードに、トリガーキーみたいなものはなく、また、ジョイパッドはカバーされていないので、これ以外にやりようがなかったのだろうと推測される せいぜい、F13-F18のファンクションキーでも割り当てて、本体側のソフトで対応するしかなさそうだ TABキーを左シフトにして、TABキーはなくして、CTRL+Iで代替するというのもありかもしれない L/Rにマウスの左クリック、右クリックを割り当てようと思ったが、それをやったらキー入力を受け付けなくなった 受け付けなくなったからか、CIRCUITPYが読み取り専用でマウントされるようになってしまった 強制的に RWモードでマウントしなおして保存しておいたオリジナルのcode.pyを書き戻した $ sudo mount -o remount,rw /dev/sda1 $ cp -p code.py.orig /run/media/araki/CIRCUITPY 今のところ、自前のcode.pyはうまく動かない デバッグなどできればいいのだが、Pythonはインデントなどに強く依存する変態言語なので、見た目大丈夫そうでも実は動かないとかあるので、本当に勘弁してほしい