No more keyboard shortcuts sounds in the popover.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,3 +2,7 @@
|
||||
arm64
|
||||
x86_64
|
||||
build
|
||||
CmdBar
|
||||
CmdBar.app
|
||||
cmdbar_updater
|
||||
src/updater
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// Created by Igor Kolokolnikov on 8/16/23.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
// MARK: - Constants
|
||||
fileprivate enum AboutLinks {
|
||||
@@ -24,12 +24,6 @@ enum Strings {
|
||||
static let activating = "Activating..."
|
||||
}
|
||||
|
||||
fileprivate enum ViewConstants {
|
||||
static let spacing2: CGFloat = 2
|
||||
static let spacing10: CGFloat = 10
|
||||
static let spacing20: CGFloat = 20
|
||||
}
|
||||
|
||||
// MARK: - Controller
|
||||
class AboutViewController: NSViewController, NSTextFieldDelegate {
|
||||
// MARK: - Views
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Cocoa
|
||||
import AppKit
|
||||
import ServiceManagement
|
||||
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
// from the coverage of the license being applied to the rest of the code.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AppKit
|
||||
import os
|
||||
|
||||
final class CBMenuBarItem: NSObject, NSWindowDelegate {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?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">
|
||||
<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>
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import Foundation
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
// MARK: - CmdFile
|
||||
class CmdFile {
|
||||
// MARK: - State
|
||||
private var timer: DispatchSourceTimer?
|
||||
private(set) var lastExec = Int(Date().timeIntervalSince1970)
|
||||
var untilNextExec: Int {
|
||||
get {
|
||||
let res = reloadTime - (Int(Date().timeIntervalSince1970) - lastExec)
|
||||
let res = reloadTime -
|
||||
(Int(Date().timeIntervalSince1970) - lastExec)
|
||||
if res <= 0 { return reloadTime }
|
||||
return res
|
||||
}
|
||||
@@ -16,11 +14,10 @@ class CmdFile {
|
||||
|
||||
private(set) var url: URL
|
||||
private(set) var reloadTime: Int
|
||||
private var statusItem: CBMenuBarItem = CBMenuBarItem()
|
||||
private(set) var statusItem: CBMenuBarItem = CBMenuBarItem()
|
||||
|
||||
private var shouldWait = false
|
||||
|
||||
// MARK: - Initializers
|
||||
init(url: URL, reloadTime: Int) {
|
||||
self.url = url
|
||||
self.reloadTime = reloadTime
|
||||
@@ -37,7 +34,6 @@ class CmdFile {
|
||||
cancelTimer()
|
||||
}
|
||||
|
||||
// MARK: - Reload Widget
|
||||
private func reloadWidget() {
|
||||
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
||||
if let url = self?.url {
|
||||
@@ -45,10 +41,13 @@ class CmdFile {
|
||||
let execResult = execute(atPath: url)
|
||||
DispatchQueue.main.async {
|
||||
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("")
|
||||
} else {
|
||||
self?.statusItem.setImage(title: nil, description: "")
|
||||
self?.statusItem.setImage(title: nil,
|
||||
description: "")
|
||||
self?.statusItem.setTitle(execResult.0)
|
||||
}
|
||||
self?.statusItem.setContents(to: execResult.body)
|
||||
@@ -58,7 +57,6 @@ class CmdFile {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Timer
|
||||
private func setupTimer() {
|
||||
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
|
||||
timer?.schedule(deadline: .now(), repeating: .seconds(reloadTime))
|
||||
@@ -91,15 +89,12 @@ class CmdFile {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CmdManager
|
||||
class CmdManager {
|
||||
//MARK: - State
|
||||
static let standard = CmdManager()
|
||||
|
||||
var paths: [CmdFile] = []
|
||||
private var defaultStatusItem: NSStatusItem?
|
||||
|
||||
// MARK: - Configure
|
||||
func configure() {
|
||||
getPaths()
|
||||
if paths.isEmpty {
|
||||
@@ -111,23 +106,31 @@ class CmdManager {
|
||||
|
||||
private func getPaths() {
|
||||
let manager = FileManager.default
|
||||
let path = manager.homeDirectoryForCurrentUser.appending(path: ".cmdbar")
|
||||
if let contents = try? manager.contentsOfDirectory(atPath: path.path()) {
|
||||
let path = manager.homeDirectoryForCurrentUser
|
||||
.appending(path: ".cmdbar")
|
||||
if let contents = try? manager
|
||||
.contentsOfDirectory(atPath: path.path()) {
|
||||
for content in contents {
|
||||
let filePath = path.appending(path: content)
|
||||
|
||||
let domains = filePath.path().components(separatedBy: ".")
|
||||
if (isValidTimeFormat(domains[domains.count - 1])) {
|
||||
paths.append(CmdFile(url: filePath, reloadTime: intervalToSeconds(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])))
|
||||
paths.append(CmdFile(url: filePath,
|
||||
reloadTime: intervalToSeconds(
|
||||
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() {
|
||||
defaultStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
defaultStatusItem = NSStatusBar.system
|
||||
.statusItem(withLength: NSStatusItem.variableLength)
|
||||
if let btn = defaultStatusItem?.button {
|
||||
let image = NSApp.applicationIconImage
|
||||
image!.size = NSSize(width: 22, height: 22)
|
||||
@@ -135,13 +138,21 @@ class CmdManager {
|
||||
}
|
||||
guard defaultStatusItem != nil else { return }
|
||||
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(title: "About", action: #selector(showAbout), keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem(title: "About",
|
||||
action: #selector(showAbout),
|
||||
keyEquivalent: ""))
|
||||
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(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
|
||||
menu.addItem(NSMenuItem(title: "Quit",
|
||||
action: #selector(NSApplication.terminate(_:)),
|
||||
keyEquivalent: "q"))
|
||||
defaultStatusItem!.menu = menu
|
||||
}
|
||||
|
||||
@@ -164,7 +175,6 @@ class CmdManager {
|
||||
reloadItems()
|
||||
}
|
||||
|
||||
// MARK: - NSMenu Functions
|
||||
@objc private func showAbout() {
|
||||
delegate.showAbout()
|
||||
}
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
import Cocoa
|
||||
import AppKit
|
||||
import Carbon
|
||||
import ServiceManagement
|
||||
import OSLog
|
||||
|
||||
fileprivate enum Metrics {
|
||||
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 contentMinWidth = 400.0
|
||||
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 contentMaxHeight = /*800.0*/ NSScreen.main!.visibleFrame.size.height * 0.8 // REVIEW: Under what circumstances can NSScreen return nil?
|
||||
static let padding = 10.0
|
||||
static let buttonSpacing = 2.0
|
||||
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 buttonSpacing = 2.0
|
||||
}
|
||||
|
||||
class CmdViewController: NSViewController {
|
||||
fileprivate static let logger = Logger(
|
||||
subsystem: Bundle.main.bundleIdentifier!,
|
||||
category: String(describing: CmdViewController.self)
|
||||
)
|
||||
|
||||
private weak var cmdFile: CmdFile?
|
||||
private var timer: DispatchSourceTimer?
|
||||
private var keyboardEvents: EventMonitor?
|
||||
@@ -332,7 +327,10 @@ class CmdViewController: NSViewController {
|
||||
}
|
||||
|
||||
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.toolTip = "Reload Current"
|
||||
}
|
||||
@@ -351,51 +349,51 @@ class CmdViewController: NSViewController {
|
||||
}
|
||||
|
||||
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 command = NSEvent.ModifierFlags.command.rawValue
|
||||
let shift = NSEvent.ModifierFlags.shift.rawValue
|
||||
let control = NSEvent.ModifierFlags.control.rawValue
|
||||
let option = NSEvent.ModifierFlags.option.rawValue
|
||||
let key = event.keyCode
|
||||
|
||||
if event.modifierFlags.contains(.shift) {
|
||||
self?.reloadButton.image = systemImage("arrow.clockwise.circle.fill", .title2, .large, .init(paletteColors: [.white, .systemCyan]))
|
||||
if modsContains(keys: OSShift, in: modifiers) {
|
||||
self?.reloadButton.image =
|
||||
systemImage("arrow.clockwise.circle.fill", .title2,
|
||||
.large,.init(paletteColors: [.white,
|
||||
.systemCyan]))
|
||||
self?.reloadButton.action = #selector(self?.reloadWidgets)
|
||||
self?.reloadButton.toolTip = "Reload All"
|
||||
} 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.toolTip = "Reload Current"
|
||||
}
|
||||
|
||||
// NOTE: Standalone keys should go last!
|
||||
if (modifiers & command) == command,
|
||||
(modifiers & (control | shift | option)) == 0,
|
||||
event.keyCode == 12 // Q
|
||||
{
|
||||
self?.terminateApp()
|
||||
} else if (modifiers & command) == command,
|
||||
(modifiers & (control | shift | option)) == 0,
|
||||
event.keyCode == 13 // W
|
||||
{
|
||||
self?.view.superview?.window?.resignKey()
|
||||
} else if (modifiers & command) == command,
|
||||
(modifiers & (control | shift | option)) == 0,
|
||||
event.keyCode == 15 // R
|
||||
{
|
||||
self?.reloadWidget()
|
||||
} else if (modifiers & (command & shift)) == command & shift,
|
||||
(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)
|
||||
} else if event.keyCode == 12 || event.keyCode == 53 { // Q, ESC
|
||||
self?.view.superview?.window?.resignKey()
|
||||
if modsContains(keys: OSCmd, in: modifiers) {
|
||||
if key == kVK_ANSI_Q {
|
||||
self?.terminateApp()
|
||||
} else if key == kVK_ANSI_W {
|
||||
self?.view.superview?.window?.resignKey()
|
||||
} else if key == kVK_ANSI_R {
|
||||
self?.reloadWidget()
|
||||
} else if key == kVK_ANSI_C {
|
||||
self?.textLabel.currentEditor()?.copy(nil)
|
||||
}
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
|
||||
@@ -1,32 +1,44 @@
|
||||
import Cocoa
|
||||
import AppKit
|
||||
import Carbon
|
||||
|
||||
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 {
|
||||
let modifiers = event.modifierFlags.rawValue
|
||||
let key = event.keyCode
|
||||
|
||||
if event.type == NSEvent.EventType.keyDown {
|
||||
if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey {
|
||||
switch event.charactersIgnoringModifiers! {
|
||||
case "x":
|
||||
if NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: self) { return true }
|
||||
case "c":
|
||||
if NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: self) { return true }
|
||||
case "v":
|
||||
if NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: self) { return true }
|
||||
case "z":
|
||||
if NSApp.sendAction(Selector(("undo:")), to: nil, from: self) { return true }
|
||||
case "a":
|
||||
if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, from: self) { return true }
|
||||
default:
|
||||
break
|
||||
if modsContains(keys: OSCmd, in: modifiers) {
|
||||
if key == kVK_ANSI_X {
|
||||
if NSApp.sendAction(#selector(NSText.cut(_:)),
|
||||
to: nil, from: self)
|
||||
{ return true }
|
||||
} else if key == kVK_ANSI_C {
|
||||
if NSApp.sendAction(#selector(NSText.copy(_:)),
|
||||
to: nil, from: self)
|
||||
{ return true }
|
||||
} else if key == kVK_ANSI_V {
|
||||
if NSApp.sendAction(#selector(NSText.paste(_:)),
|
||||
to: nil, from: self)
|
||||
{ return true }
|
||||
} 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 {
|
||||
if event.charactersIgnoringModifiers == "Z" {
|
||||
if NSApp.sendAction(Selector(("redo:")), to: nil, from: self) { return true }
|
||||
} else if modsContains(keys: OSCmd | OSShift, in: modifiers) {
|
||||
if key == kVK_ANSI_Z {
|
||||
if NSApp.sendAction(Selector(("redo:")),
|
||||
to: nil, from: self)
|
||||
{ return true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.performKeyEquivalent(with: event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
class EventMonitor {
|
||||
fileprivate let mask: NSEvent.EventTypeMask
|
||||
|
||||
@@ -1,6 +1,32 @@
|
||||
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 {
|
||||
static let beginMenuTracking = Notification.Name("com.apple.HIToolbox.beginMenuTrackingNotification")
|
||||
static let endMenuTracking = Notification.Name("com.apple.HIToolbox.endMenuTrackingNotification")
|
||||
@@ -9,10 +35,10 @@ fileprivate extension Notification.Name {
|
||||
#if(DEBUG)
|
||||
struct EndpointConstants {
|
||||
static var versionURL = "http://localhost:8081/version"
|
||||
static var appURL = "http://localhost:8081/download"
|
||||
static var appURLZip = "http://localhost:8081/static/app/CmdBar.zip"
|
||||
static var changelog = "http://localhost:8081/changelog"
|
||||
static var validateURL = "http://localhost:8081/validate"
|
||||
static var appURL = "http://localhost:8081/download"
|
||||
static var appURLZip = "http://localhost:8081/static/app/CmdBar.zip"
|
||||
static var changelog = "http://localhost:8081/changelog"
|
||||
static var validateURL = "http://localhost:8081/validate"
|
||||
}
|
||||
#else
|
||||
struct EndpointConstants {
|
||||
@@ -24,7 +50,6 @@ struct EndpointConstants {
|
||||
}
|
||||
#endif
|
||||
|
||||
//
|
||||
enum Resulting<Success, Failure> {
|
||||
case success
|
||||
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]))
|
||||
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)?
|
||||
.withSymbolConfiguration(
|
||||
NSImage.SymbolConfiguration(textStyle: size, scale: scale)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// Created by Igor Kolokolnikov on 8/26/23.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AppKit
|
||||
import CryptoKit
|
||||
|
||||
fileprivate struct LicenseModel: Codable {
|
||||
|
||||
@@ -86,7 +86,11 @@ $(EXEC).app: $(EXEC)
|
||||
cp resources/AppIcon.icns $@/Contents/Resources/ && \
|
||||
cp $(EXEC) $@/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
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
class MenulessWindow: NSWindow {
|
||||
init(viewController: NSViewController) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
class PopoverPanel: NSPanel {
|
||||
override var canBecomeKey: Bool { true }
|
||||
@@ -6,7 +6,8 @@ class PopoverPanel: NSPanel {
|
||||
init(viewController: NSViewController) {
|
||||
super.init(
|
||||
contentRect: CGRect(x: 0, y: 0, width: 100, height: 100),
|
||||
styleMask: [.titled, .nonactivatingPanel, .utilityWindow, .fullSizeContentView],
|
||||
styleMask: [.titled, .nonactivatingPanel, .utilityWindow,
|
||||
.fullSizeContentView],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
@@ -22,7 +23,8 @@ class PopoverPanel: NSPanel {
|
||||
titlebarAppearsTransparent = true
|
||||
|
||||
animationBehavior = .none
|
||||
collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary, .transient]
|
||||
collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary,
|
||||
.transient]
|
||||
isReleasedWhenClosed = false
|
||||
hidesOnDeactivate = false
|
||||
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
//
|
||||
// UpdateViewController.swift
|
||||
// CmdBar
|
||||
//
|
||||
// Created by Igor Kolokolnikov on 5/23/24.
|
||||
//
|
||||
|
||||
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 {
|
||||
case check = "Check for updates..."
|
||||
case latest = "You're up-to-date!"
|
||||
case checking = "Checking for udpates..."
|
||||
case available = "A new version is available!"
|
||||
case downloading = "Downloading update..."
|
||||
case extracting = "Extracting update..."
|
||||
case install = "Ready to Install"
|
||||
case checkFailed = "Failed to Check for Updates"
|
||||
case check = "Check for updates..."
|
||||
case latest = "You're up-to-date!"
|
||||
case checking = "Checking for udpates..."
|
||||
case available = "A new version is available!"
|
||||
case downloading = "Downloading update..."
|
||||
case extracting = "Extracting update..."
|
||||
case install = "Ready to Install"
|
||||
case checkFailed = "Failed to Check for Updates"
|
||||
case installFailed = "Failed to Install Update"
|
||||
}
|
||||
|
||||
@@ -30,9 +16,9 @@ class UpdateViewController: NSViewController, UpdateManagerDelegate {
|
||||
private weak var updaterDelegate: UpdateManagerDelegate?
|
||||
|
||||
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
|
||||
@@ -45,7 +31,9 @@ class UpdateViewController: NSViewController, UpdateManagerDelegate {
|
||||
textField.drawsBackground = false
|
||||
textField.alignment = .left
|
||||
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
|
||||
return textField
|
||||
}()
|
||||
@@ -59,18 +47,23 @@ class UpdateViewController: NSViewController, UpdateManagerDelegate {
|
||||
textField.drawsBackground = false
|
||||
textField.alignment = .left
|
||||
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
|
||||
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()
|
||||
scrollableTextView.isHidden = true
|
||||
(scrollableTextView.documentView as? NSTextView)?.isEditable = false
|
||||
// (scrollableTextView.documentView as? NSTextView)?.drawsBackground = false
|
||||
(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
|
||||
return scrollableTextView
|
||||
}()
|
||||
@@ -139,26 +132,46 @@ class UpdateViewController: NSViewController, UpdateManagerDelegate {
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
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),
|
||||
appIconImage.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing10),
|
||||
statusLabel.topAnchor
|
||||
.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),
|
||||
statusLabel.leadingAnchor.constraint(equalTo: appIconImage.trailingAnchor, constant: ViewConstants.spacing10),
|
||||
subStatusLabel.topAnchor
|
||||
.constraint(equalTo: statusLabel.bottomAnchor,
|
||||
constant: ViewConstants.spacing10),
|
||||
subStatusLabel.leadingAnchor
|
||||
.constraint(equalTo: statusLabel.leadingAnchor),
|
||||
|
||||
subStatusLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: ViewConstants.spacing10),
|
||||
subStatusLabel.leadingAnchor.constraint(equalTo: statusLabel.leadingAnchor),
|
||||
|
||||
changelogLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor),
|
||||
changelogLabel.widthAnchor
|
||||
.constraint(equalTo: stackView.widthAnchor),
|
||||
changelogLabel.heightAnchor.constraint(equalToConstant: 150),
|
||||
|
||||
stackView.topAnchor.constraint(equalTo: subStatusLabel.bottomAnchor, constant: ViewConstants.spacing2),
|
||||
stackView.leadingAnchor.constraint(equalTo: statusLabel.leadingAnchor),
|
||||
view.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: ViewConstants.spacing20),
|
||||
view.bottomAnchor.constraint(equalTo: stackView.bottomAnchor, constant: ViewConstants.spacing20),
|
||||
stackView.topAnchor
|
||||
.constraint(equalTo: subStatusLabel.bottomAnchor,
|
||||
constant: ViewConstants.spacing2),
|
||||
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:
|
||||
break
|
||||
case .latest:
|
||||
self?.statusLabel.stringValue = UpdateStatus.latest.rawValue
|
||||
self?.statusLabel.stringValue = UpdateStatus.latest
|
||||
.rawValue
|
||||
break
|
||||
case .checking:
|
||||
self?.statusLabel.stringValue = UpdateStatus.checking.rawValue
|
||||
self?.statusLabel.stringValue = UpdateStatus.checking
|
||||
.rawValue
|
||||
self?.updateButton.keyEquivalent = ""
|
||||
self?.updateButton.isEnabled = false
|
||||
case .available:
|
||||
self?.updateButton.title = "Download"
|
||||
self?.updateButton.keyEquivalent = ""
|
||||
self?.statusLabel.stringValue = UpdateStatus.available.rawValue
|
||||
self?.statusLabel.stringValue = UpdateStatus.available
|
||||
.rawValue
|
||||
self?.updateButton.action = #selector(self!.downloadUpdate)
|
||||
case .downloading:
|
||||
self?.updateButton.isEnabled = false
|
||||
self?.updateButton.title = "Install"
|
||||
self?.progressIndicator.isHidden = false
|
||||
self?.statusLabel.stringValue = UpdateStatus.downloading.rawValue
|
||||
self?.statusLabel.stringValue = UpdateStatus.downloading
|
||||
.rawValue
|
||||
case .extracting:
|
||||
self?.updateButton.isEnabled = false
|
||||
self?.updateButton.title = "Install"
|
||||
self?.progressIndicator.isHidden = false
|
||||
self?.statusLabel.stringValue = UpdateStatus.extracting.rawValue
|
||||
self?.statusLabel.stringValue = UpdateStatus.extracting
|
||||
.rawValue
|
||||
case .install:
|
||||
self?.updateButton.title = "Install"
|
||||
self?.progressIndicator.isHidden = false
|
||||
self?.statusLabel.stringValue = UpdateStatus.install.rawValue
|
||||
self?.statusLabel.stringValue = UpdateStatus.install
|
||||
.rawValue
|
||||
self?.updateButton.action = #selector(self!.installUpdate)
|
||||
case .checkFailed:
|
||||
self?.statusLabel.stringValue = UpdateStatus.checkFailed.rawValue
|
||||
self?.statusLabel.stringValue = UpdateStatus.checkFailed
|
||||
.rawValue
|
||||
case .installFailed:
|
||||
self?.statusLabel.stringValue = UpdateStatus.installFailed.rawValue
|
||||
self?.statusLabel.stringValue = UpdateStatus.installFailed
|
||||
.rawValue
|
||||
self?.updateButton.action = #selector(self!.checkForUpdate)
|
||||
}
|
||||
}
|
||||
@@ -241,8 +262,13 @@ class UpdateViewController: NSViewController, UpdateManagerDelegate {
|
||||
self?.subStatusLabel.isHidden = false
|
||||
self?.changelogLabel.isHidden = false
|
||||
for (i, change) in model.changes.enumerated() {
|
||||
(self?.changelogLabel.documentView as? NSTextView)?.string += "\u{2022} \(change)"
|
||||
if i < model.changes.count-1 { (self?.changelogLabel.documentView as? NSTextView)?.string += "\n" }
|
||||
(self?.changelogLabel.documentView as?
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user