Remove license and update about window.
This commit is contained in:
@@ -9,12 +9,13 @@ import AppKit
|
|||||||
|
|
||||||
// MARK: - Constants
|
// MARK: - Constants
|
||||||
fileprivate enum AboutLinks {
|
fileprivate enum AboutLinks {
|
||||||
static let privacyLink = "https://cmdbar.app/#privacy-policy"
|
static let website = "https://cmdbar.app"
|
||||||
static let purchaseLink = "https://cmdbar.app/purchase"
|
static let documentation = "https://cmdbar.app/documentation"
|
||||||
|
static let privacy = "https://cmdbar.app/#privacy-policy"
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Strings {
|
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 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 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"
|
static let activate = "Activate"
|
||||||
@@ -30,7 +31,8 @@ class AboutViewController: NSViewController, NSTextFieldDelegate {
|
|||||||
private var appIconImage: NSImageView = {
|
private var appIconImage: NSImageView = {
|
||||||
//let image = NSImageView(image: NSApp.applicationIconImage)
|
//let image = NSImageView(image: NSApp.applicationIconImage)
|
||||||
let image = NSImageView()
|
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.imageScaling = .scaleAxesIndependently
|
||||||
image.translatesAutoresizingMaskIntoConstraints = false
|
image.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return image
|
return image
|
||||||
@@ -38,12 +40,16 @@ class AboutViewController: NSViewController, NSTextFieldDelegate {
|
|||||||
|
|
||||||
private var appNameLabel: NSTextField = {
|
private var appNameLabel: NSTextField = {
|
||||||
let textField = 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.isEditable = false
|
||||||
textField.isBezeled = false
|
textField.isBezeled = false
|
||||||
textField.drawsBackground = false
|
textField.drawsBackground = false
|
||||||
textField.alignment = .left
|
textField.alignment = .center
|
||||||
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .body).pointSize, weight: .bold)
|
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor
|
||||||
|
.preferredFontDescriptor(forTextStyle: .title1).pointSize,
|
||||||
|
weight: .bold)
|
||||||
textField.translatesAutoresizingMaskIntoConstraints = false
|
textField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return textField
|
return textField
|
||||||
}()
|
}()
|
||||||
@@ -54,9 +60,11 @@ class AboutViewController: NSViewController, NSTextFieldDelegate {
|
|||||||
textField.isEditable = false
|
textField.isEditable = false
|
||||||
textField.isBezeled = false
|
textField.isBezeled = false
|
||||||
textField.drawsBackground = false
|
textField.drawsBackground = false
|
||||||
textField.alignment = .left
|
textField.alignment = .center
|
||||||
textField.textColor = NSColor.systemGray
|
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
|
textField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return textField
|
return textField
|
||||||
}()
|
}()
|
||||||
@@ -69,52 +77,11 @@ class AboutViewController: NSViewController, NSTextFieldDelegate {
|
|||||||
textField.isEditable = false
|
textField.isEditable = false
|
||||||
textField.isBezeled = false
|
textField.isBezeled = false
|
||||||
textField.drawsBackground = false
|
textField.drawsBackground = false
|
||||||
textField.alignment = .left
|
textField.alignment = .center
|
||||||
textField.textColor = NSColor.systemGray
|
textField.textColor = NSColor.systemGray
|
||||||
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .subheadline).pointSize, weight: .regular)
|
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor
|
||||||
textField.translatesAutoresizingMaskIntoConstraints = false
|
.preferredFontDescriptor(forTextStyle: .subheadline).pointSize,
|
||||||
return textField
|
weight: .regular)
|
||||||
}()
|
|
||||||
|
|
||||||
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.translatesAutoresizingMaskIntoConstraints = false
|
textField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return textField
|
return textField
|
||||||
}()
|
}()
|
||||||
@@ -129,73 +96,46 @@ class AboutViewController: NSViewController, NSTextFieldDelegate {
|
|||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var closeButton: NSButton = {
|
private var documentationButton: NSButton = {
|
||||||
let button = NSButton()
|
let button = NSButton()
|
||||||
button.title = "Close"
|
button.title = "Docs"
|
||||||
button.sizeToFit()
|
button.sizeToFit()
|
||||||
button.bezelStyle = .rounded
|
button.bezelStyle = .rounded
|
||||||
button.action = #selector(close)
|
button.action = #selector(documentation)
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var purchaseButton: NSButton = {
|
private var websiteButton: NSButton = {
|
||||||
let button = NSButton()
|
let button = NSButton()
|
||||||
button.title = "Purchase"
|
button.title = "CmdBar.app"
|
||||||
button.sizeToFit()
|
button.sizeToFit()
|
||||||
button.bezelStyle = .rounded
|
button.bezelStyle = .rounded
|
||||||
button.action = #selector(purchase)
|
button.action = #selector(website)
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var activateButton: NSButton = {
|
private var buttonsContainer: NSLayoutGuide = {
|
||||||
let button = NSButton()
|
let container = NSLayoutGuide()
|
||||||
button.isEnabled = false
|
return container
|
||||||
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
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
// Input delegates
|
|
||||||
emailInput.delegate = self
|
|
||||||
licenseKeyInput.delegate = self
|
|
||||||
|
|
||||||
// Program info
|
// Program info
|
||||||
view.addSubview(appIconImage)
|
view.addSubview(appIconImage)
|
||||||
view.addSubview(appNameLabel)
|
view.addSubview(appNameLabel)
|
||||||
view.addSubview(versionLabel)
|
view.addSubview(versionLabel)
|
||||||
view.addSubview(copyrightLabel)
|
view.addSubview(copyrightLabel)
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
view.addLayoutGuide(buttonsContainer)
|
||||||
view.addSubview(privacyButton)
|
view.addSubview(privacyButton)
|
||||||
|
view.addSubview(documentationButton)
|
||||||
// Divider
|
view.addSubview(websiteButton)
|
||||||
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)
|
|
||||||
|
|
||||||
setupConstraints()
|
setupConstraints()
|
||||||
}
|
}
|
||||||
@@ -203,7 +143,6 @@ class AboutViewController: NSViewController, NSTextFieldDelegate {
|
|||||||
override func viewDidAppear() {
|
override func viewDidAppear() {
|
||||||
super.viewDidAppear()
|
super.viewDidAppear()
|
||||||
self.view.window?.center()
|
self.view.window?.center()
|
||||||
checkLicense()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func loadView() {
|
override func loadView() {
|
||||||
@@ -214,155 +153,88 @@ class AboutViewController: NSViewController, NSTextFieldDelegate {
|
|||||||
private func setupConstraints() {
|
private func setupConstraints() {
|
||||||
// View.
|
// View.
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
view.widthAnchor.constraint(equalToConstant: 600),
|
view.widthAnchor.constraint(equalToConstant: 300),
|
||||||
view.heightAnchor.constraint(lessThanOrEqualToConstant: 500),
|
view.heightAnchor.constraint(lessThanOrEqualToConstant: 500),
|
||||||
])
|
])
|
||||||
|
|
||||||
// App image.
|
// App image.
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
appIconImage.widthAnchor.constraint(equalToConstant: 60),
|
appIconImage.widthAnchor.constraint(equalToConstant: 100),
|
||||||
appIconImage.heightAnchor.constraint(equalTo: appIconImage.widthAnchor, multiplier: 1),
|
appIconImage.heightAnchor
|
||||||
|
.constraint(equalTo: appIconImage.widthAnchor,
|
||||||
appIconImage.topAnchor.constraint(equalTo: view.topAnchor, constant: ViewConstants.spacing20),
|
multiplier: 1),
|
||||||
appIconImage.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing20),
|
appIconImage.topAnchor
|
||||||
|
.constraint(equalTo: view.topAnchor,
|
||||||
|
constant: ViewConstants.spacing20),
|
||||||
|
appIconImage.centerXAnchor
|
||||||
|
.constraint(equalTo: view.centerXAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
appNameLabel.topAnchor.constraint(equalTo: appIconImage.topAnchor),
|
appNameLabel.topAnchor
|
||||||
appNameLabel.leadingAnchor.constraint(equalTo: appIconImage.trailingAnchor, constant: ViewConstants.spacing10),
|
.constraint(equalTo: appIconImage.bottomAnchor,
|
||||||
appNameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
|
constant: ViewConstants.spacing20),
|
||||||
|
appNameLabel.centerXAnchor
|
||||||
|
.constraint(equalTo: view.centerXAnchor),
|
||||||
|
|
||||||
versionLabel.topAnchor.constraint(equalTo: appNameLabel.bottomAnchor, constant: ViewConstants.spacing2),
|
versionLabel.topAnchor
|
||||||
versionLabel.leadingAnchor.constraint(equalTo: appNameLabel.leadingAnchor),
|
.constraint(equalTo: appNameLabel.bottomAnchor,
|
||||||
versionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
|
constant: ViewConstants.spacing2),
|
||||||
|
versionLabel.centerXAnchor
|
||||||
|
.constraint(equalTo: view.centerXAnchor),
|
||||||
|
|
||||||
copyrightLabel.topAnchor.constraint(equalTo: versionLabel.bottomAnchor, constant: ViewConstants.spacing2),
|
copyrightLabel.topAnchor
|
||||||
copyrightLabel.leadingAnchor.constraint(equalTo: appNameLabel.leadingAnchor),
|
.constraint(equalTo: versionLabel.bottomAnchor,
|
||||||
copyrightLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
|
constant: ViewConstants.spacing10),
|
||||||
|
copyrightLabel.centerXAnchor
|
||||||
privacyButton.topAnchor.constraint(equalTo: copyrightLabel.bottomAnchor, constant: ViewConstants.spacing10),
|
.constraint(equalTo: view.centerXAnchor),
|
||||||
privacyButton.leadingAnchor.constraint(equalTo: appNameLabel.leadingAnchor),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// Buttons
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
dividerBox.topAnchor.constraint(equalTo: privacyButton.bottomAnchor, constant: ViewConstants.spacing10),
|
buttonsContainer.topAnchor
|
||||||
dividerBox.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing20),
|
.constraint(equalTo: copyrightLabel.bottomAnchor,
|
||||||
dividerBox.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
|
constant: ViewConstants.spacing20),
|
||||||
])
|
buttonsContainer.bottomAnchor
|
||||||
|
.constraint(equalTo: view.bottomAnchor,
|
||||||
|
constant: -ViewConstants.spacing20),
|
||||||
|
buttonsContainer.centerXAnchor
|
||||||
|
.constraint(equalTo: view.centerXAnchor),
|
||||||
|
|
||||||
// License labels.
|
privacyButton.topAnchor
|
||||||
NSLayoutConstraint.activate([
|
.constraint(equalTo: buttonsContainer.topAnchor),
|
||||||
licenseLabel.topAnchor.constraint(equalTo: dividerBox.bottomAnchor, constant: ViewConstants.spacing10),
|
privacyButton.bottomAnchor
|
||||||
licenseLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing20),
|
.constraint(equalTo: buttonsContainer.bottomAnchor),
|
||||||
licenseLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
|
privacyButton.leadingAnchor
|
||||||
|
.constraint(equalTo: buttonsContainer.leadingAnchor),
|
||||||
|
|
||||||
licenseInfoLabel.topAnchor.constraint(equalTo: licenseLabel.bottomAnchor, constant: ViewConstants.spacing2),
|
documentationButton.firstBaselineAnchor
|
||||||
licenseInfoLabel.leadingAnchor.constraint(equalTo: licenseLabel.leadingAnchor),
|
.constraint(equalTo: privacyButton.firstBaselineAnchor),
|
||||||
licenseInfoLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
|
documentationButton.leadingAnchor
|
||||||
])
|
.constraint(equalTo: privacyButton.trailingAnchor,
|
||||||
|
constant: ViewConstants.spacing10),
|
||||||
|
|
||||||
// License inputs.
|
websiteButton.firstBaselineAnchor
|
||||||
NSLayoutConstraint.activate([
|
.constraint(equalTo: privacyButton.firstBaselineAnchor),
|
||||||
emailInput.topAnchor.constraint(equalTo: licenseInfoLabel.bottomAnchor, constant: ViewConstants.spacing10),
|
websiteButton.leadingAnchor
|
||||||
emailInput.leadingAnchor.constraint(equalTo: licenseInfoLabel.leadingAnchor),
|
.constraint(equalTo: documentationButton.trailingAnchor,
|
||||||
emailInput.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing20),
|
constant: ViewConstants.spacing10),
|
||||||
|
websiteButton.trailingAnchor
|
||||||
licenseKeyInput.topAnchor.constraint(equalTo: emailInput.bottomAnchor, constant: ViewConstants.spacing10),
|
.constraint(equalTo: buttonsContainer.trailingAnchor),
|
||||||
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),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Button Functions
|
// MARK: - Button Functions
|
||||||
@objc private func privacy() {
|
@objc private func privacy() {
|
||||||
NSWorkspace.shared.open(URL(string: AboutLinks.privacyLink)!)
|
NSWorkspace.shared.open(URL(string: AboutLinks.privacy)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func close() {
|
@objc private func documentation() {
|
||||||
view.window?.orderOut(nil)
|
NSWorkspace.shared.open(URL(string: AboutLinks.documentation)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func purchase() {
|
@objc private func website() {
|
||||||
guard let url = URL(string: AboutLinks.purchaseLink) else { return }
|
NSWorkspace.shared.open(URL(string: AboutLinks.website)!)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,28 +12,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
setupUpdateWindow()
|
setupUpdateWindow()
|
||||||
setupNotifications()
|
setupNotifications()
|
||||||
|
|
||||||
setupLicense()
|
|
||||||
|
|
||||||
CmdManager.standard.configure()
|
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) {
|
func applicationWillTerminate(_ notification: Notification) {
|
||||||
persistMenuBar(false)
|
persistMenuBar(false)
|
||||||
CmdManager.standard.clearItems()
|
CmdManager.standard.clearItems()
|
||||||
@@ -72,6 +53,16 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
// MARK: - Setup About Window
|
// MARK: - Setup About Window
|
||||||
private func setupAboutWindow() {
|
private func setupAboutWindow() {
|
||||||
aboutWindow = MenulessWindow(viewController: AboutViewController())
|
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
|
// MARK: - Show About Window
|
||||||
|
|||||||
@@ -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<Bool, String>) -> 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
src/Makefile
12
src/Makefile
@@ -7,12 +7,12 @@ XCODE_PATH = $(shell xcode-select --print-path)
|
|||||||
|
|
||||||
EXEC = CmdBar
|
EXEC = CmdBar
|
||||||
|
|
||||||
SRCMODULES = UpdateManager.swift LicenseManager.swift \
|
SRCMODULES = UpdateManager.swift AboutViewController.swift \
|
||||||
AboutViewController.swift AppDelegate.swift CBMenuBarItem.swift \
|
AppDelegate.swift CBMenuBarItem.swift CircularProgressView.swift \
|
||||||
CircularProgressView.swift CmdManager.swift CmdViewController.swift \
|
CmdManager.swift CmdViewController.swift CopyableTextView.swift \
|
||||||
CopyableTextView.swift EditableNSTextField.swift EventMonitor.swift \
|
EditableNSTextField.swift EventMonitor.swift Helpers.swift \
|
||||||
Helpers.swift MenulessWindow.swift PopoverPanel.swift \
|
MenulessWindow.swift PopoverPanel.swift QuietView.swift \
|
||||||
QuietView.swift UpdateViewController.swift main.swift
|
UpdateViewController.swift main.swift
|
||||||
ARMOBJMODULES = $(addprefix ./arm64/,$(SRCMODULES:.swift=.o))
|
ARMOBJMODULES = $(addprefix ./arm64/,$(SRCMODULES:.swift=.o))
|
||||||
X86OBJMODULES = $(addprefix ./x86_64/,$(SRCMODULES:.swift=.o))
|
X86OBJMODULES = $(addprefix ./x86_64/,$(SRCMODULES:.swift=.o))
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
|
import Carbon
|
||||||
|
|
||||||
class MenulessWindow: NSWindow {
|
class MenulessWindow: NSWindow {
|
||||||
init(viewController: NSViewController) {
|
init(viewController: NSViewController) {
|
||||||
@@ -18,14 +19,17 @@ class MenulessWindow: NSWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func performKeyEquivalent(with event: NSEvent) -> Bool {
|
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.type == NSEvent.EventType.keyDown {
|
||||||
if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey,
|
if modsContains(keys: OSCmd, in: modifiers) &&
|
||||||
event.charactersIgnoringModifiers! == "w" {
|
key == kVK_ANSI_W
|
||||||
|
{
|
||||||
performClose(nil)
|
performClose(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.performKeyEquivalent(with: event)
|
return super.performKeyEquivalent(with: event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user