Skip to content

Commit 140102f

Browse files
committed
Adds ability to set an icon for the volume #21
1 parent 2d853bd commit 140102f

File tree

5 files changed

+136
-11
lines changed

5 files changed

+136
-11
lines changed

TmpDisk.xcodeproj/project.pbxproj

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
220082602765505100A8524B /* TmpDiskUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2200825F2765505100A8524B /* TmpDiskUITests.swift */; };
1515
220082622765505100A8524B /* TmpDiskUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220082612765505100A8524B /* TmpDiskUITestsLaunchTests.swift */; };
1616
22235EC027784F8200FBD8AC /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = 22235EBF27784F8200FBD8AC /* dsa_pub.pem */; };
17+
2226C0D8294EAFF80043D2B7 /* IconUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2226C0D7294EAFF80043D2B7 /* IconUtil.swift */; };
1718
2264C5E2276553E600C45CB8 /* StatusBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2264C5E1276553E600C45CB8 /* StatusBarController.swift */; };
1819
2264C5E62765667500C45CB8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2264C5E82765667500C45CB8 /* Localizable.strings */; };
1920
2264C5EC27657AAC00C45CB8 /* TmpDiskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2264C5EB27657AAC00C45CB8 /* TmpDiskManager.swift */; };
@@ -74,6 +75,7 @@
7475
220082612765505100A8524B /* TmpDiskUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TmpDiskUITestsLaunchTests.swift; sourceTree = "<group>"; };
7576
2200826E2765508C00A8524B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
7677
22235EBF27784F8200FBD8AC /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dsa_pub.pem; sourceTree = "<group>"; };
78+
2226C0D7294EAFF80043D2B7 /* IconUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconUtil.swift; sourceTree = "<group>"; };
7779
2264C5E1276553E600C45CB8 /* StatusBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarController.swift; sourceTree = "<group>"; };
7880
2264C5E72765667500C45CB8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
7981
2264C5E92765667700C45CB8 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
@@ -156,6 +158,7 @@
156158
children = (
157159
2264C5EA27657A7E00C45CB8 /* Views */,
158160
220082432765504E00A8524B /* AppDelegate.swift */,
161+
2226C0D7294EAFF80043D2B7 /* IconUtil.swift */,
159162
2264C5E1276553E600C45CB8 /* StatusBarController.swift */,
160163
2264C5EB27657AAC00C45CB8 /* TmpDiskManager.swift */,
161164
22D93DBD27B85E0F0024FB53 /* WindowManager.swift */,
@@ -303,7 +306,7 @@
303306
attributes = {
304307
BuildIndependentTargetsInParallel = 1;
305308
LastSwiftUpdateCheck = 1320;
306-
LastUpgradeCheck = 1340;
309+
LastUpgradeCheck = 1410;
307310
TargetAttributes = {
308311
2200823F2765504E00A8524B = {
309312
CreatedOnToolsVersion = 13.1;
@@ -394,6 +397,7 @@
394397
2264C5EE2765C34000C45CB8 /* NewTmpDiskViewController.swift in Sources */,
395398
2264C5EC27657AAC00C45CB8 /* TmpDiskManager.swift in Sources */,
396399
22D93DBE27B85E0F0024FB53 /* WindowManager.swift in Sources */,
400+
2226C0D8294EAFF80043D2B7 /* IconUtil.swift in Sources */,
397401
2264C5E2276553E600C45CB8 /* StatusBarController.swift in Sources */,
398402
22A6938D27702BDF0040FDEF /* CheckBoxTableCellView.swift in Sources */,
399403
220082442765504E00A8524B /* AppDelegate.swift in Sources */,
@@ -505,6 +509,7 @@
505509
CLANG_WARN_UNREACHABLE_CODE = YES;
506510
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
507511
COPY_PHASE_STRIP = NO;
512+
DEAD_CODE_STRIPPING = YES;
508513
DEBUG_INFORMATION_FORMAT = dwarf;
509514
ENABLE_STRICT_OBJC_MSGSEND = YES;
510515
ENABLE_TESTABILITY = YES;
@@ -567,6 +572,7 @@
567572
CLANG_WARN_UNREACHABLE_CODE = YES;
568573
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
569574
COPY_PHASE_STRIP = NO;
575+
DEAD_CODE_STRIPPING = YES;
570576
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
571577
ENABLE_NS_ASSERTIONS = NO;
572578
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -596,7 +602,8 @@
596602
CODE_SIGN_IDENTITY = "Apple Development";
597603
CODE_SIGN_STYLE = Automatic;
598604
COMBINE_HIDPI_IMAGES = YES;
599-
CURRENT_PROJECT_VERSION = 1006;
605+
CURRENT_PROJECT_VERSION = 1007;
606+
DEAD_CODE_STRIPPING = YES;
600607
DEVELOPMENT_TEAM = AGZ3AP53DM;
601608
ENABLE_HARDENED_RUNTIME = YES;
602609
GENERATE_INFOPLIST_FILE = YES;
@@ -609,8 +616,8 @@
609616
"$(inherited)",
610617
"@executable_path/../Frameworks",
611618
);
612-
MACOSX_DEPLOYMENT_TARGET = 10.11;
613-
MARKETING_VERSION = 2.0.4;
619+
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
620+
MARKETING_VERSION = 2.0.5;
614621
PRODUCT_BUNDLE_IDENTIFIER = com.imothee.TmpDisk;
615622
PRODUCT_NAME = "$(TARGET_NAME)";
616623
SKIP_INSTALL = NO;
@@ -628,7 +635,8 @@
628635
CODE_SIGN_IDENTITY = "Apple Development";
629636
CODE_SIGN_STYLE = Automatic;
630637
COMBINE_HIDPI_IMAGES = YES;
631-
CURRENT_PROJECT_VERSION = 1006;
638+
CURRENT_PROJECT_VERSION = 1007;
639+
DEAD_CODE_STRIPPING = YES;
632640
DEVELOPMENT_TEAM = AGZ3AP53DM;
633641
ENABLE_HARDENED_RUNTIME = YES;
634642
GENERATE_INFOPLIST_FILE = YES;
@@ -641,8 +649,8 @@
641649
"$(inherited)",
642650
"@executable_path/../Frameworks",
643651
);
644-
MACOSX_DEPLOYMENT_TARGET = 10.11;
645-
MARKETING_VERSION = 2.0.4;
652+
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
653+
MARKETING_VERSION = 2.0.5;
646654
PRODUCT_BUNDLE_IDENTIFIER = com.imothee.TmpDisk;
647655
PRODUCT_NAME = "$(TARGET_NAME)";
648656
SKIP_INSTALL = NO;
@@ -659,6 +667,7 @@
659667
CODE_SIGN_STYLE = Automatic;
660668
COMBINE_HIDPI_IMAGES = YES;
661669
CURRENT_PROJECT_VERSION = 1;
670+
DEAD_CODE_STRIPPING = YES;
662671
DEVELOPMENT_TEAM = AGZ3AP53DM;
663672
GENERATE_INFOPLIST_FILE = YES;
664673
LD_RUNPATH_SEARCH_PATHS = (
@@ -684,6 +693,7 @@
684693
CODE_SIGN_STYLE = Automatic;
685694
COMBINE_HIDPI_IMAGES = YES;
686695
CURRENT_PROJECT_VERSION = 1;
696+
DEAD_CODE_STRIPPING = YES;
687697
DEVELOPMENT_TEAM = AGZ3AP53DM;
688698
GENERATE_INFOPLIST_FILE = YES;
689699
LD_RUNPATH_SEARCH_PATHS = (
@@ -708,13 +718,15 @@
708718
CODE_SIGN_STYLE = Automatic;
709719
COMBINE_HIDPI_IMAGES = YES;
710720
CURRENT_PROJECT_VERSION = 1;
721+
DEAD_CODE_STRIPPING = YES;
711722
DEVELOPMENT_TEAM = AGZ3AP53DM;
712723
GENERATE_INFOPLIST_FILE = YES;
713724
LD_RUNPATH_SEARCH_PATHS = (
714725
"$(inherited)",
715726
"@executable_path/../Frameworks",
716727
"@loader_path/../Frameworks",
717728
);
729+
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
718730
MARKETING_VERSION = 1.0;
719731
PRODUCT_BUNDLE_IDENTIFIER = com.imothee.TmpDiskUITests;
720732
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -731,13 +743,15 @@
731743
CODE_SIGN_STYLE = Automatic;
732744
COMBINE_HIDPI_IMAGES = YES;
733745
CURRENT_PROJECT_VERSION = 1;
746+
DEAD_CODE_STRIPPING = YES;
734747
DEVELOPMENT_TEAM = AGZ3AP53DM;
735748
GENERATE_INFOPLIST_FILE = YES;
736749
LD_RUNPATH_SEARCH_PATHS = (
737750
"$(inherited)",
738751
"@executable_path/../Frameworks",
739752
"@loader_path/../Frameworks",
740753
);
754+
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
741755
MARKETING_VERSION = 1.0;
742756
PRODUCT_BUNDLE_IDENTIFIER = com.imothee.TmpDiskUITests;
743757
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -757,6 +771,7 @@
757771
CODE_SIGN_STYLE = Automatic;
758772
COMBINE_HIDPI_IMAGES = YES;
759773
CURRENT_PROJECT_VERSION = 5;
774+
DEAD_CODE_STRIPPING = YES;
760775
DEVELOPMENT_TEAM = AGZ3AP53DM;
761776
ENABLE_HARDENED_RUNTIME = YES;
762777
GENERATE_INFOPLIST_FILE = YES;
@@ -769,7 +784,7 @@
769784
"$(inherited)",
770785
"@executable_path/../Frameworks",
771786
);
772-
MACOSX_DEPLOYMENT_TARGET = 10.11;
787+
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
773788
MARKETING_VERSION = 2.0.4;
774789
PRODUCT_BUNDLE_IDENTIFIER = com.imothee.TmpDiskLauncher;
775790
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -789,6 +804,7 @@
789804
CODE_SIGN_STYLE = Automatic;
790805
COMBINE_HIDPI_IMAGES = YES;
791806
CURRENT_PROJECT_VERSION = 5;
807+
DEAD_CODE_STRIPPING = YES;
792808
DEVELOPMENT_TEAM = AGZ3AP53DM;
793809
ENABLE_HARDENED_RUNTIME = YES;
794810
GENERATE_INFOPLIST_FILE = YES;
@@ -801,7 +817,7 @@
801817
"$(inherited)",
802818
"@executable_path/../Frameworks",
803819
);
804-
MACOSX_DEPLOYMENT_TARGET = 10.11;
820+
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
805821
MARKETING_VERSION = 2.0.4;
806822
PRODUCT_BUNDLE_IDENTIFIER = com.imothee.TmpDiskLauncher;
807823
PRODUCT_NAME = "$(TARGET_NAME)";

TmpDisk/IconUtil.swift

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,75 @@
66
//
77

88
import Foundation
9+
import AppKit
10+
11+
class IconUtil {
12+
private let iconutilPath = "/usr/bin/iconutil"
13+
14+
static let shared: IconUtil = {
15+
let instance = IconUtil()
16+
// setup code
17+
return instance
18+
}()
19+
20+
private func createIconSet(image: NSImage, iconSetURL: URL) throws {
21+
// Create the iconset directory
22+
try FileManager.default.createDirectory(at: iconSetURL, withIntermediateDirectories: true)
23+
24+
// Write out all the icon files
25+
for dimension in [16, 32, 128, 256, 512] {
26+
for scale in [1, 2] {
27+
let size = NSSize(width: dimension * scale, height: dimension * scale)
28+
let filename = "icon_\(dimension)x\(dimension)\(scale == 1 ? "" : "@\(scale)x").png"
29+
30+
let frame = NSRect(x: 0, y: 0, width: size.width, height: size.height)
31+
guard let representation = image.bestRepresentation(for: frame, context: nil, hints: nil) else {
32+
print("Issue loading tiff from image")
33+
return
34+
}
35+
let newImage = NSImage(size: size, flipped: false, drawingHandler: { (_) -> Bool in
36+
return representation.draw(in: frame)
37+
})
38+
guard let tiffRep = newImage.tiffRepresentation,
39+
let bitmapRep = NSBitmapImageRep(data: tiffRep),
40+
let pngData = bitmapRep.representation(using: .png, properties: [:])
41+
else {
42+
return
43+
}
44+
45+
try pngData.write(to: iconSetURL.appendingPathComponent(filename))
46+
}
47+
}
48+
}
49+
50+
func convertImageToICNS(image: NSImage) throws -> String {
51+
let tmpDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
52+
let iconSetURL = tmpDir.appendingPathComponent("icon.iconset")
53+
let iconURL = tmpDir.appendingPathComponent("icon.icns")
54+
55+
// Get the iconset of all the sized images
56+
try self.createIconSet(image: image, iconSetURL: iconSetURL)
57+
58+
// Now we need to convert to an icns
59+
let process = Process()
60+
let pipe = Pipe()
61+
62+
process.standardOutput = pipe
63+
process.standardError = pipe
64+
process.arguments = ["-c", "icns", iconSetURL.lastPathComponent]
65+
66+
process.launchPath = iconutilPath
67+
process.currentDirectoryPath = tmpDir.path
68+
process.launch()
69+
process.waitUntilExit()
70+
71+
if process.terminationStatus != 0 {
72+
print("FAILED")
73+
}
74+
75+
let data = try Data(contentsOf: iconURL)
76+
try? FileManager.default.removeItem(at: tmpDir)
77+
return data.base64EncodedString()
78+
}
79+
80+
}

TmpDisk/TmpDiskManager.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ struct TmpDiskVolume: Hashable, Codable {
3333
var tmpFs: Bool = false
3434
var warnOnEject: Bool = false
3535
var folders: [String] = []
36+
var icon: String?
3637

3738
func path() -> String {
3839
if tmpFs {
@@ -53,7 +54,8 @@ struct TmpDiskVolume: Hashable, Codable {
5354
"hidden": hidden,
5455
"tmpFs": tmpFs,
5556
"warnOnEject": warnOnEject,
56-
"folders": folders
57+
"folders": folders,
58+
"icon": icon ?? "",
5759
]
5860
}
5961

@@ -137,8 +139,9 @@ class TmpDiskManager {
137139

138140
let warnOnEject = vol["warnOnEject"] as? Bool ?? false
139141
let folders = vol["folders"] as? [String] ?? []
142+
let icon = vol["icon"] as? String
140143

141-
let volume = TmpDiskVolume(name: name, size: size, indexed: indexed, hidden: hidden, tmpFs: tmpFs, warnOnEject: warnOnEject, folders: folders)
144+
let volume = TmpDiskVolume(name: name, size: size, indexed: indexed, hidden: hidden, tmpFs: tmpFs, warnOnEject: warnOnEject, folders: folders, icon: icon)
142145
autoCreateVolumes.insert(volume)
143146
}
144147
}
@@ -195,8 +198,12 @@ class TmpDiskManager {
195198
self.indexVolume(volume: volume)
196199
}
197200

201+
// Create the folders if there are any set
198202
self.createFolders(volume: volume)
199203

204+
// Create the icon if there is one set
205+
self.createIcon(volume: volume)
206+
200207
if volume.autoCreate {
201208
self.addAutoCreateVolume(volume: volume)
202209
}
@@ -325,4 +332,12 @@ class TmpDiskManager {
325332
return FileManager.default.fileExists(atPath: volume.path())
326333
}
327334

335+
func createIcon(volume: TmpDiskVolume) {
336+
if let icon = volume.icon {
337+
if let data = Data(base64Encoded: icon) {
338+
let image = NSImage(data: data)
339+
NSWorkspace.shared.setIcon(image, forFile: volume.path())
340+
}
341+
}
342+
}
328343
}

TmpDisk/Views/NewTmpDiskViewController.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class NewTmpDiskViewController: NSViewController, NSTextFieldDelegate {
3333
@IBOutlet weak var diskSizeSuffixLabel: NSTextField!
3434
@IBOutlet weak var folders: NSTextField!
3535

36+
@IBOutlet weak var icon: NSImageView!
37+
3638
@IBOutlet weak var diskSizeSelector: NSPopUpButton!
3739

3840
@IBOutlet weak var autoCreate: NSButton!
@@ -85,6 +87,24 @@ class NewTmpDiskViewController: NSViewController, NSTextFieldDelegate {
8587
}
8688
}
8789

90+
@IBAction func onIconChange(_ sender: NSImageView) {
91+
if let image = sender.image{
92+
guard image.size.width == image.size.height else {
93+
self.showError(message: NSLocalizedString("Icon must be square", comment: ""))
94+
sender.image = nil
95+
return
96+
}
97+
98+
guard let iconBase64 = try? IconUtil.shared.convertImageToICNS(image: image) else {
99+
self.showError(message: NSLocalizedString("Could not convert image to icon", comment: ""))
100+
sender.image = nil
101+
return
102+
}
103+
104+
self.volume.icon = iconBase64
105+
}
106+
}
107+
88108
@IBAction func onUseTmpFsChange(_ sender: NSButton) {
89109
if sender.state == .on {
90110
self.volume.tmpFs = true

TmpDisk/en.lproj/Localizable.strings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@
2323
"Failed to create TmpDisk" = "Failed to create TmpDisk";
2424
"Preferences" = "Preferences";
2525
"You can't change the root volume while tmpFS disks are mounted." = "You can't change the root volume while tmpFS disks are mounted.";
26+
"Icon must be square" = "Icon must be square";
27+
"Could not convert image to icon" = "Could not convert image to icon";

0 commit comments

Comments
 (0)