きみはねこみたいなにゃんにゃんなまほう

ねこもスクリプトをかくなり

Magic Trackpad の Haptic Feedback が不安定だったので結局新しいものを買った

5年ほど使い続けていた Apple Magic Trackpad の Haptic Feedback 機能が不安定になったので結局新品を買ったところ問題なく動作するようになった。 当然と言えば当然なのだが、以前 Bluetooth 接続が不安になった際に iMac 本体側のNVRAMリセットで回復した経験があったので、今ひとつ Trackpad 側が悪いと言う確証を持てなかった。 結果を見れば Trackpad 側の問題だったのだが。 症状としては以下のようなものだった。

  • たまに Haptic Feedback 機能が反応しなくなる
    • iMac を起動した直後は反応するが 1分もたたないうちに反応しなくなる
    • 電源を切って、Trackpad を充電させると反応が戻ることがあるが、これも長くはもたない
  • さらに症状が進むと、たまにクリックすら反応しなくなる

という、動いたり動かなかったりする不安定な状態が2ヶ月くらい続いていた。 個人的な感覚ではバッテリーの経年劣化的なことが起こって、十分にフィードバックを動作させる電力を得られなくなっているのではと想像している。 もしそうだとしたら、初代の Magic Trackpad で採用されていたダイビングボード方式の物理的なクリック機構の方が長期的に見ればよいのかもしれない。 ただバッテリーの問題なのであれば、バッテリーを交換できればまた使えるようになるのであまり変わらないかもしれない。 まぁ交換できないのだけれど。

使用感で言えば支点と作用点間の距離が近づくと力が必要になるダイビングボード方式よりも、板全体が横滑りする Haptic Feedback の方が断然良い。 とても良いテクノロジーだとは思うし、長年使っているので愛着もあるのだが、それにしても、こんなことがあって改めて Haptic Feedback とは迂遠なテクノロジーだと思わされた。

我々はわざわざ電力を消費してクリック感をご提供いただいているんだよ。 それを起こすための物理的な力は今でも変わらず与えているはずなんだけどな。 力を感知してクリックだと判断して電力を消費してクリック感を再現しているんだ。 おい最初の力を感知するところの力、どうにか使えないのか。 頭がどうにかなりそうだ。

オペ端用壁紙を作った

オペレーション専用の端末用の壁紙を作りました。

20210607031744 20210607031810

ここから先は危険地帯、という気概を持って望みたい端末の壁紙にどうぞ。 個人利用の範囲内であればご自由に使っていただいて構いません。

高解像度なので一部を切り取ったりしてもお洒落に決まると思います。 実際、2枚目は1枚目をトリミングしてフィルタをかけたものです。

サビの割に文字が綺麗すぎたりツッコミどころはありますが、文字の部分の構造が複雑で作り直す気概が湧かないのでとりあえずこんな感じで。 Adobe Illustratorで作成しました。

素材は https://www.beiz.jp のものを使わせていただきました。 高解像度による素材の質感がとても高品質です。

Rust で hello-world の減量

Go でビルドサイズの比較をした流れで今度は Rust で hello-world を書いてバイナリサイズの比較をしてみます。

lightbulbcat.hatenablog.com

% rustc --version
rustc 1.48.0 (7eac88abb 2020-11-16)

println!

一般的な hello-world の書き方です。

fn main() {
  println!("Hello, world!")
}

println! を使うために特に import は不要なんですね。

io

use std::io;
use std::io::prelude::*;

fn main() -> io::Result<()> {
  io::stdout().write(b"Hello, world!")?;
  Ok(())
}

Result をハンドリングすべしとか、ちょっと面倒ですね。

参考: https://doc.rust-lang.org/std/io/index.html

syscall

syscall 的なパッケージを使ってみたかったのですが、難しくてまだできていません。 それから syscall パッケージで使われている ams!llvm_asm! がまだ stable な機能ではないらしく nightly ビルドでのみ利用可能になっています。

参考: https://blog.rust-lang.org/inside-rust/2020/06/08/new-inline-asm.html

バイナリサイズ比較

rustc でビルドしてサイズを比べてみます。 最適化オプションについては Codegen options を参照してください。

  • 何も指定しない
  • -O(opt-level=2)を指定
  • opt-level=z (サイズ削減優先)を指定

を比較したところ以下のようになりました。() 内は upx -9 を適用した容量です。

default opt-level=2 opt-level=z
println 370K (132K) 369K (132K) 370K (132K)
io 376K (136K) 373K (136K) 373K (136K)

Goの2MB程度のバイナリに比べるとかなり小さめですね。 シンプルなプログラムのためか、あまり最適化オプションによる差異はありませんでした。 printlnio を比べると import しているためか io の方が容量が大きめです。

Go で hello-world の減量

Go言語ってビルドサイズが大きいですよね。 バイナリサイズを削減する ldflagsupx について、どの程度の減量が見込めるのか確認してみます。 ついでに標準ライブラリへの理解を兼ねて hello-world の書き方もいくつかのパターンで比べてみます。

% go version
go version go1.15.5 darwin/amd64

fmt

一般的な hello-world の書き方です。

func main() {
    fmt.Print("Hello, world!")
}

いつも使っている表現ですが fmt パッケージで標準出力するのにちょっと違和感を持ってました。

os

func main() {
    os.Stdout.WriteString("Hello, world!")
}

fmt に比べて、こちらの方が標準出力している感がありますね。 でも fmt の方が色んな型を渡せて便利なので fmt を使っちゃいますね。

syscall

func main() {
    syscall.Write(syscall.Stdout, []byte("Hello, world!"))
}

使ったことはありませんでしたが、意外とシンプルに書けました。 syscall.Stdoutuintptr 型の変数で os.File 型の os.Stdout とは別物です。

バイナリサイズ比較

ビルドしてサイズを比べてみます。

  • ldflags を指定して go build -ldflags="-s -w" するパターン
  • ビルドしたバイナリに upx -9 を適用したパターン
  • その両方を適用したパターン

を比較したところ以下のようになりました。

default ldflags upx ldflags+upx
fmt 2.0M 1.6M 1.1M 620K
os 1.5M 1.1M 840K 448K
syscall 1.2M 915K 708K 376K

syscall が一番小さくなりましたが、それでも何も工夫しなければ1MBを超えています。 御膳立て部分のコードが大きそうですね。

ldflags と upx はそれぞれバイナリサイズを半分程度まで削減できるみたいです。 両方使うと元の大きさの 1/3 程度の小ささになりました。 まだ何をやっているツールか把握していませんが、upx すごいですね。

本当は各ライブラリの表現を部分的に引っ張ってきてより低級な表現に展開する、みたいなことをしたかったのですが、internal なパッケージをユーザ側で利用することができず諦めました。

おまけ

ディレクトリを跨いだファイルサイズの比較に tree -h が便利

tree -h でファイルサイズを表示しつつツリー表示してくれます。 --du オプションをつけると du 的にフォルダの合計サイズも表示してくれます。

% tree --du -h
.
├── [ 303]  build.sh
├── [5.3M]  fmt
│   ├── [2.0M]  hello-world
│   ├── [1.6M]  hello-world.ldflags
│   ├── [620K]  hello-world.ldflags.upx
│   ├── [1.1M]  hello-world.upx
│   └── [ 176]  main.go
├── [3.8M]  os
│   ├── [1.5M]  hello-world
│   ├── [1.1M]  hello-world.ldflags
│   ├── [448K]  hello-world.ldflags.upx
│   ├── [840K]  hello-world.upx
│   └── [  83]  main.go
└── [3.2M]  syscall
    ├── [1.2M]  hello-world
    ├── [915K]  hello-world.ldflags
    ├── [376K]  hello-world.ldflags.upx
    ├── [708K]  hello-world.upx
    └── [ 104]  main.go

  12M used in 3 directories, 16 files

続き: Karabiner-Elements と Swift で background のアプリに特定のキーストロークを送る

ターミナルやエディタでコーディングしつつ、参考書を表示しているKindleのページ送りをしたかったのが動機です。 表題で言っている background のアプリとは最前面(foremost)ではないウィンドウで動作しているアプリを指しています。

lightbulbcat.hatenablog.com

上の記事で試したのは、指定したアプリ向けにキーストロークを送る AppleScript を Karabiner-Elements で特定のキーにより実行する、というものでした。しかし AppleScript の限界なのか background のアプリにキーストロークを送ることはできず、実際には一瞬ターゲットのアプリを foremost に持ってきてキーを送り foremost を元のアプリに戻すという処理しか書けませんでした。

Swift を使えばターゲットのアプリを activate することなく特定のアプリにキーストロークを送れるようだったので、使用感向上のため実装してみました。

Swift スクリプトの用意

アプリにキーストロークを送る send-key-to-app.swift を用意します。 AppleScript よりこちらの方が好きです。

import Foundation
import AppKit

let args = CommandLine.arguments
if args.count != 3 {
  print("\(args[0]) [APP_NAME] [KEY_CODE]")
  print("APP_NAME: should be an executable file name(not a path)")
  print("KEY_CODE: a number such as 123(LEFT), 124(RIGHT), ...")
  exit(1)
}

let appName = args[1]
let keyCode = CGKeyCode(args[2]) ?? 0

let src = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)    

func sendKeyStroke(pid: pid_t, keyCode: CGKeyCode) {
  let keyUp = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: true)
  keyUp?.postToPid(pid)
  let keyDown = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: false)
  keyDown?.postToPid(pid)
}

func getPidByName(executableFileName: String) -> Optional<pid_t> {
  let apps = NSWorkspace.shared.runningApplications
  for a in apps {
    print(a.bundleIdentifier)
    if a.executableURL?.lastPathComponent == executableFileName {
      return a.processIdentifier
    }
  }
  return nil
}

if let pid = getPidByName(executableFileName: appName) {
  sendKeyStroke(pid: pid, keyCode: keyCode)
} else {
  print("executable '\(appName)' not found in user processes")
}

始めて Swift を書きましたが、Optional 型と if let のコードが気持ちいいですね。 値を wrap した Optional の扱い方はちょっと Rust っぽいです。

Karabiner-Elements に設定する前に以下のように動作確認をしておきます。

swift send-key-to-app.swift Kindle 124

これで background で動いている Kindle のページが進めば成功です。 AppleScript のものと比べるとウィンドウ切り替えのチカチカ感が減ってとても快適です。

Karabiner-Elements の設定

冒頭で紹介した以前の記事と shell_command 以外は同じです。 適当に Command + 1 でページ送り、Command + 2 でページバックするようにしています。

{
  "profiles": [
    {
      "complex_modifications": {
        "rules": [
          {
            "description": "Kindleのページ移動",
            "manipulators": [
              {
                "from": {
                  "key_code": "1",
                  "modifiers": {
                    "mandatory": ["command"],
                    "optional": ["any"]
                  }
                },
                "to": [{"shell_command": "swift /path/to/send-key-to-app.swift Kindle 123"}],
                "type": "basic"
              },
              {
                "from": {
                  "key_code": "2",
                  "modifiers": {
                    "mandatory": ["command"],
                    "optional": ["any"]
                  }
                },
                "to": [{"shell_command": "swift /path/to/send-key-to-app.swift Kindle 124"}],
                "type": "basic"
              }
            ]
          },

更新したら「Restart Karabiner-Elements」をすれば早速反映されます。

この使い方でもいいのですが、現状ページ送りごとに毎回 swift インタプリターが実行されているので、気になる場合はビルドすると良さそうですね。

余裕があったので Swift をビルドする

swift package build --type executable でコマンド用のプロジェクトが簡単に作成できたので作っておきました。

https://github.com/asa-taka/send-key-to-app

心なしかページ遷移を繰り返してもiMacのファンが鳴らなくなった気がします。

Karabiner-Elements と AppleScript で background のアプリに特定のキーストロークを送る

ターミナルやエディタでコーディングしつつ、参考書を表示しているKindleのページ送りをしたかったのが動機です。 表題で言っている background のアプリとは最前面(foremost)ではないウィンドウで動作しているアプリを指しています。

指定したアプリ向けにキーストロークを送る AppleScript を Karabiner-Elements で特定のキーにより実行することで実現します。 「background のアプリに」と言いつつ実際にやっていることは、一瞬 foremost に持ってきてキーを送って元のアプリに戻す、という処理です。

AppleScript の用意

アプリにキーストロークを送る send-key.applescript を用意します。 AppleScript を書くのは初めてですが自然言語志向すぎて胸焼けしそうですね。

on run argv
  if (count of argv) < 2 then
    log "send-key [TARGET_APP] [KEY_CODE]"
    log "KEY_CODEs: 123(LEFT), 124(RIGHT), ..."
    error number -1721
  end if

  set targetApp to (item 1 of argv as text)
  set keyCode to (item 2 of argv as number)

  set currentApp to (path to frontmost application as text)

  activate application targetApp
  tell application "System Events" to key code keyCode
  activate application currentApp
end run

シンプルに書きたい人は変数を埋め込んで以下のようにしてもOKです。

set currentApp to (path to frontmost application as text)
activate application "Kindle"
tell application "System Events" to key code 123
activate application currentApp

事前に以下のように動作確認をしておきます。osascriptAppleScript をシェルから起動するためのものらしいです。

osascript send-key.applescript Kindle 124

これで Kindle のページが進んで元のアプリが frontmost になっていれば成功です。

background のアプリに直接キーストロークを送る仕組みが見つからなかったので、 一度ターゲットのアプリを activate してからキーストロークを送り元のアプリを再び activate しています。 なので一瞬ウィンドウがチカチカします。ホームメイド感がありますね。

Karabiner-Elements の設定

Karabiner-Elements に前述のスクリプトを起動するキーの設定をします。 デフォルトだと ~/.config/karabiner みたいです。

f:id:lightbulbcat:20201121220129p:plain

以下のような感じで profiles[].complex_modifications.rules に設定を追加します。 全体は長いので、今回関係のないフィールドは省略しています。

設定の肝は from のキーに対して shell_command で任意のコマンドを実行可能な点です。 これができるだけでも Karabiner の用途はかなり広がりますね。

ここでは適当に Command + 1 でページ送り、Command + 2 でページバックするようにしています。

{
  "profiles": [
    {
      "complex_modifications": {
        "rules": [
          {
            "description": "Kindleのページ移動",
            "manipulators": [
              {
                "from": {
                  "key_code": "1",
                  "modifiers": {
                    "mandatory": ["command"],
                    "optional": ["any"]
                  }
                },
                "to": [{"shell_command": "osascript /path/to/send-key.applescript Kindle 123"}],
                "type": "basic"
              },
              {
                "from": {
                  "key_code": "2",
                  "modifiers": {
                    "mandatory": ["command"],
                    "optional": ["any"]
                  }
                },
                "to": [{"shell_command": "osascript /path/to/send-key.applescript Kindle 124"}],
                "type": "basic"
              }
            ]
          },

設定ファイルの構造については Karabiner-Element のドキュメント をご覧ください。

更新したら「Restart Karabiner-Elements」をすれば早速反映されます。

サボったところ

Karabiner-Element の設定をいい感じにしたらいけるかもしれませんが、調べるのが面倒で放置している箇所です。

  • Kindle を起動していない時に設定したキーを打つと問答無用で Kindle が立ち上がってくる
  • 他にもあるかも…

快適にコードを書きたいというのが当初の目的なので、時間はコーディングに割くべきと、多少の不具合には目をつぶっています(正当化)。