sane-ffi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+