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

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

オペ端用壁紙を作った

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

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 が立ち上がってくる
  • 他にもあるかも…

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

Go の struct と interface で Embedding

Effective Go の Embedding の内容を試してみます。

Go では embedding を利用して継承のようなことができますが、struct と interface の違いが今ひとつ理解できていなかったため、実際にコードを書いてコンパイラに怒られながら、どういう違いがあるのか試していきたいと思います。

確認方法

こんな感じで embedding を利用して BaseMethodSuperMethod を実装するパターンを色々見ていきます。

type baseInterface interface {
    BaseMethod() string
}

type superInterface interface {
    baseInterface
    SuperMethod() string
}

実装の確認用兼、interface を満たしているかの確認用の関数を用意します。

func printSuper(name string, v superInterface) {
    fmt.Println(name+".BaseMethod ->", v.BaseMethod())
    fmt.Println(name+".SuperMethod ->", v.SuperMethod())
}

interface を interface に埋め込む

type baseInterface interface {
    BaseMethod() string
}

// 一応 baseInterface の方の実装も作っておく
type implOfBaseInterface struct{}

func (implOfBaseInterface) BaseMethod() string {
    return "implOfBaseInterface.BaseMethod"
}

type superInterface interface {
    baseInterface
    SuperMethod() string
}

type implOfSuperInterface struct{}

// interface を interface に埋め込んだ場合 BaseMethod の定義は必要
func (i implOfSuperInterface) BaseMethod() string {
    return "implOfSuperInterface.BaseMethod"
}

func (implOfSuperInterface) SuperMethod() string {
    return "implOfSuperInterface.SuperMethod"
}

impleOfSuperInterface 側でも BaseMethod の実装が必要になります。 実装を参照する先も無いので当たり前と言えば当たり前ですね。

func main() {
    printSuper("implOfSuperInterface", implOfSuperInterface{})
}

してみると

implOfSuperInterface.BaseMethod -> implOfSuperInterface.BaseMethod
implOfSuperInterface.SuperMethod -> implOfSuperInterface.SuperMethod

これも impleOfSuperInterface に実装したものがそのまま呼ばれて、当たり前の結果ですね。 このパターンの埋め込みをするメリットとしては、interface 側のメソッドの定義を省略できることくらいでしょうか。

struct を struct に埋め込む

type baseStruct struct{}

func (b baseStruct) BaseMethod() string {
    return "baseStruct.BaseMethod"
}

type superStruct struct {
    baseStruct
}

// この場合 BaseMethod は省略できるが実装してもいい
// func (s superStruct) BaseMethod() string {
//     // 明示的に baseStruct.BaseMethod を呼ぶこともできるし(あまり意味なさそう)
//     return s.baseStruct.BaseMethod()
//     // 他の処理を定義してもいい
//     return "superStruct.BaseMethod"
// }

func (s superStruct) SuperMethod() string {
    return "superStruct.SuperMethod"
}

struct を struct に埋め込んだ場合は superStruct.BaseMethod の実装を省略できるみたいです。

func main() {
    printSuper("superStruct", superStruct{})
}

してみると

superStruct.BaseMethod -> baseStruct.BaseMethod
superStruct.SuperMethod -> superStruct.SuperMethod

superStruct.BaseMethodbaseStruct.BaseMethod が呼ばれていますね。

struct を interface に埋め込む(不可)

type structEmbeddedInterface interface {
    baseStruct
    SuperMethod()
}

type implOfStructEmbeddedInterface struct{}

func (implOfStructEmbeddedInterface) SuperMethod() {
    fmt.Println("structEmbeddedInterface.SuperMethod")
}

これは不可能なパターンで、コンパイル時に interface contains embedded non-interface baseStruct と怒られます。

interface を struct に埋め込む

type interfaceEmbeddedStruct struct {
    baseInterface
}

func (interfaceEmbeddedStruct) SuperMethod() string {
    return "interfaceEmbeddedStruct.SuperMethod"
}

この場合は interfaceEmbeddedStruct.BaseMethod の実装をしなくてもコンパイルは通ります。しかし、ランタイムで panic: runtime error: invalid memory address or nil pointer dereference というエラーになります。一番タチが悪いですね。

このパターンの活用事例は悩ましいですが、考察されているページがあったので時間のある時に読みたいと思います。 https://horizoon.jp/post/2019/03/16/go_embedded_interface/

今回触って改めて、このGo言語における embedding は手を動かさないと理解できないなと思いました。単純にパターンが多いので網羅しづらいのと、網羅されても今度は長大になるため読み解く時間がないので、手元で試した方が速いなと…