2014年10月5日日曜日

LogicoolのワイヤレスマウスM545をMacで使う

LogicoolのWireless Mouse M545はMacには対応していないようで、チルトホイールを左右に傾けても無反応だし、サムボタンもわけのわからないキーに割り当てられている。Logicool Control Centerでもサポートされていない(バージョン3.9.1で確認)。

ということで、Karabiner(旧KeyRemap4MacBook)を使ってどうにかしようとしたところ、チルトホイールの左右スクロールはできるようになった。見よう見まねで書いたものだけど、private.xmlの内容を下に載せておく。具体的な設定手順はKarabinerのマニュアルが丁寧でわかりやすい。

<?xml version="1.0"?>
<root>
  <item>
    <name>MouseButton4 to Left Scrolling</name>
    <identifier>private.mousebutton4_to_left_scrolling</identifier>
    <autogen>
      __PointingButtonToKey__
      PointingButton::BUTTON4,
      KeyCode::VK_MOUSEKEY_SCROLL_LEFT
    </autogen>
  </item>
  <item>
    <name>MouseButton5 to Right Scrolling</name>
    <identifier>private.mousebutton5_to_right_scrolling</identifier>
    <autogen>
      __PointingButtonToKey__
      PointingButton::BUTTON5,
      KeyCode::VK_MOUSEKEY_SCROLL_RIGHT
    </autogen>
  </item>
</root>

KarabinerのEventViewerを使えば、マウスのボタンに対応しているキーコードも調べることができる。サムボタンはそれぞれCommandCommand+Dにマッピングされているようだ。さすがにCommandのマッピングを変えるわけにはいかないので、サムボタンの変更はKarabinerでは無理だと思われる。USB OverdriveやSteerMouseでもできなかったらしいのでサムボタンに関してはあきらめた。

ちなみに、USB OverdriveやSteerMouseは有料のソフトウェアだけど、Karabinerは無料でソースも公開されている。

2014年4月11日金曜日

RubyのReadline.readlineでデフォルトの文字列を表示する

RubyのReadline.readlineでデフォルトの文字列を表示する方法を調べたところ、本を読む RubyのReadline.readlineで初期文字列を与えるに詳しい説明があった。コードも載っていてとても参考になるのだけど、Ruby 2.0でdeprecatedになったdlを使っているためfiddleで書いてみた。

インターフェイスは、Readline.readlineに引数を追加してデフォルトの文字列を受け取るようにした。

require "readline"
require "fiddle/import"

module Readline
  module C
    extend Fiddle::Importer
    dlload "/usr/local/opt/readline/lib/libreadline.dylib"
    
    RL_STARTUP_HOOK = import_symbol("rl_startup_hook")
    
    bind("int startup_hook_callback()") do
      preput = Readline.instance_variable_get(:@preput)
      Readline.insert_text(preput)
      0
    end
  end
  
  module ReadlineWithPreput
    def readline(prompt = "", add_hist = false, preput = nil)
      @preput = preput.to_s if preput
      
      callback = @preput ? C["startup_hook_callback"].to_i : 0
      template = %w(S! I! L! Q!).find do |template|
        [0].pack(template).size == Fiddle::SIZEOF_VOIDP
      end
      C::RL_STARTUP_HOOK[0, Fiddle::SIZEOF_VOIDP] = [callback].pack(template)
      
      super prompt, add_hist
    ensure
      @preput = nil
    end
  end
  
  class << self
    prepend ReadlineWithPreput
  end
end

dlloadの引数で与えているパスは実行環境に合わせて変更する必要がある。上のコードで指定しているのは、MacでHomebrewを使ってインストールしたGNU Readline。Macに元々ある/usr/lib/libreadline.dylibの実体はlibeditで、こちらにも対応したかったのだけど、どうやってもできなかった。fiddleの使い方が正しくないのかと思い、Cで書いてもできなかった。

使い方。

p Readline.readline("> ", false, "Default Value")

2014年3月21日金曜日

Macで拡張機能を使わずにChromeのデータベースを最適化する

Chromeが重くなってきたときに、[閲覧履歴を消去...]で全て削除するとかなりの高速化が見込める。だけど、その後のウェブブラウジングが不便になってしまうので、自分はデータベースの最適化を時々行うようにしている。最適化なら効果はそこそこだけど、データが消去されることはない。

ターミナルから最適化

Chromeの起動中はファイルがロックされるので、あらかじめ終了させておく必要がある。Launchpadの[その他]グループや[アプリケーション]-[ユーティリティ]フォルダなどからターミナルを開いて、下記のコマンドを入力する(コピペでOK)。

find ~/Library/Application\ Support/Google/Chrome -type f -exec file {} \; | grep -i sqlite | sed -e "s/:.*$//" | xargs -I {} sqlite3 {} "VACUUM; REINDEX;"

やっていることは単純で、Chromeのユーザデータが保存されているフォルダから、サブフォルダにあるファイルも含めすべてのSQLiteのデータベースファイルを探し、それぞれのファイルに最適化コマンドを実行しているだけ。

sqlite3コマンドを使っているけど、OS X Tiger(10.4)以降ではプリインストールされているので、ほとんどの環境で問題なく動くと思う。自分が確認したのはMountain Lion(10.8)とSnow Leopard(10.6)で、Chromeのバージョンは33。

sqlite3のバージョンが古かったり、Chromeの更新で読み込めなくなった場合はOS X用のバイナリをダウンロード、展開してできたsqlite3/usr/local/bin/に置き、上で入力したコマンドのsqlite3の部分を/usr/local/bin/sqlite3に変更すればいい。

各ファイルのサイズを調べるには下記のようにする。最適化の前後で行えばどのくらい軽くなったかがわかる。

find ~/Library/Application\ Support/Google/Chrome -type f -exec file {} \; | grep -i sqlite | sed -e "s/:.*$//" | xargs -I {} du -h {}

ちなみに、VACUUMぐらい自動でやってるだろうと思ってChromiumのソースを確認してみたけど、履歴とサムネイルの全削除後にしかやっていないようだった。

ダブルクリックで実行

毎回コマンドを入力するのは面倒なので、次回からはダブルクリックで実行できるようにファイルに保存する。引き続きターミナルで以下のコマンドを実行すると、デスクトップにclean_chrome.shという名前のファイルができる。

echo '#!/bin/sh' > ~/Desktop/clean_chrome.sh
echo 'find ~/Library/Application\ Support/Google/Chrome -type f -exec file {} \; | grep -i sqlite | sed -e "s/:.*$//" | xargs -I {} sqlite3 {} "VACUUM; REINDEX;"' >> ~/Desktop/clean_chrome.sh
chmod +x ~/Desktop/clean_chrome.sh

ダブルクリックで実行すると結局ターミナルは開いてしまうけど、レスポンスが何もないのも不安なのでこれでいいと思う。

他のアプリケーションにも対応

ここまでChromeを対象に話を進めてきたけど、~/Library/Application\ Support/Google/Chromeの部分を変えれば、SQLiteを使うアプリケーションなら何でも対応できる。例えば、Firefoxの場合は~/Library/Application\ Support/Firefox/Profilesにすればいい。

2014年1月17日金曜日

RubyのArray#bsearch,Range#bsearchの使い方とサンプルコード

ソート済み配列に対してバイナリサーチを行うArray#bsearch, Range#bsearchの使い方について、ありがちな状況ごとにまとめてみた。

ある値が配列に含まれているか調べる

Array#bsearchでfind-anyモードを使用する。find-anyモードのブロックではstrcmpmemcmpの返り値と同じように、探している値がブロックに渡された値よりも大きければ正、等しければ0、小さければ負の整数を返す。メソッドの返り値は探している値と等しい要素か、見つからない場合はnil

ary = [1, 3, 3, 5]
p ary.bsearch {|x| 3 <=> x } != nil # => true
p ary.bsearch {|x| 2 <=> x } != nil # => false

インデックスが必要な場合はRange#bsearchを使う。

p (0...ary.size).bsearch {|i| 3 <=> ary[i] } # => 1
p (0...ary.size).bsearch {|i| 2 <=> ary[i] } # => nil

配列内の境界(boundary)について調べる

Range#bsearchでfind-minimumモードを使用する。find-minimumモードのブロックでは、探している値がブロックに渡された値と等しいか小さい場合はtrue、大きい場合はfalseを返す。メソッドの返り値はブロック内の条件を満たす最小の要素。

lower boundaryを探す。この場合は先頭の3

ary = [1, 3, 3, 5]
p (0...ary.size).bsearch {|i| ary[i] >= 3 } # => 1

upper boundaryを探す。この場合は末尾の3。ひとつ先のインデックスが返ることに注意。

p (0...ary.size).bsearch {|i| ary[i] > 3 } # => 3

等しい要素が存在しない場合は次に大きい要素(条件を満たす最小の要素)のインデックスが返る。

p (0...ary.size).bsearch {|i| ary[i] >= 2 } # => 1
p (0...ary.size).bsearch {|i| ary[i] > 2 }  # => 1

条件を満たす要素が存在しない場合はnil

p (0...ary.size).bsearch {|i| ary[i] >= 6 } # => nil
p (0...ary.size).bsearch {|i| ary[i] > 6 }  # => nil

ということで、Rangeで欲しい場合は次のようにする。

size = ary.size
lower = (0...size).bsearch {|i| ary[i] >= 3 } || size
upper = (lower...size).bsearch {|i| ary[i] > 3 } || size
p lower...upper # => 1...3

ただし、見つからない場合は始端と終端が等しくなるので、

lower == upper ? nil : lower...upper

とした方がいい場合もあるかもしれない。

ソート済み配列に挿入する

Range#bsearchでインデックスを求めてから、Array#insertを呼ぶ。 Array#insertは、第1引数で与えられたインデックスの要素の直前に、第2引数以降で与えられた要素を挿入するメソッド。

条件を満たす要素が存在しない場合はnilが返るので|| ary.sizeが必要。

ary = [1, 3, 3, 5]
i = (0...ary.size).bsearch {|i| ary[i] >= 6 } || ary.size
p ary.insert(i, 6) # => [1, 3, 3, 5, 6]

重複要素の先頭に挿入する。

ary = [1, 3, 3, 5]
i = (0...ary.size).bsearch {|i| ary[i] >= 3.0 } || ary.size
p ary.insert(i, 3.0) # => [1, 3.0, 3, 3, 5, 6]

重複要素の末尾に挿入する。

ary = [1, 3, 3, 5]
i = (0...ary.size).bsearch {|i| ary[i] > 3.0 } || ary.size
p ary.insert(i, 3.0) # => [1, 3, 3, 3.0, 5, 6]