├── .gitignore ├── images ├── printersetupicon.png └── printersetupinterface.png ├── PrinterSetup ├── Assets.xcassets │ ├── Contents.json │ ├── package.imageset │ │ ├── package.png │ │ ├── package-1.png │ │ ├── package-2.png │ │ └── Contents.json │ ├── printer.imageset │ │ ├── printer.png │ │ ├── printer-1.png │ │ ├── printer-2.png │ │ └── Contents.json │ ├── Warning.iconset │ │ ├── icon_16x16.png │ │ ├── icon_32x32.png │ │ ├── icon_128x128.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_256x256@2x.png │ │ └── icon_512x512@2x.png │ ├── defaultprintericon.iconset │ │ ├── icon_16x16.png │ │ ├── icon_32x32.png │ │ ├── icon_128x128.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_256x256@2x.png │ │ └── icon_512x512@2x.png │ ├── AppIcon.appiconset │ │ ├── PrinterSetupIkon-1024.png │ │ ├── PrinterSetupIkon-128.png │ │ ├── PrinterSetupIkon-16.png │ │ ├── PrinterSetupIkon-256.png │ │ ├── PrinterSetupIkon-257.png │ │ ├── PrinterSetupIkon-32.png │ │ ├── PrinterSetupIkon-33.png │ │ ├── PrinterSetupIkon-512.png │ │ ├── PrinterSetupIkon-513.png │ │ ├── PrinterSetupIkon-64.png │ │ └── Contents.json │ ├── monkey.imageset │ │ ├── hear-no-evil-monkey_1f649.png │ │ ├── hear-no-evil-monkey_1f649-1.png │ │ ├── hear-no-evil-monkey_1f649-2.png │ │ └── Contents.json │ └── defaultinkjetprintericon.iconset │ │ ├── icon_128x128.png │ │ ├── icon_16x16.png │ │ ├── icon_256x256.png │ │ ├── icon_32x32.png │ │ ├── icon_512x512.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_256x256@2x.png │ │ └── icon_512x512@2x.png ├── PrinterSetup.entitlements ├── Info.plist ├── Keychain.swift ├── Preferences.swift ├── AppDelegate.swift ├── Functions.swift └── Base.lproj │ └── MainMenu.xib ├── PrinterSetup.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── mikael.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── mikael.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── xcshareddata │ └── xcschemes │ │ └── PrinterSetup.xcscheme └── project.pbxproj ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /images/printersetupicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/images/printersetupicon.png -------------------------------------------------------------------------------- /images/printersetupinterface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/images/printersetupinterface.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/package.imageset/package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/package.imageset/package.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/printer.imageset/printer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/printer.imageset/printer.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/Warning.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/Warning.iconset/icon_16x16.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/Warning.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/Warning.iconset/icon_32x32.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/package.imageset/package-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/package.imageset/package-1.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/package.imageset/package-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/package.imageset/package-2.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/printer.imageset/printer-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/printer.imageset/printer-1.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/printer.imageset/printer-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/printer.imageset/printer-2.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/Warning.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/Warning.iconset/icon_128x128.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/Warning.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/Warning.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/Warning.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/Warning.iconset/icon_256x256.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/Warning.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/Warning.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/Warning.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/Warning.iconset/icon_512x512.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/Warning.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/Warning.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/Warning.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/Warning.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/Warning.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/Warning.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_16x16.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_32x32.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-1024.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-128.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-16.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-256.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-257.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-257.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-32.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-33.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-512.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-513.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-513.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/AppIcon.appiconset/PrinterSetupIkon-64.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_128x128.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_256x256.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_512x512.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultprintericon.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/monkey.imageset/hear-no-evil-monkey_1f649.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/monkey.imageset/hear-no-evil-monkey_1f649.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_128x128.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_16x16.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_256x256.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_32x32.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_512x512.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/monkey.imageset/hear-no-evil-monkey_1f649-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/monkey.imageset/hear-no-evil-monkey_1f649-1.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/monkey.imageset/hear-no-evil-monkey_1f649-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/monkey.imageset/hear-no-evil-monkey_1f649-2.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup/Assets.xcassets/defaultinkjetprintericon.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /PrinterSetup.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PrinterSetup/PrinterSetup.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PrinterSetup.xcodeproj/project.xcworkspace/xcuserdata/mikael.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaellofgren/PrinterSetup/HEAD/PrinterSetup.xcodeproj/project.xcworkspace/xcuserdata/mikael.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PrinterSetup.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/printer.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "printer-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "printer-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "printer.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/package.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "package.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "package-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "package-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/monkey.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "hear-no-evil-monkey_1f649-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "hear-no-evil-monkey_1f649.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "hear-no-evil-monkey_1f649-1.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /PrinterSetup.xcodeproj/xcuserdata/mikael.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PrinterSetup.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 738A1ACF242E66FC001FF65B 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mikael Löfgren 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PrinterSetup/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | public.app-category.utilities 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2024 Mikael Löfgren. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | NSSupportsAutomaticTermination 34 | 35 | NSSupportsSuddenTermination 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /PrinterSetup/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "PrinterSetupIkon-16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "PrinterSetupIkon-33.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "PrinterSetupIkon-32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "PrinterSetupIkon-64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "PrinterSetupIkon-128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "PrinterSetupIkon-257.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "PrinterSetupIkon-256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "PrinterSetupIkon-513.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "PrinterSetupIkon-512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "PrinterSetupIkon-1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | # PrinterSetup
3 | 4 | A simple printer utility that generates lpadmin output to add printers.
5 | It also generates lpadmin options from the PPD file.
6 | From options lookup the ip number of a bonjour (dnssd) printer (you need to be on the same network as the printer)
7 | Export output as no payload pkg and munki pkg info.
8 | From the preferences settings you can choose to generate a signed pkg and enable Cups Webinterface.
9 |
10 | 11 | You can also use it to export any bashscript by simply paste your bashscript to the Final settings field
12 | and give the output a name in the Cups print queue name field and then choose the export options.
13 | 14 |
15 | 16 | 17 | Download latest version here: https://github.com/mikaellofgren/PrinterSetup/releases 18 | 19 | System requirements: macOS 12 or later
20 | Icon: vecteezy.com
21 | 22 | ## Airprint 23 | Version 2.1 added a custom script for adding printer as Airprint (IPP, IPPS) 24 | Admin/sudo permissions is needed to also add printer icon, so run the script as sudo or export as .pkg 25 | Inspired from: 26 | https://aporlebeke.wordpress.com/2021/05/26/configuring-printers-programmatically-for-airprint-part-2-now-with-icons/ 27 | 28 | ## Export for manual distribution 29 | If you want to export for manual distribution, make sure you got the printerdriver installer
30 | then add this command before the lpadmin command, and change: CHANGE_ME_TO_PRINTERDRIVER_NAME
31 | in the command to match the name of the printerdriver installer then Export as no payload pkg.
32 |
33 | Put the no payload pkg and printerdriver installer into a folder and run only the
34 | no payload pkg, if everythings works it should first install the printerdriver and then add the printer.
35 |
36 | 37 | If you want to hide the printerdriver installer add a period before the filename
38 | dont forget to change in the command too.
39 | 40 | Create a .dmg imagefile for distribution from discutility of the folder containing the the hidden printerdriver installer and the no payload pkg.
41 |
42 | 43 | 44 | ``` 45 | #!/bin/bash 46 | PATH_SCRIPT=$(dirname "$1") 47 | /usr/sbin/installer -pkg "$PATH_SCRIPT/CHANGE_ME_TO_PRINTERDRIVER_NAME.pkg" -target / 48 | ``` 49 | -------------------------------------------------------------------------------- /PrinterSetup/Keychain.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AppKit 3 | 4 | public func getAllKeyChainIdentityItems() -> [String] { 5 | 6 | let query: [String: Any] = [ 7 | kSecClass as String : kSecClassIdentity, 8 | kSecReturnData as String : kCFBooleanTrue!, 9 | kSecReturnAttributes as String : kCFBooleanTrue!, 10 | kSecReturnRef as String : kCFBooleanTrue!, 11 | kSecMatchLimit as String: kSecMatchLimitAll 12 | ] 13 | 14 | var result: AnyObject? 15 | 16 | let lastResultCode = withUnsafeMutablePointer(to: &result) { 17 | SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) 18 | } 19 | 20 | var values = [String]() 21 | 22 | if lastResultCode == noErr { 23 | let array = result as? Array> 24 | 25 | for item in array! { 26 | let label = item[kSecAttrLabel as String] as? String 27 | if label!.contains("Developer") { 28 | values.append(label!) 29 | 30 | } 31 | 32 | } 33 | } 34 | 35 | return values 36 | } 37 | 38 | 39 | func createCertificatePopup () { 40 | 41 | let myIdentities = getAllKeyChainIdentityItems() 42 | var developerIdentitiesTemp = Set() 43 | var developerIdentities:Array = [String]() 44 | 45 | for items in myIdentities { 46 | let lastItems = items.components(separatedBy: ":").last! 47 | let lastItemsClean = String(lastItems.dropFirst()) 48 | developerIdentitiesTemp.insert(lastItemsClean) 49 | developerIdentities = Array(developerIdentitiesTemp) 50 | } 51 | 52 | if developerIdentities.isEmpty == false { 53 | appDelegate().certificatePopUp.isEnabled = true 54 | appDelegate().certificatePopUp.removeAllItems() 55 | // Add an item to the list 56 | appDelegate().certificatePopUp.addItems(withObjectValues: developerIdentities) 57 | appDelegate().certificatePopUp.selectItem(at: 0) 58 | } else { 59 | // If no certificate is found Checkbox to Off and variable to 0 60 | appDelegate().certificatePopUp.addItems(withObjectValues: ["No Developer Certificates found"]) 61 | // Set the state to Off 62 | appDelegate().signPKGButton.state = NSControl.StateValue.off 63 | statusCertificateCheckbox = 0 64 | } 65 | } 66 | 67 | func certificateCheckBox () { 68 | // If Checkbox is ON (1) try get Certificates 69 | if statusCertificateCheckbox == 1 { 70 | createCertificatePopup () 71 | } else { 72 | appDelegate().signPKGButton.state = NSControl.StateValue.off 73 | statusCertificateCheckbox = 0 74 | appDelegate().certificatePopUp.removeAllItems() 75 | 76 | appDelegate().certificatePopUp.addItem(withObjectValue: "") 77 | appDelegate().certificatePopUp.selectItem(at: 0) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /PrinterSetup.xcodeproj/xcshareddata/xcschemes/PrinterSetup.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 61 | 63 | 69 | 70 | 71 | 72 | 74 | 75 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /PrinterSetup/Preferences.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Preferences.swift 3 | // PrinterSetup 4 | // 5 | // Created by Mikael Löfgren on 2024-12-18. 6 | // Copyright © 2024 Mikael Löfgren. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | 13 | public func createPlist() { 14 | 15 | 16 | 17 | // Use with optionals ? so the Codable is more loose 18 | struct Preferences: Codable { 19 | 20 | var versionnumber: String? 21 | var pkgidentifier: String? 22 | var devcertificate: String? 23 | //var choosencommandcolor: Any? 24 | //var choosenvariablecolor: NSColor? 25 | 26 | } 27 | 28 | 29 | 30 | // Get values from Preferences Window 31 | // remove all except 0-9 and . for Version Number 32 | versionNumberRead = cleanVersion(appDelegate().versionTextField.stringValue) 33 | appDelegate().versionTextField.stringValue = versionNumberRead 34 | 35 | pkgIdentifierRead = appDelegate().identifierTextField.stringValue 36 | devCertificateRead = appDelegate().certificatePopUp.stringValue 37 | 38 | 39 | let preferences = Preferences(versionnumber: versionNumberRead, pkgidentifier: pkgIdentifierRead, devcertificate: devCertificateRead) 40 | 41 | let encoder = PropertyListEncoder() 42 | encoder.outputFormat = .xml 43 | 44 | do { 45 | let data = try encoder.encode(preferences) 46 | try data.write(to: plistPath) 47 | } catch { 48 | print(error) 49 | } 50 | } 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | public func readPlist() { 59 | struct PreferencesRead: Decodable { 60 | private enum CodingKeys: String, CodingKey { 61 | case versionnumber, pkgidentifier, devcertificate, choosencommandcolor, choosenvariablecolor 62 | } 63 | var versionnumber: String? = nil 64 | var pkgidentifier: String? = nil 65 | var devcertificate: String? = nil 66 | var choosencommandcolor: String? = nil 67 | var choosenvariablecolor: String? = nil 68 | } 69 | 70 | 71 | 72 | if FileManager.default.fileExists(atPath: plistFile){ 73 | 74 | func parsePlist() -> PreferencesRead { 75 | let data = try! Data(contentsOf: plistPath) 76 | let decoder = PropertyListDecoder() 77 | return try! decoder.decode(PreferencesRead.self, from: data) 78 | } 79 | 80 | let readPlistValues = parsePlist() 81 | 82 | if readPlistValues.versionnumber != nil { 83 | versionNumberRead = readPlistValues.versionnumber! 84 | } 85 | 86 | if readPlistValues.pkgidentifier != nil { 87 | pkgIdentifierRead = readPlistValues.pkgidentifier! 88 | } 89 | 90 | if readPlistValues.devcertificate != nil { 91 | devCertificateRead = readPlistValues.devcertificate! 92 | } 93 | 94 | 95 | 96 | } else { 97 | print("\(plistFile) not found.") 98 | } 99 | 100 | if versionNumberRead != "" { 101 | appDelegate().versionTextField.stringValue = versionNumberRead 102 | } 103 | 104 | if pkgIdentifierRead != "" { 105 | appDelegate().identifierTextField.stringValue = pkgIdentifierRead 106 | } 107 | 108 | if devCertificateRead != "" { 109 | appDelegate().certificatePopUp.removeAllItems() 110 | appDelegate().signPKGButton.state = NSControl.StateValue.on 111 | statusCertificateCheckbox = 1 112 | appDelegate().certificatePopUp.addItem(withObjectValue: devCertificateRead) 113 | appDelegate().certificatePopUp.selectItem(withObjectValue: devCertificateRead) 114 | 115 | } 116 | 117 | 118 | 119 | 120 | } 121 | 122 | -------------------------------------------------------------------------------- /PrinterSetup/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PrinterSetup 4 | // 5 | // Created by Mikael Löfgren on 2024-12-18. 6 | // Copyright © 2024 Mikael Löfgren. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import AppKit 11 | 12 | @NSApplicationMain 13 | class AppDelegate: NSObject, NSApplicationDelegate { 14 | 15 | @IBOutlet var spinner: NSProgressIndicator! 16 | @IBOutlet weak var window: NSWindow! 17 | @IBOutlet var printerslistPopup: NSPopUpButton! 18 | @IBOutlet var finalOutputTextField: NSTextView! 19 | @IBOutlet var printerPPDPopup: NSPopUpButton! 20 | @IBOutlet var cupsNameTextfield: NSTextField! 21 | @IBOutlet var printerNameTextField: NSTextField! 22 | @IBOutlet var printerLocationTextField: NSTextField! 23 | @IBOutlet var printerProtocolPopup: NSPopUpButton! 24 | @IBOutlet var printerIpaddressComboBox: NSComboBox! 25 | @IBOutlet weak var printerIconButton: NSButton! 26 | @IBOutlet weak var printerIconPathTextField: NSTextField! 27 | @IBOutlet var exportPopUp: NSPopUpButton! 28 | 29 | @IBOutlet var printerIPmenu: NSMenuItem! 30 | @IBOutlet var preferencesWindow: NSView! 31 | 32 | // Preferences 33 | @IBOutlet var versionTextField: NSTextField! 34 | @IBOutlet var identifierTextField: NSTextField! 35 | @IBOutlet var signPKGButton: NSButton! 36 | @IBOutlet var certificatePopUp: NSComboBox! 37 | @IBOutlet var commandColor: NSColorWell! 38 | @IBOutlet var variableColor: NSColorWell! 39 | @IBOutlet var enableCupsWebbutton: NSButton! 40 | @IBOutlet var cupsURLbutton: NSButton! 41 | 42 | 43 | // Preferences 44 | @IBAction func versionFieldSender(_ sender: NSTextField) { 45 | createPlist() 46 | } 47 | 48 | @IBAction func identifierFieldSender(_ sender: NSTextField) { 49 | identifierCheck () 50 | createPlist() 51 | } 52 | 53 | @IBAction func signPKGButtonPressed(_ sender: NSButton) { 54 | statusCertificateCheckbox = sender.state.rawValue 55 | certificateCheckBox () 56 | createPlist() 57 | } 58 | 59 | @IBAction func commandColorSender(_ sender: NSColorWell) { 60 | selectedCommandColor = sender.color 61 | generateFinalOutputTextField () 62 | } 63 | 64 | @IBAction func variabelColorSender(_ sender: NSColorWell) { 65 | selectedVariableColor = sender.color 66 | generateFinalOutputTextField () 67 | 68 | } 69 | 70 | 71 | @IBAction func enableCupsWebInterface(_ sender: NSButton) { 72 | statusCupsCheckbox = sender.state.rawValue 73 | cupsCheckBox () 74 | } 75 | 76 | @IBAction func openCupsWebInterface(_ sender: Any) { 77 | let url = URL(string: "http://127.0.0.1:631")! 78 | if NSWorkspace.shared.open(url) { 79 | } 80 | } 81 | 82 | 83 | 84 | 85 | 86 | // Export options 87 | @IBAction func lookupPrinterIp(_ sender: NSMenuItem) { 88 | lookupDNSSDtoIP () 89 | } 90 | 91 | @IBAction func noPayloadPkg(_ sender: Any) { 92 | exportAsPkg () 93 | } 94 | 95 | @IBAction func munkiPkgInfo(_ sender: Any) { 96 | exportAsMunkiPkgInfo () 97 | } 98 | 99 | @IBAction func outputAsPrinter(_ sender: Any) { 100 | addOutputAsPrinter () 101 | } 102 | 103 | @IBAction func refreshPrinterList(_ sender: Any) { 104 | refreshPrinter () 105 | } 106 | 107 | @IBAction func selectedPrinterSender(_ sender: NSPopUpButton) { 108 | selectedPrinterFunction () 109 | } 110 | 111 | @IBAction func selectedPPDsender(_ sender: Any) { 112 | generateFinalOutputTextField () 113 | } 114 | 115 | @IBAction func selectedCupsNameSender(_ sender: Any) { 116 | generateFinalOutputTextField () 117 | } 118 | 119 | @IBAction func selectedPrinterNameSender(_ sender: Any) { 120 | generateFinalOutputTextField () 121 | } 122 | 123 | @IBAction func selectedPrinterLocationSender(_ sender: Any) { 124 | generateFinalOutputTextField () 125 | } 126 | 127 | @IBAction func selectedProtocolSender(_ sender: Any) { 128 | generateFinalOutputTextField () 129 | } 130 | 131 | @IBAction func selectedPrinterIpSender(_ sender: Any) { 132 | generateFinalOutputTextField () 133 | } 134 | 135 | 136 | @IBAction func printerIconSender(_ sender: Any) { 137 | savePrinterIcon () 138 | } 139 | 140 | 141 | func applicationDidFinishLaunching(_ aNotification: Notification) { 142 | func appDelegate() -> AppDelegate { 143 | return NSApplication.shared.delegate as! AppDelegate 144 | } 145 | func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 146 | return false 147 | } 148 | // Start everything here 149 | appDelegate().finalOutputTextField.textStorage?.append(NSAttributedString(string:"Before start we need to index all your PPD files...This message will self-destruct when done")) 150 | appDelegate().spinner.isHidden=false 151 | self.spinner.startAnimation(self) 152 | runBackgroundPPDindex () 153 | getAllPrinters() 154 | clearEveryPrinterFields () 155 | readPlist() 156 | } 157 | 158 | func applicationWillTerminate(_ aNotification: Notification) { 159 | // Insert code here to tear down your application 160 | createPlist() 161 | // If tempfolder exist 162 | if FileManager.default.fileExists(atPath: "/tmp/PrinterSetup") { 163 | // Delete tempfolder 164 | try? FileManager.default.removeItem(atPath: "/tmp/PrinterSetup") 165 | } 166 | exit(0); 167 | 168 | 169 | } 170 | 171 | 172 | } 173 | 174 | -------------------------------------------------------------------------------- /PrinterSetup.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7300824C244B1CD900F4909F /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7300824B244B1CD900F4909F /* Preferences.swift */; }; 11 | 7324FB882447774B00224F28 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7324FB872447774B00224F28 /* Keychain.swift */; }; 12 | 738A1AD4242E66FC001FF65B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738A1AD3242E66FC001FF65B /* AppDelegate.swift */; }; 13 | 738A1AD6242E66FD001FF65B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 738A1AD5242E66FD001FF65B /* Assets.xcassets */; }; 14 | 738A1AD9242E66FD001FF65B /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 738A1AD7242E66FD001FF65B /* MainMenu.xib */; }; 15 | 738A1AE2242F387F001FF65B /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738A1AE1242F387E001FF65B /* Functions.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 7300824B244B1CD900F4909F /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; 20 | 7324FB872447774B00224F28 /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; 21 | 738A1AD0242E66FC001FF65B /* PrinterSetup.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PrinterSetup.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 738A1AD3242E66FC001FF65B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | 738A1AD5242E66FD001FF65B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 738A1AD8242E66FD001FF65B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 25 | 738A1ADA242E66FD001FF65B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 738A1ADB242E66FD001FF65B /* PrinterSetup.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PrinterSetup.entitlements; sourceTree = ""; }; 27 | 738A1AE1242F387E001FF65B /* Functions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | 738A1ACD242E66FC001FF65B /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | 738A1AC7242E66FC001FF65B = { 42 | isa = PBXGroup; 43 | children = ( 44 | 738A1AD2242E66FC001FF65B /* PrinterSetup */, 45 | 738A1AD1242E66FC001FF65B /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | 738A1AD1242E66FC001FF65B /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 738A1AD0242E66FC001FF65B /* PrinterSetup.app */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | 738A1AD2242E66FC001FF65B /* PrinterSetup */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 738A1AE1242F387E001FF65B /* Functions.swift */, 61 | 738A1AD3242E66FC001FF65B /* AppDelegate.swift */, 62 | 7324FB872447774B00224F28 /* Keychain.swift */, 63 | 7300824B244B1CD900F4909F /* Preferences.swift */, 64 | 738A1AD5242E66FD001FF65B /* Assets.xcassets */, 65 | 738A1AD7242E66FD001FF65B /* MainMenu.xib */, 66 | 738A1ADA242E66FD001FF65B /* Info.plist */, 67 | 738A1ADB242E66FD001FF65B /* PrinterSetup.entitlements */, 68 | ); 69 | path = PrinterSetup; 70 | sourceTree = ""; 71 | }; 72 | /* End PBXGroup section */ 73 | 74 | /* Begin PBXNativeTarget section */ 75 | 738A1ACF242E66FC001FF65B /* PrinterSetup */ = { 76 | isa = PBXNativeTarget; 77 | buildConfigurationList = 738A1ADE242E66FD001FF65B /* Build configuration list for PBXNativeTarget "PrinterSetup" */; 78 | buildPhases = ( 79 | 738A1ACC242E66FC001FF65B /* Sources */, 80 | 738A1ACD242E66FC001FF65B /* Frameworks */, 81 | 738A1ACE242E66FC001FF65B /* Resources */, 82 | ); 83 | buildRules = ( 84 | ); 85 | dependencies = ( 86 | ); 87 | name = PrinterSetup; 88 | productName = PrinterSetup; 89 | productReference = 738A1AD0242E66FC001FF65B /* PrinterSetup.app */; 90 | productType = "com.apple.product-type.application"; 91 | }; 92 | /* End PBXNativeTarget section */ 93 | 94 | /* Begin PBXProject section */ 95 | 738A1AC8242E66FC001FF65B /* Project object */ = { 96 | isa = PBXProject; 97 | attributes = { 98 | BuildIndependentTargetsInParallel = YES; 99 | LastSwiftUpdateCheck = 1130; 100 | LastUpgradeCheck = 1620; 101 | ORGANIZATIONNAME = "Mikael Löfgren"; 102 | TargetAttributes = { 103 | 738A1ACF242E66FC001FF65B = { 104 | CreatedOnToolsVersion = 11.3.1; 105 | }; 106 | }; 107 | }; 108 | buildConfigurationList = 738A1ACB242E66FC001FF65B /* Build configuration list for PBXProject "PrinterSetup" */; 109 | compatibilityVersion = "Xcode 9.3"; 110 | developmentRegion = en; 111 | hasScannedForEncodings = 0; 112 | knownRegions = ( 113 | en, 114 | Base, 115 | ); 116 | mainGroup = 738A1AC7242E66FC001FF65B; 117 | productRefGroup = 738A1AD1242E66FC001FF65B /* Products */; 118 | projectDirPath = ""; 119 | projectRoot = ""; 120 | targets = ( 121 | 738A1ACF242E66FC001FF65B /* PrinterSetup */, 122 | ); 123 | }; 124 | /* End PBXProject section */ 125 | 126 | /* Begin PBXResourcesBuildPhase section */ 127 | 738A1ACE242E66FC001FF65B /* Resources */ = { 128 | isa = PBXResourcesBuildPhase; 129 | buildActionMask = 2147483647; 130 | files = ( 131 | 738A1AD6242E66FD001FF65B /* Assets.xcassets in Resources */, 132 | 738A1AD9242E66FD001FF65B /* MainMenu.xib in Resources */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXResourcesBuildPhase section */ 137 | 138 | /* Begin PBXSourcesBuildPhase section */ 139 | 738A1ACC242E66FC001FF65B /* Sources */ = { 140 | isa = PBXSourcesBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 738A1AD4242E66FC001FF65B /* AppDelegate.swift in Sources */, 144 | 738A1AE2242F387F001FF65B /* Functions.swift in Sources */, 145 | 7300824C244B1CD900F4909F /* Preferences.swift in Sources */, 146 | 7324FB882447774B00224F28 /* Keychain.swift in Sources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXSourcesBuildPhase section */ 151 | 152 | /* Begin PBXVariantGroup section */ 153 | 738A1AD7242E66FD001FF65B /* MainMenu.xib */ = { 154 | isa = PBXVariantGroup; 155 | children = ( 156 | 738A1AD8242E66FD001FF65B /* Base */, 157 | ); 158 | name = MainMenu.xib; 159 | sourceTree = ""; 160 | }; 161 | /* End PBXVariantGroup section */ 162 | 163 | /* Begin XCBuildConfiguration section */ 164 | 738A1ADC242E66FD001FF65B /* Debug */ = { 165 | isa = XCBuildConfiguration; 166 | buildSettings = { 167 | ALWAYS_SEARCH_USER_PATHS = NO; 168 | CLANG_ANALYZER_NONNULL = YES; 169 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 170 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 171 | CLANG_CXX_LIBRARY = "libc++"; 172 | CLANG_ENABLE_MODULES = YES; 173 | CLANG_ENABLE_OBJC_ARC = YES; 174 | CLANG_ENABLE_OBJC_WEAK = YES; 175 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 176 | CLANG_WARN_BOOL_CONVERSION = YES; 177 | CLANG_WARN_COMMA = YES; 178 | CLANG_WARN_CONSTANT_CONVERSION = YES; 179 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 181 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 182 | CLANG_WARN_EMPTY_BODY = YES; 183 | CLANG_WARN_ENUM_CONVERSION = YES; 184 | CLANG_WARN_INFINITE_RECURSION = YES; 185 | CLANG_WARN_INT_CONVERSION = YES; 186 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 187 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 188 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 189 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 190 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 191 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 192 | CLANG_WARN_STRICT_PROTOTYPES = YES; 193 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 194 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 195 | CLANG_WARN_UNREACHABLE_CODE = YES; 196 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 197 | COPY_PHASE_STRIP = NO; 198 | DEAD_CODE_STRIPPING = YES; 199 | DEBUG_INFORMATION_FORMAT = dwarf; 200 | ENABLE_HARDENED_RUNTIME = YES; 201 | ENABLE_STRICT_OBJC_MSGSEND = YES; 202 | ENABLE_TESTABILITY = YES; 203 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 204 | GCC_C_LANGUAGE_STANDARD = gnu11; 205 | GCC_DYNAMIC_NO_PIC = NO; 206 | GCC_NO_COMMON_BLOCKS = YES; 207 | GCC_OPTIMIZATION_LEVEL = 0; 208 | GCC_PREPROCESSOR_DEFINITIONS = ( 209 | "DEBUG=1", 210 | "$(inherited)", 211 | ); 212 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 213 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 214 | GCC_WARN_UNDECLARED_SELECTOR = YES; 215 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 216 | GCC_WARN_UNUSED_FUNCTION = YES; 217 | GCC_WARN_UNUSED_VARIABLE = YES; 218 | MACOSX_DEPLOYMENT_TARGET = 10.13; 219 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 220 | MTL_FAST_MATH = YES; 221 | ONLY_ACTIVE_ARCH = YES; 222 | SDKROOT = macosx; 223 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 224 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 225 | }; 226 | name = Debug; 227 | }; 228 | 738A1ADD242E66FD001FF65B /* Release */ = { 229 | isa = XCBuildConfiguration; 230 | buildSettings = { 231 | ALWAYS_SEARCH_USER_PATHS = NO; 232 | CLANG_ANALYZER_NONNULL = YES; 233 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 234 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 235 | CLANG_CXX_LIBRARY = "libc++"; 236 | CLANG_ENABLE_MODULES = YES; 237 | CLANG_ENABLE_OBJC_ARC = YES; 238 | CLANG_ENABLE_OBJC_WEAK = YES; 239 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 240 | CLANG_WARN_BOOL_CONVERSION = YES; 241 | CLANG_WARN_COMMA = YES; 242 | CLANG_WARN_CONSTANT_CONVERSION = YES; 243 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 244 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 245 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 246 | CLANG_WARN_EMPTY_BODY = YES; 247 | CLANG_WARN_ENUM_CONVERSION = YES; 248 | CLANG_WARN_INFINITE_RECURSION = YES; 249 | CLANG_WARN_INT_CONVERSION = YES; 250 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 251 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 252 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 253 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 254 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 255 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 256 | CLANG_WARN_STRICT_PROTOTYPES = YES; 257 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 258 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 259 | CLANG_WARN_UNREACHABLE_CODE = YES; 260 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 261 | COPY_PHASE_STRIP = NO; 262 | DEAD_CODE_STRIPPING = YES; 263 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 264 | ENABLE_HARDENED_RUNTIME = YES; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 268 | GCC_C_LANGUAGE_STANDARD = gnu11; 269 | GCC_NO_COMMON_BLOCKS = YES; 270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 272 | GCC_WARN_UNDECLARED_SELECTOR = YES; 273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 274 | GCC_WARN_UNUSED_FUNCTION = YES; 275 | GCC_WARN_UNUSED_VARIABLE = YES; 276 | MACOSX_DEPLOYMENT_TARGET = 10.13; 277 | MTL_ENABLE_DEBUG_INFO = NO; 278 | MTL_FAST_MATH = YES; 279 | SDKROOT = macosx; 280 | SWIFT_COMPILATION_MODE = wholemodule; 281 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 282 | }; 283 | name = Release; 284 | }; 285 | 738A1ADF242E66FD001FF65B /* Debug */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; 290 | CODE_SIGN_ENTITLEMENTS = PrinterSetup/PrinterSetup.entitlements; 291 | CODE_SIGN_IDENTITY = "-"; 292 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 293 | CODE_SIGN_STYLE = Automatic; 294 | COMBINE_HIDPI_IMAGES = YES; 295 | DEAD_CODE_STRIPPING = YES; 296 | DEVELOPMENT_TEAM = F489D96499; 297 | ENABLE_HARDENED_RUNTIME = YES; 298 | INFOPLIST_FILE = PrinterSetup/Info.plist; 299 | INFOPLIST_KEY_CFBundleDisplayName = PrinterSetup; 300 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 301 | LD_RUNPATH_SEARCH_PATHS = ( 302 | "$(inherited)", 303 | "@executable_path/../Frameworks", 304 | ); 305 | MACOSX_DEPLOYMENT_TARGET = 12.4; 306 | MARKETING_VERSION = 2.1; 307 | PRODUCT_BUNDLE_IDENTIFIER = se.mikaellofgren.PrinterSetup; 308 | PRODUCT_NAME = "$(TARGET_NAME)"; 309 | SWIFT_VERSION = 5.0; 310 | }; 311 | name = Debug; 312 | }; 313 | 738A1AE0242E66FD001FF65B /* Release */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 317 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; 318 | CODE_SIGN_ENTITLEMENTS = PrinterSetup/PrinterSetup.entitlements; 319 | CODE_SIGN_IDENTITY = "-"; 320 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 321 | CODE_SIGN_STYLE = Automatic; 322 | COMBINE_HIDPI_IMAGES = YES; 323 | DEAD_CODE_STRIPPING = YES; 324 | DEVELOPMENT_TEAM = F489D96499; 325 | ENABLE_HARDENED_RUNTIME = YES; 326 | INFOPLIST_FILE = PrinterSetup/Info.plist; 327 | INFOPLIST_KEY_CFBundleDisplayName = PrinterSetup; 328 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 329 | LD_RUNPATH_SEARCH_PATHS = ( 330 | "$(inherited)", 331 | "@executable_path/../Frameworks", 332 | ); 333 | MACOSX_DEPLOYMENT_TARGET = 12.4; 334 | MARKETING_VERSION = 2.1; 335 | PRODUCT_BUNDLE_IDENTIFIER = se.mikaellofgren.PrinterSetup; 336 | PRODUCT_NAME = "$(TARGET_NAME)"; 337 | SWIFT_VERSION = 5.0; 338 | }; 339 | name = Release; 340 | }; 341 | /* End XCBuildConfiguration section */ 342 | 343 | /* Begin XCConfigurationList section */ 344 | 738A1ACB242E66FC001FF65B /* Build configuration list for PBXProject "PrinterSetup" */ = { 345 | isa = XCConfigurationList; 346 | buildConfigurations = ( 347 | 738A1ADC242E66FD001FF65B /* Debug */, 348 | 738A1ADD242E66FD001FF65B /* Release */, 349 | ); 350 | defaultConfigurationIsVisible = 0; 351 | defaultConfigurationName = Release; 352 | }; 353 | 738A1ADE242E66FD001FF65B /* Build configuration list for PBXNativeTarget "PrinterSetup" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | 738A1ADF242E66FD001FF65B /* Debug */, 357 | 738A1AE0242E66FD001FF65B /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | /* End XCConfigurationList section */ 363 | }; 364 | rootObject = 738A1AC8242E66FC001FF65B /* Project object */; 365 | } 366 | -------------------------------------------------------------------------------- /PrinterSetup/Functions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Functions.swift 3 | // PrinterSetup 4 | // 5 | // Created by Mikael Löfgren on 2024-12-18. 6 | // Copyright © 2024 Mikael Löfgren. All rights reserved. 7 | // 8 | import Cocoa 9 | import AppKit 10 | import Foundation 11 | 12 | // Create a subclass of AppDelegate 13 | func appDelegate() -> AppDelegate { 14 | return NSApplication.shared.delegate as! AppDelegate 15 | } 16 | 17 | var printersArray = [String] () 18 | var everyprintersArray = [(CupsPrinterName: String, CupsPPD:String,Printername:String,ShortNickName:String,Location:String,Protocol:String,IP:String,Icon:String,Ipp2ppdCreated:Bool)] () 19 | var printersProtocolArray = [String] () 20 | var printersArrayTemp = [String] () 21 | var printerInfoArrayTemp = [String] () 22 | var selectedPrinter = appDelegate().printerslistPopup.titleOfSelectedItem! 23 | // Variables for tuple printercard 24 | var cupsprintername = "" 25 | var cupsppd = "" 26 | var printername = "" 27 | var shortnickname = "" 28 | var location = "" 29 | var protocolandIp = "" 30 | var printerprotcol = "" 31 | var ipaddress = "" 32 | var iconpath = "" 33 | var ipp2ppdcreated = false 34 | var printercard = (CupsPrinterName: cupsprintername, CupsPPD: cupsppd, Printername: printername, ShortNickName: shortnickname, Location: location, Protocol: printerprotcol, IP: ipaddress, Icon: iconpath, Ipp2ppdCreated: ipp2ppdcreated) 35 | var dnssdName = "" 36 | var allPPDs = "" 37 | let ppdListFile = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/Application Support/PrinterSetup/ppds.txt").path 38 | let ppdListFilePath = URL(fileURLWithPath: ppdListFile ) 39 | var selectedShortNickName = everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.ShortNickName ?? "" 40 | var airPrintDriverIsUsed = (everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.Ipp2ppdCreated ?? false) as Bool 41 | var protocolArray = ["lpd","dnssd","smb", "ipp","ipps","http","https"] 42 | var allPPDsArray = [String] () 43 | var shortNickNameAndPPDsArray = [(ShortNickName:String, PPD:String)] () 44 | var sortedPPDsArray = [String] () 45 | var printerOptionsArray = [String] () 46 | var finalOutputString = "" 47 | var manualCupsName = "" 48 | var statusCertificateCheckbox = Int () 49 | var statusCupsCheckbox = Int () 50 | var cupsWebInterface = shell("cupsctl | grep 'WebInterface'").replacingOccurrences(of: "\n", with: "", options: [.regularExpression, .caseInsensitive]) 51 | let plistFile = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("/Library/Preferences/se.mikaellofgren.printersetup.plist").path 52 | let plistPath = URL(fileURLWithPath: plistFile) 53 | var versionNumberRead = String () 54 | var pkgIdentifierRead = String () 55 | var devCertificateRead = String () 56 | var selectedCommandColor = NSColor.black 57 | var selectedVariableColor = NSColor.purple 58 | var pkgSigned = "" 59 | var pkgUnSigned = "" 60 | extension String { 61 | /// Escapes special characters to make the string XML-compatible. 62 | var xmlEscaped: String { 63 | return self 64 | .replacingOccurrences(of: "&", with: "&") 65 | } 66 | } 67 | 68 | 69 | 70 | func clearEveryPrinterFields () { 71 | appDelegate().printerslistPopup.removeAllItems() 72 | appDelegate().printerslistPopup.addItem(withTitle: "[Choose a printer]") 73 | appDelegate().printerslistPopup.addItems(withTitles: printersArray) 74 | appDelegate().printerslistPopup.selectItem(at: 0) 75 | appDelegate().printerPPDPopup.removeAllItems() 76 | appDelegate().cupsNameTextfield.stringValue = "" 77 | appDelegate().printerNameTextField.stringValue = "" 78 | appDelegate().printerLocationTextField.stringValue = "" 79 | appDelegate().printerProtocolPopup.removeAllItems() 80 | appDelegate().printerIpaddressComboBox.removeAllItems() 81 | appDelegate().printerIpaddressComboBox.addItem(withObjectValue:"") 82 | appDelegate().printerIpaddressComboBox.selectItem(withObjectValue:"") 83 | appDelegate().printerIconButton.image = nil 84 | appDelegate().printerIconPathTextField.stringValue = "" 85 | 86 | if cupsWebInterface == "WebInterface=no" { 87 | appDelegate().enableCupsWebbutton.state = NSControl.StateValue.off 88 | statusCupsCheckbox = 0 89 | appDelegate().cupsURLbutton.isHidden=true 90 | } else { 91 | appDelegate().enableCupsWebbutton.state = NSControl.StateValue.on 92 | statusCupsCheckbox = 1 93 | appDelegate().cupsURLbutton.isHidden=false 94 | } 95 | } 96 | 97 | func createcupsNameTextfield () { 98 | appDelegate().cupsNameTextfield.stringValue = "" 99 | appDelegate().cupsNameTextfield.stringValue = everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.CupsPrinterName ?? "" 100 | } 101 | 102 | func createprinterNameTextField () { 103 | appDelegate().printerNameTextField.stringValue = "" 104 | appDelegate().printerNameTextField.stringValue = everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.Printername ?? "" 105 | } 106 | 107 | func createprinterPPDsPopup () { 108 | appDelegate().printerPPDPopup.removeAllItems() 109 | let choosenPrinterShortNickName = everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.ShortNickName ?? "" 110 | sortedPPDsArray.removeAll() 111 | sortedPPDsArray += shortNickNameAndPPDsArray.filter{$0.ShortNickName == choosenPrinterShortNickName }.map{ return $0.PPD } 112 | sortPPDsArray () 113 | appDelegate().printerPPDPopup.addItems(withTitles: sortedPPDsArray) 114 | appDelegate().printerPPDPopup.selectItem(at: 0) 115 | } 116 | 117 | 118 | func createPrinterProtocolPopup () { 119 | appDelegate().printerProtocolPopup.removeAllItems() 120 | appDelegate().printerProtocolPopup.addItem(withTitle: everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.Protocol ?? "") 121 | appDelegate().printerProtocolPopup.addItems(withTitles: protocolArray) 122 | appDelegate().printerProtocolPopup.selectItem(withTitle: everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.Protocol ?? "") 123 | if appDelegate().printerProtocolPopup.titleOfSelectedItem! == "dnssd" { 124 | appDelegate().printerIPmenu.isHidden=false 125 | } else { 126 | appDelegate().printerIPmenu.isHidden=true 127 | } 128 | } 129 | 130 | 131 | func createprinterIpaddressComboBox () { 132 | appDelegate().printerIpaddressComboBox.removeAllItems() 133 | appDelegate().printerIpaddressComboBox.addItem(withObjectValue: everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.IP ?? "") 134 | appDelegate().printerIpaddressComboBox.selectItem(withObjectValue: everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.IP ?? "") 135 | } 136 | 137 | func createprinterLocationTextField () { 138 | appDelegate().printerLocationTextField.stringValue = everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.Location ?? "" 139 | } 140 | 141 | 142 | 143 | func generateFinalOutputTextField () { 144 | let commandAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: selectedCommandColor] 145 | let variableAttributes = [NSAttributedString.Key.foregroundColor: selectedVariableColor] 146 | 147 | if selectedPrinter.contains("[Choose a printer]"){ 148 | return } 149 | else { 150 | 151 | // Variables for Finaloutput 152 | var finalPPDName = "" 153 | if appDelegate().printerPPDPopup.titleOfSelectedItem != nil { 154 | finalPPDName = appDelegate().printerPPDPopup.titleOfSelectedItem! 155 | } else { 156 | let warning = NSAlert() 157 | warning.icon = NSImage(named: "Warning") 158 | warning.addButton(withTitle: "OK") 159 | warning.messageText = "Couldnt find matching PPD" 160 | warning.alertStyle = NSAlert.Style.warning 161 | warning.informativeText = """ 162 | Make sure you got right PPD file installed 163 | """ 164 | warning.runModal() 165 | appDelegate().finalOutputTextField.string = "" 166 | return } 167 | 168 | let finalCupsName = cleanCupsName(appDelegate().cupsNameTextfield.stringValue) 169 | let finalPrinterName = appDelegate().printerNameTextField.stringValue 170 | 171 | var finalLocation = "" 172 | if appDelegate().printerLocationTextField.stringValue == "" { 173 | } else { 174 | finalLocation = appDelegate().printerLocationTextField.stringValue 175 | } 176 | 177 | var finalProtocolName = "" 178 | if appDelegate().printerProtocolPopup.titleOfSelectedItem != nil { 179 | finalProtocolName = appDelegate().printerProtocolPopup.titleOfSelectedItem! 180 | } else { 181 | let warning = NSAlert() 182 | warning.icon = NSImage(named: "Warning") 183 | warning.addButton(withTitle: "OK") 184 | warning.messageText = "Couldnt find matching Printer protocol" 185 | warning.alertStyle = NSAlert.Style.warning 186 | warning.informativeText = """ 187 | Make sure you using standard printer protocols 188 | """ 189 | warning.runModal() 190 | appDelegate().finalOutputTextField.string = "" 191 | return } 192 | 193 | var finalIpAddress = "" 194 | if appDelegate().printerIpaddressComboBox.stringValue != "" { 195 | finalIpAddress = appDelegate().printerIpaddressComboBox.stringValue 196 | } else { 197 | let warning = NSAlert() 198 | warning.icon = NSImage(named: "Warning") 199 | warning.addButton(withTitle: "OK") 200 | warning.messageText = "Couldnt find matching printer Ip" 201 | warning.alertStyle = NSAlert.Style.warning 202 | warning.informativeText = """ 203 | Make sure you using standard printer ip protocols 204 | """ 205 | warning.runModal() 206 | appDelegate().finalOutputTextField.string = "" 207 | return } 208 | 209 | if airPrintDriverIsUsed == true && finalProtocolName == "ipps" || finalProtocolName == "ipp" { 210 | // # Inspired by: https://aporlebeke.wordpress.com/2021/05/26/configuring-printers-programmatically-for-airprint-part-2-now-with-icons/ 211 | finalOutputString = """ 212 | #!/bin/bash 213 | # Script created by PrinterSetup 214 | PRINTERNAME="\(finalPrinterName)" 215 | PRINTER_CUPSNAME="\(finalCupsName)" 216 | PRINTER_IP="\(finalIpAddress)" 217 | PRINTER_LOCATION="\(finalLocation)" 218 | PRINTER_PROTOCOL="\(finalProtocolName)" 219 | PRINTER_PLIST="/tmp/$PRINTERNAME.plist" 220 | PRINTER_TEMP_PPD="/tmp/$PRINTERNAME.ppd" 221 | 222 | PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/libexec/ export PATH 223 | 224 | # Functions 225 | GET_PRINTER_ATTRIBUTES () { 226 | # Check that printer are online and responds to ipp output attributes and save to a tmp plist file 227 | PRINTER_ONLINE_AND_IPP=$(ipptool -P "$PRINTER_PLIST" ipp://"$PRINTER_IP"/ipp/print get-printer-attributes.test | grep "PASS") 228 | 229 | RETRIES=0 230 | RETRY_MAX=60 # 60 seconds 231 | 232 | while [ -z "$PRINTER_ONLINE_AND_IPP" ]; do 233 | PRINTER_ONLINE_AND_IPP=$(ipptool -P "$PRINTER_PLIST" ipp://"$PRINTER_IP"/ipp/print get-printer-attributes.test | grep "PASS") 234 | RETRIES=$(( RETRIES + 1 )) 235 | if [ "$RETRIES" -gt "$RETRY_MAX" ] ; then 236 | echo "Couldn't reach printer with ip: $PRINTER_IP after $RETRY_MAX attempts" 237 | exit 1 238 | else 239 | echo "Retry $RETRIES of $RETRY_MAX" 240 | sleep 1 241 | fi 242 | done 243 | 244 | echo "Successfully connected to: $PRINTERNAME with ip: $PRINTER_IP and printer is supporting IPP" 245 | } 246 | 247 | 248 | GENERATE_PRINTER_PPD () { 249 | # Generate the AirPrint PPD using ipp2ppd 250 | /System/Library/Printers/Libraries/ipp2ppd "ipp://$PRINTER_IP" everywhere > "$PRINTER_TEMP_PPD" 251 | 252 | # Verify the temp PPD exist 253 | if [ -f "$PRINTER_TEMP_PPD" ]; then 254 | echo "Generated Airprint PPD file to $PRINTER_TEMP_PPD" 255 | else 256 | echo "The $PRINTER_TEMP_PPD doesn't exist, exit" 257 | exit 1 258 | fi 259 | } 260 | 261 | 262 | GET_PRINTER_ICON () { 263 | # Get Icon URL from printers attributes plist 264 | ICON_LARGE_URL=$(PlistBuddy -c "Print :Tests:0:ResponseAttributes:1:printer-icons:1" "$PRINTER_PLIST" 2>/dev/null) 265 | ICON_SMALL_URL=$(PlistBuddy -c "Print :Tests:0:ResponseAttributes:1:printer-icons:0" "$PRINTER_PLIST" 2>/dev/null) 266 | 267 | if [ -n "$ICON_LARGE_URL" ]; then 268 | ICON_URL="$ICON_LARGE_URL" 269 | elif [ -n "$ICON_SMALL_URL" ]; then 270 | ICON_URL="$ICON_SMALL_URL" 271 | fi 272 | 273 | echo "Printericon URL: $ICON_URL" 274 | 275 | # Make sure /Library/Printers/Icons exist (requires sudo) 276 | if [ ! -e "/Library/Printers/Icons" ]; then 277 | mkdir -p "/Library/Printers/Icons" 278 | chmod 555 "/Library/Printers/Icons" 279 | chown root:wheel "/Library/Printers/Icons" 280 | fi 281 | 282 | # Download Icon with curl from printer 283 | PNG="/tmp/$PRINTERNAME.png" 284 | ICNS="/Library/Printers/Icons/$PRINTERNAME.icns" 285 | GET_ICON=$(curl --write-out '%{http_code}' -skL "$ICON_URL" -o "$PNG") 286 | 287 | if [[ "$GET_ICON" = 200 ]] && [ -f "$PNG" ]; then 288 | echo "Successfully downloaded: $PNG" 289 | # Convert png to .icns and save to /Library/Printers/Icons/ (requires sudo) 290 | sips -s format icns "$PNG" -o "$ICNS" 291 | else 292 | echo "Failed to download: $ICON_URL or missing downloaded $PNG, error: $GET_ICON" 293 | fi 294 | 295 | # Add the Iconpath to PPD file 296 | if [ -f "$ICNS" ]; then 297 | CURRENT_ICON_PATH=$(grep "^*APPrinterIconPath" "$PRINTER_TEMP_PPD") 298 | if [ -n "$CURRENT_ICON_PATH" ]; then 299 | # Replace the line if it exists 300 | sed -i '' "s|$CURRENT_ICON_PATH|*APPrinterIconPath: \\"/Library/Printers/Icons/$PRINTERNAME.icns\\"|" "$PRINTER_TEMP_PPD" 301 | echo "Line replaced in $PRINTER_TEMP_PPD" 302 | else 303 | # Append the new line if it doesn't exist 304 | echo "*APPrinterIconPath: \\"/Library/Printers/Icons/$PRINTERNAME.icns\\"" >> "$PRINTER_TEMP_PPD" 305 | echo "Line added to $PRINTER_TEMP_PPD" 306 | fi 307 | fi 308 | } 309 | 310 | 311 | ROOT_CHECK_TO_GET_ICON () { 312 | # Check that the script is running as root 313 | if [ "$(id -u)" != 0 ]; then 314 | echo "Functions to get printer icon must run as root." 315 | echo "Please run with sudo, skipping for now..." 316 | else 317 | GET_PRINTER_ICON 318 | fi 319 | } 320 | 321 | 322 | # Add the printer 323 | ADD_PRINTER () { 324 | lpadmin -p "$PRINTER_CUPSNAME" \\ 325 | -E \\ 326 | -D "$PRINTERNAME" \\ 327 | -P "/tmp/$PRINTERNAME.ppd" \\ 328 | -L "$PRINTER_LOCATION" \\ 329 | -v "$PRINTER_PROTOCOL"://"$PRINTER_IP" \\ 330 | -o printer-is-shared=False 2>/dev/null 331 | } 332 | 333 | # Main starts here 334 | GET_PRINTER_ATTRIBUTES 335 | GENERATE_PRINTER_PPD 336 | ROOT_CHECK_TO_GET_ICON 337 | ADD_PRINTER 338 | 339 | exit 340 | """ 341 | } else { 342 | finalOutputString = """ 343 | #!/bin/bash 344 | # Script created by PrinterSetup\n 345 | """ 346 | finalOutputString += "/usr/sbin/lpadmin -p \"FINAL_CUPS_NAME\" \\\n" 347 | finalOutputString += "-E \\\n" 348 | finalOutputString += "-D \"FINAL_PRINTER_NAME\" \\\n" 349 | finalOutputString += "-P \"\(finalPPDName)\" \\\n" 350 | } 351 | if airPrintDriverIsUsed == true && finalProtocolName == "ipps" || finalProtocolName == "ipp" { 352 | // Output for Airprint 353 | appDelegate().finalOutputTextField.string = "" 354 | appDelegate().finalOutputTextField.textStorage?.append(NSAttributedString(string: finalOutputString, attributes: commandAttributes)) 355 | } else { 356 | // Get Printer Options 357 | generatePrinterOptions () 358 | 359 | // Output Location if Locations contains something 360 | if appDelegate().printerLocationTextField.stringValue != "" { 361 | finalOutputString += "-L \"LOCATION\" \\\n" 362 | } 363 | 364 | // Make finalIpAddress output shell compatible by adding slashes before parentheses 365 | finalIpAddress = finalIpAddress.replacingOccurrences(of: "(", with: "\\(") 366 | finalIpAddress = finalIpAddress.replacingOccurrences(of: ")", with: "\\)") 367 | 368 | 369 | finalOutputString += "-v \(finalProtocolName)://\(finalIpAddress) \\\n" 370 | 371 | // Output options 372 | if printerOptionsArray.isEmpty { 373 | } else { 374 | for options in printerOptionsArray { 375 | finalOutputString += "-o \(options) \\\n" 376 | } 377 | } 378 | 379 | 380 | // Output not shared 381 | finalOutputString += "-o printer-is-shared=False 2>/dev/null\n" 382 | 383 | appDelegate().finalOutputTextField.string = "" 384 | appDelegate().finalOutputTextField.textStorage?.append(NSAttributedString(string: finalOutputString, attributes: commandAttributes)) 385 | 386 | // Find Variables and color them 387 | 388 | func colorVariabels (range: String, variable: String) { 389 | let wholeOutput = (appDelegate().finalOutputTextField.textStorage as NSAttributedString?)!.string 390 | let range = (wholeOutput as NSString).range(of: "\(range)") 391 | let attributedReplaceText = NSAttributedString(string: "\(variable)", attributes: variableAttributes) 392 | appDelegate().finalOutputTextField.textStorage?.replaceCharacters(in: range, with: attributedReplaceText) 393 | } 394 | 395 | colorVariabels(range: "FINAL_CUPS_NAME", variable: "\(finalCupsName)") 396 | colorVariabels(range: "FINAL_PRINTER_NAME", variable: "\(finalPrinterName)") 397 | colorVariabels(range: "\(finalPPDName)", variable: "\(finalPPDName)") 398 | if appDelegate().printerLocationTextField.stringValue != "" { 399 | colorVariabels(range: "LOCATION", variable: "\(finalLocation)") 400 | } 401 | colorVariabels(range: "\(finalProtocolName)://\(finalIpAddress)", variable: "\(finalProtocolName)://\(finalIpAddress)") 402 | 403 | 404 | // Color printer options 405 | if printerOptionsArray.isEmpty { 406 | } else { 407 | for options in printerOptionsArray { 408 | colorVariabels(range: "\(options)", variable: "\(options)") 409 | } 410 | } 411 | colorVariabels(range: "printer-is-shared=False", variable: "printer-is-shared=False") 412 | } 413 | } 414 | } 415 | 416 | 417 | func lookupDNSSDtoIP () { 418 | if selectedPrinter.contains("[Choose a printer]"){ 419 | appDelegate().exportPopUp.selectItem(at: 0) 420 | return } 421 | 422 | if appDelegate().printerIpaddressComboBox.stringValue != "" { 423 | dnssdName = appDelegate().printerIpaddressComboBox.stringValue 424 | dnssdName = dnssdName.removingPercentEncoding! 425 | let removeFrom = (dnssdName.range(of: ".")?.lowerBound) 426 | dnssdName = String(dnssdName.prefix(upTo: removeFrom!)) 427 | } else {return} 428 | 429 | 430 | func regexFunc (for regex: String, in text: String) -> [String] { 431 | do { 432 | let regex = try NSRegularExpression(pattern: regex) 433 | let results = regex.matches(in: text, 434 | range: NSRange(text.startIndex..., in: text)) 435 | return results.map { 436 | String(text[Range($0.range, in: text)!]) 437 | } 438 | } catch let error { 439 | print("invalid regex: \(error.localizedDescription)") 440 | return [] 441 | } 442 | } 443 | 444 | var findIpp = "" 445 | var findIpps = "" 446 | appDelegate().spinner.isHidden=false 447 | appDelegate().spinner.startAnimation(appDelegate()) 448 | 449 | DispatchQueue.global(qos: .userInteractive).async { 450 | findIpp = shell("ippfind _ipp._tcp --literal-name '\(dnssdName)' --exec ping -qc 3 {service_hostname} \\;") 451 | findIpps = shell("ippfind _ipps._tcp --literal-name '\(dnssdName)' --exec ping -qc 3 {service_hostname} \\;") 452 | 453 | DispatchQueue.main.async { 454 | appDelegate().spinner.isHidden=true 455 | appDelegate().spinner.stopAnimation(appDelegate()) 456 | var tempIP = regexFunc(for: "(?<=\\().*(?=\\))", in: findIpp) 457 | tempIP += regexFunc(for: "(?<=\\().*(?=\\))", in: findIpps) 458 | 459 | let dnssdIp = Array(Set(tempIP)) 460 | 461 | if dnssdIp.isEmpty{ 462 | appDelegate().exportPopUp.selectItem(at: 0) 463 | let warning = NSAlert() 464 | warning.icon = NSImage(named: "Warning") 465 | warning.addButton(withTitle: "OK") 466 | warning.messageText = "Couldnt resolve to ip" 467 | warning.alertStyle = NSAlert.Style.warning 468 | warning.informativeText = """ 469 | Try with this command in terminal.app: 470 | ippfind _ipp._tcp --literal-name '\(dnssdName)' --exec ping -qc 3 {service_hostname} \\; 471 | \n 472 | Also make sure that the printer is turned on and 473 | on the same network. 474 | """ 475 | warning.runModal() 476 | return } else { 477 | if airPrintDriverIsUsed == false { 478 | appDelegate().printerProtocolPopup.setTitle("lpd") 479 | } else {appDelegate().printerProtocolPopup.setTitle("ipp")} 480 | appDelegate().printerIpaddressComboBox.addItem(withObjectValue: dnssdIp.first!) 481 | appDelegate().printerIpaddressComboBox.selectItem(withObjectValue: dnssdIp.first!) 482 | appDelegate().exportPopUp.selectItem(at: 0) 483 | generateFinalOutputTextField () 484 | } 485 | } 486 | 487 | 488 | } 489 | 490 | } 491 | 492 | 493 | func exportAsPkg () { 494 | if selectedPrinter.contains("[Choose a printer]"){ 495 | appDelegate().exportPopUp.selectItem(at: 0) 496 | return } 497 | 498 | // Save Dialog 499 | let dialog = NSSavePanel(); 500 | dialog.message = "Select location for output"; 501 | dialog.showsTagField = false; 502 | dialog.showsResizeIndicator = true; 503 | dialog.showsHiddenFiles = false; 504 | dialog.canCreateDirectories = true; 505 | dialog.nameFieldStringValue = "\(selectedPrinter).pkg" 506 | 507 | if (dialog.runModal() == NSApplication.ModalResponse.OK) { 508 | let result = dialog.url // Pathname of the file 509 | if (result != nil) { 510 | let path = result!.path 511 | 512 | // Create tempdir 513 | let scriptsFolder = "/tmp/PrinterSetup/com.printersetup/scripts" 514 | let nopayloadFolder = "/tmp/PrinterSetup/com.printersetup/nopayload" 515 | 516 | 517 | if appDelegate().versionTextField.stringValue != "" { 518 | versionNumberRead = cleanVersion(appDelegate().versionTextField.stringValue) 519 | } 520 | 521 | if appDelegate().identifierTextField.stringValue != "" { 522 | pkgIdentifierRead = appDelegate().identifierTextField.stringValue 523 | } else { 524 | pkgIdentifierRead = "com.printersetup" 525 | } 526 | 527 | var selectedCertificate = "" 528 | var outputPKG = "" 529 | 530 | do { 531 | 532 | try FileManager.default.createDirectory(atPath: scriptsFolder, withIntermediateDirectories: true, attributes: nil) 533 | try FileManager.default.createDirectory(atPath: nopayloadFolder, withIntermediateDirectories: true, attributes: nil) 534 | 535 | } catch { 536 | print(error) 537 | } 538 | 539 | 540 | // Convert scriptsFolder to URL 541 | let DocumentDirURL = URL(fileURLWithPath: scriptsFolder ) 542 | 543 | 544 | // Get the text in textview to output to a tmp file for calling from pkgsbuild 545 | // Save data to file 546 | let fileName = "postinstall" 547 | let fileURL = DocumentDirURL.appendingPathComponent(fileName) 548 | let writeString = (appDelegate().finalOutputTextField.textStorage)!.string 549 | 550 | do { 551 | // Write to the file 552 | try writeString.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8) 553 | } catch let error as NSError { 554 | print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription) 555 | } 556 | 557 | // Remove Extended attributs and Chmod on tempfile 558 | _ = shell("/usr/bin/xattr -c '\(fileURL.path)'") 559 | _ = shell("/bin/chmod a+x '\(fileURL.path)'") 560 | 561 | 562 | // Output Signed pkg 563 | if statusCertificateCheckbox == 1 { 564 | selectedCertificate = appDelegate().certificatePopUp.stringValue 565 | 566 | if versionNumberRead != "" { 567 | pkgSigned = shell("/usr/bin/pkgbuild --sign '\(selectedCertificate)' --identifier '\(pkgIdentifierRead)' --version '\(versionNumberRead)' --root '\(nopayloadFolder)' --scripts '\(scriptsFolder)' '\(path)'") 568 | } else { 569 | // If no Version is added then no pkginfo is written to package database 570 | pkgSigned = shell("/usr/bin/pkgbuild --sign '\(selectedCertificate)' --identifier '\(pkgIdentifierRead)' --root '\(nopayloadFolder)' --scripts '\(scriptsFolder)' '\(path)'") 571 | } 572 | 573 | outputPKG = pkgSigned as String 574 | } else { 575 | // Output UnSigned pkg 576 | if versionNumberRead != "" { 577 | pkgUnSigned = shell("/usr/bin/pkgbuild --identifier '\(pkgIdentifierRead)' --version '\(versionNumberRead)' --root '\(nopayloadFolder)' --scripts '\(scriptsFolder)' '\(path)'") 578 | } else { 579 | pkgUnSigned = shell("/usr/bin/pkgbuild --identifier '\(pkgIdentifierRead)' --root '\(nopayloadFolder)' --scripts '\(scriptsFolder)' '\(path)'") 580 | } 581 | outputPKG = pkgUnSigned as String 582 | } 583 | if outputPKG.contains("Wrote") { 584 | 585 | 586 | let info = NSAlert() 587 | info.icon = NSImage(named: "package") 588 | info.addButton(withTitle: "OK") 589 | info.alertStyle = NSAlert.Style.informational 590 | info.messageText = "Successfully exported to:" 591 | info.informativeText = "\(path)" 592 | info.runModal() 593 | 594 | // Reset Export as option 595 | appDelegate().exportPopUp.selectItem(at: 0) 596 | } else { 597 | 598 | let warning = NSAlert() 599 | warning.icon = NSImage(named: "Warning") 600 | warning.addButton(withTitle: "OK") 601 | warning.messageText = "Something went wrong" 602 | warning.alertStyle = NSAlert.Style.warning 603 | // Show warning dialog with differents output depending if signed or not 604 | if statusCertificateCheckbox == 1 { 605 | warning.informativeText = """ 606 | Before you quit the app, try it manually by copy this command into terminal: 607 | 608 | /usr/bin/pkgbuild --sign \"\(selectedCertificate)\" --identifier com.printersetup --root \(nopayloadFolder) --scripts \(scriptsFolder) \(path) 609 | """ 610 | } else { 611 | warning.informativeText = """ 612 | Before you quit the app, try it manually by copy this command into terminal: 613 | 614 | /usr/bin/pkgbuild --identifier com.printersetup --root \(nopayloadFolder) --scripts \(scriptsFolder) \(path) 615 | """ 616 | } 617 | 618 | warning.runModal() 619 | 620 | // Reset Export as option 621 | appDelegate().exportPopUp.selectItem(at: 0) 622 | } 623 | } 624 | } else { 625 | // User clicked on "Cancel" 626 | // Dont do anything and reset Export as option 627 | appDelegate().exportPopUp.selectItem(at: 0) 628 | return 629 | } 630 | // End Save Dialog 631 | } 632 | 633 | 634 | func exportAsMunkiPkgInfo () { 635 | if selectedPrinter.contains("[Choose a printer]"){ 636 | appDelegate().exportPopUp.selectItem(at: 0) 637 | return } 638 | 639 | finalOutputString = (appDelegate().finalOutputTextField.textStorage as NSAttributedString?)!.string 640 | manualCupsName = getCupsNameFromFinalOutput(finalOutputString) 641 | manualCupsName = cleanCupsName(manualCupsName) 642 | 643 | appDelegate().spinner.isHidden=false 644 | appDelegate().spinner.startAnimation(appDelegate()) 645 | 646 | // Check if Output Cupsname matches selectedPrinter, might been manually changed 647 | if manualCupsName != selectedPrinter { 648 | selectedPrinter = manualCupsName 649 | } 650 | 651 | 652 | 653 | // Escape ampersand from the output 654 | let xmlCompatibleString = finalOutputString.xmlEscaped 655 | 656 | let pkgInfo = (""" 657 | 658 | 659 | 660 | 661 | autoremove 662 | 663 | catalogs 664 | 665 | testing 666 | 667 | description 668 | 669 | display_name 670 | \(selectedPrinter) 671 | icon_name 672 | 673 | installcheck_script 674 | #!/bin/bash 675 | PRINTER_INSTALLED=$(/usr/bin/lpstat -p | /usr/bin/awk '{print $2}' | /usr/bin/grep "\(selectedPrinter)" | /usr/bin/head -1 ) 676 | if [ "$PRINTER_INSTALLED" = "\(selectedPrinter)" ]; then 677 | echo "Printer "\(selectedPrinter)" already installed\" 678 | exit 1 679 | fi 680 | exit 681 | installer_type 682 | nopkg 683 | minimum_os_version 684 | 10.7.0 685 | name 686 | \(selectedPrinter) 687 | postinstall_script 688 | \(xmlCompatibleString)exit 0 689 | requires 690 | 691 | 692 | 693 | unattended_install 694 | 695 | uninstall_method 696 | uninstall_script 697 | uninstall_script 698 | #!/bin/bash 699 | /usr/sbin/lpadmin -x "\(selectedPrinter)\" 700 | exit 0 701 | uninstallable 702 | 703 | version 704 | 1.0 705 | 706 | 707 | """) 708 | 709 | // Save Dialog 710 | let dialog = NSSavePanel(); 711 | dialog.showsResizeIndicator = true; 712 | dialog.showsHiddenFiles = false; 713 | dialog.canCreateDirectories = true; 714 | 715 | // Default Save value, add .plist 716 | dialog.nameFieldStringValue = "\(selectedPrinter).plist" 717 | 718 | 719 | if (dialog.runModal() == NSApplication.ModalResponse.OK) { 720 | let result = dialog.url // Pathname of the file 721 | if (result != nil) { 722 | let path = result!.path 723 | 724 | let documentDirURL = URL(fileURLWithPath: path) 725 | // Save data to file 726 | let fileURL = documentDirURL 727 | let writeString = pkgInfo 728 | do { 729 | // Write to the file 730 | try writeString.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8) 731 | } catch let error as NSError { 732 | print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription) 733 | } 734 | 735 | // plutil 736 | let plutilCheck = shell("/usr/bin/plutil -lint -s '\(fileURL.path)'") 737 | if plutilCheck != "" { 738 | let warning = NSAlert() 739 | warning.icon = NSImage(named: "Warning") 740 | warning.addButton(withTitle: "OK") 741 | warning.messageText = "Something went wrong" 742 | warning.alertStyle = NSAlert.Style.warning 743 | warning.informativeText = "Try again and check output syntax" 744 | warning.runModal() 745 | return 746 | } 747 | 748 | 749 | // Reset Export as option 750 | appDelegate().spinner.isHidden=true 751 | appDelegate().spinner.stopAnimation(appDelegate()) 752 | appDelegate().exportPopUp.selectItem(at: 0) 753 | 754 | let info = NSAlert() 755 | info.icon = NSImage(named: "monkey") 756 | info.addButton(withTitle: "OK") 757 | info.alertStyle = NSAlert.Style.informational 758 | info.messageText = "Successfully exported munki pkg info to:" 759 | info.informativeText = "\(path)" 760 | info.runModal() 761 | 762 | } 763 | } else { 764 | // User clicked on "Cancel" 765 | // Dont do anything and reset Export as option 766 | appDelegate().spinner.isHidden=true 767 | appDelegate().spinner.stopAnimation(appDelegate()) 768 | appDelegate().exportPopUp.selectItem(at: 0) 769 | return 770 | } 771 | // End Save Dialog 772 | } 773 | 774 | 775 | func addOutputAsPrinter () { 776 | if selectedPrinter.contains("[Choose a printer]"){ 777 | appDelegate().exportPopUp.selectItem(at: 0) 778 | return } 779 | 780 | finalOutputString = (appDelegate().finalOutputTextField.textStorage as NSAttributedString?)!.string 781 | // do we need to check user in lpadmin group 782 | manualCupsName = getCupsNameFromFinalOutput(finalOutputString) 783 | manualCupsName = cleanCupsName(manualCupsName) 784 | appDelegate().spinner.isHidden=false 785 | appDelegate().spinner.startAnimation(appDelegate()) 786 | 787 | DispatchQueue.global(qos: .userInteractive).async { 788 | _ = shell(finalOutputString) 789 | getAllPrinters() 790 | 791 | 792 | DispatchQueue.main.async { 793 | // Back on the main thread 794 | appDelegate().printerslistPopup.removeAllItems() 795 | appDelegate().printerslistPopup.addItems(withTitles: printersArray) 796 | 797 | 798 | // Check if Output Cupsname matches CupsNameTextField, might been manually changed 799 | if manualCupsName == appDelegate().cupsNameTextfield.stringValue { 800 | appDelegate().printerslistPopup.selectItem(withTitle: appDelegate().cupsNameTextfield.stringValue) 801 | } else { 802 | appDelegate().printerslistPopup.selectItem(withTitle: manualCupsName) 803 | } 804 | appDelegate().spinner.isHidden=true 805 | appDelegate().spinner.stopAnimation(appDelegate()) 806 | appDelegate().exportPopUp.selectItem(at: 0) 807 | selectedPrinterFunction () 808 | 809 | let info = NSAlert() 810 | info.icon = NSImage(named: "printer") 811 | info.addButton(withTitle: "OK") 812 | info.alertStyle = NSAlert.Style.informational 813 | info.messageText = "Successfully try to add the printer, but please verify..." 814 | info.informativeText = "\(manualCupsName)" 815 | info.runModal() 816 | } 817 | } 818 | 819 | } 820 | 821 | 822 | func getCupsNameFromFinalOutput (_ inputString:String) -> String { 823 | var outputString = "" 824 | let range = NSRange(location: 0, length: inputString.utf16.count) 825 | let regex = try? NSRegularExpression(pattern: "(\"\\w)(\\w+\")", options: .caseInsensitive) 826 | if let match = regex?.firstMatch(in: inputString, options: [], range: range){ 827 | if let wholeRange = Range(match.range(at: 0), in: inputString) { 828 | let wholeMatch = inputString[wholeRange] 829 | outputString = String(wholeMatch) 830 | outputString = outputString.replacingOccurrences(of: "\"", with: "") 831 | 832 | } 833 | } 834 | return outputString 835 | } 836 | 837 | 838 | func generatePrinterOptions () { 839 | var choosenPPD = "" 840 | if appDelegate().printerPPDPopup.titleOfSelectedItem != nil { 841 | choosenPPD = appDelegate().printerPPDPopup.titleOfSelectedItem ?? "" 842 | } else { 843 | return } 844 | let choosenCupsPPD = everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.CupsPPD ?? "" 845 | 846 | // Create tempdir 847 | do { 848 | try FileManager.default.createDirectory(atPath: "/tmp/PrinterSetup", withIntermediateDirectories: true, attributes: nil) 849 | } catch { 850 | print(error) 851 | } 852 | 853 | // Make sure we start clean 854 | for tempfiles in ["/tmp/PrinterSetup/choosen.ppd", "/tmp/PrinterSetup/choosen.ppd"] { 855 | if FileManager.default.fileExists(atPath: tempfiles) { 856 | // Delete file 857 | try? FileManager.default.removeItem(atPath: tempfiles) 858 | } 859 | } 860 | 861 | if choosenPPD.contains("sample.drv") { 862 | _ = shell("/usr/libexec/cups/daemon/cups-driverd cat 'drv:///sample.drv/generpcl.ppd' >/tmp/PrinterSetup/choosen.ppd") 863 | _ = shell("tr '\\r' '\\n' <'\(choosenCupsPPD)' >/tmp/PrinterSetup/cups.ppd") 864 | } else { 865 | if choosenPPD.contains("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/Resources/Generic.ppd") { 866 | _ = shell("tr '\\r' '\\n' < '/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/Resources/Generic.ppd' >/tmp/PrinterSetup/choosen.ppd") 867 | _ = shell("tr '\\r' '\\n' <'\(choosenCupsPPD)' >/tmp/PrinterSetup/cups.ppd") 868 | } else { 869 | if choosenPPD.contains("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/Resources/AirPrint.ppd") { 870 | _ = shell("tr '\\r' '\\n' < '/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/Resources/AirPrint.ppd' >/tmp/PrinterSetup/choosen.ppd") 871 | _ = shell("tr '\\r' '\\n' <'\(choosenCupsPPD)' >/tmp/PrinterSetup/cups.ppd") 872 | } else { 873 | if choosenPPD.hasSuffix(".gz") { 874 | _ = shell("gzip -d <'\(choosenPPD)' | tr '\\r\\n' '\\n' >/tmp/PrinterSetup/choosen.ppd") 875 | _ = shell("tr '\\r' '\\n' <'\(choosenCupsPPD)' >/tmp/PrinterSetup/cups.ppd") 876 | } else { 877 | _ = shell("tr '\\r' '\\n' <'\(choosenPPD)' >/tmp/PrinterSetup/choosen.ppd") 878 | _ = shell("tr '\\r' '\\n' <'\(choosenCupsPPD)' >/tmp/PrinterSetup/cups.ppd") 879 | } 880 | } 881 | } 882 | } 883 | 884 | let printerOptions = shell("diff '/private/tmp/PrinterSetup/choosen.ppd' '/private/tmp/PrinterSetup/cups.ppd'") 885 | var printerOptionsArrayTemp = [String] () 886 | // Clear Arrays so we start clean 887 | printerOptionsArrayTemp.removeAll() 888 | printerOptionsArray.removeAll() 889 | printerOptionsArrayTemp = printerOptions.components(separatedBy: CharacterSet.newlines) 890 | 891 | 892 | printerOptionsArrayTemp.forEach { 893 | if $0.hasPrefix("> *Default") { 894 | var returnValue = ($0.replacingOccurrences(of: "> \\*Default", with: "", options: [.regularExpression, .caseInsensitive])) 895 | returnValue = (returnValue.replacingOccurrences(of: ": ", with: "=", options: [.regularExpression, .caseInsensitive])) 896 | printerOptionsArray += returnValue.components(separatedBy: CharacterSet.newlines) 897 | } 898 | } 899 | } 900 | 901 | 902 | func getPrinterNameFromPPDs () { 903 | appDelegate().printerPPDPopup.removeAllItems() 904 | 905 | if selectedShortNickName == "Generic PostScript Printer" { 906 | appDelegate().printerPPDPopup.removeAllItems() 907 | appDelegate().printerPPDPopup.addItems(withTitles: ["/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/Resources/Generic.ppd"] ) 908 | appDelegate().printerPPDPopup.selectItem(at: 0) 909 | return 910 | } else { 911 | 912 | if selectedShortNickName == "Generic PCL Laser Printer" { 913 | appDelegate().printerPPDPopup.removeAllItems() 914 | appDelegate().printerPPDPopup.addItems(withTitles: ["sample.drv/generpcl.ppd"] ) 915 | appDelegate().printerPPDPopup.selectItem(at: 0) 916 | return 917 | 918 | } else { 919 | //print(airPrintDriverIsUsed) 920 | if airPrintDriverIsUsed == true { 921 | appDelegate().printerPPDPopup.removeAllItems() 922 | appDelegate().printerPPDPopup.addItems(withTitles: ["/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/Resources/AirPrint.ppd"] ) 923 | appDelegate().printerPPDPopup.selectItem(at: 0) 924 | return 925 | } else { 926 | createprinterPPDsPopup () } 927 | } 928 | } 929 | } 930 | 931 | 932 | 933 | func shell(_ command: String) -> String { 934 | let task = Process() 935 | task.launchPath = "/bin/bash" 936 | task.arguments = ["-c", command] 937 | 938 | let pipe = Pipe() 939 | task.standardOutput = pipe 940 | task.launch() 941 | 942 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 943 | let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String 944 | 945 | return output 946 | } 947 | 948 | 949 | 950 | // Get ShortNickName from CupsPPD, that we later use to find the "real" ppd 951 | func getShortNickName (CupsPPD: String) -> (CupsShortNickname: String, iconPath: String, ipp2ppdCreated : Bool) { 952 | var ipp2ppdCreated = false 953 | var CupsShortNickname = "" 954 | var iconPath = "" 955 | var path = URL(fileURLWithPath: "") 956 | // if CupsPPD should be missing path /private/etc/cups/ppd/ then add it 957 | if CupsPPD.contains("/private/etc/cups/ppd/") { 958 | path = URL(fileURLWithPath: CupsPPD) 959 | } else { 960 | path = URL(fileURLWithPath: "/private/etc/cups/ppd/"+CupsPPD) 961 | } 962 | if FileManager.default.fileExists(atPath: path.path){ 963 | do { 964 | var ppdfile = try String(contentsOf: path, encoding: .utf8) 965 | ppdfile = ppdfile.replacingOccurrences(of: "\"", with: "", options: [.regularExpression, .caseInsensitive]) 966 | let cupsArrayTemp = ppdfile.components(separatedBy: CharacterSet.newlines) 967 | cupsArrayTemp.forEach { 968 | 969 | if $0.hasPrefix("*% PPD created by ipp2ppd") { 970 | ipp2ppdCreated = true 971 | } 972 | 973 | 974 | if $0.hasPrefix("*ShortNickName:") { 975 | CupsShortNickname = ($0.replacingOccurrences(of: "\\*ShortNickName:", with: "", options: [.regularExpression, .caseInsensitive])) 976 | CupsShortNickname = (CupsShortNickname.replacingOccurrences(of: "\t", with: "", options: [.regularExpression, .caseInsensitive])) 977 | if CupsShortNickname.hasPrefix(" ") { 978 | CupsShortNickname = String(CupsShortNickname.dropFirst()) 979 | } 980 | } 981 | 982 | if $0.hasPrefix("*APPrinterIconPath:") { 983 | iconPath = ($0.replacingOccurrences(of: "\\*APPrinterIconPath:", with: "", options: [.regularExpression, .caseInsensitive])) 984 | iconPath = (iconPath.replacingOccurrences(of: "\t", with: "", options: [.regularExpression, .caseInsensitive])) 985 | if iconPath.hasPrefix(" ") { 986 | iconPath = String(iconPath.dropFirst()) 987 | } 988 | 989 | } 990 | 991 | } 992 | } catch { 993 | } 994 | } 995 | return (CupsShortNickname, iconPath, ipp2ppdCreated) 996 | } 997 | 998 | func getIconImage () { 999 | if selectedShortNickName == "Generic PostScript Printer" { 1000 | let image = NSImage(named: "defaultprintericon") 1001 | appDelegate().printerIconButton.image=image 1002 | appDelegate().printerIconPathTextField.stringValue = "" 1003 | return 1004 | } 1005 | 1006 | if selectedShortNickName == "Generic PCL Laser Printer" { 1007 | let image = NSImage(named: "defaultinkjetprintericon") 1008 | appDelegate().printerIconButton.image=image 1009 | appDelegate().printerIconPathTextField.stringValue = "" 1010 | return 1011 | } 1012 | 1013 | let cupsShortNicknameAndipp2Created = everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.Icon ?? "" 1014 | if cupsShortNicknameAndipp2Created != "" { 1015 | let printerIconPath = URL(fileURLWithPath: cupsShortNicknameAndipp2Created) 1016 | if FileManager.default.fileExists(atPath: printerIconPath.path) { 1017 | do { 1018 | let imageData: Data = try Data(contentsOf: printerIconPath ) 1019 | let image = NSImage(data: imageData) 1020 | appDelegate().printerIconButton.image=image 1021 | //appDelegate().printerIconButton.image=image 1022 | appDelegate().printerIconPathTextField.stringValue = cupsShortNicknameAndipp2Created 1023 | } catch { 1024 | print("Unable to load data: \(error)") 1025 | } 1026 | } else { 1027 | // Default Icon from "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/Resources/GenericPostscriptPrinter.icns" 1028 | let image = NSImage(named: "defaultprintericon") 1029 | appDelegate().printerIconButton.image=image 1030 | print(cupsShortNicknameAndipp2Created) 1031 | // show warning, we know the path but icon not found on system 1032 | } 1033 | } else { 1034 | // Default Icon 1035 | let image = NSImage(named: "defaultprintericon") 1036 | appDelegate().printerIconButton.image=image 1037 | appDelegate().printerIconPathTextField.stringValue = "No Icon path value in /etc/cups/ppd/\(selectedPrinter).ppd" 1038 | } 1039 | } 1040 | 1041 | 1042 | 1043 | func savePrinterIcon () { 1044 | if appDelegate().printerIconPathTextField.stringValue == "No Icon path value in /etc/cups/ppd/\(selectedPrinter).ppd" || appDelegate().printerIconPathTextField.stringValue == "" { 1045 | return 1046 | } 1047 | 1048 | // Save Dialog 1049 | let dialog = NSSavePanel(); 1050 | dialog.showsResizeIndicator = true; 1051 | dialog.showsHiddenFiles = false; 1052 | dialog.canCreateDirectories = true; 1053 | 1054 | // Default Save value convert to URL and String to get last path component 1055 | let iconPathURL = URL(fileURLWithPath: everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.Icon ?? "") 1056 | let iconPath = everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.Icon ?? "" 1057 | let iconPathOnlyName = iconPathURL.lastPathComponent 1058 | let iconPathOnlyNameExtension = iconPathURL.pathExtension 1059 | 1060 | dialog.nameFieldStringValue = "\(iconPathOnlyName).\(iconPathOnlyNameExtension)" 1061 | 1062 | if (dialog.runModal() == NSApplication.ModalResponse.OK) { 1063 | let result = dialog.url // Pathname of the file 1064 | 1065 | if (result != nil) { 1066 | let path = result!.path 1067 | 1068 | let documentDirURL = URL(fileURLWithPath: path) 1069 | // Copy icon to destination, remove if exist 1070 | if FileManager.default.fileExists(atPath: documentDirURL.path) { 1071 | do { 1072 | try FileManager.default.removeItem(atPath: documentDirURL.path) 1073 | } catch { 1074 | let info = NSAlert() 1075 | info.icon = NSImage(named: "Warning") 1076 | info.addButton(withTitle: "OK") 1077 | info.alertStyle = NSAlert.Style.informational 1078 | info.messageText = "Couldnt copy file" 1079 | info.informativeText = "Try with another name or check permissions for the source and destination" 1080 | info.runModal() 1081 | } 1082 | } 1083 | do { 1084 | try FileManager.default.copyItem(atPath: iconPath, toPath: documentDirURL.path) 1085 | } catch { 1086 | let info = NSAlert() 1087 | info.icon = NSImage(named: "Warning") 1088 | info.addButton(withTitle: "OK") 1089 | info.alertStyle = NSAlert.Style.informational 1090 | info.messageText = "Couldnt copy file" 1091 | info.informativeText = "Try with another name or check permissions for the source and destination" 1092 | info.runModal() 1093 | } 1094 | 1095 | } 1096 | } 1097 | } 1098 | 1099 | // Get all printers to printersArray 1100 | func getAllPrinters () { 1101 | // Clear the arrays and variables if refresh 1102 | var printers = "" 1103 | printersArray.removeAll() 1104 | everyprintersArray.removeAll() 1105 | printersArrayTemp.removeAll() 1106 | printerInfoArrayTemp.removeAll() 1107 | selectedPrinter = "" 1108 | printers = shell("SOFTWARE= LANG=C lpstat -s") 1109 | printers = printers.replacingOccurrences(of: ": ", with: "::", options: [.regularExpression, .caseInsensitive]) 1110 | printers = printers.replacingOccurrences(of: "::", with: "|", options: [.regularExpression, .caseInsensitive]) 1111 | printers = printers.replacingOccurrences(of: "device for ", with: "", options: [.regularExpression, .caseInsensitive]) 1112 | var printersArrayTemp = printers.components(separatedBy: CharacterSet.newlines) 1113 | printersArrayTemp.removeFirst() 1114 | printersArrayTemp = printersArrayTemp.filter({ $0 != ""}) 1115 | 1116 | printersArrayTemp.forEach { 1117 | let printersArrayClean = String(($0.split(separator: "|").first!)) 1118 | printersArray += printersArrayClean.components(separatedBy: CharacterSet.newlines) 1119 | } 1120 | 1121 | 1122 | 1123 | for everyprinter in printersArray { 1124 | 1125 | // Make a copy of printersArrayTemp to get printer protocol 1126 | printersProtocolArray = printersArrayTemp 1127 | printersProtocolArray.removeAll { !$0.contains(everyprinter) } 1128 | 1129 | printersProtocolArray.forEach { 1130 | protocolandIp = String(($0.split(separator: "|").last!)) 1131 | printerprotcol = protocolandIp.components(separatedBy: "://")[0] 1132 | ipaddress = protocolandIp.components(separatedBy: "://")[1] 1133 | } 1134 | 1135 | // Get choosen printers info, where Interface=cupsppd, Description=printername, Location=location 1136 | var printerInfo = "" 1137 | printerInfo = shell("SOFTWARE= LANG=C lpstat -lp '\(everyprinter)'") 1138 | printerInfo = printerInfo.replacingOccurrences(of: "\t", with: "", options: [.regularExpression, .caseInsensitive]) 1139 | 1140 | printerInfoArrayTemp = printerInfo.components(separatedBy: CharacterSet.newlines) 1141 | 1142 | 1143 | printerInfoArrayTemp.forEach { 1144 | if $0.hasPrefix("Interface:") {cupsppd = ($0.replacingOccurrences(of: "Interface: ", with: "", options: [.regularExpression, .caseInsensitive]))} 1145 | if $0.hasPrefix("Description:") {printername = ($0.replacingOccurrences(of: "Description: ", with: "", options: [.regularExpression, .caseInsensitive]))} 1146 | if $0.hasPrefix("Location:") {location = ($0.replacingOccurrences(of: "Location: ", with: "", options: [.regularExpression, .caseInsensitive]))} 1147 | } 1148 | 1149 | printercard = (CupsPrinterName: everyprinter, CupsPPD: cupsppd, Printername: printername, ShortNickName: getShortNickName(CupsPPD: cupsppd).CupsShortNickname, Location: location, Protocol: printerprotcol, IP: ipaddress, Icon: getShortNickName(CupsPPD: cupsppd).iconPath, Ipp2ppdCreated: getShortNickName(CupsPPD: cupsppd).ipp2ppdCreated) 1150 | 1151 | everyprintersArray.append(printercard) 1152 | } 1153 | } 1154 | 1155 | 1156 | func getAllPPDs () { 1157 | let documentsPath = "/Library/Printers/PPDs/Contents/Resources" 1158 | let url = URL(fileURLWithPath: documentsPath) 1159 | let fileManager = FileManager.default 1160 | 1161 | if fileManager.fileExists(atPath: documentsPath) { 1162 | let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: url.path)! 1163 | 1164 | 1165 | while let subFolders = enumerator.nextObject() as? String { 1166 | if subFolders.hasSuffix(".lproj") { 1167 | // skip only pure folders 1168 | } else { 1169 | allPPDs += "\n/Library/Printers/PPDs/Contents/Resources/"+(subFolders) 1170 | } 1171 | } 1172 | allPPDsArray = allPPDs.components(separatedBy: CharacterSet.newlines) 1173 | allPPDsArray = allPPDsArray.filter({ $0 != ""}) 1174 | } else { 1175 | return 1176 | } 1177 | 1178 | 1179 | } 1180 | 1181 | 1182 | func getAllShortNickNameAndPrinterPPDs () { 1183 | var ppdsAndShortnickname = "" 1184 | if allPPDsArray.isEmpty { 1185 | return 1186 | } 1187 | for ppd in allPPDsArray { 1188 | var tempShortNickName = "" 1189 | 1190 | // for ppd in ppdsFiles { 1191 | if ppd.hasSuffix(".gz") { 1192 | // some ppds has Legacy linefeeds that makes zgrep fail need to convert them 1193 | tempShortNickName = shell( "gzip -d <'\(ppd)' | tr '\\r\\n' '\\n' | zgrep -som1 -E '\\*ShortNickName.+\"'") 1194 | } else { 1195 | // some ppds has Legacy linefeeds that makes zgrep fail need to convert them 1196 | tempShortNickName = shell("tr '\\r\\n' '\\n' <'\(ppd)' | zgrep -som1 -E '\\*ShortNickName.+\"'") 1197 | } 1198 | if tempShortNickName.contains("*ShortNickName:") { 1199 | tempShortNickName = tempShortNickName.replacingOccurrences(of: "\\*ShortNickName:", with: "", options: [.regularExpression, .caseInsensitive]) 1200 | tempShortNickName = tempShortNickName.replacingOccurrences(of: "\n", with: "", options: [.regularExpression, .caseInsensitive]) 1201 | tempShortNickName = tempShortNickName.replacingOccurrences(of: "\t", with: "", options: [.regularExpression, .caseInsensitive]) 1202 | tempShortNickName = tempShortNickName.replacingOccurrences(of: "\"", with: "", options: [.regularExpression, .caseInsensitive]) 1203 | 1204 | 1205 | if tempShortNickName.hasPrefix(" ") { 1206 | tempShortNickName = String(tempShortNickName.dropFirst()) 1207 | } 1208 | 1209 | // Add to variable to use for writing to file later 1210 | ppdsAndShortnickname += "\(tempShortNickName),\(ppd)\n" 1211 | } 1212 | } 1213 | // Write to file 1214 | do { 1215 | let writeString = ppdsAndShortnickname.description 1216 | try FileManager.default.createDirectory(atPath: "/Users/\(NSUserName())/Library/Application Support/PrinterSetup", withIntermediateDirectories: true, attributes: nil) 1217 | try writeString.write(to: ppdListFilePath, atomically: true, encoding: String.Encoding.utf8) 1218 | } catch { 1219 | print(error) 1220 | } 1221 | // Get all ppds from textfile 1222 | getAllPPDfromtxtFile () 1223 | 1224 | } 1225 | 1226 | 1227 | func getAllPPDfromtxtFile () { 1228 | do { 1229 | let readPPDListFile = try String(contentsOf: ppdListFilePath, encoding: String.Encoding.utf8) 1230 | let cleanPPDListFile = readPPDListFile.replacingOccurrences(of: "\"", with: "", options: .caseInsensitive, range: nil) 1231 | 1232 | let cleanPPDListFileLines = cleanPPDListFile.split(separator:"\n") 1233 | 1234 | for line in cleanPPDListFileLines { 1235 | var ppdsandshortnicknameFileTemp = [String] () 1236 | ppdsandshortnicknameFileTemp.removeAll() 1237 | ppdsandshortnicknameFileTemp = line.components(separatedBy: ",") 1238 | let tempTuple = (ShortNickName: ppdsandshortnicknameFileTemp[0], PPD: ppdsandshortnicknameFileTemp[1]) 1239 | shortNickNameAndPPDsArray.append(tempTuple) 1240 | } 1241 | } catch { 1242 | print(error) 1243 | } 1244 | } 1245 | 1246 | 1247 | func sortPPDsArray () { 1248 | var language = Locale.current.collatorIdentifier 1249 | language = String(language!.prefix(2)) 1250 | 1251 | 1252 | let searchLproj = language! + ".lproj" 1253 | var currentIndex = 0 1254 | for ppdPath in sortedPPDsArray { 1255 | if ppdPath.contains(searchLproj) { 1256 | sortedPPDsArray.remove(at: currentIndex) 1257 | sortedPPDsArray.insert(ppdPath, at: 0) 1258 | break 1259 | } 1260 | currentIndex += 1 1261 | } 1262 | } 1263 | 1264 | 1265 | func countppdListFileLines () -> Int { 1266 | var returnCount = 0 1267 | do { 1268 | let ppdListFileLines = try String(contentsOfFile: ppdListFile, encoding: String.Encoding.utf8) 1269 | let ppdListFileLinesCount = ppdListFileLines.components(separatedBy: CharacterSet.newlines) 1270 | // -1 cause it counts last linefeed 1271 | returnCount = ppdListFileLinesCount.count-1 1272 | } catch { 1273 | print(error) 1274 | } 1275 | return returnCount 1276 | } 1277 | 1278 | func ppdsTXTfileExist () -> Bool { 1279 | var exist = false 1280 | if FileManager.default.fileExists(atPath: ppdListFile) { 1281 | exist = true 1282 | } 1283 | return exist 1284 | } 1285 | 1286 | func runBackgroundPPDindex () { 1287 | DispatchQueue.global(qos: .userInteractive).async { 1288 | getAllPPDs () 1289 | 1290 | if ppdsTXTfileExist() == true { 1291 | if allPPDsArray.count == countppdListFileLines() { 1292 | // Read from file directly 1293 | getAllPPDfromtxtFile () 1294 | } else { 1295 | getAllShortNickNameAndPrinterPPDs () 1296 | } 1297 | } else { 1298 | getAllShortNickNameAndPrinterPPDs () 1299 | } 1300 | DispatchQueue.main.async { 1301 | if allPPDsArray.isEmpty { 1302 | let info = NSAlert() 1303 | info.icon = NSImage(named: "Warning") 1304 | info.addButton(withTitle: "OK") 1305 | info.alertStyle = NSAlert.Style.informational 1306 | info.messageText = "Couldnt find any PPDs to index" 1307 | info.informativeText = """ 1308 | Make sure path exist at 1309 | '/Library/Printers/PPDs/Contents/Resources' 1310 | and contains some PPDs and try again 1311 | """ 1312 | info.runModal() 1313 | } 1314 | 1315 | if ppdsTXTfileExist() == false { 1316 | let info = NSAlert() 1317 | info.icon = NSImage(named: "Warning") 1318 | info.addButton(withTitle: "OK") 1319 | info.alertStyle = NSAlert.Style.informational 1320 | info.messageText = "Couldnt find PPDs index file" 1321 | info.informativeText = """ 1322 | Make sure file exist at 1323 | '\(ppdListFile)' 1324 | and contains some info and try again 1325 | """ 1326 | info.runModal() 1327 | } 1328 | 1329 | // Back on the main thread 1330 | appDelegate().finalOutputTextField.string = "" 1331 | appDelegate().spinner.stopAnimation(appDelegate()) 1332 | appDelegate().spinner.isHidden=true 1333 | } 1334 | } 1335 | } 1336 | 1337 | 1338 | func selectedPrinterFunction () { 1339 | if appDelegate().printerslistPopup.titleOfSelectedItem != nil { 1340 | selectedPrinter = appDelegate().printerslistPopup.titleOfSelectedItem ?? "" 1341 | } else { 1342 | return } 1343 | 1344 | if selectedPrinter.contains("[Choose a printer]"){ 1345 | 1346 | clearEveryPrinterFields () 1347 | appDelegate().finalOutputTextField.string = "" 1348 | return 1349 | } else { 1350 | createprinterPPDsPopup () 1351 | createcupsNameTextfield () 1352 | createprinterNameTextField () 1353 | createprinterLocationTextField () 1354 | createPrinterProtocolPopup () 1355 | createprinterIpaddressComboBox () 1356 | selectedShortNickName = everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.ShortNickName ?? "" 1357 | airPrintDriverIsUsed = (everyprintersArray.first{ $0.CupsPrinterName == selectedPrinter }?.Ipp2ppdCreated ?? false) as Bool 1358 | getPrinterNameFromPPDs () 1359 | getIconImage () 1360 | generateFinalOutputTextField () 1361 | } 1362 | } 1363 | 1364 | 1365 | func refreshPrinter () { 1366 | // Start all over refresh printers, clear Array needs to be done 1367 | appDelegate().finalOutputTextField.string = "" 1368 | //runBackgroundPPDindex () 1369 | getAllPrinters() 1370 | clearEveryPrinterFields () 1371 | 1372 | // Reset Export as option 1373 | appDelegate().exportPopUp.selectItem(at: 0) 1374 | } 1375 | 1376 | func cleanCupsName(_ inputString:String) -> String { 1377 | var outputString = "" 1378 | if let regex = try? NSRegularExpression(pattern: "(\t|\\ |\\#|\\/)", options: .caseInsensitive) { 1379 | outputString = regex.stringByReplacingMatches(in: inputString, options: [], range: NSRange(location: 0, length: inputString.count), withTemplate: "_") 1380 | } 1381 | return outputString 1382 | } 1383 | 1384 | func cleanVersion(_ inputString:String) -> String { 1385 | var outputString = "" 1386 | if let regex = try? NSRegularExpression(pattern: "[^0-9.]", options: .caseInsensitive) { 1387 | outputString = regex.stringByReplacingMatches(in: inputString, options: [], range: NSRange(location: 0, length: inputString.count), withTemplate: "") 1388 | } 1389 | return outputString 1390 | } 1391 | 1392 | func identifierCheck () { 1393 | if appDelegate().identifierTextField.stringValue == "" { 1394 | appDelegate().identifierTextField.stringValue = "com.printersetup" 1395 | } 1396 | } 1397 | 1398 | 1399 | func cupsCheckBox () { 1400 | // If Checkbox is ON (1) enable cups webinterface 1401 | if statusCupsCheckbox == 1 { 1402 | _ = shell("cupsctl WebInterface=yes") 1403 | appDelegate().cupsURLbutton.isHidden=false 1404 | } else { 1405 | _ = shell("cupsctl WebInterface=no") 1406 | appDelegate().enableCupsWebbutton.state = NSControl.StateValue.off 1407 | statusCupsCheckbox = 0 1408 | appDelegate().cupsURLbutton.isHidden=true 1409 | 1410 | } 1411 | 1412 | } 1413 | 1414 | 1415 | // GET ALL PRINTERS PURE SWIFT save here for references 1416 | // func getAllPrinters () { 1417 | // let documentsPath = "/private/etc/cups/ppd/" 1418 | // let url = URL(fileURLWithPath: documentsPath) 1419 | // 1420 | // do { 1421 | // let directoryContents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil) 1422 | // let printers = directoryContents.filter{ $0.pathExtension == "ppd" } 1423 | // printersArray = printers.map{ $0.deletingPathExtension().lastPathComponent } 1424 | // 1425 | // 1426 | // } catch { 1427 | // print(error) 1428 | // } 1429 | // } 1430 | 1431 | 1432 | //func gettingPrinters () { 1433 | //var printerList : Unmanaged? 1434 | //let kPMServerLocal = unsafeBitCast(0, to: PMServer.self) // #define kPMServerLocal ((PMServer)NULL) 1435 | //PMServerCreatePrinterList(kPMServerLocal, &printerList) 1436 | // 1437 | //var result : [String : String] = [:] 1438 | // 1439 | //if let list = printerList { 1440 | // let retainedList = list.takeRetainedValue() 1441 | // let numberOfPrinters = CFArrayGetCount(retainedList) 1442 | // for printerIndex in 0..?>(OpaquePointer?))) 1451 | // 1452 | // } 1453 | // print("Result1 = " + result.description) // Successful 1454 | // 1455 | // 1456 | //} 1457 | // 1458 | //print("Result2 = " + result.description) // EXC_BAD_ACCESS 1459 | //} 1460 | -------------------------------------------------------------------------------- /PrinterSetup/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | Default 566 | 567 | 568 | 569 | 570 | 571 | 572 | Left to Right 573 | 574 | 575 | 576 | 577 | 578 | 579 | Right to Left 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | Default 591 | 592 | 593 | 594 | 595 | 596 | 597 | Left to Right 598 | 599 | 600 | 601 | 602 | 603 | 604 | Right to Left 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | Item 1 862 | Item 2 863 | Item 3 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | Item 1 1059 | Item 2 1060 | Item 3 1061 | 1062 | 1063 | 1064 | 1074 | 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | 1180 | 1181 | 1182 | 1183 | 1184 | 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 1192 | 1193 | 1194 | 1195 | 1196 | 1197 | 1198 | 1199 | 1200 | 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 1210 | --------------------------------------------------------------------------------