diff --git a/alt-tab-macos.xcodeproj/project.pbxproj b/alt-tab-macos.xcodeproj/project.pbxproj index f4e0295f..e1bfaa68 100644 --- a/alt-tab-macos.xcodeproj/project.pbxproj +++ b/alt-tab-macos.xcodeproj/project.pbxproj @@ -7,18 +7,24 @@ objects = { /* Begin PBXBuildFile section */ + D04BA02DD4152997C32CF50B /* StatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA0AF7C5DCF367FBB663C /* StatusItem.swift */; }; D04BA20D4A240843293B3B52 /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA56355579F78776E6D51 /* Cell.swift */; }; D04BA278D9EFA568C8D18A4C /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAD1BED44EAEB77FED8A4 /* WindowManager.swift */; }; D04BA3261C7DA5F48310E654 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA90C6C36DB1D65BC2B66 /* Application.swift */; }; D04BA57A871B7269BEBAFF84 /* Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA35456DA0DDA74F9687E /* Keyboard.swift */; }; + D04BA6368E681BE3A408AC99 /* PreferencesPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA02F476DE30C4647886C /* PreferencesPanel.swift */; }; D04BA70FF7262BF5F9E6E13B /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BADCB1C0F50340A6CAFC2 /* Preferences.swift */; }; D04BA86F2E687F573F920D93 /* HighInterpolationImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAE70D6AFD2E52A71701D /* HighInterpolationImageView.swift */; }; + D04BA8EBC0365A019A27C7EA /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA3F15EAE8D8C39B6F2CF /* Screen.swift */; }; + D04BA9119E2329DB5A35B3C7 /* ThumbnailsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAE5BBE182DD5DDFE2E3E /* ThumbnailsPanel.swift */; }; D04BA960DDD1D32A3019C835 /* CollectionViewCenterFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA3202A2C22C347E849B3 /* CollectionViewCenterFlowLayout.swift */; }; - D04BAE1A64F5572B19F9E2B1 /* preferences.json in Resources */ = {isa = PBXBuildFile; fileRef = D04BA8924E1644C932B8CBAA /* preferences.json */; }; + D04BA9CCE02D30C8164A552A /* SystemPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA2D2AD6B1CCA3F3A4DD7 /* SystemPermissions.swift */; }; D04BAEF78503D7A2CEFB9E9E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAA44C837F3A67403B9DB /* main.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + D04BA02F476DE30C4647886C /* PreferencesPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesPanel.swift; sourceTree = ""; }; + D04BA0AF7C5DCF367FBB663C /* StatusItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusItem.swift; sourceTree = ""; }; D04BA0CE87BE264C52987ED1 /* 7 windows - 2 lines - wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - wide window.jpg"; sourceTree = ""; }; D04BA0E1C5DBC07108AC2F54 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D04BA0F5EB832B8E142B654B /* 4 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "4 windows - 1 line.jpg"; sourceTree = ""; }; @@ -27,9 +33,11 @@ D04BA1FC9022590D7AA02486 /* 1 window - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "1 window - 1 line.jpg"; sourceTree = ""; }; D04BA27584274BC8C2D9809F /* 3 windows - 1 line.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "3 windows - 1 line.png"; sourceTree = ""; }; D04BA2C7B51F68651B3C60E2 /* 6 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "6 windows - 1 line.jpg"; sourceTree = ""; }; + D04BA2D2AD6B1CCA3F3A4DD7 /* SystemPermissions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemPermissions.swift; sourceTree = ""; }; D04BA3202A2C22C347E849B3 /* CollectionViewCenterFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCenterFlowLayout.swift; sourceTree = ""; }; D04BA32F25860B686DFE818A /* 3 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "3 windows - 1 line.jpg"; sourceTree = ""; }; D04BA35456DA0DDA74F9687E /* Keyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keyboard.swift; sourceTree = ""; }; + D04BA3F15EAE8D8C39B6F2CF /* Screen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = ""; }; D04BA4336B6004A0A99849AD /* package.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = package.json; sourceTree = ""; }; D04BA4B5292629AA6B560216 /* package_release.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = package_release.sh; sourceTree = ""; }; D04BA4F23325560BC0BCDDB7 /* 7 windows - 2 lines - tall window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - tall window.jpg"; sourceTree = ""; }; @@ -41,7 +49,6 @@ D04BA7B6AAB0812631BBC7A2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; D04BA7ECCE728582D9ECA613 /* determine_version.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = determine_version.sh; sourceTree = ""; }; D04BA82F792DF53958D92572 /* alt-tab-macos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "alt-tab-macos.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - D04BA8924E1644C932B8CBAA /* preferences.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = preferences.json; sourceTree = ""; }; D04BA8DDB96B0A60C3394351 /* dark-background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "dark-background.png"; sourceTree = ""; }; D04BA90C6C36DB1D65BC2B66 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; D04BA92541D46EA4F6943A72 /* package-lock.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "package-lock.json"; sourceTree = ""; }; @@ -57,6 +64,7 @@ D04BAD82F652C4ED5D8BF7AE /* 3 windows - 1 line - tall window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "3 windows - 1 line - tall window.jpg"; sourceTree = ""; }; D04BADCB1C0F50340A6CAFC2 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; D04BAE1243C9B4BE3ED1B524 /* 7 windows - 2 lines - extra wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - extra wide window.jpg"; sourceTree = ""; }; + D04BAE5BBE182DD5DDFE2E3E /* ThumbnailsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbnailsPanel.swift; sourceTree = ""; }; D04BAE70D6AFD2E52A71701D /* HighInterpolationImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighInterpolationImageView.swift; sourceTree = ""; }; D04BAF076A30A1BAFEDBEA66 /* 5 windows - 2 lines.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "5 windows - 2 lines.jpg"; sourceTree = ""; }; D04BAFA277EAE3BDDDB61110 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; @@ -91,14 +99,6 @@ ); sourceTree = ""; }; - D04BA18D1EDDD56CA89604B6 /* resources */ = { - isa = PBXGroup; - children = ( - D04BA8924E1644C932B8CBAA /* preferences.json */, - ); - path = resources; - sourceTree = ""; - }; D04BA1A461BBB5A9BE15E015 /* Products */ = { isa = PBXGroup; children = ( @@ -155,6 +155,8 @@ D04BA35456DA0DDA74F9687E /* Keyboard.swift */, D04BAD1BED44EAEB77FED8A4 /* WindowManager.swift */, D04BADCB1C0F50340A6CAFC2 /* Preferences.swift */, + D04BA3F15EAE8D8C39B6F2CF /* Screen.swift */, + D04BA2D2AD6B1CCA3F3A4DD7 /* SystemPermissions.swift */, ); path = logic; sourceTree = ""; @@ -166,6 +168,9 @@ D04BAE70D6AFD2E52A71701D /* HighInterpolationImageView.swift */, D04BA56355579F78776E6D51 /* Cell.swift */, D04BA90C6C36DB1D65BC2B66 /* Application.swift */, + D04BA02F476DE30C4647886C /* PreferencesPanel.swift */, + D04BAE5BBE182DD5DDFE2E3E /* ThumbnailsPanel.swift */, + D04BA0AF7C5DCF367FBB663C /* StatusItem.swift */, ); path = ui; sourceTree = ""; @@ -195,7 +200,6 @@ D04BA7B6AAB0812631BBC7A2 /* Info.plist */, D04BAA44C837F3A67403B9DB /* main.swift */, D04BAA1C553891551B903DA7 /* logic */, - D04BA18D1EDDD56CA89604B6 /* resources */, ); path = "alt-tab-macos"; sourceTree = ""; @@ -251,7 +255,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D04BAE1A64F5572B19F9E2B1 /* preferences.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -270,6 +273,11 @@ D04BA278D9EFA568C8D18A4C /* WindowManager.swift in Sources */, D04BA3261C7DA5F48310E654 /* Application.swift in Sources */, D04BA70FF7262BF5F9E6E13B /* Preferences.swift in Sources */, + D04BA6368E681BE3A408AC99 /* PreferencesPanel.swift in Sources */, + D04BA9119E2329DB5A35B3C7 /* ThumbnailsPanel.swift in Sources */, + D04BA8EBC0365A019A27C7EA /* Screen.swift in Sources */, + D04BA9CCE02D30C8164A552A /* SystemPermissions.swift in Sources */, + D04BA02DD4152997C32CF50B /* StatusItem.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -284,7 +292,7 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "alt-tab-macos/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "com.lwouis.alt-tab-macos"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.2; @@ -299,7 +307,7 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "alt-tab-macos/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = "com.lwouis.alt-tab-macos"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.2; diff --git a/alt-tab-macos/logic/Keyboard.swift b/alt-tab-macos/logic/Keyboard.swift index 8feceffa..53e08d2d 100644 --- a/alt-tab-macos/logic/Keyboard.swift +++ b/alt-tab-macos/logic/Keyboard.swift @@ -1,5 +1,11 @@ import Cocoa +class Keyboard { + static func listenToGlobalEvents(_ delegate: Application) { + listenToGlobalKeyboardEvents(delegate) + } +} + var eventTap: CFMachPort? func listenToGlobalKeyboardEvents(_ delegate: Application) { @@ -22,7 +28,7 @@ func listenToGlobalKeyboardEvents(_ delegate: Application) { func keyboardHandler(_ cgEvent: CGEvent, _ delegate: Application) -> Unmanaged? { if cgEvent.type == .keyDown || cgEvent.type == .keyUp || cgEvent.type == .flagsChanged { - if let event = NSEvent.init(cgEvent: cgEvent) { + if let event = NSEvent(cgEvent: cgEvent) { let keyDown = event.type == .keyDown let optionKeyEvent = event.keyCode == Preferences.metaKey let tabKeyEvent = event.keyCode == Preferences.tabKey diff --git a/alt-tab-macos/logic/Preferences.swift b/alt-tab-macos/logic/Preferences.swift index 324e54dc..26a31ca7 100644 --- a/alt-tab-macos/logic/Preferences.swift +++ b/alt-tab-macos/logic/Preferences.swift @@ -1,90 +1,173 @@ import Foundation import Cocoa -struct PreferencesDecodable: Decodable { - let maxScreenUsage: CGFloat? - let windowPadding: CGFloat? - let cellPadding: CGFloat? - let cellBorderWidth: CGFloat? - let maxThumbnailsPerRow: CGFloat? - let thumbnailMaxWidth: CGFloat? - let thumbnailMaxHeight: CGFloat? - let iconSize: CGFloat? - let fontHeight: CGFloat? - let interItemPadding: CGFloat? - let tabKey: UInt16? - let metaKey: UInt16? - let metaModifierFlagInt: UInt? - let highlightColorString : String? - let thumbnailQuality : UInt? - let windowDisplayDelay: Int? -} +let defaults = [ + "maxScreenUsage": "0.8", + "windowPadding": "20", + "cellPadding": "6", + "cellBorderWidth": "2", + "maxThumbnailsPerRow": "4", + "thumbnailMaxWidth": "0", + "thumbnailMaxHeight": "0", + "iconSize": "32", + "fontHeight": "15", + "interItemPadding": "4", + "tabKey": "48", + "metaKey": "59", + "metaModifierFlag": "262144", + "highlightColor": "white", + "thumbnailQuality": "4", + "windowDisplayDelay": "200", +] -func fileLines(_ url: URL) throws -> Int { - try String(contentsOf: url, encoding: .utf8).split(separator: "\n").count -} +class Preferences { + static var rawValues = [String: String]() + static var maxScreenUsage: CGFloat = 0 + static var windowPadding: CGFloat = 0 + static var cellPadding: CGFloat = 0 + static var cellBorderWidth: CGFloat = 0 + static var maxThumbnailsPerRow: CGFloat = 0 + static var thumbnailMaxWidth: CGFloat = 0 + static var thumbnailMaxHeight: CGFloat = 0 + static var iconSize: CGFloat = 0 + static var fontHeight: CGFloat = 0 + static var interItemPadding: CGFloat = 0 + static var tabKey: UInt16 = 0 + static var metaKey: UInt16 = 0 + static var metaModifierFlag: NSEvent.ModifierFlags = .control + static var highlightColor: NSColor = .white + static var thumbnailQuality: NSImageInterpolation = .high + static var windowDisplayDelay: DispatchTimeInterval = .milliseconds(200) + static var font: NSFont = .systemFont(ofSize: 15) -class Preferences: Decodable { - private static let decoded: PreferencesDecodable? = decodeFromJson() + private static let defaultsFile = fileFromPreferencesFolder("alt-tab-macos-defaults.json") + private static let userFile = fileFromPreferencesFolder("alt-tab-macos.json") - private static func decodeFromJson() -> PreferencesDecodable? { + static func loadFromDiskAndUpdateValues() { do { - let defaultFile = Bundle.main.url(forResource: "preferences", withExtension: "json")! - let userFile = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask) - .first! - .appendingPathComponent("Preferences", isDirectory: true) - .appendingPathComponent("alt-tab-macos.json") - try handleNoFileOrOldFile(userFile: userFile, defaultFile: defaultFile) - return try JSONDecoder().decode(PreferencesDecodable.self, from: Data(contentsOf: userFile)) + rawValues = try loadFromDisk() + try rawValues.forEach { + try updateAndValidateValue($0.key, $0.value) + } } catch { - debugPrint("Error parsing preferences.json", error) + debugPrint("Error loading preferences from JSON file on disk", error) + NSApp.terminate(NSApplication.shared) + } + } + + static func updateAndValidateValue(_ valueName: String, _ value: String) throws { + switch valueName { + case "maxScreenUsage": + maxScreenUsage = try CGFloat(value).orThrow() + case "windowPadding": + windowPadding = try CGFloat(value).orThrow() + case "cellPadding": + cellPadding = try CGFloat(value).orThrow() + case "cellBorderWidth": + cellBorderWidth = try CGFloat(value).orThrow() + case "maxThumbnailsPerRow": + maxThumbnailsPerRow = try CGFloat(value).orThrow() + case "thumbnailMaxWidth": + thumbnailMaxWidth = try CGFloat(value).orThrow() + case "thumbnailMaxHeight": + thumbnailMaxHeight = try CGFloat(value).orThrow() + case "iconSize": + iconSize = try CGFloat(value).orThrow() + case "fontHeight": + fontHeight = try CGFloat(value).orThrow() + case "interItemPadding": + interItemPadding = try CGFloat(value).orThrow() + case "tabKey": + tabKey = try UInt16(value).orThrow() + case "metaKey": + metaKey = try UInt16(value).orThrow() + case "metaModifierFlag": + metaModifierFlag = NSEvent.ModifierFlags(rawValue: try UInt(value).orThrow()) + case "highlightColor": + highlightColor = NSColor.white + case "thumbnailQuality": + thumbnailQuality = NSImageInterpolation(rawValue: try UInt(value).orThrow())! + case "windowDisplayDelay": + windowDisplayDelay = DispatchTimeInterval.milliseconds(try Int(value).orThrow()) + case "font": + font = NSFont.systemFont(ofSize: fontHeight) + default: + throw "Tried to update an unknown preference" + } + rawValues[valueName] = value + } + + static func saveRawToDisk() throws { + try saveToDisk(rawValues, userFile) + } + + private static func fileLines(_ url: URL) throws -> Int { + try String(contentsOf: url, encoding: .utf8).split(separator: "\n").count + } + + private static func loadFromDisk() throws -> [String: String] { + try handleNoFileOrOldFile(userFile) + return try JSONDecoder().decode([String: String].self, from: Data(contentsOf: userFile)) + } + + private static func handleNoFileOrOldFile(_ userFile: URL) throws { + if !FileManager.default.fileExists(atPath: defaultsFile.path) { + try saveDefaultsToDisk() + } + if !FileManager.default.fileExists(atPath: userFile.path) { + try FileManager.default.copyItem(at: defaultsFile, to: userFile) + } else { + if try fileLines(defaultsFile) > fileLines(userFile) { + // TODO: handle upgrades in a smarter way (e.g. merge files) + try FileManager.default.removeItem(at: userFile) + try FileManager.default.copyItem(at: defaultsFile, to: userFile) + } + } + } + + private static func saveDefaultsToDisk() throws { + try saveToDisk(defaults, defaultsFile) + } + + private static func saveToDisk(_ values: [String: String], _ path: URL) throws { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + try encoder + .encode(values) + .write(to: path) + } + + private static func fileFromPreferencesFolder(_ fileName: String) -> URL { + FileManager.default + .urls(for: .libraryDirectory, in: .userDomainMask) + .first! + .appendingPathComponent("Preferences", isDirectory: true) + .appendingPathComponent(fileName) + } +} + +// add CGFloat constructor from String +extension CGFloat { + init?(_ string: String) { + guard let number = NumberFormatter().number(from: string) else { return nil } + self.init(number.floatValue) } - - private static func handleNoFileOrOldFile(userFile: URL, defaultFile: URL) throws { - if !FileManager.default.fileExists(atPath: userFile.path) { - try FileManager.default.copyItem(at: defaultFile, to: userFile) - } else if try fileLines(defaultFile) > fileLines(userFile) { - // TODO: handle upgrades in a smarter way (e.g. merge files) - try FileManager.default.removeItem(at: userFile) - try FileManager.default.copyItem(at: defaultFile, to: userFile) - } - } - - // maximum width/height of the main window, in percentage of screen width/height - static let maxScreenUsage = decoded?.maxScreenUsage ?? 0.8 - // padding in the main window - static let windowPadding = decoded?.windowPadding ?? 20 - // padding in each cell - static let cellPadding = decoded?.cellPadding ?? 6 - // border width of each cell - static let cellBorderWidth = decoded?.cellBorderWidth ?? 2 - // maximum number of thumbnails on each row - static let maxThumbnailsPerRow = decoded?.maxThumbnailsPerRow ?? 4 - // maximum width of each thumbnail - static var thumbnailMaxWidth = decoded?.thumbnailMaxWidth ?? 0 - // maximum height of eacg thumbnail - static var thumbnailMaxHeight = decoded?.thumbnailMaxHeight ?? 0 - // width/height for each cell app icon - static let iconSize = decoded?.iconSize ?? 32 - // font height for each cell title - static let fontHeight = decoded?.fontHeight ?? 15 - // padding between cells within the main window - static let interItemPadding = decoded?.interItemPadding ?? 4 - // NSEvent.keyCode (e.g. leftCommand=55, leftOption=58, leftControl=59, rightCommand=54, rightOption=61) - static let tabKey = decoded?.tabKey ?? 48 - // NSEvent.keyCode (e.g. leftCommand=55, leftOption=58, leftControl=59, rightCommand=54, rightOption=61) - static let metaKey = decoded?.metaKey ?? 59 - // NSImageInterpolation (e.g. none=1, low=2, medium=4, high=3) - static let thumbnailQuality = NSImageInterpolation.init(rawValue: decoded?.thumbnailQuality ?? 3) ?? .high - // NSEvent.ModifierFlags (e.g. control=262144, command=1048576, shift=131072, option=524288) - static let metaModifierFlag = NSEvent.ModifierFlags(rawValue: decoded?.metaModifierFlagInt ?? 262144) - // delay (in ms) before the window is displayed after the shortcut is pressed - static let windowDisplayDelay = DispatchTimeInterval.milliseconds(decoded?.windowDisplayDelay ?? 200) - // color for the currently selected cell - static let highlightColor = NSColor.white - // derived properties - static let font: NSFont = .systemFont(ofSize: Preferences.fontHeight) } +// add throw-on-nil method on Optional +extension Optional { + func orThrow() throws -> Wrapped { + switch self { + case .some(let value): + return value + case .none: + throw "Optional contained nil" + } + } +} + +// allow String to be treated as Error (e.g. throw "explanation") +extension String: Error { +} diff --git a/alt-tab-macos/logic/Screen.swift b/alt-tab-macos/logic/Screen.swift new file mode 100644 index 00000000..090465a9 --- /dev/null +++ b/alt-tab-macos/logic/Screen.swift @@ -0,0 +1,42 @@ +import Foundation +import Cocoa + +class Screen { + static func listenToChanges() { + NotificationCenter.default.addObserver( + forName: NSApplication.didChangeScreenParametersNotification, + object: NSApplication.shared, + queue: OperationQueue.main + ) { notification -> Void in + updateThumbnailMaxSize() + } + } + + static func updateThumbnailMaxSize() -> Void { + if Preferences.thumbnailMaxWidth == 0 && Preferences.thumbnailMaxHeight == 0 { + do { + let main = NSScreen.main!.frame + try Preferences.updateAndValidateValue("thumbnailMaxWidth", + String((NSScreen.main!.frame.size.width * Preferences.maxScreenUsage - Preferences.windowPadding * 2) / Preferences.maxThumbnailsPerRow - Preferences.interItemPadding).orThrow()) + try Preferences.updateAndValidateValue("thumbnailMaxHeight", + String(Preferences.thumbnailMaxWidth * (main.height / main.width)).orThrow()) + try Preferences.saveRawToDisk() + } catch { + debugPrint("Error updating preferences based on screen size", error) + NSApp.terminate(NSApplication.shared) + } + } + } +} + +// add String constructor from CGFloat that round up at 1 decimal +extension String { + init?(_ cgFloat: CGFloat) { + let formatter = NumberFormatter() + formatter.maximumFractionDigits = 1 + guard let string = formatter.string(from: cgFloat as NSNumber) else { + return nil + } + self.init(string) + } +} diff --git a/alt-tab-macos/logic/SystemPermissions.swift b/alt-tab-macos/logic/SystemPermissions.swift new file mode 100644 index 00000000..8c5fb06d --- /dev/null +++ b/alt-tab-macos/logic/SystemPermissions.swift @@ -0,0 +1,20 @@ +import Foundation +import Cocoa + +class SystemPermissions { + static func ensureAccessibilityCheckboxIsChecked() { + if !AXIsProcessTrustedWithOptions(["AXTrustedCheckOptionPrompt": true] as CFDictionary) { + debugPrint("Before using this app, you need to give permission in System Preferences > Security & Privacy > Privacy > Accessibility.\nPlease authorize and re-launch.\nSee https://help.rescuetime.com/article/59-how-do-i-enable-accessibility-permissions-on-mac-osx") + NSApp.terminate(self) + } + } + + static func ensureScreenRecordingCheckboxIsChecked() { + let firstWindow = cgWindows()[0] + let windowImage = CGWindowListCreateImage(.null, .optionIncludingWindow, firstWindow[kCGWindowNumber] as! CGWindowID, [.boundsIgnoreFraming, .bestResolution]) + if windowImage == nil { + debugPrint("Before using this app, you need to give permission in System Preferences > Security & Privacy > Privacy > Screen Recording.\nPlease authorize and re-launch.\nSee https://dropshare.zendesk.com/hc/en-us/articles/360033453434-Enabling-Screen-Recording-Permission-on-macOS-Catalina-10-15-") + NSApp.terminate(self) + } + } +} diff --git a/alt-tab-macos/resources/preferences.json b/alt-tab-macos/resources/preferences.json deleted file mode 100644 index 2998345b..00000000 --- a/alt-tab-macos/resources/preferences.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "maxScreenUsage": 0.8, - "windowPadding": 20, - "cellPadding": 6, - "cellBorderWidth": 2, - "maxThumbnailsPerRow": 4, - "thumbnailMaxWidth": 0, - "thumbnailMaxHeight": 0, - "iconSize": 32, - "fontHeight": 15, - "interItemPadding": 4, - "tabKey": 48, - "metaKey": 59, - "metaModifierFlagInt": 262144, - "highlightColorString": "white", - "thumbnailQuality": 4, - "windowDisplayDelay": 200 -} diff --git a/alt-tab-macos/ui/Application.swift b/alt-tab-macos/ui/Application.swift index 6b31debc..623f7beb 100644 --- a/alt-tab-macos/ui/Application.swift +++ b/alt-tab-macos/ui/Application.swift @@ -1,19 +1,12 @@ import Foundation import Cocoa -let cellId = NSUserInterfaceItemIdentifier("Cell") - -func updateThumbnailMaxSize() -> Void { - let main = NSScreen.main!.frame - Preferences.thumbnailMaxWidth = (NSScreen.main!.frame.size.width * Preferences.maxScreenUsage - Preferences.windowPadding * 2) / Preferences.maxThumbnailsPerRow - Preferences.interItemPadding - Preferences.thumbnailMaxHeight = Preferences.thumbnailMaxWidth * (main.height / main.width) -} - -class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate, NSCollectionViewDataSource, NSCollectionViewDelegate, NSCollectionViewDelegateFlowLayout { - var item: NSStatusItem? +class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate { + static let name = "AltTab" + var statusItem: NSStatusItem? var backgroundView: NSVisualEffectView? - var collectionView_: NSCollectionView? - var window: NSPanel? + var thumbnailsPanel: ThumbnailsPanel? + var preferencesPanel: PreferencesPanel? var selectedOpenWindow: Int = 0 var numberOfColumns: Int = 0 var openWindows: [OpenWindow] = [] @@ -31,94 +24,25 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate, NSCol } func applicationDidFinishLaunching(_ aNotification: Notification) { - ensureScreenRecordingCheckboxIsChecked() - ensureAccessibilityCheckboxIsChecked() - updateThumbnailMaxSize() - makeStatusBarItem() - makeWindow() - makeCollectionView() - backgroundView = NSVisualEffectView() - backgroundView!.blendingMode = .behindWindow - backgroundView!.material = .dark - backgroundView!.state = .active - backgroundView!.addSubview(collectionView_!) - window!.contentView = backgroundView - listenToGlobalKeyboardEvents(self) - listenToScreenChanges() + SystemPermissions.ensureScreenRecordingCheckboxIsChecked() + SystemPermissions.ensureAccessibilityCheckboxIsChecked() + Preferences.loadFromDiskAndUpdateValues() + Screen.updateThumbnailMaxSize() + statusItem = StatusItem.make(self) + thumbnailsPanel = ThumbnailsPanel(self) + preferencesPanel = PreferencesPanel() + Keyboard.listenToGlobalEvents(self) + Screen.listenToChanges() + } + + @objc func showCenteredPreferencesPanel() { + showCenteredPanel(preferencesPanel!) + } + + func showCenteredPanel(_ panel: NSPanel) { + panel.center() + panel.makeKeyAndOrderFront(nil) Application.shared.activate(ignoringOtherApps: true) - Application.shared.runModal(for: window!) - Application.shared.hide(nil) - } - - func listenToScreenChanges() { - NotificationCenter.default.addObserver( - forName: NSApplication.didChangeScreenParametersNotification, - object: NSApplication.shared, - queue: OperationQueue.main - ) { notification -> Void in - updateThumbnailMaxSize() - } - } - - func applicationDidBecomeActive(_ notification: Notification) { - if !appIsBeingUsed { - focusSelectedWindow(currentlySelectedWindow()) - } - } - - func makeCollectionView() { - let layout = CollectionViewCenterFlowLayout() - layout.estimatedItemSize = NSSize(width: Preferences.thumbnailMaxWidth, height: Preferences.thumbnailMaxHeight) - layout.minimumInteritemSpacing = 5 - layout.minimumLineSpacing = 5 - collectionView_ = NSCollectionView() - collectionView_!.dataSource = self - collectionView_!.delegate = self - collectionView_!.collectionViewLayout = layout - collectionView_!.backgroundColors = [.clear] - collectionView_!.isSelectable = true - collectionView_!.allowsMultipleSelection = false - collectionView_!.register(Cell.self, forItemWithIdentifier: cellId) - } - - func makeWindow() { - window = NSPanel() - window!.styleMask = [.borderless] - window!.level = .floating - window!.animationBehavior = NSWindow.AnimationBehavior.none - window!.backgroundColor = .clear - window!.setIsVisible(true) - window!.delegate = self - } - - func makeStatusBarItem() { - item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) - item!.button!.title = "AltTab" - item!.menu = NSMenu() - item!.menu!.addItem( - withTitle: "Version #VERSION#", - action: nil, - keyEquivalent: "") - item!.menu!.addItem( - withTitle: "Quit \(ProcessInfo.processInfo.processName)", - action: #selector(NSApplication.terminate(_:)), - keyEquivalent: "q") - } - - func ensureAccessibilityCheckboxIsChecked() { - if !AXIsProcessTrustedWithOptions(["AXTrustedCheckOptionPrompt": true] as CFDictionary) { - debugPrint("Before using this app, you need to give permission in System Preferences > Security & Privacy > Privacy > Accessibility.\nPlease authorize and re-launch.\nSee https://help.rescuetime.com/article/59-how-do-i-enable-accessibility-permissions-on-mac-osx") - NSApp.terminate(self) - } - } - - func ensureScreenRecordingCheckboxIsChecked() { - let firstWindow = cgWindows()[0] - let windowImage = CGWindowListCreateImage(.null, .optionIncludingWindow, firstWindow[kCGWindowNumber] as! CGWindowID, [.boundsIgnoreFraming, .bestResolution]) - if windowImage == nil { - debugPrint("Before using this app, you need to give permission in System Preferences > Security & Privacy > Privacy > Screen Recording.\nPlease authorize and re-launch.\nSee https://dropshare.zendesk.com/hc/en-us/articles/360033453434-Enabling-Screen-Recording-Permission-on-macOS-Catalina-10-15-") - NSApp.terminate(self) - } } func computeOpenWindows() { @@ -141,44 +65,6 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate, NSCol } } - func highlightThumbnail(_ step: Int) { - collectionView_!.selectItems(at: [IndexPath(item: selectedOpenWindow, section: 0)], scrollPosition: .top) - collectionView_!.deselectItems(at: [IndexPath(item: cellWithStep(-step), section: 0)]) - } - - func computeThumbnails() { - let maxSize = NSSize(width: NSScreen.main!.frame.width * Preferences.maxScreenUsage, height: NSScreen.main!.frame.height * Preferences.maxScreenUsage) - collectionView_!.setFrameSize(maxSize) - collectionView_!.collectionViewLayout!.invalidateLayout() - collectionView_!.reloadData() - collectionView_!.layoutSubtreeIfNeeded() - window!.setContentSize(NSSize(width: collectionView_!.frame.size.width + Preferences.windowPadding * 2, height: collectionView_!.frame.size.height + Preferences.windowPadding * 2)) - backgroundView!.setFrameSize(window!.frame.size) - collectionView_!.setFrameOrigin(NSPoint(x: Preferences.windowPadding, y: Preferences.windowPadding)) - window!.center() - } - - func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { -// debugPrint("collectionView: count items", openWindows.count) - return openWindows.count - } - - func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { -// debugPrint("collectionView: make item", indexPath.item) - let item = collectionView.makeItem(withIdentifier: cellId, for: indexPath) as! Cell - item.updateWithNewContent(openWindows[indexPath.item], self.focusSelectedWindow) - return item - } - - func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { -// debugPrint("collectionView: item size") - if indexPath.item < openWindows.count { - let (width, height) = computeDownscaledSize(openWindows[indexPath.item].thumbnail) - return NSSize(width: CGFloat(width) + Preferences.cellPadding * 2, height: CGFloat(height) + max(Preferences.fontHeight, Preferences.iconSize) + Preferences.interItemPadding + Preferences.cellPadding * 2) - } - return .zero - } - func cellWithStep(_ step: Int) -> Int { selectedOpenWindow + step < 0 ? openWindows.count - 1 : (selectedOpenWindow + step) % openWindows.count } @@ -191,14 +77,14 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate, NSCol isFirstSummon = false let workItem = DispatchWorkItem { self.computeOpenWindows() - self.computeThumbnails() - self.highlightThumbnail(step) - Application.shared.unhideWithoutActivation() + self.thumbnailsPanel!.computeThumbnails() + self.thumbnailsPanel!.highlightThumbnail(step) + self.showCenteredPanel(self.thumbnailsPanel!) } workItems.append(workItem) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Preferences.windowDisplayDelay, execute: workItem) } else { - highlightThumbnail(step) + self.thumbnailsPanel!.highlightThumbnail(step) } } } @@ -211,7 +97,7 @@ class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate, NSCol workItems.forEach({ $0.cancel() }) workItems.removeAll() window?.focus() - Application.shared.hide(nil) + thumbnailsPanel!.orderOut(nil) appIsBeingUsed = false isFirstSummon = true } diff --git a/alt-tab-macos/ui/Cell.swift b/alt-tab-macos/ui/Cell.swift index 46fc42a8..49fda856 100644 --- a/alt-tab-macos/ui/Cell.swift +++ b/alt-tab-macos/ui/Cell.swift @@ -28,7 +28,7 @@ class Cell: NSCollectionViewItem { textContainer.maximumNumberOfLines = 1 textContainer.lineFragmentPadding = 0 layoutManager.addTextContainer(textContainer) - text = NSTextView.init(frame: NSRect.zero, textContainer: textContainer) + text = NSTextView(frame: NSRect.zero, textContainer: textContainer) text.drawsBackground = true text.backgroundColor = .clear text.isSelectable = false diff --git a/alt-tab-macos/ui/PreferencesPanel.swift b/alt-tab-macos/ui/PreferencesPanel.swift new file mode 100644 index 00000000..fafe65f5 --- /dev/null +++ b/alt-tab-macos/ui/PreferencesPanel.swift @@ -0,0 +1,95 @@ +import Cocoa + +class PreferencesPanel: NSPanel, NSTextViewDelegate { + var inputsMap = [NSTextView: String]() + + override init(contentRect: NSRect, styleMask style: StyleMask, backing backingStoreType: BackingStoreType, defer flag: Bool) { + super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag) + let rows = makeLabelsAndInputs() + let gridView = makeGridView(rows) + makeAndAddWarningLabel(gridView) + title = Application.name + " Preferences" + hidesOnDeactivate = false + contentView = gridView + } + + private func makeLabelsAndInputs() -> [[NSTextView]] { + var rows = [[NSTextView]]() + [ + ("maxScreenUsage", "Maximum size of the main window, in percentage of screen size"), + ("windowPadding", "Padding in the main window"), + ("cellPadding", "Padding in each cell"), + ("cellBorderWidth", "Border width of each cell"), + ("maxThumbnailsPerRow", "Maximum number of thumbnails on each row"), + ("thumbnailMaxWidth", "Maximum width of each thumbnail"), + ("thumbnailMaxHeight", "Maximum height of each thumbnail"), + ("iconSize", "Width/height for each cell app icon"), + ("fontHeight", "Font height for each cell title"), + ("interItemPadding", "Padding between cells within the main window"), + ("tabKey", "Tab key (NSEvent.keyCode)"), + ("metaKey", "Meta key (NSEvent.keyCode)"), + ("metaModifierFlag", "Meta key (NSEvent.ModifierFlags)"), +// ("highlightColor", "Color for the currently selected cell"), + ("thumbnailQuality", "NSImageInterpolation (e.g. none=1, low=2, medium=4, high=3)"), + ("windowDisplayDelay", "Delay in ms before the window is displayed after pressing the shortcut"), + ].forEach { + let p = makePreference($1, Preferences.rawValues[$0]!) + rows.append(p) + inputsMap[p[1]] = $0 + } + return rows + } + + private func makeGridView(_ rows: [[NSTextView]]) -> NSGridView { + let gridView = NSGridView(views: rows) + gridView.setContentHuggingPriority(.defaultLow, for: .horizontal) + gridView.setContentHuggingPriority(.defaultLow, for: .vertical) + gridView.widthAnchor.constraint(greaterThanOrEqualToConstant: 580).isActive = true + return gridView + } + + private func makeAndAddWarningLabel(_ gridView: NSGridView) { + let warningLabel = makeLabel("Some settings require restarting the app to apply") + warningLabel.textColor = .systemRed + warningLabel.alignment = .center + gridView.addRow(with: [warningLabel, NSGridCell.emptyContentView]) + gridView.mergeCells(inHorizontalRange: NSRange(location: 0, length: 2), verticalRange: NSRange(location: gridView.numberOfRows - 1, length: 1)) + } + + private func makePreference(_ labelText: String, _ initialValue: String) -> [NSTextView] { + let label = makeLabel(labelText) + label.alignment = .right + + let input = NSTextView() + input.delegate = self + input.font = Preferences.font + input.string = initialValue + input.widthAnchor.constraint(equalToConstant: 65).isActive = true + return [label, input] + } + + private func makeLabel(_ text: String) -> NSTextView { + let label = NSTextView() + label.drawsBackground = true + label.backgroundColor = .clear + label.isSelectable = false + label.isEditable = false + label.font = Preferences.font + label.string = text + label.enabledTextCheckingTypes = 0 + label.heightAnchor.constraint(greaterThanOrEqualToConstant: Preferences.fontHeight + Preferences.interItemPadding).isActive = true + return label + } + + func textDidEndEditing(_ notification: Notification) { + if let textView = notification.object as? NSTextView { + let key = inputsMap[textView]! + do { + try Preferences.updateAndValidateValue(key, textView.string) + try Preferences.saveRawToDisk() + } catch { + textView.string = Preferences.rawValues[key]! + } + } + } +} diff --git a/alt-tab-macos/ui/StatusItem.swift b/alt-tab-macos/ui/StatusItem.swift new file mode 100644 index 00000000..abd8d70a --- /dev/null +++ b/alt-tab-macos/ui/StatusItem.swift @@ -0,0 +1,22 @@ +import Cocoa + +class StatusItem { + static func make(_ application: Application) -> NSStatusItem { + let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + item.button!.title = Application.name + item.menu = NSMenu() + item.menu!.addItem( + withTitle: "Version #VERSION#", + action: nil, + keyEquivalent: "") + item.menu!.addItem( + withTitle: "Preferences…", + action: #selector(application.showCenteredPreferencesPanel), + keyEquivalent: ",") + item.menu!.addItem( + withTitle: "Quit \(ProcessInfo.processInfo.processName)", + action: #selector(NSApplication.terminate(_:)), + keyEquivalent: "q") + return item + } +} diff --git a/alt-tab-macos/ui/ThumbnailsPanel.swift b/alt-tab-macos/ui/ThumbnailsPanel.swift new file mode 100644 index 00000000..33af7cc7 --- /dev/null +++ b/alt-tab-macos/ui/ThumbnailsPanel.swift @@ -0,0 +1,89 @@ +import Cocoa + +class ThumbnailsPanel: NSPanel, NSCollectionViewDataSource, NSCollectionViewDelegate, NSCollectionViewDelegateFlowLayout { + var backgroundView: NSVisualEffectView? + var collectionView_: NSCollectionView? + var application: Application? + let cellId = NSUserInterfaceItemIdentifier("Cell") + + override init(contentRect: NSRect, styleMask style: StyleMask, backing backingStoreType: BackingStoreType, defer flag: Bool) { + super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag) + } + + convenience init(_ application: Application) { + self.init() + self.application = application + styleMask = [.borderless] + isFloatingPanel = true + animationBehavior = NSWindow.AnimationBehavior.none + backgroundColor = .clear + hidesOnDeactivate = false + makeThumbnailsPanelBackgroundView() + contentView = backgroundView! + } + + private func makeThumbnailsPanelBackgroundView() { + backgroundView = NSVisualEffectView() + backgroundView!.blendingMode = .behindWindow + backgroundView!.material = .dark + backgroundView!.state = .active + makeCollectionView() + backgroundView!.addSubview(collectionView_!) + } + + func makeCollectionView() { + collectionView_ = NSCollectionView() + collectionView_!.dataSource = self + collectionView_!.delegate = self + collectionView_!.collectionViewLayout = makeLayout() + collectionView_!.backgroundColors = [.clear] + collectionView_!.isSelectable = true + collectionView_!.allowsMultipleSelection = false + collectionView_!.register(Cell.self, forItemWithIdentifier: cellId) + } + + func makeLayout() -> CollectionViewCenterFlowLayout { + let layout = CollectionViewCenterFlowLayout() + layout.estimatedItemSize = NSSize(width: Preferences.thumbnailMaxWidth, height: Preferences.thumbnailMaxHeight) + layout.minimumInteritemSpacing = 5 + layout.minimumLineSpacing = 5 + return layout + } + + func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { +// debugPrint("collectionView: count items", openWindows.count) + application!.openWindows.count + } + + func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { +// debugPrint("collectionView: make item", indexPath.item) + let item = collectionView.makeItem(withIdentifier: cellId, for: indexPath) as! Cell + item.updateWithNewContent(application!.openWindows[indexPath.item], application!.focusSelectedWindow) + return item + } + + func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { +// debugPrint("collectionView: item size") + if indexPath.item < application!.openWindows.count { + let (width, height) = computeDownscaledSize(application!.openWindows[indexPath.item].thumbnail) + return NSSize(width: CGFloat(width) + Preferences.cellPadding * 2, height: CGFloat(height) + max(Preferences.fontHeight, Preferences.iconSize) + Preferences.interItemPadding + Preferences.cellPadding * 2) + } + return .zero + } + + func highlightThumbnail(_ step: Int) { + collectionView_!.selectItems(at: [IndexPath(item: application!.selectedOpenWindow, section: 0)], scrollPosition: .top) + collectionView_!.deselectItems(at: [IndexPath(item: application!.cellWithStep(-step), section: 0)]) + } + + func computeThumbnails() { + let maxSize = NSSize(width: NSScreen.main!.frame.width * Preferences.maxScreenUsage, height: NSScreen.main!.frame.height * Preferences.maxScreenUsage) + collectionView_!.setFrameSize(maxSize) + collectionView_!.collectionViewLayout!.invalidateLayout() + collectionView_!.reloadData() + collectionView_!.layoutSubtreeIfNeeded() + setContentSize(NSSize(width: collectionView_!.frame.size.width + Preferences.windowPadding * 2, height: collectionView_!.frame.size.height + Preferences.windowPadding * 2)) + backgroundView!.setFrameSize(frame.size) + collectionView_!.setFrameOrigin(NSPoint(x: Preferences.windowPadding, y: Preferences.windowPadding)) + } +} \ No newline at end of file diff --git a/ci/set_version_in_app.sh b/ci/set_version_in_app.sh index ec81fad3..d296a3f0 100755 --- a/ci/set_version_in_app.sh +++ b/ci/set_version_in_app.sh @@ -4,6 +4,6 @@ set -ex version="$(cat VERSION.txt)" # set the version for the app menubar menu text -sed -i '' -e "s/#VERSION#/$version/" alt-tab-macos/ui/Application.swift +sed -i '' -e "s/#VERSION#/$version/" alt-tab-macos/ui/StatusItem.swift # set the version in the app meta-data for the AppStore and app "Get Info" panel sed -i '' -e "s/#VERSION#/$version/" alt-tab-macos/Info.plist