Dynamic Popup/Dropdown shortcut

This commit is contained in:
Hariharan Mudaliar 2025-08-12 22:17:23 +05:30
commit e29745f8f4
6 changed files with 139 additions and 19 deletions

View file

@ -450,8 +450,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
ClipboardManager.shared.startMonitoring()
}
// Check if window mode is enabled
if Defaults[.clipboardWindowMode] {
// Determine display mode based on user preference and context
let shouldUseWindow = self.shouldUseWindowMode()
if shouldUseWindow {
// Use window interface
ClipboardWindowManager.shared.toggleClipboardWindow()
} else {
@ -622,6 +624,60 @@ class AppDelegate: NSObject, NSApplicationDelegate {
NSApplication.shared.terminate(nil)
}
// MARK: - Clipboard Display Mode Logic
private func shouldUseWindowMode() -> Bool {
let displayMode = Defaults[.clipboardDisplayMode]
switch displayMode {
case .window:
return true
case .popover:
return false
case .auto:
// Auto mode: use window if any app is in fullscreen, otherwise use popover
return isAnyAppInFullscreen()
}
}
private func isAnyAppInFullscreen() -> Bool {
// Check if any running application is in fullscreen mode
let workspace = NSWorkspace.shared
// Get all visible applications
let apps = workspace.runningApplications.filter { app in
app.activationPolicy == .regular && !app.isTerminated
}
// Check each application's windows for fullscreen state
for app in apps {
if app.isActive {
// Check if the frontmost application has fullscreen windows
let windows = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) as? [[String: Any]] ?? []
for window in windows {
if let ownerPID = window[kCGWindowOwnerPID as String] as? Int32,
ownerPID == app.processIdentifier,
let bounds = window[kCGWindowBounds as String] as? [String: Any],
let width = bounds["Width"] as? CGFloat,
let height = bounds["Height"] as? CGFloat {
// Check if window dimensions match screen dimensions (indicating fullscreen)
for screen in NSScreen.screens {
let screenFrame = screen.frame
if abs(width - screenFrame.width) < 10 && abs(height - screenFrame.height) < 10 {
return true
}
}
}
}
}
}
return false
}
private func showOnboardingWindow() {
if onboardingWindowController == nil {
let window = NSWindow(

View file

@ -136,6 +136,9 @@
},
"Appearance" : {
},
"Auto mode intelligently chooses between popover (desktop) and window (fullscreen apps) for the best experience. You can override this with manual selection." : {
},
"Automatically check for updates" : {
"localizations" : {
@ -388,6 +391,9 @@
},
"Disk Write" : {
},
"Display Mode" : {
},
"Download" : {
@ -1196,9 +1202,6 @@
},
"When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery." : {
},
"Window Mode shows clipboard in a moveable window like Raycast with tabs for history and favorites. When disabled, clipboard appears in a popover attached to the Dynamic Island." : {
},
"With %lld graphs enabled, the Dynamic Island will expand horizontally to accommodate all graphs in a single row." : {

View file

@ -63,7 +63,25 @@ struct DynamicIslandHeader: View {
if Defaults[.enableClipboardManager] && Defaults[.showClipboardIcon] {
Button(action: {
showClipboardPopover.toggle()
// Use the same smart logic as the keyboard shortcut
let displayMode = Defaults[.clipboardDisplayMode]
let shouldUseWindow: Bool
switch displayMode {
case .window:
shouldUseWindow = true
case .popover:
shouldUseWindow = false
case .auto:
// For header button clicks, prefer popover since user is already interacting with notch
shouldUseWindow = false
}
if shouldUseWindow {
ClipboardWindowManager.shared.toggleClipboardWindow()
} else {
showClipboardPopover.toggle()
}
}) {
Capsule()
.fill(.black)

View file

@ -1589,7 +1589,7 @@ struct ClipboardSettings: View {
@Default(.enableClipboardManager) var enableClipboardManager
@Default(.clipboardHistorySize) var clipboardHistorySize
@Default(.showClipboardIcon) var showClipboardIcon
@Default(.clipboardWindowMode) var clipboardWindowMode
@Default(.clipboardDisplayMode) var clipboardDisplayMode
var body: some View {
Form {
@ -1612,7 +1612,24 @@ struct ClipboardSettings: View {
Section {
Defaults.Toggle("Show Clipboard Icon", key: .showClipboardIcon)
Defaults.Toggle("Window Mode", key: .clipboardWindowMode)
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Display Mode")
Spacer()
Picker("Display Mode", selection: $clipboardDisplayMode) {
ForEach(ClipboardDisplayMode.allCases, id: \.self) { mode in
Text(mode.displayName).tag(mode)
}
}
.pickerStyle(.menu)
.frame(width: 140)
}
Text(clipboardDisplayMode.description)
.font(.caption)
.foregroundColor(.secondary)
.padding(.leading, 0)
}
HStack {
Text("History Size")
@ -1650,7 +1667,7 @@ struct ClipboardSettings: View {
} header: {
Text("Settings")
} footer: {
Text("Window Mode shows clipboard in a moveable window like Raycast with tabs for history and favorites. When disabled, clipboard appears in a popover attached to the Dynamic Island.")
Text("Auto mode intelligently chooses between popover (desktop) and window (fullscreen apps) for the best experience. You can override this with manual selection.")
}
Section {

View file

@ -17,7 +17,10 @@ class ClipboardWindowManager: ObservableObject {
func showClipboardWindow() {
if let existingWindow = clipboardWindow {
// Ensure window appears above fullscreen apps
existingWindow.level = .screenSaver
existingWindow.makeKeyAndOrderFront(nil)
existingWindow.orderFrontRegardless() // Force window to front even above fullscreen
NSApp.activate(ignoringOtherApps: true)
return
}
@ -32,24 +35,24 @@ class ClipboardWindowManager: ObservableObject {
window.title = "Clipboard Manager"
window.isMovableByWindowBackground = true
window.level = .floating
window.level = .screenSaver // Use screenSaver level to appear above fullscreen apps
window.isReleasedWhenClosed = false
window.hasShadow = true
window.titlebarAppearsTransparent = true
window.styleMask.insert(.fullSizeContentView)
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] // Allow on all spaces and above fullscreen
// Set minimum and maximum sizes
window.minSize = NSSize(width: 350, height: 250)
window.maxSize = NSSize(width: 600, height: 500)
// Center the window
if let screen = NSScreen.main {
let screenFrame = screen.visibleFrame
let windowFrame = window.frame
let x = (screenFrame.width - windowFrame.width) / 2 + screenFrame.minX
let y = (screenFrame.height - windowFrame.height) / 2 + screenFrame.minY
window.setFrameOrigin(NSPoint(x: x, y: y))
}
// Center the window on the current screen (important for fullscreen apps)
let currentScreen = NSScreen.main ?? NSScreen.screens.first!
let screenFrame = currentScreen.frame // Use full frame instead of visibleFrame for fullscreen compatibility
let windowFrame = window.frame
let x = (screenFrame.width - windowFrame.width) / 2 + screenFrame.minX
let y = (screenFrame.height - windowFrame.height) / 2 + screenFrame.minY
window.setFrameOrigin(NSPoint(x: x, y: y))
// Set the content view
let contentView = ClipboardWindow()
@ -63,6 +66,7 @@ class ClipboardWindowManager: ObservableObject {
self.clipboardWindow = window
window.makeKeyAndOrderFront(nil)
window.orderFrontRegardless() // Force window to front even above fullscreen apps
NSApp.activate(ignoringOtherApps: true)
}

View file

@ -30,6 +30,28 @@ enum CalendarSelectionState: Codable, Defaults.Serializable {
case selected(Set<String>)
}
enum ClipboardDisplayMode: String, CaseIterable, Codable, Defaults.Serializable {
case auto = "auto" // Automatically choose: popover on desktop, window in fullscreen
case popover = "popover" // Always use popover (attached to notch)
case window = "window" // Always use standalone window
var displayName: String {
switch self {
case .auto: return "Auto (Smart)"
case .popover: return "Popover"
case .window: return "Window"
}
}
var description: String {
switch self {
case .auto: return "Automatically chooses the best display mode based on context"
case .popover: return "Always shows clipboard attached to the Dynamic Island"
case .window: return "Always shows clipboard in a standalone moveable window"
}
}
}
enum HideNotchOption: String, Defaults.Serializable {
case always
case nowPlayingOnly
@ -170,7 +192,7 @@ extension Defaults.Keys {
static let enableClipboardManager = Key<Bool>("enableClipboardManager", default: true)
static let clipboardHistorySize = Key<Int>("clipboardHistorySize", default: 3)
static let showClipboardIcon = Key<Bool>("showClipboardIcon", default: true)
static let clipboardWindowMode = Key<Bool>("clipboardWindowMode", default: true)
static let clipboardDisplayMode = Key<ClipboardDisplayMode>("clipboardDisplayMode", default: .auto)
// MARK: Keyboard Shortcuts
static let enableShortcuts = Key<Bool>("enableShortcuts", default: true)