🎉 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:
2025-06-28 16:15:15 +02:00
commit 0dabed11d2
63 changed files with 25727 additions and 0 deletions

View 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 }
}