ffi-cups 0.1.1 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2e82ad0fb30fa3e5c587658a3eef2af2044b3e06564e581c68e3b6d2d7e18db
4
- data.tar.gz: c0f9f05991f788f5e4f2e10cf2e603fea2bb3eb45061d8318bd769e92be2b2f0
3
+ metadata.gz: 2cd36e5fc525b811e3fedc0ef3b015aa47e04cf93528510a539ae3562a0e0357
4
+ data.tar.gz: ca00e58337c11b4291dc1debd2908e21e3bb772d4127779fc28937043fd77aa6
5
5
  SHA512:
6
- metadata.gz: 4d1d560770915cf815dd32172d4707068401b9b7892babc5c3d099d99de7e10a567dd4a59a7543510f20d0a266361cf73b1e0abf23a971d04c4795d6be6d7c70
7
- data.tar.gz: 6e3400f9edef9223e67fc46fbe4c51b1d7492c0e99d3a9e60360d3bb7745df9c6a79eb2d35ca7a805740dbe158f0848b3b4d4f2a3094ac140f1a7bb58ab49656
6
+ metadata.gz: 2f751bb3648d4b66926dc962a469f6446de032c0d309892ff8defae327051c5425bdb54ad7bf52f557e11d4f03e120269abc63b17a0091ac47652042e6350bfa
7
+ data.tar.gz: 5726071fba04a620382afc78954c3f0790c496befdecc0cad3a3c8327cf440c4fd9f68e03a96d695fa6c2ef737bbcdb709f49cbe89889feb7eaa20292b1c2f14
data/.gitignore CHANGED
@@ -3,6 +3,8 @@ log/
3
3
  pkg/
4
4
  *.gem
5
5
 
6
+ Gemfile.lock
7
+
6
8
  # YARD
7
9
  .yardopts
8
10
  .yardoc
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
1
  # ffi-cups
2
+ [![Gem Version](https://badge.fury.io/rb/ffi-cups.svg)](https://badge.fury.io/rb/ffi-cups)
3
+
2
4
  ffi-cups is a FFI wrapper around libcups providing access to the Cups API.
3
5
  It was written using Ruby 2.7.0.
4
6
 
@@ -20,38 +22,50 @@ gem install ffi-cups
20
22
  ffi-cups requires libcups2 to be installed
21
23
 
22
24
  ## TODO
23
- - send print jobs by creating the job itself first.
25
+ - Job's state
26
+ - Cancel jobs
27
+ - Get all jobs with filters
24
28
 
25
29
  ## Example usage
26
30
  ```ruby
27
31
  require 'ffi-cups'
28
32
 
29
33
  printers = Cups::Printer.get_destinations
30
- # [#<Cups::Printer:0x000055fe50b178e0 @name="HP_Officejet_J4500_series_ubuntu", @options={"device-uri"=>"ipps://HP%20Officejet%20J4500%20series%20%40%20ubuntu._ipps._tcp.local/cups", "printer-info"=>"HP Officejet J4500 series @ ubuntu", "printer-location"=>"", "printer-make-and-model"=>"HP Officejet j4585 All-in-one Printer", "printer-type"=>"16814110"}>, #<Cups::Printer:0x000055fe50b15798 @name="Virtual_PDF_Printer", @options={"copies"=>"1", "device-uri"=>"cups-pdf:/", "finishings"=>"3", "job-cancel-after"=>"10800", "job-hold-until"=>"no-hold", "job-priority"=>"50", "job-sheets"=>"none,none", "marker-change-time"=>"0", "number-up"=>"1", "printer-commands"=>"AutoConfigure,Clean,PrintSelfTestPage", "printer-info"=>"Virtual PDF Printer", "printer-is-accepting-jobs"=>"true", "printer-is-shared"=>"false", "printer-is-temporary"=>"false", "printer-location"=>"", "printer-make-and-model"=>"Generic CUPS-PDF Printer (w/ options)", "printer-state"=>"3", "printer-state-change-time"=>"1617884232", "printer-state-reasons"=>"none", "printer-type"=>"10547276", "printer-uri-supported"=>"ipp://localhost/printers/Virtual_PDF_Printer"}>]
34
+ # [#<Cups::Printer:0x000055fe50b15798 @name="Virtual_PDF_Printer",
35
+ # @options={"copies"=>"1", "device-uri"=>"cups-pdf:/", "finishings"=>"3"
36
+ # "job-cancel-after"=>"10800", "job-hold-until"=>"no-hold", ...
31
37
 
32
38
  printer = Cups::Printer.get_destination("Virtual_PDF_Printer")
33
- #<Cups::Printer:0x0000560f1d4e0958 @name="Virtual_PDF_Printer", @options={"copies"=>"1", "device-uri"=>"cups-pdf:/", "finishings"=>"3", "job-cancel-after"=>"10800", "job-hold-until"=>"no-hold", "job-priority"=>"50", "job-sheets"=>"none,none", "marker-change-time"=>"0", "number-up"=>"1", "printer-commands"=>"AutoConfigure,Clean,PrintSelfTestPage", "printer-info"=>"Virtual PDF Printer", "printer-is-accepting-jobs"=>"true", "printer-is-shared"=>"false", "printer-is-temporary"=>"false", "printer-location"=>"", "printer-make-and-model"=>"Generic CUPS-PDF Printer (w/ options)", "printer-state"=>"3", "printer-state-change-time"=>"1617884232", "printer-state-reasons"=>"none", "printer-type"=>"10547276", "printer-uri-supported"=>"ipp://localhost/printers/Virtual_PDF_Printer"}>
39
+ # <Cups::Printer:0x0000560f1d4e0958 @name="Virtual_PDF_Printer", @options={"copies"=>"1"
40
+ # "device-uri"=>"cups-pdf:/", ...
41
+
42
+ printer.state
43
+ # :idle
44
+
45
+ printer.state_reasons
46
+ # ["none"]
47
+
48
+ # Print a file (PDF, JPG, etc) you can pass a hash of printing options if you
49
+ # want to override the printer's default. See Cups::Constants for more options
50
+ options = {
51
+ Cups::CUPS_MEDIA => Cups::CUPS_MEDIA_A4,
52
+ Cups::CUPS_ORIENTATION => Cups::CUPS_ORIENTATION_LANDSCAPE
53
+ }
54
+
55
+ job = printer.print_file('/tmp/example.jpg', 'Title', options)
34
56
 
35
57
  ```
36
58
 
37
59
  ## Remote CUPS Server
38
60
  You may create a connection object passing a :hostname and/or :port arguments.
39
61
 
40
- You are in charge of the connection object, remember to close the
41
- connection once you stop using it.
42
-
43
62
  ```ruby
44
63
  # Create a Connection object with hostname and/or port
45
- connection = Cups::Connection.instance(hostname:'print.example.com')
46
-
47
- # Create a http pointer with CUPS API
48
- http = connection.httpConnect2
64
+ connection = Cups::Connection.instance('print.example.com')
49
65
 
50
66
  # Get all printers from the remote connection
51
- remote_printers = Cups::Printer.get_destinations(http)
67
+ remote_printers = Cups::Printer.get_destinations(connection)
52
68
 
53
- # Close connection and release pointer
54
- Cups::Connection.close(http)
55
69
  ```
56
70
 
57
71
  ## License
data/lib/ffi-cups.rb CHANGED
@@ -36,4 +36,5 @@ require 'ffi-cups/ffi/array'
36
36
 
37
37
  # wrappers
38
38
  require 'ffi-cups/connection'
39
+ require 'ffi-cups/job'
39
40
  require 'ffi-cups/printer'
@@ -8,19 +8,20 @@ module Cups
8
8
 
9
9
  private_class_method :new
10
10
 
11
- def initialize(args={})
12
- @hostname = args.fetch(:hostname) { Cups::CUPS_HTTP_DEFAULT }
13
- @port = args.fetch(:port) { 631 }
11
+ def initialize(hostname, port=nil)
12
+ @hostname = hostname
13
+ @port = port.nil? ? 631 : port
14
14
  end
15
15
 
16
16
  # Returns or creates a singleton instance
17
- # @param args [Hash] hash argument with hostname and port
17
+ # @param hostname [String]
18
+ # @param port [Integer]
18
19
  # @return [Object] a connection object
19
- def self.instance(args)
20
+ def self.instance(hostname, port=nil)
20
21
  return @instance if @instance
21
22
 
22
23
  @instance_mutex.synchronize do
23
- @instance ||= new(args)
24
+ @instance ||= new(hostname, port)
24
25
  end
25
26
 
26
27
  @instance
@@ -49,8 +50,7 @@ module Cups
49
50
  # Wrapper around {::FFI::Cups::Http#httpClose}
50
51
  # @param http (Pointer)
51
52
  def self.close(http)
52
- FFI::Cups::Http.httpClose(pointer)
53
- pointer.autorelease = true
53
+ FFI::Cups::Http.httpClose(http)
54
54
  end
55
55
  end
56
56
  end
@@ -23,6 +23,14 @@ module FFI::Cups
23
23
  # {https://www.cups.org/doc/cupspm.html#cupsCheckDestSupported}
24
24
  attach_function 'cupsCheckDestSupported', [:pointer, :pointer, :pointer, :string, :string], :int, blocking: true
25
25
 
26
+ # Get the supported values/capabilities for the destination.
27
+ # @overload cupsCopyDestInfo(pointer, pointer)
28
+ # @param http [Pointer]
29
+ # @param dest [Pointer]
30
+ # @return [Pointer] Destination information
31
+ # {https://www.cups.org/doc/cupspm.html#cupsCopyDestInfo}
32
+ attach_function 'cupsCopyDestInfo', [:pointer, :pointer], :pointer, blocking: true
33
+
26
34
  # Get the named destination from the list.
27
35
  # @overload cupsGetDest(string, string, int, pointer)
28
36
  # @param name [String]
@@ -46,7 +54,7 @@ module FFI::Cups
46
54
  # @param number [Integer] of destinations
47
55
  # @param pointer [Pointer] to destinations
48
56
  # {https://www.cups.org/doc/cupspm.html#cupsFreeDests}
49
- attach_function 'cupsFreeDests', [ :int, :pointer ], :void, blocking: true
57
+ attach_function 'cupsFreeDests', [:int, :pointer], :void, blocking: true
50
58
 
51
59
  # Prints a File
52
60
  # @overload cupsPrintFile(string, string, string, int, pointer)
data/lib/ffi-cups/job.rb CHANGED
@@ -1,13 +1,11 @@
1
1
  module Cups
2
2
  class Job
3
- attr_accessor :id, :title, :printer
4
-
3
+ attr_reader :id, :title, :printer
5
4
  def initialize(id, title, printer=nil)
6
5
  @id = id
7
6
  @title = title
8
7
  @printer = printer
9
8
  end
10
-
11
9
  end
12
10
  end
13
11
 
@@ -1,12 +1,13 @@
1
1
  module Cups
2
2
  class Printer
3
- attr_reader :name, :options
3
+ attr_reader :name, :options, :connection
4
4
 
5
5
  # @param name [String] printer's name
6
6
  # @param options [Hash] printer's options
7
- def initialize(name, options={})
7
+ def initialize(name, options={}, connection=nil)
8
8
  @name = name
9
9
  @options = options
10
+ @connection = connection
10
11
  end
11
12
 
12
13
  # Returns the printer state
@@ -26,36 +27,80 @@ module Cups
26
27
  options['printer-state-reasons'].split(/,/)
27
28
  end
28
29
 
30
+ def print_file(filename, title, options={})
31
+ raise "File not found: #{filename}" unless File.exists? filename
32
+
33
+ http = @connection.nil? ? nil : @connection.httpConnect2
34
+ # Get all destinations with cupsGetDests2
35
+ dests = FFI::MemoryPointer.new :pointer
36
+ num_dests = FFI::Cups.cupsGetDests2(http, dests)
37
+
38
+ # Get the destination from name with cupsGetDest
39
+ p_dest = FFI::Cups.cupsGetDest(@name, nil, num_dests, dests.get_pointer(0))
40
+ dest = Cups::Struct::Destination.new(p_dest)
41
+ raise "Destination with name: #{@name} not found!" if dest.null?
42
+
43
+ p_options = nil
44
+ num_options = 0
45
+ unless options.empty?
46
+ p_options = FFI::MemoryPointer.new :pointer
47
+ options.each do |k, v|
48
+ unless self.class.cupsCheckDestSupported(p_dest, k, v, http)
49
+ raise "Option:#{k} #{v if v} not supported for printer: #{@name}"
50
+ end
51
+ num_options = FFI::Cups.cupsAddOption(k, v, num_options, p_options)
52
+ end
53
+ p_options = p_options.get_pointer(0)
54
+ end
55
+
56
+ job_id = FFI::Cups.cupsPrintFile2(http, @name, filename, title, num_options, p_options)
57
+ job = Cups::Job.new(job_id, title, self)
58
+
59
+ if job_id.zero?
60
+ last_error = Cups.cupsLastErrorString()
61
+ self.class.cupsFreeOptions(num_options, p_options) unless options.empty?
62
+ raise last_error
63
+ end
64
+
65
+ self.class.cupsFreeOptions(num_options, p_options) unless options.empty?
66
+ self.class.cupsFreeDests(num_dests, dests)
67
+ Cups::Connection.close(http)
68
+ return job
69
+ end
70
+
29
71
  # Get all destinations (printer devices)
30
72
  # @param connection [Pointer] http pointer from {Cups::Connection#httpConnect2}
31
73
  def self.get_destinations(connection=nil)
32
74
  pointer = FFI::MemoryPointer.new :pointer
33
- destinations = cupsGetDests2(connection, pointer)
75
+ dests = cupsGetDests2(pointer, connection)
34
76
  printers = []
35
- destinations.each do |k, d|
36
- destination_opt = cupsOptions(d)
37
- options = {}
38
-
39
- destination_opt.each do |k, o|
40
- options[o[:name].dup] = o[:value].dup
41
- end
42
-
43
- printer = Cups::Printer.new(d[:name].dup, options)
77
+ dests.each do |d|
78
+ printer = Cups::Printer.new(d[:name].dup, printer_options(d), connection)
44
79
  printers.push(printer)
45
80
  end
46
- cupsFreeDests(destinations.keys.count, pointer)
81
+ cupsFreeDests(dests.count, pointer)
47
82
  return printers
48
83
  end
49
84
 
50
85
  # Get a destination by name
51
86
  # @param name [String] name of the printer
52
87
  # @param connection [Pointer] http pointer from {Cups::Connection#httpConnect2}
88
+ # @return [Printer] a printer object
53
89
  def self.get_destination(name, connection=nil)
54
- printers = get_destinations(connection)
55
- printers.each do |p|
56
- return p if p.name == name
57
- end
58
- raise "Destination with name: #{name} not found!"
90
+ http = connection.nil? ? nil : connection.httpConnect2
91
+ # Get all destinations with cupsGetDests2
92
+ dests = FFI::MemoryPointer.new :pointer
93
+ num_dests = FFI::Cups.cupsGetDests2(http, dests)
94
+
95
+ # Get the destination from name with cupsGetDest
96
+ p_dest = FFI::Cups.cupsGetDest(name, nil, num_dests, dests.get_pointer(0))
97
+ dest = Cups::Struct::Destination.new(p_dest)
98
+ raise "Destination with name: #{name} not found!" if dest.null?
99
+
100
+ printer = Cups::Printer.new(dest[:name].dup, printer_options(dest), connection)
101
+ cupsFreeDests(num_dests, dests)
102
+ Cups::Connection.close(http) if http
103
+ return printer
59
104
  end
60
105
 
61
106
  private
@@ -63,43 +108,69 @@ module Cups
63
108
  # @param connection [Pointer] http pointer from {Cups::Connection#httpConnect2}
64
109
  # @param pointer [Pointer] pointer to the destinations
65
110
  # @return [Hash] hashmap of destination structs
66
- def self.cupsGetDests2(connection=nil, pointer)
67
- count_destinations = FFI::Cups.cupsGetDests2(connection, pointer)
68
- struct_size = Cups::Struct::Destination.size
69
- destinations = {}
70
- count_destinations.times do |index|
71
- destination = Cups::Struct::Destination.new(pointer.get_pointer(0) + (struct_size * index))
72
- destinations[index] = destination
111
+ def self.cupsGetDests2(pointer, connection=nil)
112
+ http = connection.nil? ? nil : connection.httpConnect2
113
+ num_dests = FFI::Cups.cupsGetDests2(http, pointer)
114
+ size = Cups::Struct::Destination.size
115
+ destinations = []
116
+ num_dests.times do |i|
117
+ destination = Cups::Struct::Destination.new(pointer.get_pointer(0) + (size * i))
118
+ destinations.push(destination)
73
119
  end
120
+ Cups::Connection.close(http) if http
74
121
  return destinations
75
122
  end
76
123
 
124
+ # Wrapper around {::FFI::Cups#cupsCheckDestSupported}
125
+ # @param dest [Pointer] pointer to the destination
126
+ # @param option [String]
127
+ # @param value [String]
128
+ # @param connection [Pointer] http pointer from {Cups::Connection#httpConnect2}
129
+ # @return [Boolean] true if supported, false otherwise
130
+ def self.cupsCheckDestSupported(dest, option, value, connection=nil)
131
+ info = FFI::Cups.cupsCopyDestInfo(connection, dest)
132
+ i = FFI::Cups.cupsCheckDestSupported(connection, dest, info, option, value)
133
+ return !i.zero?
134
+ end
135
+
77
136
  # Returns a destination's options
78
- # @param destination [Object] {Cups::Struct::Destination} object
79
- # @return [Hash] hashmap of destination' options as {Cups::Struct::Option}
80
- def self.cupsOptions(destination)
81
- struct_size = Cups::Struct::Option.size
82
- options = {}
137
+ # @param dest [Object] {Cups::Struct::Destination} object
138
+ # @return [Hash] hash of destination' options as {Cups::Struct::Option}
139
+ def self.cups_options(dest)
140
+ size = Cups::Struct::Option.size
141
+ options = []
142
+
143
+ dest[:num_options].times do |i|
144
+ option = Cups::Struct::Option.new(dest[:options] + (size * i))
145
+ options.push(option)
146
+ end
147
+ return options
148
+ end
83
149
 
84
- destination[:num_options].times do |index|
85
- option = Cups::Struct::Option.new(destination[:options] + (struct_size * index))
86
- options[index] = option
150
+ # Returns a destination's options as hash
151
+ # @param dest [Object] {Cups::Struct::Destination} object
152
+ # @return [Hash] hash of destination' options
153
+ def self.printer_options(dest)
154
+ dest_opts = cups_options(dest)
155
+ options = {}
156
+ dest_opts.each do |o|
157
+ options[o[:name].dup] = o[:value].dup
87
158
  end
88
159
  return options
89
160
  end
90
161
 
91
162
  # Wrapper around {::FFI::Cups#cupsFreeDests}
92
- # @param number_destinations [Integer]
163
+ # @param num_dests [Integer]
93
164
  # @param pointer [Pointer] pointer to the destinations
94
- def self.cupsFreeDests(number_destinations, pointer)
95
- FFI::Cups.cupsFreeDests(number_destinations, pointer.get_pointer(0))
165
+ def self.cupsFreeDests(num_dests, pointer)
166
+ FFI::Cups.cupsFreeDests(num_dests, pointer.get_pointer(0))
96
167
  end
97
168
 
98
169
  # Wrapper around {::FFI::Cups#cupsFreeOptions}
99
- # @param number_options [Integer]
170
+ # @param num_opts [Integer]
100
171
  # @param pointer [Pointer] pointer to the options
101
- def self.cupsFreeOptions(number_options, pointer)
102
- FFI::Cups.cupsFreeOptions(number_options, pointer.get_pointer(0))
172
+ def self.cupsFreeOptions(num_opts, pointer)
173
+ FFI::Cups.cupsFreeOptions(num_opts, pointer)
103
174
  end
104
175
  end
105
176
  end
@@ -1,3 +1,3 @@
1
1
  module Cups
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.9"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-cups
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hugo Marquez
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-04-16 00:00:00.000000000 Z
12
+ date: 2021-04-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi
@@ -35,7 +35,6 @@ extra_rdoc_files: []
35
35
  files:
36
36
  - ".gitignore"
37
37
  - Gemfile
38
- - Gemfile.lock
39
38
  - LICENSE
40
39
  - README.md
41
40
  - Rakefile
data/Gemfile.lock DELETED
@@ -1,38 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- ffi-cups (0.1.0)
5
- ffi (~> 1.15.0)
6
-
7
- GEM
8
- remote: http://rubygems.org/
9
- specs:
10
- byebug (11.1.3)
11
- diff-lcs (1.4.4)
12
- ffi (1.15.0)
13
- rake (12.3.3)
14
- rspec (3.10.0)
15
- rspec-core (~> 3.10.0)
16
- rspec-expectations (~> 3.10.0)
17
- rspec-mocks (~> 3.10.0)
18
- rspec-core (3.10.1)
19
- rspec-support (~> 3.10.0)
20
- rspec-expectations (3.10.1)
21
- diff-lcs (>= 1.2.0, < 2.0)
22
- rspec-support (~> 3.10.0)
23
- rspec-mocks (3.10.2)
24
- diff-lcs (>= 1.2.0, < 2.0)
25
- rspec-support (~> 3.10.0)
26
- rspec-support (3.10.2)
27
-
28
- PLATFORMS
29
- ruby
30
-
31
- DEPENDENCIES
32
- byebug
33
- ffi-cups!
34
- rake (~> 12.0)
35
- rspec (~> 3.0)
36
-
37
- BUNDLED WITH
38
- 2.1.2