1 Commits
Author SHA1 Message Date
TZGyn 2312e09105 Shorten the icon reveal when opening a tray menu
The menu is its own window anchored where the item was revealed, so it
survives if the bar re-hides immediately. Revealing for ~0.3s instead of
over a second makes the flash far less noticeable while still opening
the menu in view.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
2026-09-27 02:53:41 +08:00
4 changed files with 58 additions and 9 deletions
+10 -6
View File
@@ -135,12 +135,16 @@ preferred position controls where the hidden/kept boundary lands:
hidden too; above ~300 app icons (Now Playing, TextInput) leak back. hidden too; above ~300 app icons (Now Playing, TextInput) leak back.
- `debug.spacerPos` (Double) overrides the seed for tuning without rebuilding. - `debug.spacerPos` (Double) overrides the seed for tuning without rebuilding.
- Pressing a tray tile calls `MenuBarItemManager.press`. A hidden item sits - Pressing a tray tile calls `MenuBarItemManager.press`. A hidden item sits
off-screen, and its menu would open off-screen too, so press briefly off-screen, and its menu opens off-screen too (verified with `--diag-press`:
`suspend()`s the hider, waits ~0.18s for the layout to settle, performs the the menu window lands at negative x). So press briefly `suspend()`s the hider,
accessibility action (`kAXPressAction`, then `kAXShowMenuAction`), and waits ~0.2s for the layout to settle, performs the accessibility action
`resume()`s ~1s later. The reveal is momentary, not for the whole time the (`kAXPressAction`, then `kAXShowMenuAction`), and `resume()`s ~0.1s later. The
panel is open. macOS does not let us place or anchor another app's menu menu is its own window anchored at the item's on-screen spot, so it survives
anywhere else, so a momentary reveal is the only way to show it in view. re-hiding.
- **Cannot reveal a single icon.** The spacer hides a contiguous block to its
left, so a press can only reveal the whole block. Isolating one item would
need either per-item ordering control (not available) or covering the icons
with an overlay window instead of moving them off-screen.
- **Order matters**: `AppDelegate` must call `setupStatusItem()` *before* - **Order matters**: `AppDelegate` must call `setupStatusItem()` *before*
`MenuBarHider.shared.prepare()`. If the hider contracts the bar first, the `MenuBarHider.shared.prepare()`. If the hider contracts the bar first, the
hamburger is created into the collapsed space and lands off-screen. hamburger is created into the collapsed space and lands off-screen.
+21
View File
@@ -48,6 +48,27 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
setupStatusItem() setupStatusItem()
MenuBarHider.shared.prepare() MenuBarHider.shared.prepare()
if CommandLine.arguments.contains("--diag-press") {
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
MenuBarHider.shared.suspend()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2.25) {
if let item = MenuBarItemManager.enumerate().first(where: {
$0.title.lowercased().contains("wireguard")
}) {
NSLog("diag-press: pressing \(item.appName) after brief reveal")
_ = AXUIElementPerformAction(item.element, kAXPressAction as CFString)
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2.45) {
MenuBarHider.shared.resume()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4.5) {
dumpWindows()
}
return
}
if CommandLine.arguments.contains("--diag") { if CommandLine.arguments.contains("--diag") {
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
dumpMenuBarItems() dumpMenuBarItems()
+4 -3
View File
@@ -117,10 +117,11 @@ final class MenuBarItemManager: ObservableObject {
return return
} }
hider.suspend() hider.suspend()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.18) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
_ = self.performPress(item) _ = self.performPress(item)
// Let the menu finish appearing before the bar contracts again. // The menu is its own window, positioned at the item's on-screen
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { // spot, so the bar can be re-hidden almost immediately.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
hider.resume() hider.resume()
} }
} }
+23
View File
@@ -1,5 +1,28 @@
import AppKit import AppKit
import ApplicationServices import ApplicationServices
import CoreGraphics
/// Diagnostic used by `open SimpleTopBar.app --args --diag-press`. Presses a
/// hidden item's accessibility element without revealing it and dumps the
/// resulting high-level windows, to see where its menu actually opens.
func dumpWindows() {
var lines: [String] = []
if let list = CGWindowListCopyWindowInfo([], kCGNullWindowID) as? [[String: Any]] {
for window in list {
let layer = window[kCGWindowLayer as String] as? Int ?? -1
guard layer > 20 else { continue }
let owner = window[kCGWindowOwnerName as String] as? String ?? "?"
let bounds = window[kCGWindowBounds as String] as? [String: Any] ?? [:]
let x = bounds["X"] as? Double ?? 0
let y = bounds["Y"] as? Double ?? 0
let width = bounds["Width"] as? Double ?? 0
let height = bounds["Height"] as? Double ?? 0
lines.append("\(owner) layer=\(layer) frame=\(Int(x)),\(Int(y)) \(Int(width))x\(Int(height))")
}
}
try? lines.joined(separator: "\n").write(toFile: "/tmp/simpletopbar-windows.txt", atomically: true, encoding: .utf8)
exit(0)
}
/// Diagnostic used by `open SimpleTopBar.app --args --dump-items`. Writes the /// Diagnostic used by `open SimpleTopBar.app --args --dump-items`. Writes the
/// live Accessibility view of the menu bar to /tmp so it can be inspected /// live Accessibility view of the menu bar to /tmp so it can be inspected