sane-ffi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in scanner.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,36 @@
1
+ = SANE FFI
2
+
3
+ Scanner Access Now Easier in Ruby using FFI. This gem provides
4
+ bindings to SANE library using Ruby FFI. You can easily access to the
5
+ device state (i.e. buttons), setup device parameters, scan images,
6
+ etc.
7
+
8
+ == Usage
9
+
10
+ >> require "sane"
11
+ => true
12
+ >> Sane.open { |sane| puts sane.devices.inspect }
13
+ [#<Sane::Device:"genesys:libusb:002:032">]
14
+ => nil
15
+ >> Sane.open { |sane| puts sane.devices.first.name }
16
+ genesys:libusb:002:032
17
+ => nil
18
+ >> Sane.open { |sane| puts sane.devices.first.vendor }
19
+ Canon
20
+ => nil
21
+ >> Sane.open { |sane| puts sane.devices.first.model }
22
+ LiDE 100
23
+ => nil
24
+ >> Sane.open { |sane| puts sane.devices.first.type }
25
+ flatbed scanner
26
+ => nil
27
+ >> Sane.open { |sane| sane.devices.first.open { |device| puts device.describe(:copy) } }
28
+ {:name=>"copy", :title=>"Copy button", :desc=>"Copy button", :type=>:bool, :unit=>:none, :size=>4, :cap=>70}
29
+ => nil
30
+ >> Sane.open { |sane| sane.devices.first.open { |device| puts device[:copy] } }
31
+ false
32
+ => nil
33
+
34
+ == Copyright
35
+
36
+ Copyright (c) 2011 Jakub Kuźma
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/lib/sane/api.rb ADDED
@@ -0,0 +1,89 @@
1
+ class Sane
2
+ module API
3
+ extend FFI::Library
4
+
5
+ ffi_lib "sane"
6
+
7
+ enum :status, [:good, 0, :unsupported, :cancelled, :device_busy, :inval, :eof, :jammed, :no_docs, :cover_open, :io_error, :no_mem, :access_denied]
8
+ enum :value_type, [:bool, 0, :int, :fixed, :string, :button, :group]
9
+ enum :unit, [:none, 0, :pixel, :bit, :mm, :dpi, :percent, :microsecond]
10
+ enum :action, [:get_value, 0, :set_value, :set_auto]
11
+ enum :frame, [:gray, :rgb, :red, :green, :blue]
12
+
13
+ class Device < FFI::Struct
14
+ layout :name, :string, :vendor, :string, :model, :string, :type, :string
15
+
16
+ def to_hash
17
+ {
18
+ :name => self[:name],
19
+ :vendor => self[:vendor],
20
+ :model => self[:model],
21
+ :type => self[:type]
22
+ }
23
+ end
24
+ end
25
+
26
+ class OptionDescriptor < FFI::Struct
27
+ class ConstraintType < FFI::Union
28
+ layout :string_list, :pointer, :word_list, :pointer, :range, :pointer
29
+ end
30
+ layout :name, :string, :title, :string, :desc, :string, :type, :value_type, :unit, :unit, :size, :int, :cap, :int, :constraint_type, ConstraintType
31
+
32
+ def to_hash;
33
+ {
34
+ :name => self[:name],
35
+ :title => self[:title],
36
+ :desc => self[:desc],
37
+ :type => self[:type],
38
+ :unit => self[:unit],
39
+ :size => self[:size],
40
+ :cap => self[:cap]
41
+ }
42
+ end
43
+ end
44
+
45
+ class Parameters < FFI::Struct
46
+ layout :format, :frame, :last_frame, :int, :bytes_per_line, :int, :pixels_per_line, :int, :lines, :int, :depth, :int
47
+
48
+ def to_hash
49
+ {
50
+ :format => self[:format],
51
+ :last_frame => self[:last_frame],
52
+ :bytes_per_line => self[:bytes_per_line],
53
+ :pixels_per_line => self[:pixels_per_line],
54
+ :lines => self[:lines],
55
+ :depth => self[:depth]
56
+ }
57
+ end
58
+ end
59
+
60
+ # extern SANE_Status sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize);
61
+ attach_function :sane_init, [:pointer, :pointer], :status
62
+ # extern void sane_exit (void);
63
+ attach_function :sane_exit, [], :void
64
+ # extern SANE_Status sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only);
65
+ attach_function :sane_get_devices, [:pointer, :int], :status
66
+ # extern SANE_Status sane_open (SANE_String_Const devicename, SANE_Handle * handle);
67
+ attach_function :sane_open, [:string, :pointer], :status
68
+ # extern void sane_close (SANE_Handle handle);
69
+ attach_function :sane_close, [:pointer], :void
70
+ # extern const SANE_Option_Descriptor * sane_get_option_descriptor (SANE_Handle handle, SANE_Int option);
71
+ attach_function :sane_get_option_descriptor, [:pointer, :int], :pointer
72
+ # extern SANE_Status sane_control_option (SANE_Handle handle, SANE_Int option, SANE_Action action, void *value, SANE_Int * info);
73
+ attach_function :sane_control_option, [:pointer, :int, :action, :pointer, :pointer], :status
74
+ # extern SANE_Status sane_get_parameters (SANE_Handle handle, SANE_Parameters * params);
75
+ attach_function :sane_get_parameters, [:pointer, :pointer], :status
76
+ # extern SANE_Status sane_start (SANE_Handle handle);
77
+ attach_function :sane_start, [:pointer], :status
78
+ # extern SANE_Status sane_read (SANE_Handle handle, SANE_Byte * data, SANE_Int max_length, SANE_Int * length);
79
+ attach_function :sane_read, [:pointer, :pointer, :int, :pointer], :status
80
+ # extern void sane_cancel (SANE_Handle handle);
81
+ attach_function :sane_cancel, [:pointer], :void
82
+ # extern SANE_Status sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking);
83
+ attach_function :sane_set_io_mode, [:pointer, :int], :status
84
+ # extern SANE_Status sane_get_select_fd (SANE_Handle handle, SANE_Int * fd);
85
+ attach_function :sane_get_select_fd, [:pointer, :pointer], :status
86
+ # extern SANE_String_Const sane_strstatus (SANE_Status status);
87
+ attach_function :sane_strstatus, [:status], :string
88
+ end
89
+ end
@@ -0,0 +1,133 @@
1
+ class Sane
2
+ class Device
3
+ attr_reader :name, :vendor, :model, :type
4
+
5
+ def initialize(options)
6
+ @name = options[:name]
7
+ @vendor = options[:vendor]
8
+ @model = options[:model]
9
+ @type = options[:type]
10
+ @handle = nil
11
+ end
12
+
13
+ def closed?
14
+ @handle.nil?
15
+ end
16
+
17
+ def open?
18
+ !closed?
19
+ end
20
+
21
+ def open
22
+ ensure_closed!
23
+ @handle = Sane.instance.send(:open, @name)
24
+ if block_given?
25
+ begin
26
+ yield(self)
27
+ ensure
28
+ close
29
+ end
30
+ end
31
+ end
32
+
33
+ def close
34
+ ensure_open!
35
+ Sane.instance.send(:close, @handle)
36
+ @handle = nil
37
+ end
38
+
39
+ def start
40
+ ensure_open!
41
+ Sane.instance.start(@handle)
42
+ end
43
+
44
+ def read
45
+ ensure_open!
46
+ Sane.instance.send(:read, @handle)
47
+ end
48
+
49
+ def cancel
50
+ ensure_open!
51
+ Sane.instance.send(:cancel, @handle)
52
+ end
53
+
54
+ def sync
55
+ ensure_open!
56
+ Sane.instance.send(:set_io_mode, @handle, false)
57
+ end
58
+
59
+ def async
60
+ ensure_open!
61
+ Sane.instance.send(:set_io_mode, @handle, true)
62
+ end
63
+
64
+ def option_count
65
+ ensure_open!
66
+ Sane.instance.send(:get_option, @handle, 0)
67
+ end
68
+
69
+ def parameters
70
+ ensure_open!
71
+ Sane.instance.send(:get_parameters, @handle)
72
+ end
73
+
74
+ def [](option)
75
+ ensure_open!
76
+ Sane.instance.send(:get_option, @handle, option_lookup(option))
77
+ end
78
+
79
+ def []=(option, value)
80
+ ensure_open!
81
+ Sane.instance.send(:set_option, @handle, option_lookup(option), value)
82
+ end
83
+
84
+ def option_descriptors
85
+ option_count.times.map { |i| Sane.instance.send(:get_option_descriptor, @handle, i) }
86
+ end
87
+
88
+ def option_names
89
+ option_descriptors.map { |option| option[:name] }
90
+ end
91
+
92
+ def option_values
93
+ option_count.times.map do |i|
94
+ begin
95
+ self[i]
96
+ rescue Error => e
97
+ if e.status == :inval
98
+ nil # we can't read values of some options (i.e. buttons), ignore them
99
+ else
100
+ raise e
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def options
107
+ Hash[*option_names.zip(option_values).flatten]
108
+ end
109
+
110
+ def option_lookup(option_name)
111
+ return option_name if (0..option_count).include?(option_name)
112
+ option_descriptors.index { |option| option[:name] == option_name.to_s } or raise(ArgumentError, "Option not found: #{option_name}")
113
+ end
114
+
115
+ def describe(option)
116
+ option_descriptors[option_lookup(option)]
117
+ end
118
+
119
+ def inspect
120
+ %Q{#<#{self.class.name}:"#{name}">}
121
+ end
122
+
123
+ private
124
+
125
+ def ensure_closed!
126
+ raise("Device is already open") if open?
127
+ end
128
+
129
+ def ensure_open!
130
+ raise("Device is closed") if closed?
131
+ end
132
+ end
133
+ end
data/lib/sane/error.rb ADDED
@@ -0,0 +1,10 @@
1
+ class Sane
2
+ class Error < StandardError
3
+ attr_reader :status
4
+
5
+ def initialize(message, status)
6
+ super(message)
7
+ @status = status
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ class Sane
2
+ VERSION = "0.1.0"
3
+ end
data/lib/sane.rb ADDED
@@ -0,0 +1,177 @@
1
+ require "singleton"
2
+ require "forwardable"
3
+
4
+ require "ffi"
5
+
6
+ require "sane"
7
+ require "sane/api"
8
+ require "sane/error"
9
+ require "sane/device"
10
+
11
+ class Sane
12
+ include Singleton
13
+
14
+ attr_reader :version
15
+
16
+ def self.open
17
+ instance.send(:init)
18
+ yield(instance)
19
+ ensure
20
+ instance.send(:exit)
21
+ end
22
+
23
+ def devices
24
+ get_devices.map { |device| Device.new(device) }
25
+ end
26
+
27
+ private
28
+
29
+ def not_initialized?
30
+ version.nil?
31
+ end
32
+
33
+ def initialized?
34
+ !not_initialized?
35
+ end
36
+
37
+ def init
38
+ ensure_not_initialized!
39
+ version_code = FFI::MemoryPointer.new(:int)
40
+ check_status!(API.sane_init(version_code, FFI::Pointer::NULL))
41
+ @version = version_code.read_int
42
+ end
43
+
44
+ def exit
45
+ ensure_initialized!
46
+ API.sane_exit
47
+ @version = nil
48
+ end
49
+
50
+ def get_devices
51
+ ensure_initialized!
52
+ devices_pointer_pointer = FFI::MemoryPointer.new(:pointer)
53
+ check_status!(API.sane_get_devices(devices_pointer_pointer, 0))
54
+ devices_pointer = devices_pointer_pointer.read_pointer
55
+ result = []
56
+ until devices_pointer.read_pointer.null?
57
+ result << API::Device.new(devices_pointer.read_pointer).to_hash
58
+ devices_pointer += FFI.type_size(:pointer)
59
+ end
60
+ result
61
+ end
62
+
63
+ def open(device_name)
64
+ ensure_initialized!
65
+ device_handle_pointer = FFI::MemoryPointer.new(:pointer)
66
+ check_status!(API.sane_open(device_name, device_handle_pointer))
67
+ device_handle_pointer.read_pointer
68
+ end
69
+
70
+ def close(device_handle)
71
+ ensure_initialized!
72
+ API.sane_close(device_handle)
73
+ end
74
+
75
+ def get_option_descriptor(device_handle, option)
76
+ ensure_initialized!
77
+ result = API.sane_get_option_descriptor(device_handle, option)
78
+ API::OptionDescriptor.new(result).to_hash
79
+ end
80
+
81
+ def get_option(device_handle, option)
82
+ ensure_initialized!
83
+ descriptor = get_option_descriptor(device_handle, option)
84
+
85
+ case descriptor[:type]
86
+ when :string
87
+ value_pointer = FFI::MemoryPointer.new(:pointer)
88
+ when :bool, :int, :fixed
89
+ value_pointer = FFI::MemoryPointer.new(:int)
90
+ else
91
+ return nil
92
+ end
93
+
94
+ check_status!(API.sane_control_option(device_handle, option, :get_value, value_pointer, FFI::Pointer::NULL))
95
+
96
+ case descriptor[:type]
97
+ when :string
98
+ value_pointer.read_string
99
+ when :bool
100
+ !value_pointer.read_int.zero?
101
+ when :int, :fixed
102
+ value_pointer.read_int
103
+ end
104
+ end
105
+
106
+ def set_option(device_handle, option, value)
107
+ ensure_initialized!
108
+ descriptor = get_option_descriptor(device_handle, option)
109
+
110
+ case descriptor[:type]
111
+ when :string
112
+ value_pointer = FFI::MemoryPointer.from_string(value)
113
+ when :int, :fixed
114
+ value_pointer = FFI::MemoryPointer.new(:int).write_int(value)
115
+ when :bool
116
+ value_pointer = FFI::MemoryPointer.new(:int).write_int(value ? 1 : 0)
117
+ else
118
+ return nil
119
+ end
120
+
121
+ check_status!(API.sane_control_option(device_handle, option, :set_value, value_pointer, FFI::Pointer::NULL))
122
+
123
+ case descriptor[:type]
124
+ when :string
125
+ value_pointer.read_string
126
+ when :bool
127
+ !value_pointer.read_int.zero?
128
+ when :int, :fixed
129
+ value_pointer.read_int
130
+ end
131
+ end
132
+
133
+ def start(handle)
134
+ ensure_initialized!
135
+ API.sane_start(handle)
136
+ end
137
+
138
+ def read(handle, size = 64 * 1024)
139
+ ensure_initialized!
140
+ data_pointer = FFI::MemoryPointer.new(:char, size)
141
+ length_pointer = FFI::MemoryPointer.new(:int)
142
+ check_status!(API.sane_read(handle, data_pointer, size, length_pointer))
143
+ data_pointer.read_string(length_pointer.read_int)
144
+ end
145
+
146
+ def strstatus(status)
147
+ ensure_initialized!
148
+ API.sane_strstatus(status)
149
+ end
150
+
151
+ def get_parameters(handle)
152
+ ensure_initialized!
153
+ parameters_pointer = FFI::MemoryPointer.new(API::Parameters.size)
154
+ check_status!(API.sane_get_parameters(handle, parameters_pointer))
155
+ API::Parameters.new(parameters_pointer).to_hash
156
+ end
157
+
158
+ def set_io_mode(handle, non_blocking)
159
+ check_status!(API.sane_set_io_mode(handle, non_blocking ? 1 : 0))
160
+ end
161
+
162
+ def cancel(handle)
163
+ API.sane_cancel(handle)
164
+ end
165
+
166
+ def ensure_not_initialized!
167
+ raise("SANE library is already initialized") if initialized?
168
+ end
169
+
170
+ def ensure_initialized!
171
+ raise("SANE library is not initialized") if not_initialized?
172
+ end
173
+
174
+ def check_status!(status)
175
+ raise(Error.new(strstatus(status), status)) unless status == :good
176
+ end
177
+ end
data/sane-ffi.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "sane/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "sane-ffi"
7
+ s.version = Sane::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Jakub Kuźma"]
10
+ s.email = ["qoobaa@gmail.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{SANE bindings}
13
+ s.description = %q{Scanner Access now Easier}
14
+
15
+ s.add_dependency "ffi"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sane-ffi
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - "Jakub Ku\xC5\xBAma"
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-06-01 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ffi
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ description: Scanner Access now Easier
27
+ email:
28
+ - qoobaa@gmail.com
29
+ executables: []
30
+
31
+ extensions: []
32
+
33
+ extra_rdoc_files: []
34
+
35
+ files:
36
+ - .gitignore
37
+ - Gemfile
38
+ - README.rdoc
39
+ - Rakefile
40
+ - lib/sane.rb
41
+ - lib/sane/api.rb
42
+ - lib/sane/device.rb
43
+ - lib/sane/error.rb
44
+ - lib/sane/version.rb
45
+ - sane-ffi.gemspec
46
+ homepage: ""
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.8.4
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: SANE bindings
73
+ test_files: []
74
+