ffi-cups 0.1.1 → 0.1.9

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.
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