From d31763e66f5a3997453612682eca98fb5b1d9817 Mon Sep 17 00:00:00 2001 From: igor Date: Wed, 15 Jan 2025 17:11:18 -0800 Subject: [PATCH] Remove license and update about window. --- src/AboutViewController.swift | 312 ++++++++++------------------------ src/AppDelegate.swift | 29 ++-- src/LicenseManager.swift | 129 -------------- src/Makefile | 12 +- src/MenulessWindow.swift | 10 +- 5 files changed, 115 insertions(+), 377 deletions(-) delete mode 100644 src/LicenseManager.swift diff --git a/src/AboutViewController.swift b/src/AboutViewController.swift index 30ecb83..982b29c 100644 --- a/src/AboutViewController.swift +++ b/src/AboutViewController.swift @@ -9,12 +9,13 @@ import AppKit // MARK: - Constants fileprivate enum AboutLinks { - static let privacyLink = "https://cmdbar.app/#privacy-policy" - static let purchaseLink = "https://cmdbar.app/purchase" + static let website = "https://cmdbar.app" + static let documentation = "https://cmdbar.app/documentation" + static let privacy = "https://cmdbar.app/#privacy-policy" } enum Strings { - static let copyright = "Copyright © 2024 GarikMI. All rights reserved." + static let copyright = "Copyright © 2024\nGarikMI. All rights reserved." static let evaluationTitle = "License - Evaluation" static let evaluationMessage = "You are currently using evaluation license. CmdBar will quit after 20 minutes. If you already own a license, enter it below or purchase a license." static let activate = "Activate" @@ -30,7 +31,8 @@ class AboutViewController: NSViewController, NSTextFieldDelegate { private var appIconImage: NSImageView = { //let image = NSImageView(image: NSApp.applicationIconImage) let image = NSImageView() - image.image = NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath) + image.image = NSWorkspace.shared + .icon(forFile: Bundle.main.bundlePath) image.imageScaling = .scaleAxesIndependently image.translatesAutoresizingMaskIntoConstraints = false return image @@ -38,12 +40,16 @@ class AboutViewController: NSViewController, NSTextFieldDelegate { private var appNameLabel: NSTextField = { let textField = NSTextField() - textField.stringValue = (Bundle.main.infoDictionary?["CFBundleName"] as? String) ?? "NOT FOUND" + textField.stringValue = + (Bundle.main.infoDictionary?["CFBundleName"] as? String) ?? + "NOT FOUND" textField.isEditable = false textField.isBezeled = false textField.drawsBackground = false - textField.alignment = .left - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .body).pointSize, weight: .bold) + textField.alignment = .center + textField.font = NSFont.systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .title1).pointSize, + weight: .bold) textField.translatesAutoresizingMaskIntoConstraints = false return textField }() @@ -54,9 +60,11 @@ class AboutViewController: NSViewController, NSTextFieldDelegate { textField.isEditable = false textField.isBezeled = false textField.drawsBackground = false - textField.alignment = .left + textField.alignment = .center textField.textColor = NSColor.systemGray - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .subheadline).pointSize, weight: .regular) + textField.font = NSFont.systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .subheadline).pointSize, + weight: .regular) textField.translatesAutoresizingMaskIntoConstraints = false return textField }() @@ -69,52 +77,11 @@ class AboutViewController: NSViewController, NSTextFieldDelegate { textField.isEditable = false textField.isBezeled = false textField.drawsBackground = false - textField.alignment = .left + textField.alignment = .center textField.textColor = NSColor.systemGray - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .subheadline).pointSize, weight: .regular) - textField.translatesAutoresizingMaskIntoConstraints = false - return textField - }() - - private var licenseLabel: NSTextField = { - let textField = NSTextField() - textField.stringValue = Strings.evaluationTitle - textField.isEditable = false - textField.isBezeled = false - textField.drawsBackground = false - textField.alignment = .left - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .title2).pointSize, weight: .bold) - textField.translatesAutoresizingMaskIntoConstraints = false - return textField - }() - - private var licenseInfoLabel: NSTextField = { - let textField = NSTextField() - textField.stringValue = Strings.evaluationMessage - textField.cell?.truncatesLastVisibleLine = true - textField.maximumNumberOfLines = 6 - textField.isEditable = false - textField.isBezeled = false - textField.drawsBackground = false - textField.alignment = .left - textField.textColor = NSColor.systemGray - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .subheadline).pointSize, weight: .regular) - textField.translatesAutoresizingMaskIntoConstraints = false - return textField - }() - - private var emailInput: EditableNSTextField = { - let textField = EditableNSTextField() - textField.placeholderString = "Email" - textField.bezelStyle = .roundedBezel - textField.translatesAutoresizingMaskIntoConstraints = false - return textField - }() - - private var licenseKeyInput: EditableNSTextField = { - let textField = EditableNSTextField() - textField.placeholderString = "License Key" - textField.bezelStyle = .roundedBezel + textField.font = NSFont.systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .subheadline).pointSize, + weight: .regular) textField.translatesAutoresizingMaskIntoConstraints = false return textField }() @@ -129,73 +96,46 @@ class AboutViewController: NSViewController, NSTextFieldDelegate { return button }() - private var closeButton: NSButton = { + private var documentationButton: NSButton = { let button = NSButton() - button.title = "Close" + button.title = "Docs" button.sizeToFit() button.bezelStyle = .rounded - button.action = #selector(close) + button.action = #selector(documentation) button.translatesAutoresizingMaskIntoConstraints = false return button }() - private var purchaseButton: NSButton = { + private var websiteButton: NSButton = { let button = NSButton() - button.title = "Purchase" + button.title = "CmdBar.app" button.sizeToFit() button.bezelStyle = .rounded - button.action = #selector(purchase) + button.action = #selector(website) button.translatesAutoresizingMaskIntoConstraints = false return button }() - private var activateButton: NSButton = { - let button = NSButton() - button.isEnabled = false - button.title = "Activate" - button.keyEquivalent = "\r" - button.sizeToFit() - button.bezelStyle = .rounded - button.action = #selector(activate) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private var dividerBox: NSBox = { - let box = NSBox() - box.boxType = .separator - box.translatesAutoresizingMaskIntoConstraints = false - return box + private var buttonsContainer: NSLayoutGuide = { + let container = NSLayoutGuide() + return container }() // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() - // Input delegates - emailInput.delegate = self - licenseKeyInput.delegate = self - // Program info view.addSubview(appIconImage) view.addSubview(appNameLabel) view.addSubview(versionLabel) view.addSubview(copyrightLabel) + + // Buttons + view.addLayoutGuide(buttonsContainer) view.addSubview(privacyButton) - - // Divider - view.addSubview(dividerBox) - - // License - view.addSubview(licenseLabel) - view.addSubview(licenseInfoLabel) - view.addSubview(emailInput) - view.addSubview(licenseKeyInput) - - // Action buttons - view.addSubview(closeButton) - view.addSubview(purchaseButton) - view.addSubview(activateButton) + view.addSubview(documentationButton) + view.addSubview(websiteButton) setupConstraints() } @@ -203,7 +143,6 @@ class AboutViewController: NSViewController, NSTextFieldDelegate { override func viewDidAppear() { super.viewDidAppear() self.view.window?.center() - checkLicense() } override func loadView() { @@ -214,155 +153,88 @@ class AboutViewController: NSViewController, NSTextFieldDelegate { private func setupConstraints() { // View. NSLayoutConstraint.activate([ - view.widthAnchor.constraint(equalToConstant: 600), + view.widthAnchor.constraint(equalToConstant: 300), view.heightAnchor.constraint(lessThanOrEqualToConstant: 500), ]) // App image. NSLayoutConstraint.activate([ - appIconImage.widthAnchor.constraint(equalToConstant: 60), - appIconImage.heightAnchor.constraint(equalTo: appIconImage.widthAnchor, multiplier: 1), - - appIconImage.topAnchor.constraint(equalTo: view.topAnchor, constant: ViewConstants.spacing20), - appIconImage.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing20), + appIconImage.widthAnchor.constraint(equalToConstant: 100), + appIconImage.heightAnchor + .constraint(equalTo: appIconImage.widthAnchor, + multiplier: 1), + appIconImage.topAnchor + .constraint(equalTo: view.topAnchor, + constant: ViewConstants.spacing20), + appIconImage.centerXAnchor + .constraint(equalTo: view.centerXAnchor), ]) // Title NSLayoutConstraint.activate([ - appNameLabel.topAnchor.constraint(equalTo: appIconImage.topAnchor), - appNameLabel.leadingAnchor.constraint(equalTo: appIconImage.trailingAnchor, constant: ViewConstants.spacing10), - appNameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20), + appNameLabel.topAnchor + .constraint(equalTo: appIconImage.bottomAnchor, + constant: ViewConstants.spacing20), + appNameLabel.centerXAnchor + .constraint(equalTo: view.centerXAnchor), - versionLabel.topAnchor.constraint(equalTo: appNameLabel.bottomAnchor, constant: ViewConstants.spacing2), - versionLabel.leadingAnchor.constraint(equalTo: appNameLabel.leadingAnchor), - versionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20), + versionLabel.topAnchor + .constraint(equalTo: appNameLabel.bottomAnchor, + constant: ViewConstants.spacing2), + versionLabel.centerXAnchor + .constraint(equalTo: view.centerXAnchor), - copyrightLabel.topAnchor.constraint(equalTo: versionLabel.bottomAnchor, constant: ViewConstants.spacing2), - copyrightLabel.leadingAnchor.constraint(equalTo: appNameLabel.leadingAnchor), - copyrightLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20), - - privacyButton.topAnchor.constraint(equalTo: copyrightLabel.bottomAnchor, constant: ViewConstants.spacing10), - privacyButton.leadingAnchor.constraint(equalTo: appNameLabel.leadingAnchor), + copyrightLabel.topAnchor + .constraint(equalTo: versionLabel.bottomAnchor, + constant: ViewConstants.spacing10), + copyrightLabel.centerXAnchor + .constraint(equalTo: view.centerXAnchor), ]) + // Buttons NSLayoutConstraint.activate([ - dividerBox.topAnchor.constraint(equalTo: privacyButton.bottomAnchor, constant: ViewConstants.spacing10), - dividerBox.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing20), - dividerBox.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20), - ]) + buttonsContainer.topAnchor + .constraint(equalTo: copyrightLabel.bottomAnchor, + constant: ViewConstants.spacing20), + buttonsContainer.bottomAnchor + .constraint(equalTo: view.bottomAnchor, + constant: -ViewConstants.spacing20), + buttonsContainer.centerXAnchor + .constraint(equalTo: view.centerXAnchor), - // License labels. - NSLayoutConstraint.activate([ - licenseLabel.topAnchor.constraint(equalTo: dividerBox.bottomAnchor, constant: ViewConstants.spacing10), - licenseLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing20), - licenseLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20), + privacyButton.topAnchor + .constraint(equalTo: buttonsContainer.topAnchor), + privacyButton.bottomAnchor + .constraint(equalTo: buttonsContainer.bottomAnchor), + privacyButton.leadingAnchor + .constraint(equalTo: buttonsContainer.leadingAnchor), - licenseInfoLabel.topAnchor.constraint(equalTo: licenseLabel.bottomAnchor, constant: ViewConstants.spacing2), - licenseInfoLabel.leadingAnchor.constraint(equalTo: licenseLabel.leadingAnchor), - licenseInfoLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20), - ]) + documentationButton.firstBaselineAnchor + .constraint(equalTo: privacyButton.firstBaselineAnchor), + documentationButton.leadingAnchor + .constraint(equalTo: privacyButton.trailingAnchor, + constant: ViewConstants.spacing10), - // License inputs. - NSLayoutConstraint.activate([ - emailInput.topAnchor.constraint(equalTo: licenseInfoLabel.bottomAnchor, constant: ViewConstants.spacing10), - emailInput.leadingAnchor.constraint(equalTo: licenseInfoLabel.leadingAnchor), - emailInput.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20), - - licenseKeyInput.topAnchor.constraint(equalTo: emailInput.bottomAnchor, constant: ViewConstants.spacing10), - licenseKeyInput.leadingAnchor.constraint(equalTo: licenseInfoLabel.leadingAnchor), - licenseKeyInput.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20), - ]) - - // License buttons. - NSLayoutConstraint.activate([ - activateButton.topAnchor.constraint(equalTo: licenseKeyInput.bottomAnchor, constant: ViewConstants.spacing20), - activateButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20), - activateButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -ViewConstants.spacing20), - - purchaseButton.trailingAnchor.constraint(equalTo: activateButton.leadingAnchor, constant: -ViewConstants.spacing10), - purchaseButton.lastBaselineAnchor.constraint(equalTo: activateButton.lastBaselineAnchor), - - closeButton.trailingAnchor.constraint(equalTo: purchaseButton.leadingAnchor, constant: -ViewConstants.spacing10), - closeButton.lastBaselineAnchor.constraint(equalTo: purchaseButton.lastBaselineAnchor), + websiteButton.firstBaselineAnchor + .constraint(equalTo: privacyButton.firstBaselineAnchor), + websiteButton.leadingAnchor + .constraint(equalTo: documentationButton.trailingAnchor, + constant: ViewConstants.spacing10), + websiteButton.trailingAnchor + .constraint(equalTo: buttonsContainer.trailingAnchor), ]) } // MARK: - Button Functions @objc private func privacy() { - NSWorkspace.shared.open(URL(string: AboutLinks.privacyLink)!) + NSWorkspace.shared.open(URL(string: AboutLinks.privacy)!) } - @objc private func close() { - view.window?.orderOut(nil) + @objc private func documentation() { + NSWorkspace.shared.open(URL(string: AboutLinks.documentation)!) } - @objc private func purchase() { - guard let url = URL(string: AboutLinks.purchaseLink) else { return } - NSWorkspace.shared.open(url) - } - - @objc private func activate() { - let email = emailInput.stringValue - let key = licenseKeyInput.stringValue - if email.isEmpty || key.isEmpty { return } - - activateButton.isEnabled = false - activateButton.title = Strings.activating - - // TODO: Notify the user if registration failed. - LicenseManager.standard.activateLicense(email: email, key: key) { [weak self] _ in - DispatchQueue.main.async { self?.checkLicense() } - } - } - - @objc private func deactivate() { - LicenseManager.standard.deactivateLicense() - checkLicense() - } - - private func checkLicense() { - if LicenseManager.standard.isRegistered() { - activateButton.isEnabled = true - activateButton.title = Strings.deactivate - activateButton.action = #selector(deactivate) - - licenseLabel.stringValue = Strings.proTitle - licenseInfoLabel.stringValue = Strings.proMessage - - emailInput.isEnabled = false - licenseKeyInput.isEnabled = false - - let license = LicenseManager.standard.getLicense() - emailInput.stringValue = license.email - licenseKeyInput.stringValue = - "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXX" + - license.key.dropFirst(license.key.count - 5) - } else { - activateButton.isEnabled = false - activateButton.title = Strings.activate - activateButton.action = #selector(activate) - - licenseLabel.stringValue = Strings.evaluationTitle - licenseInfoLabel.stringValue = Strings.evaluationMessage - - emailInput.isEnabled = true - licenseKeyInput.isEnabled = true - - emailInput.stringValue = "" - licenseKeyInput.stringValue = "" - - emailInput.becomeFirstResponder() - } - -#if(DEBUG) - emailInput.stringValue = "igorkolokolnikov@icloud.com" - licenseKeyInput.stringValue = "137A5010-A95A-4A8C-A037-6DF16B6CA666" -#endif - } -} - -extension AboutViewController { - func controlTextDidChange(_ obj: Notification) { - activateButton.isEnabled = !(emailInput.stringValue.isEmpty || licenseKeyInput.stringValue.isEmpty) + @objc private func website() { + NSWorkspace.shared.open(URL(string: AboutLinks.website)!) } } diff --git a/src/AppDelegate.swift b/src/AppDelegate.swift index 498c827..e38bf8c 100644 --- a/src/AppDelegate.swift +++ b/src/AppDelegate.swift @@ -11,28 +11,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { setupAboutWindow() setupUpdateWindow() setupNotifications() - - setupLicense() CmdManager.standard.configure() } - - private func setupLicense() { - let licenseManager = LicenseManager.standard - if !licenseManager.isRegistered() { - showAbout() - licenseManager.clearLicense() - licenseManager.startTimer() - } - } - - private func setupUpdateWindow() { - let controller = UpdateViewController() - controller.setUpdateDelegate() - - updateWindow = MenulessWindow(viewController: controller) - updateWindow.title = "Software Update" - } func applicationWillTerminate(_ notification: Notification) { persistMenuBar(false) @@ -72,6 +53,16 @@ class AppDelegate: NSObject, NSApplicationDelegate { // MARK: - Setup About Window private func setupAboutWindow() { aboutWindow = MenulessWindow(viewController: AboutViewController()) + aboutWindow.level = .statusBar + } + + private func setupUpdateWindow() { + let controller = UpdateViewController() + controller.setUpdateDelegate() + + updateWindow = MenulessWindow(viewController: controller) + updateWindow.title = "Software Update" + updateWindow.level = .statusBar } // MARK: - Show About Window diff --git a/src/LicenseManager.swift b/src/LicenseManager.swift deleted file mode 100644 index 40a5669..0000000 --- a/src/LicenseManager.swift +++ /dev/null @@ -1,129 +0,0 @@ -// -// LicenseManager.swift -// CmdBar -// -// Created by Igor Kolokolnikov on 8/26/23. -// - -import AppKit -import CryptoKit - -fileprivate struct LicenseModel: Codable { - let valid: Bool - let error: String - let email: String - let key: String -} - -// Converts string to a hash. -fileprivate func hashString(_ string: String) -> String { - return SHA256.hash(data: Data(string.utf8)).compactMap { String(format: "%02x", $0) }.joined() -} - -// Converts passphrase to a SymmetricKey. -fileprivate func passToKey(from passphrase: String) -> SymmetricKey { - let passphraseData = Data(passphrase.utf8) - let keyData = SHA256.hash(data: passphraseData).withUnsafeBytes { buffer in - return Data(buffer) - } - return SymmetricKey(data: keyData) -} - -final class LicenseManager { - static let standard = LicenseManager() - - private let passkey = passToKey(from: "B2B67FB0E22B814CE5A6E7DA729A068BF21C9490919EA0B42FCEB322F9C9BA82") - private let defaults = UserDefaults.standard - private var timer: DispatchSourceTimer? - private var quitAfterMinutes = 20.0 * 60.0 // quit after 20 minutes - - func isRegistered() -> Bool { - guard let email = defaults.object(forKey: "Email") as? String, - let key = defaults.object(forKey: "Key") as? String, - let licenseData = defaults.object(forKey: "License") as? Data, - let unsealedBox = try? AES.GCM.SealedBox(combined: licenseData), - let licenseHashData = try? AES.GCM.open(unsealedBox, using: passkey), - !email.isEmpty, - !key.isEmpty - else { return false } - - let currentHashData = Data(SHA256.hash(data: Data(hashString("\(email)\(key)\(NSUserName())").utf8)) - .compactMap { String(format: "%02x", $0) }.joined().utf8) - - return licenseHashData == currentHashData - } - - func activateLicense(email: String, key: String, completionHandler: @escaping (_ data: Resulting) -> Void) { - guard let url = URL(string: EndpointConstants.validateURL)?.appending(component: key) - else { completionHandler(.failure); return } - - URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in - guard - let data = data, - error == nil, - let response = response as? HTTPURLResponse, - (200...299).contains(response.statusCode) - else { completionHandler(.failure); return } - - guard let jsonResponse = try? JSONDecoder().decode(LicenseModel.self, from: data) - else { completionHandler(.failure); return } - - if jsonResponse.email == email && jsonResponse.key == key { - self?.saveLicense(with: email, and: key) - self?.stopTimer() - completionHandler(.success) - } else { - completionHandler(.failure) - } - }.resume() - } - - func deactivateLicense() { - clearLicense() - startTimer() - } - - func getLicense() -> (email: String, key: String) { - guard - let email = defaults.object(forKey: "Email") as? String, - let key = defaults.object(forKey: "Key") as? String - else { return ("", "") } - return (email, key) - } - - private func saveLicense(with email: String, and key: String) { - let defaults = UserDefaults.standard - - let licenseHashData = Data(SHA256.hash(data: Data(hashString("\(email)\(key)\(NSUserName())").utf8)) - .compactMap { String(format: "%02x", $0) }.joined().utf8) - - guard let sealedBoxData = try? AES.GCM.seal(licenseHashData, using: passkey).combined else { return } - defaults.set(email, forKey: "Email") - defaults.set(key, forKey: "Key") - defaults.set(sealedBoxData, forKey: "License") - } - - func clearLicense() { - defaults.set(nil, forKey: "Email") - defaults.set(nil, forKey: "Key") - defaults.set(nil, forKey: "License") - } - - // MARK: - Timer - func startTimer() { - timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global()) - timer?.schedule( - deadline: .now() + quitAfterMinutes, - repeating: DispatchTimeInterval.never - ) - timer?.setEventHandler { - DispatchQueue.main.async { NSApplication.shared.terminate(self) } - } - timer?.resume() - } - - func stopTimer() { - timer?.cancel() - timer = nil - } -} diff --git a/src/Makefile b/src/Makefile index 06ba4b1..a2585eb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,12 +7,12 @@ XCODE_PATH = $(shell xcode-select --print-path) EXEC = CmdBar -SRCMODULES = UpdateManager.swift LicenseManager.swift \ - AboutViewController.swift AppDelegate.swift CBMenuBarItem.swift \ - CircularProgressView.swift CmdManager.swift CmdViewController.swift \ - CopyableTextView.swift EditableNSTextField.swift EventMonitor.swift \ - Helpers.swift MenulessWindow.swift PopoverPanel.swift \ - QuietView.swift UpdateViewController.swift main.swift +SRCMODULES = UpdateManager.swift AboutViewController.swift \ + AppDelegate.swift CBMenuBarItem.swift CircularProgressView.swift \ + CmdManager.swift CmdViewController.swift CopyableTextView.swift \ + EditableNSTextField.swift EventMonitor.swift Helpers.swift \ + MenulessWindow.swift PopoverPanel.swift QuietView.swift \ + UpdateViewController.swift main.swift ARMOBJMODULES = $(addprefix ./arm64/,$(SRCMODULES:.swift=.o)) X86OBJMODULES = $(addprefix ./x86_64/,$(SRCMODULES:.swift=.o)) diff --git a/src/MenulessWindow.swift b/src/MenulessWindow.swift index e80a510..e06a1e5 100644 --- a/src/MenulessWindow.swift +++ b/src/MenulessWindow.swift @@ -1,4 +1,5 @@ import AppKit +import Carbon class MenulessWindow: NSWindow { init(viewController: NSViewController) { @@ -18,14 +19,17 @@ class MenulessWindow: NSWindow { } override func performKeyEquivalent(with event: NSEvent) -> Bool { - let commandKey = NSEvent.ModifierFlags.command.rawValue + let modifiers = event.modifierFlags.rawValue + let key = event.keyCode if event.type == NSEvent.EventType.keyDown { - if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey, - event.charactersIgnoringModifiers! == "w" { + if modsContains(keys: OSCmd, in: modifiers) && + key == kVK_ANSI_W + { performClose(nil) } } + return super.performKeyEquivalent(with: event) } }