[シェル起動編]格安Linux板G-clusterでテレビにシェルを表示してみた

## この記事では

格安Linux板G-clusterをテレビに繋いで、USBキーボードを刺すと、シェルを表示できるようにしていきます！

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293301/c6686e1b-5532-7cbf-b6dd-39ff797a23d3.gif)

# 前提条件

前回投稿した記事、[[準備編] 格安Linux板G-clusterで遊んでみた(画面出力はしないよ)](https://qiita.com/gpioblink/items/d5f458a1e33bb433adfd)」で、UARTでパソコンからG-clusterのシェルにアクセスしているところから始まります！！

今回は、「画面に自由な文字列を表示できるようにする」ことを目標に進めていきます！(今回は前回予定していたPython導入はやりません。。次の次くらいにやるかも)

文字列を表示するといっても、ただ文字を表示しただけではつまらないので、簡単なシェルを表示できるようにしていこうと思います。

# 大まかな手順

1. アプリをいろいろ解析して画面表示について調べる
2. USBキーボードから情報を取得できるようにする
3. シェルになるように適当なプログラムを書く

# アプリをいろいろ解析して画面表示について調べる

「画面にシェルを出す」と聞いて、それなら「Ctrl+Alt+F1」とかやればttyに切り替わるのではと思うかもしれません。しかし、それはXの上での話です。別にG-cluster上でXが動いているわけじゃないので(今後はやりたいけど)、自分で画面から作らないといけません。

ファイルシステム上に、「remotectl_pipe」という、画面表示に使っているそうなパイプがあったので、この名前でstringsをかけてみました。

すると、以下のようなものが見つかりました

```
echo "firstboot $WELCOME" > /cavium/remotectl_pipe
echo 'firstboot 5' > /cavium/remotectl_pipe
echo gc_warning Firmware found > /cavium/remotectl_pipe
echo wifi_err ap > /cavium/remotectl_pipe
echo network_connect > /cavium/remotectl_pipe
echo network_disconnect > /cavium/remotectl_pipe
```

このような形で、基本的には常に用意してある画面を、パイプで要求して表示する形になっているようです。

ここで、このコマンドをいろいろ叩いているうちに、あることに気が付きました。

ここの`echo gc_warning Firmware found > /cavium/remotectl_pipe`で、`Firmware found`の部分は自由変更できるようです。

というわけで、画面に任意の文字を表示できるようになりました。`\n`や日本語もいけるので、そこそこ表示には困らなそうです。

# USBキーボードから情報を取得できるようにする

Linuxのカーネルレベルで、USBキーボードを挿すと自動でマウントしてくれる機能が有ります。実際に刺してみると、`/dev/event*`が追加され、catで開いた状態でキーボードを触ると、文字が出てくるようになります。

![output2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293301/18589f80-94d4-b8a4-285d-9161b80be99f.gif)

しかし、整形されていないので、なんのことだか分かりません。テレビ上にキーコードを表示するようにしてみました。コードは[Accessing Keys from Linux Input Device](https://stackoverflow.com/questions/20943322/accessing-keys-from-linux-input-device)ほぼこのサイトのまま、表示部分だけを変更しています。

```c
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>
#include <string.h>
#include <stdio.h>

static const char *const evval[3] = {
    "RELEASED",
    "PRESSED ",
    "REPEATED"
};

int main(void)
{
    const char *dev = "/dev/input/event1";
    struct input_event ev;
    ssize_t n;
    int fd;

    fd = open(dev, O_RDONLY);
    if (fd == -1) {
        fprintf(stderr, "Cannot open %s: %s.\n", dev, strerror(errno));
        return EXIT_FAILURE;
    }

    while (1) {
        n = read(fd, &ev, sizeof ev);
        if (n == (ssize_t)-1) {
            if (errno == EINTR)
                continue;
            else
                break;
        } else
        if (n != sizeof ev) {
            errno = EIO;
            break;
        }
        if (ev.type == EV_KEY && ev.value >= 0 && ev.value <= 2)
        printf("%s 0x%04x (%d)\n", evval[ev.value], (int)ev.code, (int)ev.code);
    }

    fflush(stdout);
    fprintf(stderr, "%s.\n", strerror(errno));
    return EXIT_FAILURE;
}
```

これで、画面上にキーコードを表示できました。

![output3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293301/c0d9eb54-9604-4b19-3d79-ecba28fa5347.gif)

# シェルになるように適当なプログラムを書く

何も考えずに書いたので、大文字とかスクロール処理も実装していませんが、こんな感じでシェルを動かせます。

```c
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>
#include <string.h>
#include <stdio.h>

#define N 100000

static const char *const evval[3] = {
    "RELEASED",
    "PRESSED ",
    "REPEATED"
};

int main(void)
{
    const char *dev = "/dev/event1";
    // const char *dev = "/dev/input/event3";

    // ref: https://gist.github.com/rickyzhang82/8581a762c9f9fc6ddb8390872552c250
    const int keytable[127] = {' ', ' ', '1', '2', '3', '4', '5', '6', '7', '8', 
                               '9', '0', '-', '=', ' ', ' ', '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', ',', '.', '/', ' ', '*', ' ', ' ', ' ', ' ', 
                               ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 
                               ' ', ' ', ' ', ' ', '-', ' ', ' ', ' ', '+', ' ', 
                               ' ', ' ', ' ', ' ', ' ', '.', ' ', ' ', ' ', ' ', 
                               ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 
                               ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 
                               ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 
                               ' ', ' ', ' ', ' ', ' ', ' ', ' '
   };
  
    struct input_event ev;
    ssize_t n;
    int fd;
    char result[N] = {'\0'};
    
    char inputing[N] = {'\0'};
    char inputkey;
    int inputi = 0;
  
    FILE *file;
	  char line[N];

    fd = open(dev, O_RDONLY);
    if (fd == -1) {
        fprintf(stderr, "Cannot open %s: %s.\n", dev, strerror(errno));
        return EXIT_FAILURE;
    }

    while (1) {
        n = read(fd, &ev, sizeof ev);
        if (n == (ssize_t)-1) {
            if (errno == EINTR)
                continue;
            else
                break;
        } else
        if (n != sizeof ev) {
            errno = EIO;
            break;
        }
        if (ev.type == EV_KEY && ev.value == 1) {
          inputkey = keytable[(int)ev.code];
          if((int)ev.code == 28) {
            sprintf(result, "%s &> cmdtmp", inputing);
            system(result);
            
            strcat(inputing, "\\n");
            file = fopen("cmdtmp", "r");
	          while(fgets(line, N, file)!=NULL){
              line[strcspn(line, "\r\n")] = 0;
		          strcat(inputing, line);
              strcat(inputing, "\\n");
	          }          
	          fclose(file);
                                   
            sprintf(result, "echo \"gc_warning > %s\" > /cavium/remotectl_pipe", inputing);
            printf("%s\n", result);
            system(result);
            
            inputing[0] = '\0';
            inputi = 0;
          } else {
            inputing[inputi] = inputkey;
            inputing[inputi+1] = '\0';
            inputi++;
            sprintf(result, "echo \"gc_warning > %s\" > /cavium/remotectl_pipe", inputing);
            printf("%s\n", result);
            system(result);
          }
          //printf("%s 0x%04x (%d)\n", evval[ev.value], (int)ev.code, (int)ev.code);
          //sprintf(result, "echo \"gc_warning %s 0x%04x (%d)\" > /cavium/remotectl_pipe", evval[ev.value], (int)ev.code, (int)ev.code);
          //sprintf(result, "echo \"gc_warning %s 0x%04x (%d)\" > remotectl_pipe", evval[ev.value], (int)ev.code, (int)ev.code);
          //system(result);
          //printf("%s\n", result);
        }
        
    }

    fflush(stdout);
    fprintf(stderr, "%s.\n", strerror(errno));
    return EXIT_FAILURE;
}
```

これで、冒頭のgifのようにシェルをテレビ上で動かすことができました。

# 今後

今回は、とりあえずシェル実行とテレビ画面の利用ができることを書いただけなんで、正直あまり満足していません。

次の目標は、G-clusterのUSBファームウェアアップデート機能を利用して、ハード改造なしでイメージを焼き、任意のプログラムを動かせるようにする予定です。まだまだ時間がかかりそうなので、気長にお待ちください。。

その後は、大量にg-clusterを購入しているので、Wi-Fiモジュールとして等間隔に並べて位置検出に利用するなど、様々な「遊び」をしていく予定です。

進捗が出たら投稿していきます！ありがとうございました。