mac 環境構築メモ

OS

環境設定

アプリ

画像からQuineを生成するGemを作った

AAQ - Ascii Art Quine

できること

画像からRubyQuineをつくれます。たとえば、Octocatの画像をQuineにしてみます。

f:id:yskoht:20180721224507p:plain
Octcat

出力されるアスキーアートは自分自身を出力するRubyのプログラムになっています。

f:id:yskoht:20180721231717g:plain

aaq Octocat.png | tee OctocatQuine.rb
ruby OctocatQuine.rb

--colorオプションを付けるとカラフルになります。これもQuineになっているので、エスケープシーケンスを取り除けばRubyで実行できます。

f:id:yskoht:20180721234346g:plain

ruby OctocatQuine.rb --color
ruby OctocatQuine.rb --color | ruby -ne 'puts $_.gsub(/\e.*?m/, "")'  | ruby  #  エスケープシーケンスを除いて実行

インストール

以下の環境で動作確認しています。

rmagickを使っているので、 ImageMagickを先にインストールしておきます。 ImageMagickのバージョン7を入れてしまうと、rmagickのインストール時にエラー1が出てしまうので、バージョン6をインストールします。

brew install imagemagick@6
echo 'export PATH="/usr/local/opt/imagemagick@6/bin:${PATH}"' >> ~/.bash_profile
echo 'export PKG_CONFIG_PATH="/usr/local/opt/imagemagick@6/lib/pkgconfig:${PKG_CONFIG_PATH}"' >> ~/.bash_profile

gemでaaqをインストールします。

gem install aaq

動作確認します。

$ aaq -v
aaq 0.1.2

Enjoy!

参考

Quineの作り方

gifはasciinemaasciicast2gifを使いました。

asciinema rec screen.cast
docker run --rm -v $PWD:/data asciinema/asciicast2gif -s 1.8 -t monokai screen.cast screen.gif

Gemの公開方法

色のエスケープシーケンス

Ubuntu 18.04 環境構築メモ

インストールを3回やるはめになったのでメモ。

インストール

設定

  • 背景
  • Dock
    • アイコンサイズ
  • 地域と言語
    • English
  • ユニバーサルアクセス
    • カーソル点滅OFF
  • 電源
    • ブランクスクリーン

CapsLockをCtrlにする

gsettings set org.gnome.desktop.input-sources xkb-options "['ctrl:nocaps']"

linuxbrew

Linuxbrew | The Homebrew package manager for Linux

sudo gem install activesupport plist backports
sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)"

test -d ~/.linuxbrew && eval $(~/.linuxbrew/bin/brew shellenv)
test -d /home/linuxbrew/.linuxbrew && eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)
test -r ~/.bash_profile && echo "eval \$($(brew --prefix)/bin/brew shellenv)" >>~/.bash_profile
echo "eval \$($(brew --prefix)/bin/brew shellenv)" >>~/.profile

apt

sudo apt install -y vim emacs tmux screen git curl 
sudo apt install -y build-essential gcc make openssl libssl-dev libbz2-dev libreadline-dev libsqlite3-dev zlib1g-dev

deb

sudo dpkg -i *.deb

Dotfiles

git clone https://github.com/yskoht/dotfiles.git ~/dotfiles
cd dotfiles
make install setup

Python

git clone https://github.com/pyenv/pyenv.git ~/.pyenv
git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv
pyenv install 3.6.5
pyenv global 3.6.5
pip install --upgrade pip

Ruby

sudo apt install -y rbenv ruby-build
git clone https://github.com/jf/rbenv-gemset.git $HOME/.rbenv/plugins/rbenv-gemset
rbenv install 2.4.1
rbenv global 2.4.1

Node.js

sudo apt install -y nodejs npm

fish

sudo apt-add-repository ppa:fish-shell/release-2
sudo apt-get update
sudo apt-get -y install fish
sudo apt install -y peco
curl -Lo ~/.config/fish/functions/fisher.fish --create-dirs git.io/fisherman
chsh
fisher z
fisher 0rax/fish-bd
fisher oh-my-fish/plugin-peco
fisher simnalamburt/shellder

powerline font

git clone https://github.com/powerline/fonts.git
./fonts/install.sh

fcitx

sudo apt install -y fcitx-mozc
sudo apt remove -y ibus

Javascriptを調べたときのメモ

ES2015, ES2016, ES2017

Promise, async/await

Tutorial: Intro To Reactをやったメモ

追加課題のメモです。初心者です。

1. Display the location for each move in the format (col, row) in the move history list.

@@ -49,6 +49,8 @@ class Game extends React.Component {
     this.state = {
       history: [{
         squares: Array(9).fill(null),
+        col: null,
+        row: null,
       }],
       stepNumber: 0,
       xIsNext: true,
@@ -66,6 +68,8 @@ class Game extends React.Component {
     this.setState({
       history: history.concat([{
         squares: squares,
+        col: (i % 3) + 1,
+        row: Math.floor(i / 3) + 1,
       }]),
       stepNumber: history.length,
       xIsNext: !this.state.xIsNext,
@@ -86,7 +90,7 @@ class Game extends React.Component {
 
     const moves = history.map((step, move) => {
       const desc = move ?
-        'Go to move #' + move :
+        'Go to move #' + move + ' (' + step.col + ',' + step.row + ')' :
         'Go to game start';
       return (
         <li key={move}>

状態にcolrowを持つように変更。

2. Bold the currently selected item in the move list.

index.js

@@ -92,9 +92,10 @@ class Game extends React.Component {
       const desc = move ?
         'Go to move #' + move + ' (' + step.col + ',' + step.row + ')' :
         'Go to game start';
+      const textStyle = (move === this.state.stepNumber) ? 'bold'  : null;
       return (
         <li key={move}>
-          <button onClick={() => this.jumpTo(move)}>{desc}</button>
+          <button className={textStyle} onClick={() => this.jumpTo(move)}>{desc}</button>
         </li>
       );
     });

style.css

@@ -48,3 +48,7 @@ ol, ul {
 .game-info {
   margin-left: 20px;
 }
+
+.bold {
+  font-weight: bold;
+}

classNameを切り替え。

3. Rewrite Board to use two loops to make the squares instead of hardcoding them.

@@ -16,30 +16,21 @@ class Board extends React.Component {
       <Square
         value={this.props.squares[i]}
         onClick={() => this.props.onClick(i)}
+        key={i}
       />
     );
   }
 
   render() {
-    return (
-      <div>
-        <div className="board-row">
-          {this.renderSquare(0)}
-          {this.renderSquare(1)}
-          {this.renderSquare(2)}
-        </div>
-        <div className="board-row">
-          {this.renderSquare(3)}
-          {this.renderSquare(4)}
-          {this.renderSquare(5)}
-        </div>
-        <div className="board-row">
-          {this.renderSquare(6)}
-          {this.renderSquare(7)}
-          {this.renderSquare(8)}
-        </div>
-      </div>
-    );
+    let squares = [];
+    for(let row = 0; row < 3; row++) {
+      let rows = [];
+      for(let col = 0; col < 3; col++) {
+        rows.push(this.renderSquare(row*3 + col));
+      }
+      squares.push(<div className="board-row" key={row}>{rows}</div>);
+    }
+    return (<div>{squares}</div>);
   }
 }

keyを追加。

4. Add a toggle button that lets you sort the moves in either ascending or descending order.

@@ -45,6 +45,7 @@ class Game extends React.Component {
       }],
       stepNumber: 0,
       xIsNext: true,
+      isDescendingOrder: false,
     };
   }
 
@@ -74,12 +75,18 @@ class Game extends React.Component {
     });
   }
 
+  toggleOrder() {
+    this.setState({
+      isDescendingOrder: !this.state.isDescendingOrder,
+    });
+  }
+
   render() {
     const history = this.state.history;
     const current = history[this.state.stepNumber];
     const winner = calculateWinner(current.squares);
 
-    const moves = history.map((step, move) => {
+    let moves = history.map((step, move) => {
       const desc = move ?
         'Go to move #' + move + ' (' + step.col + ',' + step.row + ')' :
         'Go to game start';
@@ -90,6 +97,14 @@ class Game extends React.Component {
         </li>
       );
     });
+    let movesList;
+    if(this.state.isDescendingOrder) {
+      moves.reverse();
+      movesList = <ol reversed>{moves}</ol>;
+    }
+    else {
+      movesList = <ol>{moves}</ol>;
+    }
 
     let status;
     if(winner) {
@@ -108,7 +123,8 @@ class Game extends React.Component {
         </div>
         <div className="game-info">
           <div>{status}</div>
-          <ol>{moves}</ol>
+          <button onClick={() => this.toggleOrder()}>toggle</button>
+          {movesList}
         </div>
       </div>
     );

状態にisDescendingOrderを追加。<ol>まで逆順にする必要はなかったかもしれない。

5. When someone wins, highlight the three squares that caused the win.

index.js

@@ -3,8 +3,9 @@ import ReactDOM from 'react-dom';
 import './style.css';
 
 function Square(props) {
+  const buttonClass = props.isHighlight ? "square highlight" : "square";
   return (
-    <button className="square" onClick={props.onClick}>
+    <button className={buttonClass} onClick={props.onClick}>
       {props.value}
     </button>
   );
@@ -17,6 +18,7 @@ class Board extends React.Component {
         value={this.props.squares[i]}
         onClick={() => this.props.onClick(i)}
         key={i}
+        isHighlight={this.props.highlightLine.includes(i)}
       />
     );
   }
@@ -84,7 +86,8 @@ class Game extends React.Component {
   render() {
     const history = this.state.history;
     const current = history[this.state.stepNumber];
-    const winner = calculateWinner(current.squares);
+    const w = calculateWinner(current.squares);
+    const [winner, line] = (w) ? w : [null, [null]];
 
     let moves = history.map((step, move) => {
       const desc = move ?
@@ -119,6 +122,7 @@ class Game extends React.Component {
           <Board 
             squares={current.squares}
             onClick={(i) => this.handleClick(i)}
+            highlightLine={line}
           />
         </div>
         <div className="game-info">
@@ -152,7 +156,7 @@ function calculateWinner(squares) {
   for(let i = 0; i < lines.length; i++) {
     const [a, b, c] = lines[i];
     if(squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
-      return squares[a];
+      return [squares[a], lines[i]];
     }
   }
   return null;

style.css

@@ -40,6 +40,10 @@ ol, ul {
   background: #ddd;
 }
 
+.highlight {
+  color: #ff0000;
+}
+
 .game {
   display: flex;
   flex-direction: row;

calculateWinnerでハイライトするラインを返すように修正。そのラインをBoardpropsに渡す。squareの添字がラインに含まれていたらハイライトするようにhighlightclassNameに追加する。

6. When no one wins, display a message about the result being a draw.

@@ -113,7 +113,11 @@ class Game extends React.Component {
     if(winner) {
       status = 'Winner: ' + winner;
     } else {
-      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
+      if(this.state.stepNumber === 9) {
+        status = 'Draw';
+      } else {
+        status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
+      }
     }
 
     return (

あれ、なんかこれ簡単じゃね?と思って書いたけど、これだと全てのマスが埋まった時にDrawと表示されるだけなので、"When no one wins,"じゃない。

@@ -55,7 +55,9 @@ class Game extends React.Component {
     const history = this.state.history.slice(0, this.state.stepNumber + 1);
     const current = history[history.length - 1];
     const squares = current.squares.slice();
-    if(calculateWinner(squares) || squares[i]) {
+    if(calculateWinner(squares) ||
+       squares[i] ||
+       isDraw(squares, this.state.xIsNext)) {
       return;
     }
     squares[i] = this.state.xIsNext ? 'X' : 'O';
@@ -113,7 +115,11 @@ class Game extends React.Component {
     if(winner) {
       status = 'Winner: ' + winner;
     } else {
-      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
+      if(isDraw(current.squares, this.state.xIsNext)) {
+        status = 'Draw';
+      } else {
+        status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
+      }
     }
 
     return (
@@ -160,4 +166,19 @@ function calculateWinner(squares) {
     }
   }
   return null;
-} 
+}
+
+function isDraw(squares, xIsNext) {
+  if(calculateWinner(squares)) {
+    return false;
+  }
+
+  for(let i = 0; i < squares.length; i++) {
+    if(squares[i] === null) {
+      let sqrs = squares.slice();
+      sqrs[i] = xIsNext ? 'X' : 'O';
+      if(isDraw(sqrs, !xIsNext) === false) return false;
+    }
+  }
+  return true;
+}

isDraw()を追加。引き分けの時にtrue, それ以外(どちらかが勝てるパターンが残っている場合)はfalseを返す。

全探索でざっくり512パターン。イミュータブルが大事だよ、と書かれていたのでslice()してるけど、計算量とかメモリってどのくらいまでなら許容されるのかなぁ。

Helixの親指拡張を作った

Thumbhelix Rev1

f:id:yskoht:20180427001120j:plain

PCBとケースデータを置いていますが、いくつか不具合があります。Rev1を実際に作るのはおすすめしません。参考程度でお願いします。

先行例

既にHelixにトラックポイントを実装されている方がいます。使用感が気になります。

デモ動画

左側の隅に左ボタン、右側の隅に右ボタン、拡張キーにスクロールボタンを割り当てています。

このトラックポイントを使って細かい作業は難しいです。

不具合

凡ミスのまとめです。

PCBの裏表が逆になった

f:id:yskoht:20180428144808j:plain

上記写真の左が表で、右が裏になるように設計していたんですが、アクリルプレートとPCBの隙間が無くてマイコンと抵抗がつけられませんでした。

それじゃあ裏表逆にして使おう。ジョイスティックとスイッチはリバーシブルで使えるパターンにしたし、と思ったのですが、スイッチは表面で使う用の配線しかしていませんでした。

さらに、キーのパターンがHelixと逆になってました。使用上は問題ないですが気持ち悪い。

f:id:yskoht:20180428150706j:plain
スイッチのパターンが上下さかさま&配線忘れ

スイッチが入りずらい、行が揃ってない、バックライトLEDがない

KailhロープロファイルスイッチのPCBマウントの足が入らないです(無理に押し込めば入る)。どうやらもう少し穴を大きくすべきだったらしい。

f:id:yskoht:20180428151715j:plain

Helixのキーと行を揃えるつもりで設計したのですが、ちょっとだけ下にずれてしまいました。 バックライトLEDはもともとつける予定がなかったのですが、実物を見てみるとそこだけLEDが無いのはやっぱりかっこ悪いですね。

f:id:yskoht:20180429005414j:plain

トラックポイントの操作感が悪い

ThinkPadトラックポイントに比べて動かすときに力が要ります。 これはジョイスティックのデバイス自身の問題なのでどうしようもないですが、 カーソルの速度制御などはソフトで改善できる気がするので頑張りたいです。

実装について

ハードウェア構成

f:id:yskoht:20180429023406p:plain

HelixのProMicroがI2Cマスタ、Attiny85がI2Cスレーブです。 スレーブはJoystickの状態をanalogRead()、スイッチの状態をdigitalRead()で読み取り、マスタからリクエストがあったら、それぞれの状態をI2Cで返します。

Attiny85にはICSPでプログラムをアップロードします。

パーツリスト

部品 メーカー名 定数・型番 個数 備考
抵抗 - 4.7k 2 リード付き・1/6W。I2Cプルアップ用
抵抗 - 10k 1 3216チップサイズ抵抗。SWプルアップ用
マイコン Atmel ATtiny85-20SUR 1 Atmel ATtiny25, ATtiny45, ATtiny85 Datasheet
ジョイスティック Top-Up Industry Corporation JT8P-3.2T-B10K-1-16Y 1 TOP-UP INDUSTRY CORP. -- JT8P-3.2T
秋月電子JT8P-3.2T- -1-16Y Model (1)
ジョイスティック用キャップ - - 1 今回はWholesale Product Snapshot Product name is for 3D analog joystick cap for PSP 2000 PSP 3000 thumbsticks thumb sticks capを加工して使用
スイッチ Kailh PG1350-* 1 CherryMX互換スイッチも可
PCB - - 1 thumbhelix_device/rev1/pcb at master · yskoht/thumbhelix_device
アクリルプレート - - ボトム・トップ各1 thumbhelix_device/rev1/case at master · yskoht/thumbhelix_device
Kailh Low Profileの場合、厚さは1.5mm~2.0mmが適
ネジ・ナット・ワッシャー - M2 - Kailh Low Profileの場合は10mmネジが適
ゴム足 - - - -

マイコン、ジョイスティックは秋月電子通商で購入しました。 ジョイスティック用のキャップはAliExpress.comで"PSP joystick cap"等で検索すると見つかります。ただし、少し削らないとはまりません。

PCBの作成

f:id:yskoht:20180429143218j:plain

以下のページと書籍を参考にKicadで作ってALLPCB.comに発注しました。

Order Timeが2018/4/8 23:42:50でFlow Progressが以下でした。

f:id:yskoht:20180429144158p:plain

アクリルケースの作成

大量ならオンラインで発注、少量・短TATならコワーキングスペーススペースに材料を持ち込み、と使い分けられるようにいろいろ経験したかったので、今回はPonokocoromozaの2か所にお願いすることにしました。

加工のためのデータはPonokoとcoromozaで異なるので2種類のデータを作る必要があります。

Ponoko用.svgの作成

GUIでポチポチやるのは面倒だと思ったので、Pythonのsvgwriteライブラリで作りました。

スクリプトはこんな感じになりました。生成されたSVGInkscapeのテンプレートに張り付けてアップロードしました。

stroke, stroke_width, stroke_opacity, fillあたりは当然として、stroke_miterlimit, stroke_dasharrayも適切に設定しないとPonokoにアップロードできないっぽいです。

coromoza用.aiの作成

coromozaのデータ形式はIllustraor形式なので.svg.aiに変換します。

Illustratorは持っていなかったので、Illustratorが使えるPCをkinko'sでレンタルすることにしました。

店頭のPCでゼロからデータを作るのは大変だと思いますが、.svg.aiに変換して線の色を変える程度であれば20分で十分でした。

f:id:yskoht:20180429153558j:plain
Ponokoに発注したアクリル

f:id:yskoht:20180429153611j:plain
coromozaで加工したアクリルの仮組

どちらの加工も思った通りにできてよかったです。

スレーブ側ソフトウェア

ATtiny85のソフト作成にはATTinyCoreを使用しました。Arduino IDEと既存のライブラリを利用できるので簡単でした。

送信データはアライン詰めれば3Byteで十分ですが、シンプルにしたかったので5Byte送っています。

マスター側ソフトウェア

I2Cマスタ

マスター側は LUFAのTWIライブラリを使っています。

スレーブにリクエストを送り、データを読みだすところはこんな感じになっています。サンプルのまんまですね。

void th_read(Thumbhelix *th)
{
    if(TWI_StartTransmission((THUMBHELIX_ADDR << 1) | TWI_ADDRESS_READ, 20) == TWI_ERROR_NoError)
    {
        uint8_t byte1, byte2, byte3, byte4, byte5;
        TWI_ReceiveByte(&byte1, false);
        TWI_ReceiveByte(&byte2, false);
        TWI_ReceiveByte(&byte3, false);
        TWI_ReceiveByte(&byte4, false);
        TWI_ReceiveByte(&byte5, true);
        TWI_StopTransmission();
...

Pointing Device

読みだした値からQMK FirmwareのPointing Deviceを使ってカーソル移動、スクロール、クリックを行っています。

‘mouseReport.buttons‘は3bitから5bitを使うと書いてありますが、MSBが0bitです。

当初、マウスボタンのエミュレートにはQmkFirmwareのMouse Keysを使おうと思ったのですが、 Pointing DeviceとMouse Keysを両方有効にするとそれぞれでhost_mouse_send()が呼ばれてしまい、 Mouse Keysでのドラッグが出来ませんでした1

結局はプログラムをシンプルにしたかったのと、バイナリサイズがきつかったので結局Pointing Deviceのみを使うようにしています。

Makefileについて

qmk_firmware/users以下にThumbhelixのファームを置くようにしました。使い方あってるのか謎です。

rules.mkincludeコメントアウトすれば無効にできます。

写真

f:id:yskoht:20180430221341j:plain

f:id:yskoht:20180430221355j:plain

f:id:yskoht:20180430221402j:plain

感想

  • Helixにシリアル口が出てるのすごい良いですね。自作キーボードにはもれなくシリアル口をつけておいてほしい
  • モノが実用できるレベルに達しなかったのは残念でしたが、PCB発注やアクリル加工の経験が得られたのは良かったです
    • 特にPCBの設計は初めてでしたが、この程度なら簡単にできるんだなと感動しました
    • 改善はしていきたいです。モチベーションが続けばですが

  1. Pointing DeviceとMouse Keysを両方使う場合は、mousekey_send(void)host_mouse_send()コメントアウトして、Mouse Keysのmouse_reportpointing_device_task内で書き換えれば所望の動作が得られました。が、両方使うメリットはあんまりないかも

Helix ビルドログ

Helix を作りました

運よく、2018/3/6にあったHelixの一般販売でステンレス5行版キットを購入することが出来ました。

f:id:yskoht:20180415221400j:plain

公式のビルドガイド(helix/buildguide_jp.md)を見ながら順に作成しました。

それから、以下の動画内でHelixの作成風景が紹介されています(16:10頃から)。これがとても参考になりました。

作成風景

いくつかの作業だけ写真を載せておきます。

ダイオードの実装

SMDダイオードのはんだ付けは初めてでした。こて先は白光 こて先 1C型 T18-C1を使いました。

f:id:yskoht:20180415221608j:plain

バックライトLEDの実装

ビルドガイドには220℃と書かれていますが、はんだが全然溶けなかったので270℃弱くらいで短時間を意識してはんだ付けしました。

TwitterでLED実装に関してアドバイスをいただきました(ありがとうございます)。この治具は、おそらく基板の裏から添えることで、はんだ付けする際にLEDが穴の下に落ちてしまうのを防ぐためのものだと思います。基板の個体差に依ると思うのですが、自分の基板はわりとLEDの大きさぴったりだったので、ポリミイドテープで止めておくだけで十分でしたが、LEDがポロポロ落ちて困るといったときはあると便利かもしれません。

Pro Microのはんだ付けまで終わった時点でLEDのテストをしました。 以下のKeymapを使うことで簡単にテストすることができました。

キースイッチの実装

ロープロファイルスイッチも使いたいし、CherryMX互換スイッチも使いたいなー」みたい優柔不断なことを考えていたところ、Twitterで以下の記事を教えていただき、Mill-Maxソケットというものを知りました。

ただ、このソケットはロープロファイル用の穴には入らないんですね。(これは完全に自分が確認しなかったのが悪いです。@nillpoさん、こちらこそお騒がせしてすみません。)

ということで、今回はKailhロープロファイルスイッチの茶軸をPCB直に実装することにしました。

ファームウェアビルド

以下の記事にファームウエアの設定ファイルについて書かれていますが、自分も設定ファイルを間違えてRGBLIGHT_ENABLE = yesしてるのにLEDつかないなーと少し悩みました。この記事に出会えてよかったです。

オーディオケーブル

いくつかオーディオケーブルを試しましたが、相性が結構あるらしく、キーが反応しないケーブルやバックライトLEDがつかないケーブルがありました。おかしいなと思ったら、いくつか試してみるのがいいのかもしれません。

完成

開始から10時間かかりました。

f:id:yskoht:20180415234105j:plain

f:id:yskoht:20180415234127j:plain

f:id:yskoht:20180415234132j:plain

f:id:yskoht:20180415234135j:plain

キーマップ

f:id:yskoht:20180416000018p:plain
QWERTYレイヤー

f:id:yskoht:20180416000033p:plain
LOWERレイヤー

f:id:yskoht:20180416000037p:plain
RAISEレイヤー

f:id:yskoht:20180416000041p:plain
ADJUSTレイヤー

感想

  • PCBの緻密さがかっこいい、シルクのデザインがかっこいい
  • バックライトLEDかっこいい、OLEDかっこいい
  • ステンレスプレートの重厚感、しっかり感がめちゃめちゃいい
  • ロープロファイルスイッチかっこいい。ただ自分は平面のキーキャップよりはアールがかかったキーキャップのほうが好きかも
  • 下段7キーあるの便利
  • はんだ付けは少し大変
  • 1回の通販でほとんどのパーツが全部集まるのすごい