Files
shotscreen/ShotScreen/Sources/MenuManager.swift
Nick Roodenrijs 0dabed11d2 🎉 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.
2025-06-28 16:15:15 +02:00

783 lines
33 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 }
}