ffi-cups 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f2e82ad0fb30fa3e5c587658a3eef2af2044b3e06564e581c68e3b6d2d7e18db
4
+ data.tar.gz: c0f9f05991f788f5e4f2e10cf2e603fea2bb3eb45061d8318bd769e92be2b2f0
5
+ SHA512:
6
+ metadata.gz: 4d1d560770915cf815dd32172d4707068401b9b7892babc5c3d099d99de7e10a567dd4a59a7543510f20d0a266361cf73b1e0abf23a971d04c4795d6be6d7c70
7
+ data.tar.gz: 6e3400f9edef9223e67fc46fbe4c51b1d7492c0e99d3a9e60360d3bb7745df9c6a79eb2d35ca7a805740dbe158f0848b3b4d4f2a3094ac140f1a7bb58ab49656
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ .bundle/
2
+ log/
3
+ pkg/
4
+ *.gem
5
+
6
+ # YARD
7
+ .yardopts
8
+ .yardoc
9
+ doc/*
10
+
11
+ # Byebug
12
+ .byebug_history
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ffi-cups.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
8
+ gem 'byebug'
data/Gemfile.lock ADDED
@@ -0,0 +1,38 @@
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
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2021 Hugo Marquez & Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # ffi-cups
2
+ ffi-cups is a FFI wrapper around libcups providing access to the Cups API.
3
+ It was written using Ruby 2.7.0.
4
+
5
+ ## About
6
+ This is a complete rewrite of the cupsffi gem, it will deprecate
7
+ functions deprecated by the CUPS API, if you wish to continue to use them,
8
+ use the cupsffi gem.
9
+
10
+ ## Authors
11
+ - Hugo Marquez @ www.hugomarquez.mx
12
+ - Contributors @ https://github.com/hugomarquez/ffi-cups/graphs/contributors
13
+
14
+ ## Installation
15
+ ```bash
16
+ gem install ffi-cups
17
+ ```
18
+
19
+ ## Setup
20
+ ffi-cups requires libcups2 to be installed
21
+
22
+ ## TODO
23
+ - send print jobs by creating the job itself first.
24
+
25
+ ## Example usage
26
+ ```ruby
27
+ require 'ffi-cups'
28
+
29
+ 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"}>]
31
+
32
+ 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"}>
34
+
35
+ ```
36
+
37
+ ## Remote CUPS Server
38
+ You may create a connection object passing a :hostname and/or :port arguments.
39
+
40
+ You are in charge of the connection object, remember to close the
41
+ connection once you stop using it.
42
+
43
+ ```ruby
44
+ # 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
49
+
50
+ # Get all printers from the remote connection
51
+ remote_printers = Cups::Printer.get_destinations(http)
52
+
53
+ # Close connection and release pointer
54
+ Cups::Connection.close(http)
55
+ ```
56
+
57
+ ## License
58
+ The MIT License
59
+
60
+ Copyright (c) 2021 Hugo Marquez & Contributors
61
+
62
+ Permission is hereby granted, free of charge, to any person obtaining a copy
63
+ of this software and associated documentation files (the "Software"), to deal
64
+ in the Software without restriction, including without limitation the rights
65
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
66
+ copies of the Software, and to permit persons to whom the Software is
67
+ furnished to do so, subject to the following conditions:
68
+
69
+ The above copyright notice and this permission notice shall be included in
70
+ all copies or substantial portions of the Software.
71
+
72
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
73
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
74
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
75
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
76
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
77
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
78
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ffi-cups"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/ffi-cups.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ require_relative 'lib/ffi-cups/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "ffi-cups"
5
+ s.version = Cups::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ["Hugo Marquez", "Nathan Ehresman"]
8
+ s.email = ["hugomarquez.dev@gmail.com"]
9
+ s.homepage = "https://github.com/hugomarquez/ffi-cups"
10
+ s.summary = "FFI wrapper around libcups"
11
+ s.description = "Simple wrapper around libcups to give CUPS printing capabilities to Ruby apps."
12
+ s.license = "MIT"
13
+ s.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ s.add_runtime_dependency "ffi", "~> 1.15.0"
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ s.bindir = "exe"
23
+ s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+
25
+ s.require_paths = ["lib"]
26
+ end
data/lib/ffi-cups.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'ffi'
2
+ require 'byebug'
3
+
4
+ module Cups
5
+ extend FFI::Library
6
+
7
+ begin
8
+ if ENV['CUPS_LIB']
9
+ ffi_lib(ENV['CUPS_LIB'])
10
+ else
11
+ ffi_lib('cups')
12
+ end
13
+ rescue LoadError => e
14
+ raise LoadError, "Didn't find libcups on your system."
15
+ end
16
+ end
17
+
18
+ # Constants
19
+ require 'ffi-cups/constants'
20
+
21
+ # Enums
22
+ require 'ffi-cups/enum/http/status'
23
+ # IPP
24
+ require 'ffi-cups/enum/ipp/status'
25
+ require 'ffi-cups/enum/ipp/job_state'
26
+
27
+ # Structs
28
+ require 'ffi-cups/struct/option'
29
+ require 'ffi-cups/struct/destination'
30
+ require 'ffi-cups/struct/job'
31
+
32
+ # Functions
33
+ require 'ffi-cups/ffi/cups'
34
+ require 'ffi-cups/ffi/http'
35
+ require 'ffi-cups/ffi/array'
36
+
37
+ # wrappers
38
+ require 'ffi-cups/connection'
39
+ require 'ffi-cups/printer'
@@ -0,0 +1,57 @@
1
+ module Cups
2
+ class Connection
3
+ extend Gem::Deprecate
4
+
5
+ attr_accessor :hostname, :port
6
+
7
+ @instance_mutex = Mutex.new
8
+
9
+ private_class_method :new
10
+
11
+ def initialize(args={})
12
+ @hostname = args.fetch(:hostname) { Cups::CUPS_HTTP_DEFAULT }
13
+ @port = args.fetch(:port) { 631 }
14
+ end
15
+
16
+ # Returns or creates a singleton instance
17
+ # @param args [Hash] hash argument with hostname and port
18
+ # @return [Object] a connection object
19
+ def self.instance(args)
20
+ return @instance if @instance
21
+
22
+ @instance_mutex.synchronize do
23
+ @instance ||= new(args)
24
+ end
25
+
26
+ @instance
27
+ end
28
+
29
+ # Wrapper around {::FFI::Cups::Http#httpConnectEncrypt}
30
+ # @deprecated Use {#httpConnect2} instead
31
+ # @return [Pointer] a http pointer
32
+ def httpConnectEncrypt
33
+ http = FFI::Cups::Http.httpConnectEncrypt(hostname, port, FFI::Cups.cupsEncryption())
34
+ raise "Print server at #{hostname}:#{port} is not available" if http.null?
35
+ return http
36
+ end
37
+ deprecate :httpConnectEncrypt, "This function is deprecated by CUPS, please use httpConnect2 instead", 2025, 12
38
+
39
+ # Wrapper around {::FFI::Cups::Http#httpConnect2}
40
+ # Creates a http connection to a print server
41
+ # @return [Pointer] a http pointer
42
+ def httpConnect2
43
+ http = FFI::Cups::Http.httpConnect2(hostname, port, nil, 0, FFI::Cups.cupsEncryption(), 1, 30000, nil)
44
+ raise "Print server at #{hostname}:#{port} is not available" if http.null?
45
+ return http
46
+ end
47
+
48
+ # Closes the http connection and autoreleases the pointer
49
+ # Wrapper around {::FFI::Cups::Http#httpClose}
50
+ # @param http (Pointer)
51
+ def self.close(http)
52
+ FFI::Cups::Http.httpClose(pointer)
53
+ pointer.autorelease = true
54
+ end
55
+ end
56
+ end
57
+
@@ -0,0 +1,95 @@
1
+ module Cups
2
+ # cups.h
3
+ CUPS_FORMAT_JPEG = "image/jpeg"
4
+ CUPS_FORMAT_PDF = "application/pdf"
5
+ CUPS_FORMAT_TEXT = "text/plain"
6
+ CUPS_JOBID_ALL = -1
7
+ CUPS_WHICHJOBS_ALL = -1
8
+ CUPS_WHICHJOBS_ACTIVE = 0
9
+ CUPS_WHICHJOBS_COMPLETED = 1
10
+ CUPS_HTTP_DEFAULT = nil
11
+
12
+ # Options and Values
13
+ CUPS_COPIES = "copies"
14
+ CUPS_COPIES_SUPPORTED = "copies-supported"
15
+
16
+ CUPS_FINISHINGS = "CUPS_FINISHINGS"
17
+ CUPS_FINISHINGS_SUPPORTED = "finishings-supported"
18
+
19
+ CUPS_FINISHINGS_BIND = "7"
20
+ CUPS_FINISHINGS_COVER = "6"
21
+ CUPS_FINISHINGS_FOLD = "10"
22
+ CUPS_FINISHINGS_NONE = "3"
23
+ CUPS_FINISHINGS_PUNCH = "5"
24
+ CUPS_FINISHINGS_STAPLE = "4"
25
+ CUPS_FINISHINGS_TRIM = "11"
26
+
27
+ CUPS_MEDIA = "media"
28
+ CUPS_MEDIA_READY = "media-ready"
29
+ CUPS_MEDIA_SUPPORTED = "media-supported"
30
+
31
+ CUPS_MEDIA_3X5 = "na_index-3x5_3x5in"
32
+ CUPS_MEDIA_4X6 = "na_index-4x6_4x6in"
33
+ CUPS_MEDIA_5X7 = "na_5x7_5x7in"
34
+ CUPS_MEDIA_8X10 = "na_govt-letter_8x10in"
35
+ CUPS_MEDIA_A3 = "iso_a3_297x420mm"
36
+ CUPS_MEDIA_A4 = "iso_a4_210x297mm"
37
+ CUPS_MEDIA_A5 = "iso_a5_148x210mm"
38
+ CUPS_MEDIA_A6 = "iso_a6_105x148mm"
39
+ CUPS_MEDIA_ENV10 = "na_number-10_4.125x9.5in"
40
+ CUPS_MEDIA_ENVDL = "iso_dl_110x220mm"
41
+ CUPS_MEDIA_LEGAL = "na_legal_8.5x14in"
42
+ CUPS_MEDIA_LETTER = "na_letter_8.5x11in"
43
+ CUPS_MEDIA_PHOTO_L = "oe_photo-l_3.5x5in"
44
+ CUPS_MEDIA_SUPERBA3 = "na_super-b_13x19in"
45
+ CUPS_MEDIA_TABLOID = "na_ledger_11x17in"
46
+
47
+ CUPS_MEDIA_SOURCE = "media-source"
48
+ CUPS_MEDIA_SOURCE_SUPPORTED = "media-source-supported"
49
+
50
+ CUPS_MEDIA_SOURCE_AUTO = "auto"
51
+ CUPS_MEDIA_SOURCE_MANUAL = "manual"
52
+
53
+ CUPS_MEDIA_TYPE = "media-type"
54
+ CUPS_MEDIA_TYPE_SUPPORTED = "media-type-supported"
55
+
56
+ CUPS_MEDIA_TYPE_AUTO = "auto"
57
+ CUPS_MEDIA_TYPE_ENVELOPE = "envelope"
58
+ CUPS_MEDIA_TYPE_LABELS = "labels"
59
+ CUPS_MEDIA_TYPE_LETTERHEAD = "stationery-letterhead"
60
+ CUPS_MEDIA_TYPE_PHOTO = "photographic"
61
+ CUPS_MEDIA_TYPE_PHOTO_GLOSSY = "photographic-glossy"
62
+ CUPS_MEDIA_TYPE_PHOTO_MATTE = "photographic-matte"
63
+ CUPS_MEDIA_TYPE_PLAIN = "stationery"
64
+ CUPS_MEDIA_TYPE_TRANSPARENCY = "transparency"
65
+
66
+ CUPS_NUMBER_UP = "number-up"
67
+ CUPS_NUMBER_UP_SUPPORTED = "number-up-supported"
68
+
69
+ CUPS_ORIENTATION = "orientation-requested"
70
+ CUPS_ORIENTATION_SUPPORTED = "orientation-requested-supported"
71
+
72
+ CUPS_ORIENTATION_PORTRAIT = "3"
73
+ CUPS_ORIENTATION_LANDSCAPE = "4"
74
+
75
+ CUPS_PRINT_COLOR_MODE = "print-color-mode"
76
+ CUPS_PRINT_COLOR_MODE_SUPPORTED = "print-color-mode-supported"
77
+
78
+ CUPS_PRINT_COLOR_MODE_AUTO = "auto"
79
+ CUPS_PRINT_COLOR_MODE_MONOCHROME = "monochrome"
80
+ CUPS_PRINT_COLOR_MODE_COLOR = "color"
81
+
82
+ CUPS_PRINT_QUALITY = "print-quality"
83
+ CUPS_PRINT_QUALITY_SUPPORTED = "print-quality-supported"
84
+
85
+ CUPS_PRINT_QUALITY_DRAFT = "3"
86
+ CUPS_PRINT_QUALITY_NORMAL = "4"
87
+ CUPS_PRINT_QUALITY_HIGH = "5"
88
+
89
+ CUPS_SIDES = "sides"
90
+ CUPS_SIDES_SUPPORTED = "sides-supported"
91
+
92
+ CUPS_SIDES_ONE_SIDED = "one-sided"
93
+ CUPS_SIDES_TWO_SIDED_PORTRAIT = "two-sided-long-edge"
94
+ CUPS_SIDES_TWO_SIDED_LANDSCAPE = "two-sided-short-edge"
95
+ end
@@ -0,0 +1,50 @@
1
+ module Cups::Enum
2
+ module HTTP
3
+ extend FFI::Library
4
+
5
+ Status = enum [
6
+ :http_error, -1,
7
+ :http_continue, 100,
8
+ :http_switching_protocols,
9
+ :http_ok, 200,
10
+ :http_created,
11
+ :http_accepted,
12
+ :http_not_authoritative,
13
+ :http_no_content,
14
+ :http_reset_content,
15
+ :http_partial_content,
16
+ :http_multiple_choices, 300,
17
+ :http_moved_permanently,
18
+ :http_moved_temporarily,
19
+ :http_see_other,
20
+ :http_not_modified,
21
+ :http_use_proxy,
22
+ :http_bad_request, 400,
23
+ :http_unauthorized,
24
+ :http_payment_required,
25
+ :http_forbidden,
26
+ :http_not_found,
27
+ :http_method_not_allowed,
28
+ :http_not_acceptable,
29
+ :http_proxy_authentication,
30
+ :http_request_timeout,
31
+ :http_conflict,
32
+ :http_gone,
33
+ :http_length_required,
34
+ :http_precondition,
35
+ :http_request_too_large,
36
+ :http_uri_too_long,
37
+ :http_unsupported_mediatype,
38
+ :http_requested_range,
39
+ :http_expectation_failed,
40
+ :http_upgrade_required, 426,
41
+ :http_server_error, 500,
42
+ :http_not_implemented,
43
+ :http_bad_gateway,
44
+ :http_service_unavailable,
45
+ :http_gateway_timeout,
46
+ :http_not_supported,
47
+ :http_authorization_canceled, 1000
48
+ ]
49
+ end
50
+ end
@@ -0,0 +1,15 @@
1
+ module Cups::Enum
2
+ module IPP
3
+ extend FFI::Library
4
+
5
+ JobState = enum [
6
+ :pending, 3,
7
+ :held,
8
+ :processing,
9
+ :stopped,
10
+ :canceled,
11
+ :aborted,
12
+ :completed
13
+ ]
14
+ end
15
+ end
@@ -0,0 +1,53 @@
1
+ module Cups::Enum
2
+ module IPP
3
+ extend FFI::Library
4
+
5
+ Status = enum [
6
+ :ipp_ok, 0,
7
+ :ipp_ok_subst,
8
+ :ipp_ok_conflict,
9
+ :ipp_ok_ignored_subscriptions,
10
+ :ipp_ok_ignored_notifications,
11
+ :ipp_ok_too_many_events,
12
+ :ipp_ok_but_cancel_subscription,
13
+ :ipp_ok_events_complete,
14
+ :ipp_redirection_other_site, 512,
15
+ :cups_see_other, 640,
16
+ :ipp_bad_request, 1024,
17
+ :ipp_forbidden,
18
+ :ipp_not_authenticated,
19
+ :ipp_not_authorized,
20
+ :ipp_not_possible,
21
+ :ipp_timeout,
22
+ :ipp_not_found,
23
+ :ipp_gone,
24
+ :ipp_request_entity,
25
+ :ipp_request_value,
26
+ :ipp_document_format,
27
+ :ipp_attributes,
28
+ :ipp_uri_scheme,
29
+ :ipp_charset,
30
+ :ipp_conflict,
31
+ :ipp_compression_not_supported,
32
+ :ipp_compression_error,
33
+ :ipp_document_format_error,
34
+ :ipp_document_access_error,
35
+ :ipp_attributes_not_settable,
36
+ :ipp_ignored_all_subscriptions,
37
+ :ipp_too_many_subscriptions,
38
+ :ipp_ignored_all_notifications,
39
+ :ipp_print_support_file_not_found,
40
+ :ipp_internal_error, 1280,
41
+ :ipp_operation_not_supported,
42
+ :ipp_service_unavailable,
43
+ :ipp_version_not_supported,
44
+ :ipp_device_error,
45
+ :ipp_temporary_error,
46
+ :ipp_not_accepting,
47
+ :ipp_printer_busy,
48
+ :ipp_error_job_canceled,
49
+ :ipp_multiple_jobs_not_supported,
50
+ :ipp_printer_is_deactivated
51
+ ]
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+ module FFI::Cups
2
+ module Array
3
+ extend FFI::Library
4
+
5
+ ffi_lib('cups')
6
+
7
+ # @overload cupsArrayFirst(pointer)
8
+ # @param pointer [Pointer] to _cups_array_s struct
9
+ # @return [Pointer] void pointer to first element
10
+ attach_function 'cupsArrayFirst', [:pointer], :pointer, blocking: true
11
+
12
+ # @overload cupsArrayNext(pointer)
13
+ # @param pointer [Pointer] to _cups_array_s struct
14
+ # @return [Pointer] void pointer to first element
15
+ attach_function 'cupsArrayNext', [:pointer], :pointer, blocking: true
16
+ end
17
+ end
@@ -0,0 +1,176 @@
1
+ # cups.h
2
+ # {https://www.cups.org/doc/cupspm.html Programming Manual}
3
+ # {https://github.com/apple/cups/blob/master/cups/cups.h cups.h source}
4
+
5
+ module FFI::Cups
6
+ extend FFI::Library
7
+
8
+ ffi_lib('cups')
9
+
10
+ # Get the current encryption settings.
11
+ # @return [Integer] encryption settings
12
+ # {https://www.cups.org/doc/cupspm.html#cupsEncryption}
13
+ attach_function 'cupsEncryption', [], :int, blocking: true
14
+
15
+ # Check that the option and value are supported by the destination.
16
+ # @overload cupsCheckDestSupported(pointer, pointer, pointer, string, string)
17
+ # @param http [Pointer]
18
+ # @param destination [Pointer]
19
+ # @param dinfo [Pointer]
20
+ # @param option [String]
21
+ # @param value [String]
22
+ # @return [Integer] 1 if supported, 0 otherwise
23
+ # {https://www.cups.org/doc/cupspm.html#cupsCheckDestSupported}
24
+ attach_function 'cupsCheckDestSupported', [:pointer, :pointer, :pointer, :string, :string], :int, blocking: true
25
+
26
+ # Get the named destination from the list.
27
+ # @overload cupsGetDest(string, string, int, pointer)
28
+ # @param name [String]
29
+ # @param instance [String] instance name or NULL
30
+ # @param num_dests [Integer] number of destinations
31
+ # @param destinations [Pointer]
32
+ # @return [Pointer, NULL] destination or NULL
33
+ # {https://www.cups.org/doc/cupspm.html#cupsGetDest}
34
+ attach_function 'cupsGetDest', [:string, :string, :int, :pointer], :pointer, blocking: true
35
+
36
+ # Get the list of destinations from the specified server.
37
+ # @overload cupsGetDests2(pointer, pointer)
38
+ # @param pointer [Pointer] to http connection for server or CUPS_HTTP_DEFAULT
39
+ # @param pointer [Pointer] to destinations
40
+ # @return [Integer] number of destinations
41
+ # {https://www.cups.org/doc/cupspm.html#cupsGetDests2}
42
+ attach_function 'cupsGetDests2', [:pointer, :pointer], :int, blocking: true
43
+
44
+ # Free the memory used by the list of destinations.
45
+ # @overload cupsFreeDests(int, pointer)
46
+ # @param number [Integer] of destinations
47
+ # @param pointer [Pointer] to destinations
48
+ # {https://www.cups.org/doc/cupspm.html#cupsFreeDests}
49
+ attach_function 'cupsFreeDests', [ :int, :pointer ], :void, blocking: true
50
+
51
+ # Prints a File
52
+ # @overload cupsPrintFile(string, string, string, int, pointer)
53
+ # @param name [String] of the printer
54
+ # @param name [String] of the file
55
+ # @param title [String] of the job
56
+ # @param number [Integer] of options
57
+ # @param pointer [Pointer] to a CupsOptionS struct
58
+ # @return [Integer] job number or 0 on error
59
+ attach_function 'cupsPrintFile', [ :string, :string, :string, :int, :pointer ], :int, blocking: true
60
+
61
+ # Prints a file from a specific connection
62
+ # @overload cupsPrintFile2(pointer, string, string, string, int, pointer)
63
+ # @param pointer [Pointer] to an http connection
64
+ # @param name [String] of the printer
65
+ # @param name [String] of the file
66
+ # @param title [String] of the title
67
+ # @param number [Integer] of options
68
+ # @param pointer [Pointer] to a CupsOptionS struct
69
+ # @return [Integer] number of the job or 0 on error
70
+ attach_function 'cupsPrintFile2', [ :pointer, :string, :string, :string, :int, :pointer ], :int, blocking: true
71
+
72
+ # Returns the last error in string format
73
+ # @return [String] error
74
+ attach_function 'cupsLastErrorString', [], :string, blocking: true
75
+
76
+ # Cancel a job on a destination.
77
+ # @overload cupsCancelDestJob(string, int)
78
+ # @param connection [Pointer] to a http server
79
+ # @param pointer [Pointer] to a destination
80
+ # @param id [Integer] of the job
81
+ # @return [Integer] IPP status
82
+ # {https://www.cups.org/doc/cupspm.html#cupsCancelDestJob}
83
+ attach_function 'cupsCancelDestJob', [:string, :int], :void, blocking: true
84
+
85
+ # Cancel a job on a destination name
86
+ # @overload cupsCancelJob(string, int)
87
+ # @param name [String] of the destination
88
+ # @param id [Ingeger] of the job
89
+ attach_function 'cupsCancelJob', [:string, :int], :void, blocking: true
90
+
91
+ # Cancel a job on a destination
92
+ # @overload cupsCancelJob2(pointer, string, int)
93
+ # @param pointer [Pointer] of the http connection
94
+ # @param name [String] of the destination
95
+ # @param id [Ingeger] of the job
96
+ attach_function 'cupsCancelJob2', [:pointer, :string, :int], :void, blocking: true
97
+
98
+ # Get the jobs from a connection
99
+ # @overload cupsGetJobs2(pointer, pointer, string, int, int)
100
+ # @param pointer [Pointer] to a http connection object
101
+ # @param pointer [Pointer] to Cups::Struct::Job
102
+ # @param name [String] of the printer
103
+ # @param number [Integer] of type of job (0 == all users, 1 == mine)
104
+ # @param which [Integer] jobs (CUPS_WHICHJOBS_ALL, CUPS_WHICHJOBS_ACTIVE, or CUPS_WHICHJOBS_COMPLETED)
105
+ # @return [Integer] number of jobs
106
+ # {https://www.cups.org/doc/cupspm.html#cupsGetJobs2}
107
+ attach_function 'cupsGetJobs2', [:pointer, :pointer, :string, :int, :int], :int, blocking: true
108
+
109
+ # Get the jobs
110
+ # @overload cupsGetJobs(pointer, string, int, int)
111
+ # @param pointer [Pointer] to Cups::Struct::Job to populate
112
+ # @param name [String] of the printer
113
+ # @param number [Integer] of type of job (0 == all users, 1 == mine)
114
+ # @param which [Integer] jobs (CUPS_WHICHJOBS_ALL, CUPS_WHICHJOBS_ACTIVE, or CUPS_WHICHJOBS_COMPLETED)
115
+ # @return [Integer] number of jobs
116
+ attach_function 'cupsGetJobs', [:pointer, :string, :int, :int], :int, blocking: true
117
+
118
+ # Free memory used by job data.
119
+ # @overload cupsFreeJobs(int, pointer)
120
+ # @param number [Integer] of jobs
121
+ # @param pointer [Pointer] to the first Cups::Struct::Job to free
122
+ # {https://www.cups.org/doc/cupspm.html#cupsFreeJobs}
123
+ attach_function 'cupsFreeJobs', [:int, :pointer ], :void, blocking: true
124
+
125
+ # Create a job on a destination.
126
+ # @overload cupsCreateJob(pointer, string, string, int, pointer)
127
+ # @param pointer [Pointer] to http connection to server or CUPS_HTTP_DEFAULT
128
+ # @param name [String] of the printer
129
+ # @param title [String] of the job
130
+ # @param number [Integer] of options
131
+ # @param pointer [Pointer] to a Cups::Struct::Option object
132
+ # @return [Integer] job number or 0 on error
133
+ attach_function 'cupsCreateJob', [:pointer, :string, :string, :int, :pointer], :int, blocking: true
134
+
135
+ # Start a document
136
+ # @overload cupsStartDocument(pointer, string, int, string, string, int)
137
+ # @param pointer [Pointer] to http connection to server or CUPS_HTTP_DEFAULT
138
+ # @param name [String] of the printer
139
+ # @param id [Integer] of the job
140
+ # @param name [String] of the document
141
+ # @param mime_type [String] format of the document
142
+ # @param number [Integer] of the last document (1 for last document in job, 0 otherwise)
143
+ # @return [Cups::Enum::HTTP::Status] HttpStatus
144
+ # {https://www.cups.org/doc/cupspm.html#submitting-a-print-job}
145
+ attach_function 'cupsStartDocument', [:pointer, :string, :int, :string, :string, :int], Cups::Enum::HTTP::Status, blocking: true
146
+
147
+ # @overload cupsWriteRequestData(pointer, pointer, bytesize)
148
+ # @param pointer [Pointer] to http connection to server or CUPS_HTTP_DEFAULT
149
+ # @param data [Pointer] in a character string pointer
150
+ # @param length [Byte] of data
151
+ # @return [Cups::Enum::HTTP::Status] HttpStatus
152
+ attach_function 'cupsWriteRequestData', [:pointer, :pointer, :size_t], Cups::Enum::HTTP::Status, blocking: true
153
+
154
+ # @overload cupsFinishDocument(pointer, string)
155
+ # @param pointer [Pointer] to http connection to server or CUPS_HTTP_DEFAULT
156
+ # @param name [String] of the printer
157
+ # @return [Cups::Enum::IPP::Status] IppStatus
158
+ attach_function 'cupsFinishDocument', [:pointer, :string], Cups::Enum::IPP::Status, blocking: true
159
+
160
+ # Add an option to an option array.
161
+ # @overload cupsAddOption(string, string, int, pointer)
162
+ # @param name [String] of option
163
+ # @param value [String] of option
164
+ # @param number [Integer] of options
165
+ # @param pointer [Pointer] to options
166
+ # @return [Integer] number of options
167
+ # {https://www.cups.org/doc/cupspm.html#cupsAddOption}
168
+ attach_function 'cupsAddOption', [:string, :string, :int, :pointer], :int, blocking: true
169
+
170
+ # Free all memory used by options.
171
+ # @overload cupsFreeOptions(int, pointer)
172
+ # @param number [Integer] of options
173
+ # @param pointer [Pointer] to options
174
+ # {https://www.cups.org/doc/cupspm.html#cupsFreeOptions}
175
+ attach_function 'cupsFreeOptions', [:int, :pointer], :void, blocking: true
176
+ end
@@ -0,0 +1,40 @@
1
+ module FFI::Cups
2
+ module Http
3
+ extend FFI::Library
4
+
5
+ ffi_lib('cups')
6
+
7
+ # @overload httpConnectEncrypt(string, int, int)
8
+ # @param hostname [String]
9
+ # @param port [int]
10
+ # @param encryption_settings [int]
11
+ # @return [Pointer] pointer to a http_t object
12
+ # @deprecated Please use httpConnect2 instead
13
+ attach_function 'httpConnectEncrypt', [ :string, :int, :int], :pointer, blocking: true
14
+
15
+ # @overload httpClose(pointer)
16
+ # @param pointer [Pointer] to a http_t object
17
+ attach_function 'httpClose', [ :pointer ], :void, blocking: true
18
+
19
+ # Get a list of addresses for a hostname.
20
+ # @overload httpAddrGetList(string, int, string)
21
+ # @param hostname [String]
22
+ # @param family [Integer]
23
+ # @param port [String]
24
+ # @return [Pointer] List of addresses or NULL
25
+ attach_function 'httpAddrGetList', [:string, :int, :string], :pointer, blocking: true
26
+
27
+ # Connect to a HTTP server.
28
+ # @overload httpConnect2(string, int, pointer, int, int, int, int, pointer)
29
+ # @param hostname [String]
30
+ # @param port [Integer]
31
+ # @param addrlist [Pointer] can be NULL
32
+ # @param family [Integer] (AF_UNSPEC=0)
33
+ # @param encryption_settings [Integer] from FFI::Cups.cupsEncryption()
34
+ # @param blocking [Integer]
35
+ # @param msec [Integer] use 5000 or less
36
+ # @param cancel[Pointer] integer pointer, can be NULL
37
+ # @return [Pointer] http_t object
38
+ attach_function 'httpConnect2', [:string, :int, :pointer, :int, :int, :int, :int, :pointer], :pointer, blocking: true
39
+ end
40
+ end
@@ -0,0 +1,13 @@
1
+ module Cups
2
+ class Job
3
+ attr_accessor :id, :title, :printer
4
+
5
+ def initialize(id, title, printer=nil)
6
+ @id = id
7
+ @title = title
8
+ @printer = printer
9
+ end
10
+
11
+ end
12
+ end
13
+
@@ -0,0 +1,105 @@
1
+ module Cups
2
+ class Printer
3
+ attr_reader :name, :options
4
+
5
+ # @param name [String] printer's name
6
+ # @param options [Hash] printer's options
7
+ def initialize(name, options={})
8
+ @name = name
9
+ @options = options
10
+ end
11
+
12
+ # Returns the printer state
13
+ # @return [Symbol] printer state from options
14
+ def state
15
+ case options['printer-state']
16
+ when '3' then :idle
17
+ when '4' then :printing
18
+ when '5' then :stopped
19
+ else :unkown
20
+ end
21
+ end
22
+
23
+ # Returns the reason for the printer state
24
+ # @return [Array] array of reasons in string format
25
+ def state_reasons
26
+ options['printer-state-reasons'].split(/,/)
27
+ end
28
+
29
+ # Get all destinations (printer devices)
30
+ # @param connection [Pointer] http pointer from {Cups::Connection#httpConnect2}
31
+ def self.get_destinations(connection=nil)
32
+ pointer = FFI::MemoryPointer.new :pointer
33
+ destinations = cupsGetDests2(connection, pointer)
34
+ 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)
44
+ printers.push(printer)
45
+ end
46
+ cupsFreeDests(destinations.keys.count, pointer)
47
+ return printers
48
+ end
49
+
50
+ # Get a destination by name
51
+ # @param name [String] name of the printer
52
+ # @param connection [Pointer] http pointer from {Cups::Connection#httpConnect2}
53
+ 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!"
59
+ end
60
+
61
+ private
62
+ # Wrapper around {::FFI::Cups#cupsGetDests2}
63
+ # @param connection [Pointer] http pointer from {Cups::Connection#httpConnect2}
64
+ # @param pointer [Pointer] pointer to the destinations
65
+ # @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
73
+ end
74
+ return destinations
75
+ end
76
+
77
+ # 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 = {}
83
+
84
+ destination[:num_options].times do |index|
85
+ option = Cups::Struct::Option.new(destination[:options] + (struct_size * index))
86
+ options[index] = option
87
+ end
88
+ return options
89
+ end
90
+
91
+ # Wrapper around {::FFI::Cups#cupsFreeDests}
92
+ # @param number_destinations [Integer]
93
+ # @param pointer [Pointer] pointer to the destinations
94
+ def self.cupsFreeDests(number_destinations, pointer)
95
+ FFI::Cups.cupsFreeDests(number_destinations, pointer.get_pointer(0))
96
+ end
97
+
98
+ # Wrapper around {::FFI::Cups#cupsFreeOptions}
99
+ # @param number_options [Integer]
100
+ # @param pointer [Pointer] pointer to the options
101
+ def self.cupsFreeOptions(number_options, pointer)
102
+ FFI::Cups.cupsFreeOptions(number_options, pointer.get_pointer(0))
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,9 @@
1
+ module Cups::Struct
2
+ class Destination < FFI::Struct
3
+ layout :name, :string,
4
+ :instance, :string,
5
+ :is_default, :int,
6
+ :num_options, :int,
7
+ :options, :pointer
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Cups::Struct
2
+ class Job < FFI::Struct
3
+ layout :id, :int,
4
+ :dest, :string,
5
+ :title, :string,
6
+ :user, :string,
7
+ :format, :string,
8
+ :state, Cups::Enum::IPP::JobState,
9
+ :size, :int,
10
+ :priority, :int,
11
+ :completed_time, :long,
12
+ :creation_time, :long,
13
+ :processing_time, :long
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module Cups::Struct
2
+ class Option < FFI::Struct
3
+ layout :name, :string, :value, :string
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Cups
2
+ VERSION = "0.1.1"
3
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffi-cups
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Hugo Marquez
8
+ - Nathan Ehresman
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2021-04-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ffi
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 1.15.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 1.15.0
28
+ description: Simple wrapper around libcups to give CUPS printing capabilities to Ruby
29
+ apps.
30
+ email:
31
+ - hugomarquez.dev@gmail.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".gitignore"
37
+ - Gemfile
38
+ - Gemfile.lock
39
+ - LICENSE
40
+ - README.md
41
+ - Rakefile
42
+ - bin/console
43
+ - bin/setup
44
+ - ffi-cups.gemspec
45
+ - lib/ffi-cups.rb
46
+ - lib/ffi-cups/connection.rb
47
+ - lib/ffi-cups/constants.rb
48
+ - lib/ffi-cups/enum/http/status.rb
49
+ - lib/ffi-cups/enum/ipp/job_state.rb
50
+ - lib/ffi-cups/enum/ipp/status.rb
51
+ - lib/ffi-cups/ffi/array.rb
52
+ - lib/ffi-cups/ffi/cups.rb
53
+ - lib/ffi-cups/ffi/http.rb
54
+ - lib/ffi-cups/job.rb
55
+ - lib/ffi-cups/printer.rb
56
+ - lib/ffi-cups/struct/destination.rb
57
+ - lib/ffi-cups/struct/job.rb
58
+ - lib/ffi-cups/struct/option.rb
59
+ - lib/ffi-cups/version.rb
60
+ homepage: https://github.com/hugomarquez/ffi-cups
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.3.0
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubygems_version: 3.1.2
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: FFI wrapper around libcups
83
+ test_files: []