Redouane Red
Redouane Red

Reputation: 539

Ruby copy to clipboard with Fiddle

I am trying to copy a string to the clipboard with the Fiddle module,but this code fails.

require'fiddle';
user32 = Fiddle.dlopen 'USER32.dll';
$openClipboard = Fiddle::Function.new(user32['OpenClipboard'],[Fiddle::TYPE_INT],Fiddle::TYPE_INT);
$closeClipboard = Fiddle::Function.new(user32['CloseClipboard'],[],Fiddle::TYPE_INT);
$emptyClipboard = Fiddle::Function.new(user32['EmptyClipboard'],[],Fiddle::TYPE_INT);
$setClipboardData = Fiddle::Function.new(user32['SetClipboardData'],[Fiddle::TYPE_INT,Fiddle::TYPE_VOIDP],Fiddle::TYPE_VOIDP);
class Clipboard
    def initialize
        @closed = false;
        puts "openClipboard : #{$openClipboard.call(0)}";
        puts "emptyClipboard : #{$emptyClipboard.call}";
    end
    def data=(d)
        return 'Cannot write to closed clipboard' if @closed;
        puts "setClipboardData : #{$setClipboardData.call(1,d)}"; # 1 is CF_TEXT
    end
    def close
        return 'Already closed' if @closed;
        @closed = true;
        puts "closeClipboard : #{$closeClipboard.call}";
    end
end
c = Clipboard.new
puts 'going to write';
gets;
c.data = 'red'; # Should write 'red' to the clipboard
p 'after writing to the clipboard';
gets;
c.close;
p 'closed';
gets

But it's failing in the #data= method. (This is just an attempt to translate the code at http://www.codeproject.com/Articles/2242/Using-the-Clipboard-Part-I-Transferring-Simple-Tex). Any idea on how to do it without using an external gem/library?

Upvotes: 1

Views: 759

Answers (1)

peter
peter

Reputation: 42207

Can't help you with fiddle because Ruby 1.9.3 doesn't support the dlopen method. In my library of scripts I found this working example. Found it at https://github.com/janlelis/clipboard/blob/master/lib/clipboard/windows.rb

Can't find open3 in my list of gems so I suppose it is part of the standard library. In any way, to interact at such level with the OS you will need some kind of gem, incorporated or not, fiddle is also a gem.

require 'open3'

module Clipboard; end

module Clipboard::Windows
  extend self

  CF_TEXT = 1
  CF_UNICODETEXT = 13
  GMEM_MOVEABLE = 2

  # get ffi function handlers
  begin
    require 'ffi'
  rescue LoadError
    raise LoadError, 'Could not load the required ffi gem, install it with: gem install ffi'
  end

  module User32
    extend FFI::Library
    ffi_lib "user32"
    ffi_convention :stdcall

    attach_function :open,  :OpenClipboard,    [ :long ], :long
    attach_function :close, :CloseClipboard,   [       ], :long
    attach_function :empty, :EmptyClipboard,   [       ], :long
    attach_function :get,   :GetClipboardData, [ :long ], :long
    attach_function :set,   :SetClipboardData, [ :long, :long ], :long
  end

  module Kernel32
    extend FFI::Library
    ffi_lib 'kernel32'
    ffi_convention :stdcall

    attach_function :lock,   :GlobalLock,   [ :long ], :pointer
    attach_function :unlock, :GlobalUnlock, [ :long ], :long
    attach_function :size,   :GlobalSize,   [ :long ], :long
    attach_function :alloc,  :GlobalAlloc,  [ :long, :long ], :long
  end

  # see http://www.codeproject.com/KB/clipboard/archerclipboard1.aspx
  def paste(_ = nil)
    ret = ""
      if 0 != User32.open( 0 )
        hclip = User32.get( CF_UNICODETEXT )
        if hclip && 0 != hclip
          pointer_to_data = Kernel32.lock( hclip )
          data = ""
          # Windows Unicode is ended by to null bytes, so get the whole string
          size = Kernel32.size( hclip )
          data << pointer_to_data.get_bytes( 0, size - 2 )
          if RUBY_VERSION >= '1.9'
            ret = data.force_encoding("UTF-16LE").encode(Encoding.default_external) # TODO catch bad encodings
          else # 1.8: fallback to simple CP850 encoding
            require 'iconv'
            utf8 = Iconv.iconv( "UTF-8", "UTF-16LE", data)[0]
            ret = Iconv.iconv( "CP850", "UTF-8", utf8)[0]
          end
        if data && 0 != data
          Kernel32.unlock( hclip )
        end
      end
      User32.close( )
    end
    ret || ""
  end

  def clear
    if 0 != User32.open( 0 )
      User32.empty( )
      User32.close( )
    end
    paste
  end

  def copy(data_to_copy)
    if ( RUBY_VERSION >= '1.9' ) && 0 != User32.open( 0 )
      User32.empty( )
      data = data_to_copy.encode("UTF-16LE") # TODO catch bad encodings
      data << 0
      handler = Kernel32.alloc( GMEM_MOVEABLE, data.bytesize )
      pointer_to_data = Kernel32.lock( handler )
      pointer_to_data.put_bytes( 0, data, 0, data.bytesize )
      Kernel32.unlock( handler )
      User32.set( CF_UNICODETEXT, handler )
      User32.close( )
    else # don't touch anything
      Open3.popen3( 'clip' ){ |input,_,_| input << data_to_copy } # depends on clip (available by default since Vista)
    end
    paste
  end
end

Clipboard::Windows.copy("test")
puts Clipboard::Windows.paste

If you don't mind to install a gem, here is a much simpler solution, works on windows7 64 bit, Ruby 1.9.3.

#gem install clipboard
require 'clipboard'

Clipboard.copy("This is a sentence that has been copied to your clipboard")
puts Clipboard.paste

Upvotes: 2

Related Questions