Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2312e09105 | ||
|
|
9c5416d27e | ||
|
|
61d2689cbc |
@@ -134,6 +134,17 @@ preferred position controls where the hidden/kept boundary lands:
|
||||
- Verified with `--diag-hide` across positions 100–400. Below ~200 Wi-Fi gets
|
||||
hidden too; above ~300 app icons (Now Playing, TextInput) leak back.
|
||||
- `debug.spacerPos` (Double) overrides the seed for tuning without rebuilding.
|
||||
- Pressing a tray tile calls `MenuBarItemManager.press`. A hidden item sits
|
||||
off-screen, and its menu opens off-screen too (verified with `--diag-press`:
|
||||
the menu window lands at negative x). So press briefly `suspend()`s the hider,
|
||||
waits ~0.2s for the layout to settle, performs the accessibility action
|
||||
(`kAXPressAction`, then `kAXShowMenuAction`), and `resume()`s ~0.1s later. The
|
||||
menu is its own window anchored at the item's on-screen spot, so it survives
|
||||
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*
|
||||
`MenuBarHider.shared.prepare()`. If the hider contracts the bar first, the
|
||||
hamburger is created into the collapsed space and lands off-screen.
|
||||
|
||||
@@ -48,6 +48,27 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
setupStatusItem()
|
||||
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") {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
|
||||
dumpMenuBarItems()
|
||||
|
||||
@@ -103,10 +103,31 @@ final class MenuBarItemManager: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens the real menu bar item by pressing its accessibility element, so
|
||||
/// its menu/popover appears at its true screen location.
|
||||
@discardableResult
|
||||
func press(_ item: MenuBarItem) -> Bool {
|
||||
/// Opens the real menu bar item, so its menu/popover appears.
|
||||
///
|
||||
/// A hidden item sits off-screen, and its menu would open off-screen too.
|
||||
/// So briefly reveal the items, let the layout settle, press the item, then
|
||||
/// hide again shortly after. The reveal is momentary rather than lasting the
|
||||
/// whole time the panel is open.
|
||||
func press(_ item: MenuBarItem) {
|
||||
let hider = MenuBarHider.shared
|
||||
let needsReveal = hider.isHiding
|
||||
guard needsReveal else {
|
||||
_ = performPress(item)
|
||||
return
|
||||
}
|
||||
hider.suspend()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
_ = self.performPress(item)
|
||||
// The menu is its own window, positioned at the item's on-screen
|
||||
// spot, so the bar can be re-hidden almost immediately.
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
hider.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func performPress(_ item: MenuBarItem) -> Bool {
|
||||
if AXUIElementPerformAction(item.element, kAXPressAction as CFString) == .success {
|
||||
return true
|
||||
}
|
||||
@@ -252,6 +273,22 @@ final class MenuBarHider: ObservableObject {
|
||||
isHiding = hiding
|
||||
}
|
||||
|
||||
/// Temporarily reveal the items (e.g. while the panel is open) so pressing
|
||||
/// a tray icon opens its menu at its real, on-screen position. Keeps the
|
||||
/// user's `enabled` preference untouched.
|
||||
func suspend() {
|
||||
guard enabled else { return }
|
||||
spacer?.length = 0
|
||||
isHiding = false
|
||||
}
|
||||
|
||||
/// Restore hiding after a `suspend()`.
|
||||
func resume() {
|
||||
guard enabled else { return }
|
||||
spacer?.length = Self.hiddenLength
|
||||
isHiding = true
|
||||
}
|
||||
|
||||
/// Put the menu bar back the way we found it. Called on quit. Leaves the
|
||||
/// user's preference intact.
|
||||
func restore() {
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
import AppKit
|
||||
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
|
||||
/// live Accessibility view of the menu bar to /tmp so it can be inspected
|
||||
|
||||
Reference in New Issue
Block a user