Warning: preg_replace(): Compilation failed: invalid range in character class at offset 14 in /home/ojhs/www/blog/wp-content/plugins/wp-app-store-connect/wp-appstore-connect.php on line 894
Warning: preg_replace(): Compilation failed: invalid range in character class at offset 4 in /home/ojhs/www/blog/wp-content/plugins/wp-app-store-connect/wp-appstore-connect.php on line 897
Warning: preg_replace(): Compilation failed: invalid range in character class at offset 14 in /home/ojhs/www/blog/wp-content/plugins/wp-app-store-connect/wp-appstore-connect.php on line 894
Warning: preg_replace(): Compilation failed: invalid range in character class at offset 4 in /home/ojhs/www/blog/wp-content/plugins/wp-app-store-connect/wp-appstore-connect.php on line 897
こんにちは。
iOS 8がインストール可能になってから、だいぶ時間が経ち、対応するアプリも増え、ずいぶん安定してきました。今回の記事は、そんなiOS 8の目玉機能(?)の一つ、サードパーティ製キーボードを使って、PCから直接iOSの端末に入力してやろうという試みです。スマートフォンでは、LINEをはじめとしたチャットアプリが普及しています。外出先で手軽にメッセージが交換できるのは魅力的ですが、小さな画面上で大量の文章を入力するのは骨が折れる作業です。やはり家でどっしりとPCの前に座ってやりたい時もあるでしょう。そういえばWindows版のLINEとかいうのもありましたが、筆者のPCはすべてLinuxなので使い物にならないんですねー。
え!?
素直にBluetoothキーボードを使えばいいじゃないかって!?
実は、筆者はBluetoothキーボードを持っていないのです。すごくほしいのですが。
さて、それでは本題に入ります。まず、ベースとなるアプリを探します。
Wifi Keyboard無料 iPhone/iPad兼用 |
面倒くさいのでサードパーティー製キーボードの追加の方法は書きません。アプリをダウンロードして、設定画面からそのキーボードを追加し、「フルアクセスを許可」のチェックを入れてやればOKですね。その作業が終わったらiOS側で何かしらのテキストボックスにフォーカスを当ててキーボードを表示させ、Wifi Keyboardにしてやります。すると、自動的にhttpサーバーが立ち上がるようです。準備ができたらPCのブラウザからアクセスしてください。アドレスは、スクリーンキーボードのスペースキーの部分に表示されています。
ほほう。便利そうではないか。このtextって書いてあるところに文字を入力するのだな。下の4つのボタンは音量調整か。これは長文作成でも使えるじゃないか。
実際にtextと書いてあるテキストボックスに何かしらの文字を入力してやります。「Hello World!」とかでしょうか。するとどうでしょう。打った文字は即座にiOS側に転送されているではありませんか。さらにBackspaceやEnter、カーソルキーまで使えるとは驚きです。
終わり。
というわけには行きませんね。どうもこのキーボードは英語にしか対応していないようです。結論から言ってしまえば、ブックマークレットで対応します。PCのブラウザでWifi Keyboardを開いて、以下のブックマークレットを実行してください。
javascript:(function(){document.body.style.paddingTop="50px";var%20c=document.createElement("form");var%20d=document.createElement("input");onWebSocketClose=function(){ws_connected=false;updateElements()};disableInputElements=function(a){var%20b=$('.input-item');b.attr("disabled",a);if(a){b.addClass("disabled")}else{b.removeClass("disabled")}};c.onsubmit=function(){if(!ws_connected){setupSocket();return%20false}sendMessageText(d.value);(d.value==""&&sendMessageKey(13));d.value="";return%20false};d.onkeydown=function(e){(e.keyCode==8&&d.selectionStart==0)&&sendMessageKey(e.keyCode);(e.keyCode==46&&d.selectionStart==d.value.length)&&(sendMessageCursorPosition(1)||sendMessageKey(8));(e.keyCode==37&&d.selectionStart==0)&&sendMessageCursorPosition(-1);(e.keyCode==39&&d.selectionStart==d.value.length)&&sendMessageCursorPosition(1)};d.style.cssText="display:block;position:fixed;top:0px;left:10%;width:80%;font-size:30px;padding:10px%2010px;border:solid%203px%20#666;border-top:none;border-radius:0px%200px%2010px%2010px;box-shadow:rgba(0,0,0,0.65)%200px%200px%206px%203px";document.body.appendChild(c);c.appendChild(d);$(d).select()})();
超適当に作りました。というわけでブックマークレットの登録方法は省略します。文字数は1200文字くらいでしょうか。Firefoxでしか試していないので、古いブラウザでは動作しないかもしれません。
実行すると画面上部ににゅいっとテキストボックスが現れ、そこに文字を入力していくという形になります。
普通の方はこれで終わり。
さて、ここからはどうやって日本語入力が解決するかを延々と書いていくギークな記事です。
なぜ、通常の方法では日本語が入力できないのでしょうか。それは使っているとなんとなくわかってきますね。一定時間ごとにテキストボックスの内容が自動的に削除されています。どんな仕組みなのでしょうか。クライアントのJavaScriptを覗いてみます。どうやら、ソースは
js/index.js
にあるようですね。さっそく開いてみると、次の一行が目につきます。
ws = new WebSocket(ws_url);
ほほう。WebSocketを使っているではないか。見かけによらず洒落た方法を使っているアプリですな。どのようにしてiOS側と同期しているかを探すと、以下のコードが見つかりました。
function keyPress(keyCode, e) { sendMessageKey(keyCode); }
なるほどKeyPressイベントを捉えてそのキーコードをWebSocketで送信しているようです。しかし残念なことにこの方法では変換操作が必要な日本語は動作しません。それならば一度PC上で入力し、そのデータをiOS側に転送するという方法で何とかしましょう。まずは入力欄をどのように作るかです。
var form = document.createElement("form"); var input = document.createElement("input"); input.style.cssText = "display: block;" + "position: fixed; top: 0px; left:10%;" + "width: 80%; font-size:30px;" + "padding: 10px 10px;" + "border: solid 3px #666; border-top: none; border-radius:0px 0px 10px 10px;" + "box-shadow: rgba(0,0,0,0.65) 0px 0px 6px 3px"; document.body.appendChild(form); form.appendChild(input); input.focus();
ダサいとお怒りの方はどうぞcssTextをご自由に編集してください。そうしたらもう一度ソースに戻ります。
function sendMessageKey(value) { sendMessage(MSG_TYPE_KEY, value); } function sendMessageText(value) { sendMessage(MSG_TYPE_TEXT, value); }
いやー。このわかりやすさが素晴らしいですね。これを元に、inputに来たEnterキーをformのonsubmitで捉え、入力内容をiOSに送信します。
form.onsubmit = function(){ if (!ws_connected) { setupSocket(); } else if (input.value == "") { sendMessageKey(13); } else { sendMessageText(input.value); input.value = ""; } return false; };
ws_connectedはWebSocketがつながっているかどうかを示すbooleanの変数です。もし接続が途絶えている場合、再接続を試みます。return false;を忘れないように。また、もし入力がなかったら(input.valueが空だったら)、iOS側で改行させるためにEnterキーのキーコードを送信しています。
さらに改良を加えていきます。
input.onkeydown = function(e){ if (e.keyCode == 8 && input.selectionStart == 0) { sendMessageKey(8); // Backspace } else if (e.keyCode==46 && input.selectionStart == input.value.length) { sendMessageCursorPosition(1); sendMessageKey(8); // Delete } else if (e.keyCode==37 && input.selectionStart == 0) { sendMessageCursorPosition(-1); // Left Arrow } else if (e.keyCode==39 && input.selectionStart == input.value.length) { sendMessageCursorPosition(1); // Right Arrow } };
sendMessageCursorPositionは、カーソルを移動させるためのシグナルを送る関数です。詳しい使い方はよくわかっていないのですが、引数に1を指定するとカーソルは一つ右に、-1を指定すると一つ左に動くようです。Deleteはそのままキーコードを送ってやるだけでは動作しなかったので、カーソルを一つ右に動かしたあと、Backspaceを送ることで擬似的に実装しています。
最後に、デフォルトの動作を少しばかり上書きしてやります。
function onWebSocketClose() { ws_connected = false; updateElements(); reconnect(); }
onWebSocketCloseは、WebSocket接続が途絶えた時にコールバックされます。こいつは自動的に再接続を試みますが、あまり当てにならないので204行目を消します。
function disableInputElements(disabled) { var items = $('.input-item'); items.attr("disabled", disabled); if (disabled) { items.addClass("disabled"); } else { items.removeClass("disabled"); items.select(); } }
こちらの問題は268行目。勝手にフォーカスを持って行かれてはたまりません。こいつも無効化。実装はシンプルに上書きでやりました。
onWebSocketClose = function() { ws_connected = false; updateElements(); }; disableInputElements = function(disabled) { var items = $('.input-item'); items.attr("disabled", disabled); if (disabled) { items.addClass("disabled"); } else { items.removeClass("disabled"); } };
これで完成です。
一応まとめると、
(function(){ document.body.style.paddingTop = "50px"; var form = document.createElement("form"); var input = document.createElement("input"); onWebSocketClose = function() { ws_connected = false; updateElements(); }; disableInputElements = function(disabled) { var items = $('.input-item'); items.attr("disabled", disabled); if (disabled) { items.addClass("disabled"); } else { items.removeClass("disabled"); } }; form.onsubmit = function(){ if (!ws_connected) { setupSocket(); } else if (input.value == "") { sendMessageKey(13); } else { sendMessageText(input.value); input.value = ""; } return false; }; input.onkeydown = function(e){ if (e.keyCode == 8 && input.selectionStart == 0) { sendMessageKey(8); // Backspace } else if (e.keyCode==46 && input.selectionStart == input.value.length) { sendMessageCursorPosition(1); sendMessageKey(8); // Delete } else if (e.keyCode==37 && input.selectionStart == 0) { sendMessageCursorPosition(-1); // Left Arrow } else if (e.keyCode==39 && input.selectionStart == input.value.length) { sendMessageCursorPosition(1); // Right Arrow } }; input.style.cssText = "display: block;" + "position: fixed; top: 0px; left:10%;" + "width: 80%; font-size:30px;" + "padding: 10px 10px;" + "border: solid 3px #666; border-top: none; border-radius:0px 0px 10px 10px;" + "box-shadow: rgba(0,0,0,0.65) 0px 0px 6px 3px"; document.body.appendChild(form); form.appendChild(input); $(input).select(); })();
こいつをpackerで圧縮してブックマークレットにしたのが最初のコードです。いやはや、長い道のりだった。でもこういうのって作っていて楽しいですね。需要があるかといえば限りなく無いに近いのかもしれませんが、なにか近未来的なものを感じます。