普段あまり Windows は使わない(正確にはブラウザとメーラと PuTTY しか使ってない)のですが、ちょっと思い立ってツールを作るために Ruby を使って Windows のクリップボードの内容を取得する方法を調べてみたところ、1.9 で使える方法が簡単に見つからなかったのでメモしておきます。Windows 上の Ruby は arton さんの ActiveScriptRuby 1.9.1-p378 を対象にしています。
まず 1.8 版での情報はすぐにいくつか見つかります。るびまの第0008号のWin32OLE 活用法の記事より。
http://jp.rubyist.net/magazine/?cmd=view&p=0008-Win32OLE
ここでは以下のような選択肢をあげたうえで独自に Win32API を使ってクリップボードを取得するサンプルコードが例示されています。
- VisualuRuby の vr/clipboard
- るびまの第0005回で紹介されていた Win32Utils(win32-clipboard という RubyGems パッケージがあります)
- 依存しているパッケージに拡張ライブラリを含むものがあって gem install でインストールされるのは 1.8 版。1.9 版のバイナリパッケージを配布してないか探してみましたが見つからず。
- cygwin 版であれば、`cat /dev/clipboard` を実行するという方法や `getclip` というコマンドを実行する方法
- ASR なので対象外
- Win32OLE を使って Internet Explorer の COM オブジェクトを経由する方法
- Win32API を利用したサンプルコード
- 1.9 では GetGlobalLock の返り値が Integer になってしまうので動作しない
というわけで ASR の 1.9 版で手軽にできる方法は全滅状態でした。
Win32API で動かす方法がなぜうまくいかなくなったのかなと調べてみると、1.9 から Win32API は Ruby/DL2 (1.9 からは dl として標準添付されている拡張ライブラリ)を利用したラッパとして定義されていて、これまでポインタを String として返していた(多分)ものがアドレスの整数がそのまま返るように変更されてしまったので GetGlobalLock が Integer を返すようになってうまくいかないみたいでした。
整数のアドレスをポインタとして扱うには DL::CPtr を使えばいいみたいだったので*1そこだけちょっと手を入れて以下のようにすると、クリップボードのテキストを Ruby から取得できました。
require "dl" require "win32api" module ClipBoard OpenClipboard = Win32API.new("user32", "OpenClipboard", ["I"], "I") CloseClipboard = Win32API.new("user32", "CloseClipboard", [], "I") GetClipboardData = Win32API.new("user32", "GetClipboardData", ["I"], "I") GlobalLock = Win32API.new("kernel32", "GlobalLock", ["I"], "P") GlobalUnlock = Win32API.new("kernel32", "GlobalUnlock", ["I"], "I") # Clipboard contents format CF_TEXT = 1 def get_text data = nil while OpenClipboard.Call(0) == 0 sleep 0.2 end begin handle = GetClipboardData.Call(CF_TEXT) if handle != 0 if ptr = GlobalLock.Call(handle) begin data = DL::CPtr.new(ptr).to_s ensure GlobalUnlock.Call(handle) end end end ensure CloseClipboard.Call() end data end module_function :get_text end if $0 == __FILE__ text = ClipBoard.get_text puts text end
*1:というのを調べるのにソースを読まないといけなかったわけですが……