No more keyboard shortcuts sounds in the popover.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,3 +2,7 @@
|
|||||||
arm64
|
arm64
|
||||||
x86_64
|
x86_64
|
||||||
build
|
build
|
||||||
|
CmdBar
|
||||||
|
CmdBar.app
|
||||||
|
cmdbar_updater
|
||||||
|
src/updater
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
// Created by Igor Kolokolnikov on 8/16/23.
|
// Created by Igor Kolokolnikov on 8/16/23.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import AppKit
|
||||||
|
|
||||||
// MARK: - Constants
|
// MARK: - Constants
|
||||||
fileprivate enum AboutLinks {
|
fileprivate enum AboutLinks {
|
||||||
@@ -24,12 +24,6 @@ enum Strings {
|
|||||||
static let activating = "Activating..."
|
static let activating = "Activating..."
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate enum ViewConstants {
|
|
||||||
static let spacing2: CGFloat = 2
|
|
||||||
static let spacing10: CGFloat = 10
|
|
||||||
static let spacing20: CGFloat = 20
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Controller
|
// MARK: - Controller
|
||||||
class AboutViewController: NSViewController, NSTextFieldDelegate {
|
class AboutViewController: NSViewController, NSTextFieldDelegate {
|
||||||
// MARK: - Views
|
// MARK: - Views
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Cocoa
|
import AppKit
|
||||||
import ServiceManagement
|
import ServiceManagement
|
||||||
|
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
// from the coverage of the license being applied to the rest of the code.
|
// from the coverage of the license being applied to the rest of the code.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import AppKit
|
||||||
import os
|
import os
|
||||||
|
|
||||||
final class CBMenuBarItem: NSObject, NSWindowDelegate {
|
final class CBMenuBarItem: NSObject, NSWindowDelegate {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict/>
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<false/>
|
||||||
|
<key>com.apple.security.get-task-allow</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import Foundation
|
import AppKit
|
||||||
import Cocoa
|
|
||||||
|
|
||||||
// MARK: - CmdFile
|
|
||||||
class CmdFile {
|
class CmdFile {
|
||||||
// MARK: - State
|
|
||||||
private var timer: DispatchSourceTimer?
|
private var timer: DispatchSourceTimer?
|
||||||
private(set) var lastExec = Int(Date().timeIntervalSince1970)
|
private(set) var lastExec = Int(Date().timeIntervalSince1970)
|
||||||
var untilNextExec: Int {
|
var untilNextExec: Int {
|
||||||
get {
|
get {
|
||||||
let res = reloadTime - (Int(Date().timeIntervalSince1970) - lastExec)
|
let res = reloadTime -
|
||||||
|
(Int(Date().timeIntervalSince1970) - lastExec)
|
||||||
if res <= 0 { return reloadTime }
|
if res <= 0 { return reloadTime }
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@@ -16,11 +14,10 @@ class CmdFile {
|
|||||||
|
|
||||||
private(set) var url: URL
|
private(set) var url: URL
|
||||||
private(set) var reloadTime: Int
|
private(set) var reloadTime: Int
|
||||||
private var statusItem: CBMenuBarItem = CBMenuBarItem()
|
private(set) var statusItem: CBMenuBarItem = CBMenuBarItem()
|
||||||
|
|
||||||
private var shouldWait = false
|
private var shouldWait = false
|
||||||
|
|
||||||
// MARK: - Initializers
|
|
||||||
init(url: URL, reloadTime: Int) {
|
init(url: URL, reloadTime: Int) {
|
||||||
self.url = url
|
self.url = url
|
||||||
self.reloadTime = reloadTime
|
self.reloadTime = reloadTime
|
||||||
@@ -37,7 +34,6 @@ class CmdFile {
|
|||||||
cancelTimer()
|
cancelTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Reload Widget
|
|
||||||
private func reloadWidget() {
|
private func reloadWidget() {
|
||||||
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
||||||
if let url = self?.url {
|
if let url = self?.url {
|
||||||
@@ -45,10 +41,13 @@ class CmdFile {
|
|||||||
let execResult = execute(atPath: url)
|
let execResult = execute(atPath: url)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if execResult.title.isEmpty {
|
if execResult.title.isEmpty {
|
||||||
self?.statusItem.setImage(title: "exclamationmark.triangle.fill", description: "Warning")
|
self?.statusItem.setImage(
|
||||||
|
title: "exclamationmark.triangle.fill",
|
||||||
|
description: "Warning")
|
||||||
self?.statusItem.setTitle("")
|
self?.statusItem.setTitle("")
|
||||||
} else {
|
} else {
|
||||||
self?.statusItem.setImage(title: nil, description: "")
|
self?.statusItem.setImage(title: nil,
|
||||||
|
description: "")
|
||||||
self?.statusItem.setTitle(execResult.0)
|
self?.statusItem.setTitle(execResult.0)
|
||||||
}
|
}
|
||||||
self?.statusItem.setContents(to: execResult.body)
|
self?.statusItem.setContents(to: execResult.body)
|
||||||
@@ -58,7 +57,6 @@ class CmdFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Timer
|
|
||||||
private func setupTimer() {
|
private func setupTimer() {
|
||||||
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
|
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
|
||||||
timer?.schedule(deadline: .now(), repeating: .seconds(reloadTime))
|
timer?.schedule(deadline: .now(), repeating: .seconds(reloadTime))
|
||||||
@@ -91,15 +89,12 @@ class CmdFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - CmdManager
|
|
||||||
class CmdManager {
|
class CmdManager {
|
||||||
//MARK: - State
|
|
||||||
static let standard = CmdManager()
|
static let standard = CmdManager()
|
||||||
|
|
||||||
var paths: [CmdFile] = []
|
var paths: [CmdFile] = []
|
||||||
private var defaultStatusItem: NSStatusItem?
|
private var defaultStatusItem: NSStatusItem?
|
||||||
|
|
||||||
// MARK: - Configure
|
|
||||||
func configure() {
|
func configure() {
|
||||||
getPaths()
|
getPaths()
|
||||||
if paths.isEmpty {
|
if paths.isEmpty {
|
||||||
@@ -111,23 +106,31 @@ class CmdManager {
|
|||||||
|
|
||||||
private func getPaths() {
|
private func getPaths() {
|
||||||
let manager = FileManager.default
|
let manager = FileManager.default
|
||||||
let path = manager.homeDirectoryForCurrentUser.appending(path: ".cmdbar")
|
let path = manager.homeDirectoryForCurrentUser
|
||||||
if let contents = try? manager.contentsOfDirectory(atPath: path.path()) {
|
.appending(path: ".cmdbar")
|
||||||
|
if let contents = try? manager
|
||||||
|
.contentsOfDirectory(atPath: path.path()) {
|
||||||
for content in contents {
|
for content in contents {
|
||||||
let filePath = path.appending(path: content)
|
let filePath = path.appending(path: content)
|
||||||
|
|
||||||
let domains = filePath.path().components(separatedBy: ".")
|
let domains = filePath.path().components(separatedBy: ".")
|
||||||
if (isValidTimeFormat(domains[domains.count - 1])) {
|
if (isValidTimeFormat(domains[domains.count - 1])) {
|
||||||
paths.append(CmdFile(url: filePath, reloadTime: intervalToSeconds(from: domains[domains.count - 1])))
|
paths.append(CmdFile(url: filePath,
|
||||||
} else if (domains.last == "sh" && isValidTimeFormat(domains[domains.count - 2])) {
|
reloadTime: intervalToSeconds(
|
||||||
paths.append(CmdFile(url: filePath, reloadTime: intervalToSeconds(from: domains[domains.count - 2])))
|
from: domains[domains.count - 1])))
|
||||||
|
} else if domains.last == "sh" &&
|
||||||
|
isValidTimeFormat(domains[domains.count - 2]) {
|
||||||
|
paths.append(CmdFile(url: filePath,
|
||||||
|
reloadTime: intervalToSeconds(
|
||||||
|
from: domains[domains.count - 2])))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupMenu() {
|
private func setupMenu() {
|
||||||
defaultStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
defaultStatusItem = NSStatusBar.system
|
||||||
|
.statusItem(withLength: NSStatusItem.variableLength)
|
||||||
if let btn = defaultStatusItem?.button {
|
if let btn = defaultStatusItem?.button {
|
||||||
let image = NSApp.applicationIconImage
|
let image = NSApp.applicationIconImage
|
||||||
image!.size = NSSize(width: 22, height: 22)
|
image!.size = NSSize(width: 22, height: 22)
|
||||||
@@ -135,13 +138,21 @@ class CmdManager {
|
|||||||
}
|
}
|
||||||
guard defaultStatusItem != nil else { return }
|
guard defaultStatusItem != nil else { return }
|
||||||
let menu = NSMenu()
|
let menu = NSMenu()
|
||||||
menu.addItem(NSMenuItem(title: "Reload", action: #selector(reloadWidgets), keyEquivalent: ""))
|
menu.addItem(NSMenuItem(title: "Reload",
|
||||||
|
action: #selector(reloadWidgets),
|
||||||
|
keyEquivalent: ""))
|
||||||
menu.addItem(NSMenuItem.separator())
|
menu.addItem(NSMenuItem.separator())
|
||||||
menu.addItem(NSMenuItem(title: "About", action: #selector(showAbout), keyEquivalent: ""))
|
menu.addItem(NSMenuItem(title: "About",
|
||||||
|
action: #selector(showAbout),
|
||||||
|
keyEquivalent: ""))
|
||||||
menu.addItem(NSMenuItem.separator())
|
menu.addItem(NSMenuItem.separator())
|
||||||
menu.addItem(NSMenuItem(title: "Check for Updates...", action: #selector(checkForUpdates), keyEquivalent: ""))
|
menu.addItem(NSMenuItem(title: "Check for Updates...",
|
||||||
|
action: #selector(checkForUpdates),
|
||||||
|
keyEquivalent: ""))
|
||||||
menu.addItem(NSMenuItem.separator())
|
menu.addItem(NSMenuItem.separator())
|
||||||
menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
|
menu.addItem(NSMenuItem(title: "Quit",
|
||||||
|
action: #selector(NSApplication.terminate(_:)),
|
||||||
|
keyEquivalent: "q"))
|
||||||
defaultStatusItem!.menu = menu
|
defaultStatusItem!.menu = menu
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +175,6 @@ class CmdManager {
|
|||||||
reloadItems()
|
reloadItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - NSMenu Functions
|
|
||||||
@objc private func showAbout() {
|
@objc private func showAbout() {
|
||||||
delegate.showAbout()
|
delegate.showAbout()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
import Cocoa
|
import AppKit
|
||||||
|
import Carbon
|
||||||
import ServiceManagement
|
import ServiceManagement
|
||||||
import OSLog
|
|
||||||
|
|
||||||
fileprivate enum Metrics {
|
fileprivate enum Metrics {
|
||||||
static let contentMinWidth = 400.0
|
static let contentMinWidth = 400.0
|
||||||
static let contentMaxWidth = /*600.0*/ NSScreen.main!.visibleFrame.size.width * 0.7 // REVIEW: Under what circumstances can NSScreen return nil?
|
static let contentMaxWidth = /*600.0*/ NSScreen.main!.visibleFrame.size.width * 0.7 // WARNING: Under what circumstances can NSScreen return nil?
|
||||||
static let contentMinHeight = 160.0
|
static let contentMinHeight = 160.0
|
||||||
static let contentMaxHeight = /*800.0*/ NSScreen.main!.visibleFrame.size.height * 0.8 // REVIEW: Under what circumstances can NSScreen return nil?
|
static let contentMaxHeight = /*800.0*/ NSScreen.main!.visibleFrame.size.height * 0.8 // WARNING: Under what circumstances can NSScreen return nil?
|
||||||
static let padding = 10.0
|
static let padding = 10.0
|
||||||
static let buttonSpacing = 2.0
|
static let buttonSpacing = 2.0
|
||||||
}
|
}
|
||||||
|
|
||||||
class CmdViewController: NSViewController {
|
class CmdViewController: NSViewController {
|
||||||
fileprivate static let logger = Logger(
|
|
||||||
subsystem: Bundle.main.bundleIdentifier!,
|
|
||||||
category: String(describing: CmdViewController.self)
|
|
||||||
)
|
|
||||||
|
|
||||||
private weak var cmdFile: CmdFile?
|
private weak var cmdFile: CmdFile?
|
||||||
private var timer: DispatchSourceTimer?
|
private var timer: DispatchSourceTimer?
|
||||||
private var keyboardEvents: EventMonitor?
|
private var keyboardEvents: EventMonitor?
|
||||||
@@ -332,7 +327,10 @@ class CmdViewController: NSViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func resetReloadButton() {
|
private func resetReloadButton() {
|
||||||
reloadButton.image = systemImage("arrow.clockwise.circle.fill", .title2, .large, .init(paletteColors: [.white, .systemBlue]))
|
reloadButton.image = systemImage("arrow.clockwise.circle.fill",
|
||||||
|
.title2, .large,
|
||||||
|
.init(paletteColors: [.white,
|
||||||
|
.systemBlue]))
|
||||||
reloadButton.action = #selector(reloadWidget)
|
reloadButton.action = #selector(reloadWidget)
|
||||||
reloadButton.toolTip = "Reload Current"
|
reloadButton.toolTip = "Reload Current"
|
||||||
}
|
}
|
||||||
@@ -351,52 +349,52 @@ class CmdViewController: NSViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupKeyEvents() {
|
private func setupKeyEvents() {
|
||||||
keyboardEvents = LocalEventMonitor(mask: [.flagsChanged, .keyDown], handler: { [weak self] event in
|
keyboardEvents = LocalEventMonitor(mask: [.flagsChanged, .keyDown],
|
||||||
|
handler:
|
||||||
|
{ [weak self] event in
|
||||||
let modifiers = event.modifierFlags.rawValue
|
let modifiers = event.modifierFlags.rawValue
|
||||||
let command = NSEvent.ModifierFlags.command.rawValue
|
let key = event.keyCode
|
||||||
let shift = NSEvent.ModifierFlags.shift.rawValue
|
|
||||||
let control = NSEvent.ModifierFlags.control.rawValue
|
|
||||||
let option = NSEvent.ModifierFlags.option.rawValue
|
|
||||||
|
|
||||||
if event.modifierFlags.contains(.shift) {
|
if modsContains(keys: OSShift, in: modifiers) {
|
||||||
self?.reloadButton.image = systemImage("arrow.clockwise.circle.fill", .title2, .large, .init(paletteColors: [.white, .systemCyan]))
|
self?.reloadButton.image =
|
||||||
|
systemImage("arrow.clockwise.circle.fill", .title2,
|
||||||
|
.large,.init(paletteColors: [.white,
|
||||||
|
.systemCyan]))
|
||||||
self?.reloadButton.action = #selector(self?.reloadWidgets)
|
self?.reloadButton.action = #selector(self?.reloadWidgets)
|
||||||
self?.reloadButton.toolTip = "Reload All"
|
self?.reloadButton.toolTip = "Reload All"
|
||||||
} else {
|
} else {
|
||||||
self?.reloadButton.image = systemImage("arrow.clockwise.circle.fill", .title2, .large, .init(paletteColors: [.white, .systemBlue]))
|
self?.reloadButton.image =
|
||||||
|
systemImage("arrow.clockwise.circle.fill", .title2,
|
||||||
|
.large, .init(paletteColors: [.white,
|
||||||
|
.systemBlue]))
|
||||||
self?.reloadButton.action = #selector(self?.reloadWidget)
|
self?.reloadButton.action = #selector(self?.reloadWidget)
|
||||||
self?.reloadButton.toolTip = "Reload Current"
|
self?.reloadButton.toolTip = "Reload Current"
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Standalone keys should go last!
|
if modsContains(keys: OSCmd, in: modifiers) {
|
||||||
if (modifiers & command) == command,
|
if key == kVK_ANSI_Q {
|
||||||
(modifiers & (control | shift | option)) == 0,
|
|
||||||
event.keyCode == 12 // Q
|
|
||||||
{
|
|
||||||
self?.terminateApp()
|
self?.terminateApp()
|
||||||
} else if (modifiers & command) == command,
|
} else if key == kVK_ANSI_W {
|
||||||
(modifiers & (control | shift | option)) == 0,
|
|
||||||
event.keyCode == 13 // W
|
|
||||||
{
|
|
||||||
self?.view.superview?.window?.resignKey()
|
self?.view.superview?.window?.resignKey()
|
||||||
} else if (modifiers & command) == command,
|
} else if key == kVK_ANSI_R {
|
||||||
(modifiers & (control | shift | option)) == 0,
|
|
||||||
event.keyCode == 15 // R
|
|
||||||
{
|
|
||||||
self?.reloadWidget()
|
self?.reloadWidget()
|
||||||
} else if (modifiers & (command & shift)) == command & shift,
|
} else if key == kVK_ANSI_C {
|
||||||
(modifiers & (control | option)) == 0,
|
|
||||||
event.keyCode == 15 // R
|
|
||||||
{
|
|
||||||
self?.reloadWidgets()
|
|
||||||
} else if (modifiers & command) == command,
|
|
||||||
(modifiers & (control | shift | option)) == 0,
|
|
||||||
event.keyCode == 8 // C
|
|
||||||
{
|
|
||||||
self?.textLabel.currentEditor()?.copy(nil)
|
self?.textLabel.currentEditor()?.copy(nil)
|
||||||
} else if event.keyCode == 12 || event.keyCode == 53 { // Q, ESC
|
}
|
||||||
|
} else if modsContains(keys: OSCmd | OSShift, in: modifiers) {
|
||||||
|
if key == kVK_ANSI_R {
|
||||||
|
// NOTE: We need to resign the window in order to
|
||||||
|
// prevent the program from intercepting the
|
||||||
|
// events, which result in sounds beign made for
|
||||||
|
// missing performKeyEquivalent.
|
||||||
|
self?.view.window?.resignKey()
|
||||||
|
self?.reloadWidgets()
|
||||||
|
}
|
||||||
|
} else if modsContainsNone(in: modifiers) {
|
||||||
|
if key == kVK_ANSI_Q || key == kVK_Escape {
|
||||||
self?.view.superview?.window?.resignKey()
|
self?.view.superview?.window?.resignKey()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return event
|
return event
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,32 +1,44 @@
|
|||||||
import Cocoa
|
import AppKit
|
||||||
|
import Carbon
|
||||||
|
|
||||||
final class EditableNSTextField: NSTextField {
|
final class EditableNSTextField: NSTextField {
|
||||||
private let commandKey = NSEvent.ModifierFlags.command.rawValue
|
|
||||||
private let commandShiftKey = NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue
|
|
||||||
|
|
||||||
override func performKeyEquivalent(with event: NSEvent) -> Bool {
|
override func performKeyEquivalent(with event: NSEvent) -> Bool {
|
||||||
|
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) {
|
||||||
switch event.charactersIgnoringModifiers! {
|
if key == kVK_ANSI_X {
|
||||||
case "x":
|
if NSApp.sendAction(#selector(NSText.cut(_:)),
|
||||||
if NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: self) { return true }
|
to: nil, from: self)
|
||||||
case "c":
|
{ return true }
|
||||||
if NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: self) { return true }
|
} else if key == kVK_ANSI_C {
|
||||||
case "v":
|
if NSApp.sendAction(#selector(NSText.copy(_:)),
|
||||||
if NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: self) { return true }
|
to: nil, from: self)
|
||||||
case "z":
|
{ return true }
|
||||||
if NSApp.sendAction(Selector(("undo:")), to: nil, from: self) { return true }
|
} else if key == kVK_ANSI_V {
|
||||||
case "a":
|
if NSApp.sendAction(#selector(NSText.paste(_:)),
|
||||||
if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, from: self) { return true }
|
to: nil, from: self)
|
||||||
default:
|
{ return true }
|
||||||
break
|
} else if key == kVK_ANSI_Z {
|
||||||
|
if NSApp.sendAction(Selector(("undo:")),
|
||||||
|
to: nil, from: self)
|
||||||
|
{ return true }
|
||||||
|
} else if key == kVK_ANSI_A {
|
||||||
|
if NSApp.sendAction(
|
||||||
|
#selector(NSResponder.selectAll(_:)),
|
||||||
|
to: nil, from: self)
|
||||||
|
{ return true }
|
||||||
}
|
}
|
||||||
} else if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey {
|
} else if modsContains(keys: OSCmd | OSShift, in: modifiers) {
|
||||||
if event.charactersIgnoringModifiers == "Z" {
|
if key == kVK_ANSI_Z {
|
||||||
if NSApp.sendAction(Selector(("redo:")), to: nil, from: self) { return true }
|
if NSApp.sendAction(Selector(("redo:")),
|
||||||
|
to: nil, from: self)
|
||||||
|
{ return true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.performKeyEquivalent(with: event)
|
return super.performKeyEquivalent(with: event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Cocoa
|
import AppKit
|
||||||
|
|
||||||
class EventMonitor {
|
class EventMonitor {
|
||||||
fileprivate let mask: NSEvent.EventTypeMask
|
fileprivate let mask: NSEvent.EventTypeMask
|
||||||
|
|||||||
@@ -1,6 +1,32 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
|
import Carbon
|
||||||
|
|
||||||
|
let OSCtrl = NSEvent.ModifierFlags.control.rawValue
|
||||||
|
let OSCmd = NSEvent.ModifierFlags.command.rawValue
|
||||||
|
let OSOpt = NSEvent.ModifierFlags.option.rawValue
|
||||||
|
let OSShift = NSEvent.ModifierFlags.shift.rawValue
|
||||||
|
let OSMods = UInt(OSCtrl | OSCmd | OSOpt | OSShift)
|
||||||
|
|
||||||
|
func modsContains(keys: UInt, in modifiers: UInt) -> Bool {
|
||||||
|
return (modifiers & keys) == keys && ((modifiers ^ keys) & OSMods) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func modsContainsNone(in modifiers: UInt) -> Bool {
|
||||||
|
return (modifiers & OSMods) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ViewConstants {
|
||||||
|
static let spacing2: CGFloat = 2
|
||||||
|
static let spacing5: CGFloat = 2
|
||||||
|
static let spacing10: CGFloat = 10
|
||||||
|
static let spacing15: CGFloat = 15
|
||||||
|
static let spacing20: CGFloat = 20
|
||||||
|
static let spacing25: CGFloat = 25
|
||||||
|
static let spacing30: CGFloat = 30
|
||||||
|
static let spacing35: CGFloat = 35
|
||||||
|
static let spacing40: CGFloat = 40
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Tracking Notifications
|
|
||||||
fileprivate extension Notification.Name {
|
fileprivate extension Notification.Name {
|
||||||
static let beginMenuTracking = Notification.Name("com.apple.HIToolbox.beginMenuTrackingNotification")
|
static let beginMenuTracking = Notification.Name("com.apple.HIToolbox.beginMenuTrackingNotification")
|
||||||
static let endMenuTracking = Notification.Name("com.apple.HIToolbox.endMenuTrackingNotification")
|
static let endMenuTracking = Notification.Name("com.apple.HIToolbox.endMenuTrackingNotification")
|
||||||
@@ -24,7 +50,6 @@ struct EndpointConstants {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//
|
|
||||||
enum Resulting<Success, Failure> {
|
enum Resulting<Success, Failure> {
|
||||||
case success
|
case success
|
||||||
case failure
|
case failure
|
||||||
@@ -156,7 +181,10 @@ func getColors(_ light: NSColor, _ dark: NSColor, for appearance: NSAppearance)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ex: systemImage("sunrise.circle.fill", .title2, .large, .init(paletteColors: [.white, .systemOrange]))
|
// ex: systemImage("sunrise.circle.fill", .title2, .large, .init(paletteColors: [.white, .systemOrange]))
|
||||||
func systemImage(_ name: String, _ size: NSFont.TextStyle, _ scale: NSImage.SymbolScale, _ configuration: NSImage.SymbolConfiguration) -> NSImage? {
|
func systemImage(_ name: String, _ size: NSFont.TextStyle,
|
||||||
|
_ scale: NSImage.SymbolScale,
|
||||||
|
_ configuration: NSImage.SymbolConfiguration) -> NSImage?
|
||||||
|
{
|
||||||
return NSImage(systemSymbolName: name, accessibilityDescription: nil)?
|
return NSImage(systemSymbolName: name, accessibilityDescription: nil)?
|
||||||
.withSymbolConfiguration(
|
.withSymbolConfiguration(
|
||||||
NSImage.SymbolConfiguration(textStyle: size, scale: scale)
|
NSImage.SymbolConfiguration(textStyle: size, scale: scale)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
// Created by Igor Kolokolnikov on 8/26/23.
|
// Created by Igor Kolokolnikov on 8/26/23.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import AppKit
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
||||||
fileprivate struct LicenseModel: Codable {
|
fileprivate struct LicenseModel: Codable {
|
||||||
|
|||||||
@@ -86,7 +86,11 @@ $(EXEC).app: $(EXEC)
|
|||||||
cp resources/AppIcon.icns $@/Contents/Resources/ && \
|
cp resources/AppIcon.icns $@/Contents/Resources/ && \
|
||||||
cp $(EXEC) $@/Contents/MacOS/ && \
|
cp $(EXEC) $@/Contents/MacOS/ && \
|
||||||
cp cmdbar_updater $@/Contents/MacOS/ && \
|
cp cmdbar_updater $@/Contents/MacOS/ && \
|
||||||
codesign -s ${DEVELOPER_ID} -f --timestamp -o runtime $(EXEC).app
|
$(if $(DEBUG), \
|
||||||
|
codesign --entitlements Grapp.entitlements \
|
||||||
|
-s ${APPLE_DEVELOPMENT} -f --timestamp -o runtime $(EXEC).app, \
|
||||||
|
codesign -s ${APPLE_DEVELOPER_ID_APPLICATION} -f --timestamp \
|
||||||
|
-o runtime $(EXEC).app)
|
||||||
|
|
||||||
all: zip cmdbar_updater $(EXEC).app
|
all: zip cmdbar_updater $(EXEC).app
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Cocoa
|
import AppKit
|
||||||
|
|
||||||
class MenulessWindow: NSWindow {
|
class MenulessWindow: NSWindow {
|
||||||
init(viewController: NSViewController) {
|
init(viewController: NSViewController) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Cocoa
|
import AppKit
|
||||||
|
|
||||||
class PopoverPanel: NSPanel {
|
class PopoverPanel: NSPanel {
|
||||||
override var canBecomeKey: Bool { true }
|
override var canBecomeKey: Bool { true }
|
||||||
@@ -6,7 +6,8 @@ class PopoverPanel: NSPanel {
|
|||||||
init(viewController: NSViewController) {
|
init(viewController: NSViewController) {
|
||||||
super.init(
|
super.init(
|
||||||
contentRect: CGRect(x: 0, y: 0, width: 100, height: 100),
|
contentRect: CGRect(x: 0, y: 0, width: 100, height: 100),
|
||||||
styleMask: [.titled, .nonactivatingPanel, .utilityWindow, .fullSizeContentView],
|
styleMask: [.titled, .nonactivatingPanel, .utilityWindow,
|
||||||
|
.fullSizeContentView],
|
||||||
backing: .buffered,
|
backing: .buffered,
|
||||||
defer: false
|
defer: false
|
||||||
)
|
)
|
||||||
@@ -22,7 +23,8 @@ class PopoverPanel: NSPanel {
|
|||||||
titlebarAppearsTransparent = true
|
titlebarAppearsTransparent = true
|
||||||
|
|
||||||
animationBehavior = .none
|
animationBehavior = .none
|
||||||
collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary, .transient]
|
collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary,
|
||||||
|
.transient]
|
||||||
isReleasedWhenClosed = false
|
isReleasedWhenClosed = false
|
||||||
hidesOnDeactivate = false
|
hidesOnDeactivate = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,5 @@
|
|||||||
//
|
|
||||||
// UpdateViewController.swift
|
|
||||||
// CmdBar
|
|
||||||
//
|
|
||||||
// Created by Igor Kolokolnikov on 5/23/24.
|
|
||||||
//
|
|
||||||
|
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
fileprivate enum ViewConstants {
|
|
||||||
static let spacing2: CGFloat = 2
|
|
||||||
static let spacing10: CGFloat = 10
|
|
||||||
static let spacing20: CGFloat = 20
|
|
||||||
static let spacing40: CGFloat = 40
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate enum UpdateStatus: String {
|
fileprivate enum UpdateStatus: String {
|
||||||
case check = "Check for updates..."
|
case check = "Check for updates..."
|
||||||
case latest = "You're up-to-date!"
|
case latest = "You're up-to-date!"
|
||||||
@@ -30,9 +16,9 @@ class UpdateViewController: NSViewController, UpdateManagerDelegate {
|
|||||||
private weak var updaterDelegate: UpdateManagerDelegate?
|
private weak var updaterDelegate: UpdateManagerDelegate?
|
||||||
|
|
||||||
private var appIconImage: NSImageView = {
|
private var appIconImage: NSImageView = {
|
||||||
//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
|
||||||
@@ -45,7 +31,9 @@ class UpdateViewController: NSViewController, UpdateManagerDelegate {
|
|||||||
textField.drawsBackground = false
|
textField.drawsBackground = false
|
||||||
textField.alignment = .left
|
textField.alignment = .left
|
||||||
textField.textColor = NSColor(name: nil) { getColors(.black, .white, for: $0) }
|
textField.textColor = NSColor(name: nil) { getColors(.black, .white, for: $0) }
|
||||||
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .body).pointSize, weight: .bold)
|
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor
|
||||||
|
.preferredFontDescriptor(forTextStyle: .body).pointSize,
|
||||||
|
weight: .bold)
|
||||||
textField.translatesAutoresizingMaskIntoConstraints = false
|
textField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return textField
|
return textField
|
||||||
}()
|
}()
|
||||||
@@ -59,18 +47,23 @@ class UpdateViewController: NSViewController, UpdateManagerDelegate {
|
|||||||
textField.drawsBackground = false
|
textField.drawsBackground = false
|
||||||
textField.alignment = .left
|
textField.alignment = .left
|
||||||
textField.textColor = .gray
|
textField.textColor = .gray
|
||||||
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .body).pointSize, weight: .bold)
|
textField.font = NSFont.systemFont(ofSize: NSFontDescriptor
|
||||||
|
.preferredFontDescriptor(forTextStyle: .body).pointSize,
|
||||||
|
weight: .bold)
|
||||||
textField.translatesAutoresizingMaskIntoConstraints = false
|
textField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return textField
|
return textField
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var changelogLabel: NSScrollView = { // TODO: Improve some more. Maybe make it scrollable.
|
// TODO: Improve some more. Maybe make it scrollable.
|
||||||
|
private var changelogLabel: NSScrollView = {
|
||||||
let scrollableTextView = NSTextView.scrollableTextView()
|
let scrollableTextView = NSTextView.scrollableTextView()
|
||||||
scrollableTextView.isHidden = true
|
scrollableTextView.isHidden = true
|
||||||
(scrollableTextView.documentView as? NSTextView)?.isEditable = false
|
(scrollableTextView.documentView as? NSTextView)?.isEditable = false
|
||||||
// (scrollableTextView.documentView as? NSTextView)?.drawsBackground = false
|
|
||||||
(scrollableTextView.documentView as? NSTextView)?.textColor = .gray
|
(scrollableTextView.documentView as? NSTextView)?.textColor = .gray
|
||||||
(scrollableTextView.documentView as? NSTextView)?.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .body).pointSize, weight: .regular)
|
(scrollableTextView.documentView as? NSTextView)?.font =
|
||||||
|
NSFont.systemFont(ofSize: NSFontDescriptor
|
||||||
|
.preferredFontDescriptor(forTextStyle: .body).pointSize,
|
||||||
|
weight: .regular)
|
||||||
scrollableTextView.translatesAutoresizingMaskIntoConstraints = false
|
scrollableTextView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return scrollableTextView
|
return scrollableTextView
|
||||||
}()
|
}()
|
||||||
@@ -139,26 +132,46 @@ class UpdateViewController: NSViewController, UpdateManagerDelegate {
|
|||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
appIconImage.widthAnchor.constraint(equalToConstant: 70),
|
appIconImage.widthAnchor.constraint(equalToConstant: 70),
|
||||||
appIconImage.heightAnchor.constraint(equalTo: appIconImage.widthAnchor, multiplier: 1),
|
appIconImage.heightAnchor
|
||||||
|
.constraint(equalTo: appIconImage.widthAnchor,
|
||||||
|
multiplier: 1),
|
||||||
|
appIconImage.topAnchor.constraint(equalTo: view.topAnchor,
|
||||||
|
constant: ViewConstants.spacing10),
|
||||||
|
appIconImage.leadingAnchor
|
||||||
|
.constraint(equalTo: view.leadingAnchor,
|
||||||
|
constant: ViewConstants.spacing10),
|
||||||
|
|
||||||
appIconImage.topAnchor.constraint(equalTo: view.topAnchor, constant: ViewConstants.spacing10),
|
statusLabel.topAnchor
|
||||||
appIconImage.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing10),
|
.constraint(equalTo: appIconImage.topAnchor,
|
||||||
|
constant: ViewConstants.spacing10),
|
||||||
|
statusLabel.leadingAnchor
|
||||||
|
.constraint(equalTo: appIconImage.trailingAnchor,
|
||||||
|
constant: ViewConstants.spacing10),
|
||||||
|
|
||||||
statusLabel.topAnchor.constraint(equalTo: appIconImage.topAnchor, constant: ViewConstants.spacing2),
|
subStatusLabel.topAnchor
|
||||||
statusLabel.leadingAnchor.constraint(equalTo: appIconImage.trailingAnchor, constant: ViewConstants.spacing10),
|
.constraint(equalTo: statusLabel.bottomAnchor,
|
||||||
|
constant: ViewConstants.spacing10),
|
||||||
|
subStatusLabel.leadingAnchor
|
||||||
|
.constraint(equalTo: statusLabel.leadingAnchor),
|
||||||
|
|
||||||
subStatusLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: ViewConstants.spacing10),
|
changelogLabel.widthAnchor
|
||||||
subStatusLabel.leadingAnchor.constraint(equalTo: statusLabel.leadingAnchor),
|
.constraint(equalTo: stackView.widthAnchor),
|
||||||
|
|
||||||
changelogLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor),
|
|
||||||
changelogLabel.heightAnchor.constraint(equalToConstant: 150),
|
changelogLabel.heightAnchor.constraint(equalToConstant: 150),
|
||||||
|
|
||||||
stackView.topAnchor.constraint(equalTo: subStatusLabel.bottomAnchor, constant: ViewConstants.spacing2),
|
stackView.topAnchor
|
||||||
stackView.leadingAnchor.constraint(equalTo: statusLabel.leadingAnchor),
|
.constraint(equalTo: subStatusLabel.bottomAnchor,
|
||||||
view.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: ViewConstants.spacing20),
|
constant: ViewConstants.spacing2),
|
||||||
view.bottomAnchor.constraint(equalTo: stackView.bottomAnchor, constant: ViewConstants.spacing20),
|
stackView.leadingAnchor
|
||||||
|
.constraint(equalTo: statusLabel.leadingAnchor),
|
||||||
|
view.trailingAnchor
|
||||||
|
.constraint(equalTo: stackView.trailingAnchor,
|
||||||
|
constant: ViewConstants.spacing20),
|
||||||
|
view.bottomAnchor
|
||||||
|
.constraint(equalTo: stackView.bottomAnchor,
|
||||||
|
constant: ViewConstants.spacing20),
|
||||||
|
|
||||||
buttonsStackView.heightAnchor.constraint(equalTo: updateButton.heightAnchor),
|
buttonsStackView.heightAnchor
|
||||||
|
.constraint(equalTo: updateButton.heightAnchor),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,36 +205,44 @@ class UpdateViewController: NSViewController, UpdateManagerDelegate {
|
|||||||
case .check:
|
case .check:
|
||||||
break
|
break
|
||||||
case .latest:
|
case .latest:
|
||||||
self?.statusLabel.stringValue = UpdateStatus.latest.rawValue
|
self?.statusLabel.stringValue = UpdateStatus.latest
|
||||||
|
.rawValue
|
||||||
break
|
break
|
||||||
case .checking:
|
case .checking:
|
||||||
self?.statusLabel.stringValue = UpdateStatus.checking.rawValue
|
self?.statusLabel.stringValue = UpdateStatus.checking
|
||||||
|
.rawValue
|
||||||
self?.updateButton.keyEquivalent = ""
|
self?.updateButton.keyEquivalent = ""
|
||||||
self?.updateButton.isEnabled = false
|
self?.updateButton.isEnabled = false
|
||||||
case .available:
|
case .available:
|
||||||
self?.updateButton.title = "Download"
|
self?.updateButton.title = "Download"
|
||||||
self?.updateButton.keyEquivalent = ""
|
self?.updateButton.keyEquivalent = ""
|
||||||
self?.statusLabel.stringValue = UpdateStatus.available.rawValue
|
self?.statusLabel.stringValue = UpdateStatus.available
|
||||||
|
.rawValue
|
||||||
self?.updateButton.action = #selector(self!.downloadUpdate)
|
self?.updateButton.action = #selector(self!.downloadUpdate)
|
||||||
case .downloading:
|
case .downloading:
|
||||||
self?.updateButton.isEnabled = false
|
self?.updateButton.isEnabled = false
|
||||||
self?.updateButton.title = "Install"
|
self?.updateButton.title = "Install"
|
||||||
self?.progressIndicator.isHidden = false
|
self?.progressIndicator.isHidden = false
|
||||||
self?.statusLabel.stringValue = UpdateStatus.downloading.rawValue
|
self?.statusLabel.stringValue = UpdateStatus.downloading
|
||||||
|
.rawValue
|
||||||
case .extracting:
|
case .extracting:
|
||||||
self?.updateButton.isEnabled = false
|
self?.updateButton.isEnabled = false
|
||||||
self?.updateButton.title = "Install"
|
self?.updateButton.title = "Install"
|
||||||
self?.progressIndicator.isHidden = false
|
self?.progressIndicator.isHidden = false
|
||||||
self?.statusLabel.stringValue = UpdateStatus.extracting.rawValue
|
self?.statusLabel.stringValue = UpdateStatus.extracting
|
||||||
|
.rawValue
|
||||||
case .install:
|
case .install:
|
||||||
self?.updateButton.title = "Install"
|
self?.updateButton.title = "Install"
|
||||||
self?.progressIndicator.isHidden = false
|
self?.progressIndicator.isHidden = false
|
||||||
self?.statusLabel.stringValue = UpdateStatus.install.rawValue
|
self?.statusLabel.stringValue = UpdateStatus.install
|
||||||
|
.rawValue
|
||||||
self?.updateButton.action = #selector(self!.installUpdate)
|
self?.updateButton.action = #selector(self!.installUpdate)
|
||||||
case .checkFailed:
|
case .checkFailed:
|
||||||
self?.statusLabel.stringValue = UpdateStatus.checkFailed.rawValue
|
self?.statusLabel.stringValue = UpdateStatus.checkFailed
|
||||||
|
.rawValue
|
||||||
case .installFailed:
|
case .installFailed:
|
||||||
self?.statusLabel.stringValue = UpdateStatus.installFailed.rawValue
|
self?.statusLabel.stringValue = UpdateStatus.installFailed
|
||||||
|
.rawValue
|
||||||
self?.updateButton.action = #selector(self!.checkForUpdate)
|
self?.updateButton.action = #selector(self!.checkForUpdate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,8 +262,13 @@ class UpdateViewController: NSViewController, UpdateManagerDelegate {
|
|||||||
self?.subStatusLabel.isHidden = false
|
self?.subStatusLabel.isHidden = false
|
||||||
self?.changelogLabel.isHidden = false
|
self?.changelogLabel.isHidden = false
|
||||||
for (i, change) in model.changes.enumerated() {
|
for (i, change) in model.changes.enumerated() {
|
||||||
(self?.changelogLabel.documentView as? NSTextView)?.string += "\u{2022} \(change)"
|
(self?.changelogLabel.documentView as?
|
||||||
if i < model.changes.count-1 { (self?.changelogLabel.documentView as? NSTextView)?.string += "\n" }
|
NSTextView)?.string +=
|
||||||
|
"\u{2022} \(change)"
|
||||||
|
if i < model.changes.count-1 {
|
||||||
|
(self?.changelogLabel.documentView as?
|
||||||
|
NSTextView)?.string += "\n"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ FRAMEWORKS = -framework AppKit
|
|||||||
|
|
||||||
$(EXEC): ./arm64/$(EXEC) ./x86_64/$(EXEC)
|
$(EXEC): ./arm64/$(EXEC) ./x86_64/$(EXEC)
|
||||||
lipo -create -output $(EXEC) $^ && \
|
lipo -create -output $(EXEC) $^ && \
|
||||||
codesign -s ${DEVELOPER_ID} -f --timestamp -o runtime $(EXEC)
|
codesign -s ${APPLE_DEVELOPER_ID_APPLICATION} -f --timestamp \
|
||||||
|
-o runtime $(EXEC)
|
||||||
|
|
||||||
all: $(EXEC)
|
all: $(EXEC)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user