├── .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 |
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 |
903 |
904 |
905 |
906 |
907 |
908 |
909 |
910 |
911 |
912 |
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
926 |
931 |
936 |
941 |
946 |
947 |
948 |
949 |
950 |
951 |
952 |
953 |
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 |
--------------------------------------------------------------------------------