Help us understand the problem. What is going on with this article?

DigiKeyboardが日本語キーボード搭載PCで記号を正しく出力しない問題を解決する

Digisparkを用いて自作キーボードを作ると,記号が正確に出力されない&一部の記号は出力すらできない問題にブチ当たります.この記事ではこれを解決する方法を紹介しています.

2019.06.27追記
僕は使ったことないので何とも言えませんがArduino LeonardoのKeyboardライブラリでも同様の問題が起きるらしいですね.もしかしたらこの記事応用できるかも...?

環境

  • USB2.0ポートを搭載したWindows 8.1マシン
  • Arduino IDE 1.8.4
  • 中華Digispark

DigiKeyboardで起きる問題

DigiKeyboardをそのままの状態で使用すると,USキーボードと日本語キーボードのレイアウトの差から以下の2つの問題が起きます.

記号が間違って出力される

以下のようなスケッチを書いたとします.

DigiKeyboard.print("https://");

しかし実行してみると,出力される文字列は
https+//
となります.このように,print()関数やsendKeyStroke()関数で意図した記号とは違う記号が出力されてしまうことがあります.

出力されない記号がある

今度は以下のようなスケッチを書いたとします.

DigiKeyboard.print("C:\Users\Toramin10\Downloads");

しかし実行結果は
C+UsersToramin10Downloads
となり,先ほどのようにコロンがプラスになってしまうばかりか,このようにバックスラッシュは出力すらされていません.|と_でも同じ症状になってしまいます.

解決法

解決法としては,

  • 誤出力を見越してあらかじめ記号を置換しておく
  • ヘッダファイルをいじって日本語環境に対応させる

という2通りの方法が考えられます.

あまり高頻度でスケッチを書き換えない場合や,何より\_|を出力させなくていい場合は,置換させるだけの方がお手軽だと思います.この方法についてはこちらの過去記事に置換表を載せてありますので参考にしてください.

DigisparkをUSBキーボードにして一発入力させてみた

しかし頻繁にスケッチを書き換えたい場合,この方法では毎回人力で置換する必要があり面倒です.また最大の問題点としてこの方法では\_|が依然出力できないままですから,パスの指定させるぞ!とかパスワード自動入力ドングルを作ろう!といった場合は絶望的です.

このようなケースでは,若干手間がかかりますが日本語環境に合わせてライブラリをいじることで解決できます.

いじるファイルは2つ

修正が必要なファイルは以下の2つです.
◎ライブラリに手を加える際はあくまで自己責任でお願いします◎

  • DigiKeyboard.h:出力不可能な文字を無くすための修正が必要
  • scancode-ascii-table.h:誤出力を解消するための修正が必要

いずれもDigistumpのパッケージに含まれるDigisparkKeyboardライブラリのなかにあります.見つからないときは,Arduino IDEの ファイル>スケッチ例>DigisparkKeyboard>Keyboard からサンプルスケッチを開いて,そこで スケッチ>スケッチのフォルダを表示 とやるとライブラリがあるフォルダにたどり着くことができます.

まずASCIIから

まずscancode-ascii-table.hDigiKeyboard.hから参照されているので,こちらから修正していきます.これはprint()関数で文字列を取得した時に,その各々の文字がキーボードのどのキーに当たるのか判別するのに使われます.こいつがアメリカ仕様になっているために日本語キーボード搭載のPCでは間違って出力されてしまうわけです.

/* ASCII:  10 */ 40,

ファイルを開くとこのようなものが並んでいます.
これは,おおまかには「ASCIIコード10の文字に当たるキーのIDは40」という意味です.ASCIIコードで十進数の10に当たる文字は「改行」で,日本語キーボードでUsage ID(Dec)が40のキーは「Enterキー」なのでこれは間違っていません.

/* ASCII:  58 */ 179,

しかしこれを見てみると,ASCIIコード58の「:」は,IDが179のキーに当たるということになっています.日本語キーボードで179は「Shift + ;」ですが,日本語キーボードで「Shift + ;」を押すと+になってしまいます!これが誤出力の原因です(USキーボードではShift + ;で:になります).

/* ASCII:  58 */ 52,

実際にはIDが52の「:」キーに対応するべきですからこのように書き換えます.
こんなふうにして,全ての行について点検を行い,押すべきキーと違うIDになっていれば修正します.

なんだそれ!だるい!!!

分かります.とてもだるいですね.でも案外すぐ終わりました.
それでもだるいものはだるいので,これを置いておきますね...
KeyBoardwithID.png
本来はUSキーボードと日本語キーボードを見比べてUSBのHID Usage IDを調べるのですがだるすぎるのでまとめました.Shiftと一緒に押すときは+128してください.

ASCIIの文字コード表はググれば出てきます.

2019.06.27追記
この一覧は,USレイアウトのキーボードのキートップだけが日本仕様に付け替えられている場合を想定しています(僕のThinkPad X260がそうだったので).もしもうまくいかなかったら自力でやってみてください.

Internationalキー問題

ここでひと工夫が必要なのですが,scancode-ascii-table.hを修正する際,ASCII 92, 95, 124はそれぞれIDを素直に135, 263, 265にするとあとでうまくいかなくなってしまいます.というのもそれらこそが\_|にあたり,入力にはInternationalに指定された日本語キーボード特有のキーを使用するからです.

この例外なキーたちがDigiKeyboardと相性がバッチバチに悪いらしいので,ここではそれぞれのIDを104, 105, 106に設定しておきます(別のキーだと思い込ませます).

ここまでで,今までprint()関数で記号が誤って出力されていた問題は解決しています.scancode-ascii-table.hを上書き保存して,試しにいろんな記号を出力させてみてください.

次にDigiKeyboard.hを

今度は出力不可能な文字をなくしにかかります.DigiKeyboard.hを開きます.

まずここです.40行目からHIDのレポートディスクリプタの中身が書いてあります.

PROGMEM char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = { /* USB report descriptor */
  0x05, 0x01,                    // USAGE_PAGE (Generic Desktop) 
  0x09, 0x06,                    // USAGE (Keyboard) 
  0xa1, 0x01,                    // COLLECTION (Application) 
  0x05, 0x07,                    //   USAGE_PAGE (Keyboard) 
  0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl) 
  0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI) 
  0x15, 0x00,                    //   LOGICAL_MINIMUM (0) 
  0x25, 0x01,                    //   LOGICAL_MAXIMUM (1) 
  0x75, 0x01,                    //   REPORT_SIZE (1) 
  0x95, 0x08,                    //   REPORT_COUNT (8) 
  0x81, 0x02,                    //   INPUT (Data,Var,Abs) 
  0x95, 0x01,           //   REPORT_COUNT (simultaneous keystrokes) 
  0x75, 0x08,                    //   REPORT_SIZE (8) 
  0x25, 0x65,                    //   LOGICAL_MAXIMUM (101) 
  0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated)) 
  0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application) 
  0x81, 0x00,                    //   INPUT (Data,Ary,Abs) 
  0xc0                           // END_COLLECTION 
};

下から3段目,USAGE_MAXIMUM(Keyboard Application)と書いてあるところですが,このままではInternationlキーたちが使えるキーに含まれません.
以下のように書き直します.

  0x29, 0x8c,                    //   USAGE_MAXIMUM (Keyboard International6)

日本語キーボードではInternational6というキーまでが搭載されうる(と思う)ので,そのUsage IDまでひろげておきます.

次!!ずーっと下がって199行目から,print()関数で受け取った文字列にsendKeyStroke()をあてていく行程にかかわる部分(だと思うの)ですが,

size_t write(uint8_t chr) {
    uint8_t data = pgm_read_byte_near(ascii_to_scan_code_table + (chr - 8));
    sendKeyStroke(data & 0b01111111, data >> 7 ? MOD_SHIFT_RIGHT : 0);
    return 1;
  }

ここに,先ほど104, 105, 106にしておいた理由があります.
というのも,例えば\のIDを135にすると\と大文字DのIDが同じになってしまい,Dも\もDと出力されてしまいます.

このような不都合を回避するために,まずscancode-ascii-table.hで当該のキーのIDを「表示されないキーで」「できるだけ普通のPCのキーと被りにくい」ダミーのIDに差し替えます.ここではF13, F14, F15キーに充てられている104, 105, 106を使いました.

次に,上の箇所で「もしダミーにしたIDが来たらそれは\(や_や|)だぞ!」という条件分岐を設定し,ここで初めて正しいキーのIDをsendKeyStroke()に渡してやります.(すなわちレポートディスクリプタのUsage Maximumを広げた時点で,もしこの条件分岐を設定しなくても,スケッチ内でprint("\");の代わりにsendKeyStroke(135);を使えば\の出力は可能です.)

僕はこのようにしました.

size_t write(uint8_t chr) {
    uint8_t data = pgm_read_byte_near(ascii_to_scan_code_table + (chr - 8));

    switch (data){
    case 104:
        sendKeyStroke(135);
        break;
    case 105:
        sendKeyStroke(135, MOD_SHIFT_RIGHT);
        break;
    case 106:
        sendKeyStroke(137, MOD_SHIFT_RIGHT);
        break;
    default:
        sendKeyStroke(data & 0b01111111, data >> 7 ? MOD_SHIFT_RIGHT : 0);
    }

    return 1;
  }

ヘッダファイルの修正点はこれで以上です.
これで一応僕のDigisparkくんはprint()関数できちんと出力してくれるようになりました!

失敗してしまったら

もし編集中に事故が起きて,取り返しのつかない感じになった場合は こちら .

まとめ

以上は僕が趣味の範囲で考えた方法になりますので,もっとこうした方がいいだろ!それは邪道すぎるぞ!等ありましたらコメントにお願いします.いくらか皆様の参考になればと思います!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした