🎉 ShotScreen v1.0 - Initial Release
🚀 First official release of ShotScreen with complete feature set: ✨ Core Features: - Advanced screenshot capture system - Multi-monitor support - Professional UI/UX design - Automated update system with Sparkle - Apple notarized & code signed 🛠 Technical Excellence: - Native Swift macOS application - Professional build & deployment pipeline - Comprehensive error handling - Memory optimized performance 📦 Distribution Ready: - Professional DMG packaging - Apple notarization complete - No security warnings for users - Ready for public distribution This is the foundation release that establishes ShotScreen as a premium screenshot tool for macOS.
This commit is contained in:
783
ShotScreen/Sources/MenuManager.swift
Normal file
783
ShotScreen/Sources/MenuManager.swift
Normal file
@@ -0,0 +1,783 @@
|
||||
import AppKit
|
||||
|
||||
// MARK: - Menu Management
|
||||
class MenuManager {
|
||||
weak var delegate: MenuManagerDelegate?
|
||||
private var statusItem: NSStatusItem?
|
||||
|
||||
init(delegate: MenuManagerDelegate) {
|
||||
self.delegate = delegate
|
||||
|
||||
// Add observer for shortcut changes to update menu
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(updateShortcuts),
|
||||
name: .shortcutSettingChanged,
|
||||
object: nil
|
||||
)
|
||||
|
||||
// Add observer for desktop icons setting changes
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(handleDesktopIconsSettingChanged),
|
||||
name: .hideDesktopIconsSettingChanged,
|
||||
object: nil
|
||||
)
|
||||
|
||||
// Add observer for desktop widgets setting changes
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(handleDesktopWidgetsSettingChanged),
|
||||
name: .hideDesktopWidgetsSettingChanged,
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
func setupMenu() {
|
||||
setupMainMenu()
|
||||
setupStatusBarMenu()
|
||||
|
||||
// Set initial shortcuts
|
||||
updateShortcuts()
|
||||
}
|
||||
|
||||
private func setupMainMenu() {
|
||||
// Create main menu
|
||||
let mainMenu = NSMenu()
|
||||
|
||||
// Application menu (first menu)
|
||||
let appMenu = NSMenu()
|
||||
let appName = "ShotScreen"
|
||||
|
||||
// About item with info icon
|
||||
let aboutItem = NSMenuItem(title: "About \(appName)", action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)), keyEquivalent: "")
|
||||
if let infoIcon = NSImage(systemSymbolName: "info.circle", accessibilityDescription: "About") {
|
||||
infoIcon.size = NSSize(width: 16, height: 16)
|
||||
aboutItem.image = infoIcon
|
||||
}
|
||||
appMenu.addItem(aboutItem)
|
||||
appMenu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Settings item with gear icon
|
||||
let settingsItem = NSMenuItem(title: "Settings", action: #selector(MenuManager.openSettings), keyEquivalent: ",")
|
||||
settingsItem.target = self
|
||||
if let gearIcon = NSImage(systemSymbolName: "gearshape", accessibilityDescription: "Settings") {
|
||||
gearIcon.size = NSSize(width: 16, height: 16)
|
||||
settingsItem.image = gearIcon
|
||||
}
|
||||
appMenu.addItem(settingsItem)
|
||||
|
||||
// Show Stash item with folder icon
|
||||
let showStashItem = NSMenuItem(title: "Show Stash", action: #selector(MenuManager.showStash), keyEquivalent: "s")
|
||||
showStashItem.target = self
|
||||
if let folderIcon = NSImage(systemSymbolName: "folder", accessibilityDescription: "Folder") {
|
||||
folderIcon.size = NSSize(width: 16, height: 16)
|
||||
showStashItem.image = folderIcon
|
||||
}
|
||||
appMenu.addItem(showStashItem)
|
||||
|
||||
// NIEUW: Reset First Launch Wizard (for testing)
|
||||
appMenu.addItem(NSMenuItem.separator())
|
||||
let resetWizardItem = NSMenuItem(title: "Reset First Launch Wizard", action: #selector(MenuManager.resetFirstLaunchWizard), keyEquivalent: "")
|
||||
resetWizardItem.target = self
|
||||
if let resetIcon = NSImage(systemSymbolName: "arrow.clockwise", accessibilityDescription: "Reset") {
|
||||
resetIcon.size = NSSize(width: 16, height: 16)
|
||||
resetWizardItem.image = resetIcon
|
||||
}
|
||||
appMenu.addItem(resetWizardItem)
|
||||
|
||||
appMenu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Quit item with power icon
|
||||
let quitItem = NSMenuItem(title: "Quit \(appName)", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
|
||||
if let powerIcon = NSImage(systemSymbolName: "power", accessibilityDescription: "Quit") {
|
||||
powerIcon.size = NSSize(width: 16, height: 16)
|
||||
quitItem.image = powerIcon
|
||||
}
|
||||
appMenu.addItem(quitItem)
|
||||
|
||||
// Add app menu to main menu
|
||||
let appMenuItem = NSMenuItem(title: appName, action: nil, keyEquivalent: "")
|
||||
appMenuItem.submenu = appMenu
|
||||
mainMenu.addItem(appMenuItem)
|
||||
|
||||
// Set the menu
|
||||
NSApplication.shared.mainMenu = mainMenu
|
||||
}
|
||||
|
||||
private func setupStatusBarMenu() {
|
||||
// Create status item in menu bar
|
||||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
if let button = statusItem?.button {
|
||||
// Use MenuIcon for status bar instead of AppIcon
|
||||
if let bundle = Bundle.main.url(forResource: "MenuIcon", withExtension: "png", subdirectory: "images"),
|
||||
let menuIcon = NSImage(contentsOf: bundle) {
|
||||
menuIcon.size = NSSize(width: 16, height: 16)
|
||||
button.image = menuIcon
|
||||
} else if let menuIcon = NSImage(named: "MenuIcon") {
|
||||
menuIcon.size = NSSize(width: 16, height: 16)
|
||||
button.image = menuIcon
|
||||
} else if let screenshotIcon = NSImage(systemSymbolName: "camera.aperture", accessibilityDescription: "ShotScreen") {
|
||||
// Fallback to aperture icon if menu icon not available
|
||||
screenshotIcon.size = NSSize(width: 16, height: 16)
|
||||
button.image = screenshotIcon
|
||||
} else {
|
||||
// Final fallback to green dot
|
||||
button.image = NSImage(named: "NSStatusAvailable")
|
||||
}
|
||||
|
||||
// Debug: Print all status bar items
|
||||
debugPrintStatusBarItems()
|
||||
|
||||
// Create a menu for the status item
|
||||
let statusMenu = NSMenu()
|
||||
|
||||
// Take Screenshot menu item with camera icon
|
||||
let screenshotItem = NSMenuItem(title: "Take Screenshot", action: #selector(MenuManager.triggerScreenshot), keyEquivalent: "")
|
||||
screenshotItem.target = self
|
||||
if let cameraIcon = NSImage(systemSymbolName: "camera", accessibilityDescription: "Camera") {
|
||||
cameraIcon.size = NSSize(width: 16, height: 16)
|
||||
screenshotItem.image = cameraIcon
|
||||
}
|
||||
updateScreenshotShortcut(screenshotItem) // Set initial shortcut
|
||||
statusMenu.addItem(screenshotItem)
|
||||
|
||||
// NIEUW: Capture Whole Screen (Double Hotkey) menu item with monitor icon
|
||||
let wholeScreenItem = NSMenuItem(title: "Capture This Screen", action: #selector(MenuManager.triggerWholeScreenCapture), keyEquivalent: "")
|
||||
wholeScreenItem.target = self
|
||||
if let monitorIcon = NSImage(systemSymbolName: "display", accessibilityDescription: "Monitor") {
|
||||
monitorIcon.size = NSSize(width: 16, height: 16)
|
||||
wholeScreenItem.image = monitorIcon
|
||||
}
|
||||
updateWholeScreenCaptureShortcut(wholeScreenItem) // Set initial shortcut
|
||||
statusMenu.addItem(wholeScreenItem)
|
||||
|
||||
// NIEUW: Capture All Screens menu item
|
||||
let allScreensItem = NSMenuItem(title: "Capture All Screens", action: #selector(MenuManager.triggerAllScreensCapture), keyEquivalent: "")
|
||||
allScreensItem.target = self
|
||||
if let screensIcon = NSImage(systemSymbolName: "rectangle.3.group", accessibilityDescription: "All Screens") {
|
||||
screensIcon.size = NSSize(width: 16, height: 16)
|
||||
allScreensItem.image = screensIcon
|
||||
}
|
||||
updateAllScreensCaptureShortcut(allScreensItem) // Set initial shortcut
|
||||
statusMenu.addItem(allScreensItem)
|
||||
|
||||
// NIEUW: Simple Window Capture menu item (native macOS)
|
||||
let windowCaptureItem = NSMenuItem(title: "Capture Window", action: #selector(MenuManager.triggerNativeWindowCapture), keyEquivalent: "")
|
||||
windowCaptureItem.target = self
|
||||
if let windowIcon = NSImage(systemSymbolName: "macwindow", accessibilityDescription: "Window") {
|
||||
windowIcon.size = NSSize(width: 16, height: 16)
|
||||
windowCaptureItem.image = windowIcon
|
||||
}
|
||||
updateWindowCaptureShortcut(windowCaptureItem) // Set initial shortcut display
|
||||
statusMenu.addItem(windowCaptureItem)
|
||||
|
||||
// Add separator between screenshot actions and other options
|
||||
statusMenu.addItem(NSMenuItem.separator())
|
||||
|
||||
// NIEUW: Toggle Desktop Icons menu item
|
||||
let hideIconsItem = NSMenuItem(title: "", action: #selector(MenuManager.toggleDesktopIcons), keyEquivalent: "")
|
||||
hideIconsItem.target = self
|
||||
updateDesktopIconsMenuItem(hideIconsItem) // Set initial title and icon
|
||||
statusMenu.addItem(hideIconsItem)
|
||||
print("➕ Desktop Icons menu item added with initial state")
|
||||
|
||||
// NIEUW: Toggle Desktop Widgets menu item
|
||||
let hideWidgetsItem = NSMenuItem(title: "", action: #selector(MenuManager.toggleDesktopWidgets), keyEquivalent: "")
|
||||
hideWidgetsItem.target = self
|
||||
updateDesktopWidgetsMenuItem(hideWidgetsItem) // Set initial title and icon
|
||||
statusMenu.addItem(hideWidgetsItem)
|
||||
print("➕ Desktop Widgets menu item added with initial state")
|
||||
|
||||
// Add separator between screenshot actions and other options
|
||||
statusMenu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Show Stash menu item with folder icon
|
||||
let stashItem = NSMenuItem(title: "Show Stash", action: #selector(MenuManager.showStash), keyEquivalent: "")
|
||||
stashItem.target = self
|
||||
if let folderIcon = NSImage(systemSymbolName: "folder", accessibilityDescription: "Folder") {
|
||||
folderIcon.size = NSSize(width: 16, height: 16)
|
||||
stashItem.image = folderIcon
|
||||
}
|
||||
statusMenu.addItem(stashItem)
|
||||
|
||||
|
||||
|
||||
// 🔄 UPDATE: Check for Updates menu item with arrow icon
|
||||
let updatesItem = NSMenuItem(title: "Check for Updates", action: #selector(MenuManager.checkForUpdates), keyEquivalent: "")
|
||||
updatesItem.target = self
|
||||
updatesItem.isEnabled = UpdateManager.shared.isUpdaterAvailable
|
||||
if let updateIcon = NSImage(systemSymbolName: "arrow.clockwise.circle", accessibilityDescription: "Check for Updates") {
|
||||
updateIcon.size = NSSize(width: 16, height: 16)
|
||||
updatesItem.image = updateIcon
|
||||
}
|
||||
|
||||
|
||||
statusMenu.addItem(NSMenuItem.separator())
|
||||
|
||||
// NIEUW: Reset First Launch Wizard (for testing) - also in status bar menu
|
||||
let resetWizardItem = NSMenuItem(title: "Reset First Launch Wizard", action: #selector(MenuManager.resetFirstLaunchWizard), keyEquivalent: "")
|
||||
resetWizardItem.target = self
|
||||
if let resetIcon = NSImage(systemSymbolName: "arrow.clockwise", accessibilityDescription: "Reset") {
|
||||
resetIcon.size = NSSize(width: 16, height: 16)
|
||||
resetWizardItem.image = resetIcon
|
||||
}
|
||||
statusMenu.addItem(resetWizardItem)
|
||||
|
||||
statusMenu.addItem(NSMenuItem.separator())
|
||||
// Settings menu item with gear icon
|
||||
let settingsItem = NSMenuItem(title: "Settings", action: #selector(MenuManager.openSettings), keyEquivalent: "")
|
||||
settingsItem.target = self
|
||||
if let gearIcon = NSImage(systemSymbolName: "gearshape", accessibilityDescription: "Settings") {
|
||||
gearIcon.size = NSSize(width: 16, height: 16)
|
||||
settingsItem.image = gearIcon
|
||||
}
|
||||
statusMenu.addItem(settingsItem)
|
||||
// Quit menu item with power icon
|
||||
let quitItem = NSMenuItem(title: "Quit ShotScreen", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "")
|
||||
if let powerIcon = NSImage(systemSymbolName: "power", accessibilityDescription: "Quit") {
|
||||
powerIcon.size = NSSize(width: 16, height: 16)
|
||||
quitItem.image = powerIcon
|
||||
}
|
||||
statusMenu.addItem(quitItem)
|
||||
|
||||
statusItem?.menu = statusMenu
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Menu Actions
|
||||
@objc func triggerScreenshot() {
|
||||
delegate?.triggerScreenshot()
|
||||
}
|
||||
|
||||
@objc func triggerWholeScreenCapture() {
|
||||
delegate?.triggerWholeScreenCapture()
|
||||
}
|
||||
|
||||
@objc func triggerNativeWindowCapture() {
|
||||
delegate?.triggerNativeWindowCapture()
|
||||
}
|
||||
|
||||
@objc func triggerAllScreensCapture() {
|
||||
delegate?.triggerAllScreensCapture()
|
||||
}
|
||||
|
||||
@objc func openSettings() {
|
||||
delegate?.openSettings()
|
||||
}
|
||||
|
||||
@objc func showStash() {
|
||||
delegate?.showStash()
|
||||
}
|
||||
|
||||
@objc func resetFirstLaunchWizard() {
|
||||
delegate?.resetFirstLaunchWizard()
|
||||
}
|
||||
|
||||
@objc func toggleDesktopIcons() {
|
||||
delegate?.toggleDesktopIcons()
|
||||
}
|
||||
|
||||
@objc func toggleDesktopWidgets() {
|
||||
delegate?.toggleDesktopWidgets()
|
||||
}
|
||||
|
||||
@objc func checkForUpdates() {
|
||||
delegate?.checkForUpdates()
|
||||
}
|
||||
|
||||
// MARK: - Dynamic Version Helper
|
||||
private func getDynamicAboutTitle() -> String {
|
||||
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
|
||||
return "ShotScreen v\(version)"
|
||||
}
|
||||
|
||||
@objc func showAbout() {
|
||||
delegate?.showAbout()
|
||||
}
|
||||
|
||||
@objc func exitApp() {
|
||||
delegate?.shouldTerminate = true
|
||||
NSApplication.shared.terminate(self)
|
||||
}
|
||||
|
||||
// MARK: - Menu Item Updates
|
||||
private func updateDesktopIconsMenuItem(_ menuItem: NSMenuItem) {
|
||||
let isHidden = SettingsManager.shared.hideDesktopIconsDuringScreenshot
|
||||
|
||||
if isHidden {
|
||||
menuItem.title = "Show Desktop Icons"
|
||||
// Try specific icon first, fallback to generic folder icon
|
||||
if let icon = NSImage(systemSymbolName: "folder.badge.minus", accessibilityDescription: "Desktop Icons Hidden") {
|
||||
icon.size = NSSize(width: 16, height: 16)
|
||||
menuItem.image = icon
|
||||
} else if let fallbackIcon = NSImage(systemSymbolName: "folder", accessibilityDescription: "Folder") {
|
||||
fallbackIcon.size = NSSize(width: 16, height: 16)
|
||||
menuItem.image = fallbackIcon
|
||||
} else {
|
||||
menuItem.image = nil
|
||||
}
|
||||
} else {
|
||||
menuItem.title = "Hide Desktop Icons"
|
||||
// Try specific icon first, fallback to generic folder icon
|
||||
if let icon = NSImage(systemSymbolName: "folder.badge.plus", accessibilityDescription: "Desktop Icons Visible") {
|
||||
icon.size = NSSize(width: 16, height: 16)
|
||||
menuItem.image = icon
|
||||
} else if let fallbackIcon = NSImage(systemSymbolName: "folder", accessibilityDescription: "Folder") {
|
||||
fallbackIcon.size = NSSize(width: 16, height: 16)
|
||||
menuItem.image = fallbackIcon
|
||||
} else {
|
||||
menuItem.image = nil
|
||||
}
|
||||
}
|
||||
|
||||
print("🖥️ Desktop Icons menu item updated: '\(menuItem.title)' (Hidden: \(isHidden))")
|
||||
}
|
||||
|
||||
private func updateDesktopWidgetsMenuItem(_ menuItem: NSMenuItem) {
|
||||
let isHidden = SettingsManager.shared.hideDesktopWidgetsDuringScreenshot
|
||||
|
||||
if isHidden {
|
||||
menuItem.title = "Show Desktop Widgets"
|
||||
// Try specific widget icons first, fallback to simpler rectangle icon
|
||||
if let icon = NSImage(systemSymbolName: "rectangle.3.group.bubble", accessibilityDescription: "Desktop Widgets Hidden") {
|
||||
icon.size = NSSize(width: 16, height: 16)
|
||||
menuItem.image = icon
|
||||
} else if let fallbackIcon = NSImage(systemSymbolName: "rectangle.3.group", accessibilityDescription: "Widgets") {
|
||||
fallbackIcon.size = NSSize(width: 16, height: 16)
|
||||
menuItem.image = fallbackIcon
|
||||
} else if let simpleIcon = NSImage(systemSymbolName: "rectangle.grid.2x2", accessibilityDescription: "Grid") {
|
||||
simpleIcon.size = NSSize(width: 16, height: 16)
|
||||
menuItem.image = simpleIcon
|
||||
} else {
|
||||
menuItem.image = nil
|
||||
}
|
||||
} else {
|
||||
menuItem.title = "Hide Desktop Widgets"
|
||||
// Try specific widget icons first, fallback to simpler rectangle icon
|
||||
if let icon = NSImage(systemSymbolName: "rectangle.3.group.bubble.fill", accessibilityDescription: "Desktop Widgets Visible") {
|
||||
icon.size = NSSize(width: 16, height: 16)
|
||||
menuItem.image = icon
|
||||
} else if let fallbackIcon = NSImage(systemSymbolName: "rectangle.3.group.fill", accessibilityDescription: "Widgets") {
|
||||
fallbackIcon.size = NSSize(width: 16, height: 16)
|
||||
menuItem.image = fallbackIcon
|
||||
} else if let simpleIcon = NSImage(systemSymbolName: "rectangle.grid.2x2.fill", accessibilityDescription: "Grid") {
|
||||
simpleIcon.size = NSSize(width: 16, height: 16)
|
||||
menuItem.image = simpleIcon
|
||||
} else {
|
||||
menuItem.image = nil
|
||||
}
|
||||
}
|
||||
|
||||
print("📱 Desktop Widgets menu item updated: '\(menuItem.title)' (Hidden: \(isHidden))")
|
||||
}
|
||||
|
||||
// Public method to refresh menu when settings change
|
||||
func refreshDesktopIconsMenuItem() {
|
||||
print("🔄 Refreshing desktop icons menu item...")
|
||||
if let menu = statusItem?.menu {
|
||||
// Find the desktop icons menu item (it should be the one with our action)
|
||||
var foundItem = false
|
||||
for item in menu.items {
|
||||
if item.action == #selector(MenuManager.toggleDesktopIcons) {
|
||||
updateDesktopIconsMenuItem(item)
|
||||
foundItem = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundItem {
|
||||
print("⚠️ Desktop icons menu item not found!")
|
||||
}
|
||||
} else {
|
||||
print("⚠️ Status item menu not available!")
|
||||
}
|
||||
}
|
||||
|
||||
// Public method to refresh widgets menu when settings change
|
||||
func refreshDesktopWidgetsMenuItem() {
|
||||
print("🔄 Refreshing desktop widgets menu item...")
|
||||
if let menu = statusItem?.menu {
|
||||
// Find the desktop widgets menu item (it should be the one with our action)
|
||||
var foundItem = false
|
||||
for item in menu.items {
|
||||
if item.action == #selector(MenuManager.toggleDesktopWidgets) {
|
||||
updateDesktopWidgetsMenuItem(item)
|
||||
foundItem = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundItem {
|
||||
print("⚠️ Desktop widgets menu item not found!")
|
||||
}
|
||||
} else {
|
||||
print("⚠️ Status item menu not available!")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Settings Change Handlers
|
||||
@objc private func handleDesktopIconsSettingChanged() {
|
||||
print("📢 Notification received: Desktop Icons setting changed")
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.refreshDesktopIconsMenuItem()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func handleDesktopWidgetsSettingChanged() {
|
||||
print("📢 Notification received: Desktop Widgets setting changed")
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.refreshDesktopWidgetsMenuItem()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Shortcut Management
|
||||
@objc func updateShortcuts() {
|
||||
// Update shortcuts in both main menu and status bar menu
|
||||
updateMenuShortcuts()
|
||||
}
|
||||
|
||||
private func updateMenuShortcuts() {
|
||||
// Update status bar menu shortcuts
|
||||
if let menu = statusItem?.menu {
|
||||
for item in menu.items {
|
||||
if item.action == #selector(MenuManager.triggerScreenshot) {
|
||||
updateScreenshotShortcut(item)
|
||||
} else if item.action == #selector(MenuManager.triggerWholeScreenCapture) {
|
||||
updateWholeScreenCaptureShortcut(item)
|
||||
} else if item.action == #selector(MenuManager.triggerNativeWindowCapture) {
|
||||
updateWindowCaptureShortcut(item)
|
||||
} else if item.action == #selector(MenuManager.triggerAllScreensCapture) {
|
||||
updateAllScreensCaptureShortcut(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update main menu shortcuts if needed
|
||||
updateMainMenuShortcuts()
|
||||
}
|
||||
|
||||
private func updateMainMenuShortcuts() {
|
||||
// Update shortcuts in the main application menu
|
||||
if let mainMenu = NSApplication.shared.mainMenu {
|
||||
// Find the application menu (first submenu)
|
||||
if let appMenuItem = mainMenu.items.first,
|
||||
let appSubmenu = appMenuItem.submenu {
|
||||
for item in appSubmenu.items {
|
||||
if item.action == #selector(MenuManager.openSettings) {
|
||||
// Settings already has "," shortcut, keep it
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateScreenshotShortcut(_ menuItem: NSMenuItem) {
|
||||
let settings = SettingsManager.shared
|
||||
|
||||
if settings.useCustomShortcut && settings.customShortcutModifiers != 0 && settings.customShortcutKey != 0 {
|
||||
// Use custom shortcut - display only, same as others
|
||||
let shortcutText = getShortcutDisplay(modifiers: settings.customShortcutModifiers, keyCode: settings.customShortcutKey)
|
||||
setMenuItemWithRightAlignedShortcut(menuItem, title: "Take Area Screenshot", shortcut: shortcutText)
|
||||
} else {
|
||||
// Use default Cmd+Shift+4 - display only, positioned slightly to the left
|
||||
setMenuItemWithRightAlignedShortcut(menuItem, title: "Take Area Screenshot", shortcut: "⌘⇧4")
|
||||
}
|
||||
|
||||
// Clear actual key equivalents since these are display-only
|
||||
menuItem.keyEquivalent = ""
|
||||
menuItem.keyEquivalentModifierMask = []
|
||||
}
|
||||
|
||||
private func updateWholeScreenCaptureShortcut(_ menuItem: NSMenuItem) {
|
||||
let settings = SettingsManager.shared
|
||||
let baseShortcut: String
|
||||
|
||||
if settings.useCustomShortcut && settings.customShortcutModifiers != 0 && settings.customShortcutKey != 0 {
|
||||
// Use custom shortcut
|
||||
baseShortcut = getShortcutDisplay(modifiers: settings.customShortcutModifiers, keyCode: settings.customShortcutKey)
|
||||
} else {
|
||||
// Use default shortcut
|
||||
baseShortcut = "⌘⇧4"
|
||||
}
|
||||
|
||||
// Display shortcut as: 2 x [shortcut] (double hotkey for whole screen)
|
||||
setMenuItemWithRightAlignedShortcut(menuItem, title: "Capture Screen", shortcut: "2 x \(baseShortcut)")
|
||||
|
||||
// Clear actual key equivalents since these are display-only
|
||||
menuItem.keyEquivalent = ""
|
||||
menuItem.keyEquivalentModifierMask = []
|
||||
}
|
||||
|
||||
private func updateWindowCaptureShortcut(_ menuItem: NSMenuItem) {
|
||||
let settings = SettingsManager.shared
|
||||
let baseShortcut: String
|
||||
|
||||
if settings.useCustomShortcut && settings.customShortcutModifiers != 0 && settings.customShortcutKey != 0 {
|
||||
// Use custom shortcut
|
||||
baseShortcut = getShortcutDisplay(modifiers: settings.customShortcutModifiers, keyCode: settings.customShortcutKey)
|
||||
} else {
|
||||
// Use default shortcut
|
||||
baseShortcut = "⌘⇧4"
|
||||
}
|
||||
|
||||
// Display shortcut as: [shortcut] → ␣ (indicating spacebar during selection)
|
||||
setMenuItemWithRightAlignedShortcut(menuItem, title: "Capture Window", shortcut: "\(baseShortcut) → ␣")
|
||||
|
||||
// Clear actual key equivalents since these are display-only
|
||||
menuItem.keyEquivalent = ""
|
||||
menuItem.keyEquivalentModifierMask = []
|
||||
}
|
||||
|
||||
private func updateAllScreensCaptureShortcut(_ menuItem: NSMenuItem) {
|
||||
let settings = SettingsManager.shared
|
||||
let baseShortcut: String
|
||||
|
||||
if settings.useCustomShortcut && settings.customShortcutModifiers != 0 && settings.customShortcutKey != 0 {
|
||||
// Use custom shortcut
|
||||
baseShortcut = getShortcutDisplay(modifiers: settings.customShortcutModifiers, keyCode: settings.customShortcutKey)
|
||||
} else {
|
||||
// Use default shortcut
|
||||
baseShortcut = "⌘⇧4"
|
||||
}
|
||||
|
||||
// Triple hotkey for all screens capture
|
||||
setMenuItemWithRightAlignedShortcut(menuItem, title: "Capture All Screens", shortcut: "3 x \(baseShortcut)")
|
||||
|
||||
// Clear actual key equivalents since these are display-only
|
||||
menuItem.keyEquivalent = ""
|
||||
menuItem.keyEquivalentModifierMask = []
|
||||
}
|
||||
|
||||
private func setMenuItemWithRightAlignedShortcut(_ menuItem: NSMenuItem, title: String, shortcut: String) {
|
||||
// Create attributed string with proper tab stop for right alignment
|
||||
let attributedTitle = NSMutableAttributedString()
|
||||
|
||||
// Create paragraph style with right-aligned tab stop
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
|
||||
// Calculate the right tab stop position (approximately 250 points from left margin)
|
||||
// This gives us consistent right alignment for all shortcuts
|
||||
let tabStopPosition: CGFloat = 250.0
|
||||
|
||||
// Create a right-aligned tab stop
|
||||
let tabStop = NSTextTab(textAlignment: .right, location: tabStopPosition, options: [:])
|
||||
paragraphStyle.tabStops = [tabStop]
|
||||
|
||||
// Add the main title with normal styling
|
||||
let titleString = NSAttributedString(string: title, attributes: [
|
||||
.font: NSFont.menuFont(ofSize: 0),
|
||||
.paragraphStyle: paragraphStyle
|
||||
])
|
||||
attributedTitle.append(titleString)
|
||||
|
||||
// Add tab character to move to the tab stop
|
||||
let tabString = NSAttributedString(string: "\t", attributes: [
|
||||
.paragraphStyle: paragraphStyle
|
||||
])
|
||||
attributedTitle.append(tabString)
|
||||
|
||||
// Add the shortcut with different styling at the tab stop (right-aligned)
|
||||
let shortcutString = NSAttributedString(string: shortcut, attributes: [
|
||||
.foregroundColor: NSColor.secondaryLabelColor,
|
||||
.font: NSFont.menuFont(ofSize: 0),
|
||||
.paragraphStyle: paragraphStyle
|
||||
])
|
||||
attributedTitle.append(shortcutString)
|
||||
|
||||
menuItem.attributedTitle = attributedTitle
|
||||
}
|
||||
|
||||
private func getShortcutDisplay(modifiers: UInt, keyCode: UInt16) -> String {
|
||||
var result = ""
|
||||
|
||||
// Add modifier symbols in correct order: Control, Option, Shift, Command
|
||||
if modifiers & (1 << 3) != 0 { result += "⌃" } // Control
|
||||
if modifiers & (1 << 2) != 0 { result += "⌥" } // Option
|
||||
if modifiers & (1 << 1) != 0 { result += "⇧" } // Shift
|
||||
if modifiers & (1 << 0) != 0 { result += "⌘" } // Command
|
||||
|
||||
let keyString = keyCodeToDisplayString(keyCode)
|
||||
result += keyString
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private func keyCodeToDisplayString(_ keyCode: UInt16) -> String {
|
||||
// Convert keyCode to display string (uppercase for better readability)
|
||||
switch keyCode {
|
||||
case 0: return "A"
|
||||
case 1: return "S"
|
||||
case 2: return "D"
|
||||
case 3: return "F"
|
||||
case 4: return "H"
|
||||
case 5: return "G"
|
||||
case 6: return "Z"
|
||||
case 7: return "X"
|
||||
case 8: return "C"
|
||||
case 9: return "V"
|
||||
case 11: return "B"
|
||||
case 12: return "Q"
|
||||
case 13: return "W"
|
||||
case 14: return "E"
|
||||
case 15: return "R"
|
||||
case 16: return "Y"
|
||||
case 17: return "T"
|
||||
case 18: return "1"
|
||||
case 19: return "2"
|
||||
case 20: return "3"
|
||||
case 21: return "4"
|
||||
case 22: return "6"
|
||||
case 23: return "5"
|
||||
case 24: return "="
|
||||
case 25: return "9"
|
||||
case 26: return "7"
|
||||
case 27: return "-"
|
||||
case 28: return "8"
|
||||
case 29: return "0"
|
||||
case 30: return "]"
|
||||
case 31: return "O"
|
||||
case 32: return "U"
|
||||
case 33: return "["
|
||||
case 34: return "I"
|
||||
case 35: return "P"
|
||||
case 37: return "L"
|
||||
case 38: return "J"
|
||||
case 39: return "'"
|
||||
case 40: return "K"
|
||||
case 41: return ";"
|
||||
case 42: return "\\"
|
||||
case 43: return ","
|
||||
case 44: return "/"
|
||||
case 45: return "N"
|
||||
case 46: return "M"
|
||||
case 47: return "."
|
||||
case 50: return "`"
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
|
||||
private func getModifierSymbols(_ modifier: UInt) -> String {
|
||||
var symbols: [String] = []
|
||||
|
||||
if modifier & (1 << 3) != 0 { symbols.append("⌃") } // Control
|
||||
if modifier & (1 << 2) != 0 { symbols.append("␣") } // Option (now Space symbol)
|
||||
if modifier & (1 << 1) != 0 { symbols.append("⇧") } // Shift
|
||||
if modifier & (1 << 0) != 0 { symbols.append("⌘") } // Command
|
||||
|
||||
return symbols.joined()
|
||||
}
|
||||
|
||||
// MARK: - License Management
|
||||
func setAppEnabled(_ enabled: Bool) {
|
||||
print("🔐 LICENSE: Setting app menu enabled state to: \(enabled)")
|
||||
|
||||
guard let menu = statusItem?.menu else {
|
||||
print("⚠️ LICENSE: Status item menu not available!")
|
||||
return
|
||||
}
|
||||
|
||||
// Disable/enable screenshot-related menu items
|
||||
for item in menu.items {
|
||||
if item.action == #selector(MenuManager.triggerScreenshot) ||
|
||||
item.action == #selector(MenuManager.triggerWholeScreenCapture) ||
|
||||
item.action == #selector(MenuManager.triggerNativeWindowCapture) ||
|
||||
item.action == #selector(MenuManager.triggerAllScreensCapture) ||
|
||||
item.action == #selector(MenuManager.showStash) ||
|
||||
item.action == #selector(MenuManager.toggleDesktopIcons) ||
|
||||
item.action == #selector(MenuManager.toggleDesktopWidgets) {
|
||||
item.isEnabled = enabled
|
||||
}
|
||||
// Keep Settings, Updates, About, and Quit always enabled
|
||||
}
|
||||
|
||||
// Update menu icon to indicate license status
|
||||
if !enabled {
|
||||
// Show different icon for trial/expired - use lock icon
|
||||
if let lockIcon = NSImage(systemSymbolName: "lock.fill", accessibilityDescription: "License Required") {
|
||||
lockIcon.size = NSSize(width: 16, height: 16)
|
||||
statusItem?.button?.image = lockIcon
|
||||
}
|
||||
statusItem?.button?.toolTip = "ShotScreen - License Required"
|
||||
} else {
|
||||
// Restore normal menu icon for licensed
|
||||
if let bundle = Bundle.main.url(forResource: "MenuIcon", withExtension: "png", subdirectory: "images"),
|
||||
let menuIcon = NSImage(contentsOf: bundle) {
|
||||
menuIcon.size = NSSize(width: 16, height: 16)
|
||||
statusItem?.button?.image = menuIcon
|
||||
} else if let menuIcon = NSImage(named: "MenuIcon") {
|
||||
menuIcon.size = NSSize(width: 16, height: 16)
|
||||
statusItem?.button?.image = menuIcon
|
||||
} else if let screenshotIcon = NSImage(systemSymbolName: "camera.aperture", accessibilityDescription: "ShotScreen") {
|
||||
screenshotIcon.size = NSSize(width: 16, height: 16)
|
||||
statusItem?.button?.image = screenshotIcon
|
||||
}
|
||||
statusItem?.button?.toolTip = "ShotScreen"
|
||||
}
|
||||
|
||||
// Clear any title to prevent duplicate icons
|
||||
statusItem?.button?.title = ""
|
||||
}
|
||||
|
||||
// MARK: - Cleanup
|
||||
func cleanup() {
|
||||
print("🧹 MenuManager: Cleaning up...")
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
|
||||
if let item = statusItem {
|
||||
NSStatusBar.system.removeStatusItem(item)
|
||||
print("🧹 STATUS: Removed status bar item successfully")
|
||||
}
|
||||
statusItem = nil
|
||||
}
|
||||
|
||||
deinit {
|
||||
print("🗑️ MenuManager: Deinitializing and cleaning up")
|
||||
cleanup()
|
||||
}
|
||||
|
||||
private func debugPrintStatusBarItems() {
|
||||
print("🔍 DEBUG: Checking all status bar items...")
|
||||
|
||||
// Try to get information about other status bar items
|
||||
let statusBar = NSStatusBar.system
|
||||
print("🔍 DEBUG: System status bar available: \(statusBar)")
|
||||
|
||||
// Check if there might be multiple ShotScreen processes running
|
||||
let runningApps = NSWorkspace.shared.runningApplications
|
||||
let shotScreenApps = runningApps.filter { $0.bundleIdentifier?.contains("ShotScreen") == true || $0.localizedName?.contains("ShotScreen") == true }
|
||||
|
||||
print("🔍 DEBUG: Found \(shotScreenApps.count) ShotScreen-related processes:")
|
||||
for app in shotScreenApps {
|
||||
print(" - \(app.localizedName ?? "Unknown") (PID: \(app.processIdentifier))")
|
||||
}
|
||||
|
||||
// Check for screenshot-related apps
|
||||
let screenshotApps = runningApps.filter {
|
||||
let name = $0.localizedName?.lowercased() ?? ""
|
||||
let bundle = $0.bundleIdentifier?.lowercased() ?? ""
|
||||
return name.contains("screenshot") || name.contains("capture") || name.contains("screen") ||
|
||||
bundle.contains("screenshot") || bundle.contains("capture") || bundle.contains("screen")
|
||||
}
|
||||
|
||||
print("🔍 DEBUG: Found \(screenshotApps.count) screenshot/capture-related apps:")
|
||||
for app in screenshotApps {
|
||||
print(" - \(app.localizedName ?? "Unknown"): \(app.bundleIdentifier ?? "No Bundle ID")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MenuManager Delegate Protocol
|
||||
protocol MenuManagerDelegate: AnyObject {
|
||||
func triggerScreenshot()
|
||||
func triggerWholeScreenCapture()
|
||||
func triggerNativeWindowCapture()
|
||||
func triggerAllScreensCapture()
|
||||
func openSettings()
|
||||
func showStash()
|
||||
func resetFirstLaunchWizard()
|
||||
func toggleDesktopIcons()
|
||||
func toggleDesktopWidgets()
|
||||
func checkForUpdates()
|
||||
func showAbout()
|
||||
var shouldTerminate: Bool { get set }
|
||||
}
|
||||
Reference in New Issue
Block a user