Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 52 additions & 23 deletions Barik/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import SwiftUI

final class AppDelegate: NSObject, NSApplicationDelegate {
private var backgroundPanel: NSPanel?
private var menuBarPanel: NSPanel?
private var backgroundPanels: [NSPanel] = []
private var menuBarPanels: [NSPanel] = []

func applicationDidFinishLaunching(_ notification: Notification) {
if let error = ConfigManager.shared.initError {
Expand Down Expand Up @@ -35,29 +35,58 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

/// Configures and displays the background and menu bar panels.
private func setupPanels() {
guard let screenFrame = NSScreen.main?.frame else { return }
setupPanel(
&backgroundPanel,
frame: screenFrame,
level: Int(CGWindowLevelForKey(.desktopWindow)),
hostingRootView: AnyView(BackgroundView()))
setupPanel(
&menuBarPanel,
frame: screenFrame,
level: Int(CGWindowLevelForKey(.backstopMenu)),
hostingRootView: AnyView(MenuBarView()))
}
let monitorMode = ConfigManager.shared.config.monitors.mode
let screens: [NSScreen]

/// Sets up an NSPanel with the provided parameters.
private func setupPanel(
_ panel: inout NSPanel?, frame: CGRect, level: Int,
hostingRootView: AnyView
) {
if let existingPanel = panel {
existingPanel.setFrame(frame, display: true)
return
switch monitorMode {
case .main:
if let mainScreen = NSScreen.main {
screens = [mainScreen]
} else {
return
}
case .all:
screens = NSScreen.screens
}

// Remove excess panels if screens were reduced
while backgroundPanels.count > screens.count {
backgroundPanels.removeLast().close()
}
while menuBarPanels.count > screens.count {
menuBarPanels.removeLast().close()
}

// Create or update panels for each screen
for (index, screen) in screens.enumerated() {
let screenFrame = screen.frame

if index < backgroundPanels.count {
// Update existing panel
backgroundPanels[index].setFrame(screenFrame, display: true)
menuBarPanels[index].setFrame(screenFrame, display: true)
} else {
// Create new panels
let backgroundPanel = createPanel(
frame: screenFrame,
level: Int(CGWindowLevelForKey(.desktopWindow)),
hostingRootView: AnyView(BackgroundView())
)
let menuBarPanel = createPanel(
frame: screenFrame,
level: Int(CGWindowLevelForKey(.backstopMenu)),
hostingRootView: AnyView(MenuBarView())
)
backgroundPanels.append(backgroundPanel)
menuBarPanels.append(menuBarPanel)
}
}
}

/// Creates an NSPanel with the provided parameters.
private func createPanel(
frame: CGRect, level: Int, hostingRootView: AnyView
) -> NSPanel {
let newPanel = NSPanel(
contentRect: frame,
styleMask: [.nonactivatingPanel],
Expand All @@ -69,7 +98,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
newPanel.collectionBehavior = [.canJoinAllSpaces]
newPanel.contentView = NSHostingView(rootView: hostingRootView)
newPanel.orderFront(nil)
panel = newPanel
return newPanel
}

private func showFatalConfigError(message: String) {
Expand Down
46 changes: 37 additions & 9 deletions Barik/Config/ConfigModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ struct RootToml: Decodable {
var yabai: YabaiConfig?
var aerospace: AerospaceConfig?
var experimental: ExperimentalConfig?
var monitors: MonitorsConfig?
var widgets: WidgetsSection

init() {
self.theme = nil
self.yabai = nil
self.aerospace = nil
self.monitors = nil
self.widgets = WidgetsSection(displayed: [], others: [:])
}
}
Expand All @@ -26,18 +28,22 @@ struct Config {
var theme: String {
rootToml.theme ?? "light"
}

var yabai: YabaiConfig {
rootToml.yabai ?? YabaiConfig()
}

var aerospace: AerospaceConfig {
rootToml.aerospace ?? AerospaceConfig()
}

var experimental: ExperimentalConfig {
rootToml.experimental ?? ExperimentalConfig()
}

var monitors: MonitorsConfig {
rootToml.monitors ?? MonitorsConfig()
}
}

typealias ConfigData = [String: TOMLValue]
Expand Down Expand Up @@ -408,35 +414,35 @@ enum BackgroundForegroundHeight: Decodable {
case barikDefault
case menuBar
case float(Float)

init(from decoder: Decoder) throws {
if let floatValue = try? decoder.singleValueContainer().decode(Float.self) {
self = .float(floatValue)
return
}

if let intValue = try? decoder.singleValueContainer().decode(Int.self) {
self = .float(Float(intValue))
return
}

if let stringValue = try? decoder.singleValueContainer().decode(String.self) {
if stringValue == "default" {
self = .barikDefault
return
}

if stringValue == "menu-bar" {
self = .menuBar
return
}

throw DecodingError.dataCorruptedError(
in: try decoder.singleValueContainer(),
debugDescription: "Expected 'default', 'menu-bar' or a float value, but found \(stringValue)"
)
}

throw DecodingError.typeMismatch(
ForegroundPadding.self,
DecodingError.Context(
Expand All @@ -447,3 +453,25 @@ enum BackgroundForegroundHeight: Decodable {
}
}

struct MonitorsConfig: Decodable {
let mode: MonitorMode

enum CodingKeys: String, CodingKey {
case mode
}

init() {
self.mode = .all
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
mode = try container.decodeIfPresent(MonitorMode.self, forKey: .mode) ?? .all
}
}

enum MonitorMode: String, Decodable {
case main = "main"
case all = "all"
}

107 changes: 83 additions & 24 deletions Barik/MenuBarPopup/MenuBarPopup.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SwiftUI

private var panel: NSPanel?
private var panels: [NSPanel] = []

class HidingPanel: NSPanel, NSWindowDelegate {
var hideTimer: Timer?
Expand Down Expand Up @@ -35,11 +35,17 @@ class HidingPanel: NSPanel, NSWindowDelegate {

class MenuBarPopup {
static var lastContentIdentifier: String? = nil
static var currentScreenIndex: Int? = nil

static func show<Content: View>(
rect: CGRect, id: String, @ViewBuilder content: @escaping () -> Content
) {
guard let panel = panel else { return }
// Determine which screen the widget is on
let screenIndex = getScreenIndex(for: rect)
guard screenIndex < panels.count else { return }

let panel = panels[screenIndex]
currentScreenIndex = screenIndex

if panel.isKeyWindow, lastContentIdentifier == id {
NotificationCenter.default.post(name: .willHideWindow, object: nil)
Expand Down Expand Up @@ -108,27 +114,80 @@ class MenuBarPopup {
}

static func setup() {
guard let screen = NSScreen.main?.visibleFrame else { return }
let panelFrame = NSRect(
x: 0,
y: 0,
width: screen.size.width,
height: screen.size.height
)

let newPanel = HidingPanel(
contentRect: panelFrame,
styleMask: [.nonactivatingPanel],
backing: .buffered,
defer: false
)

newPanel.level = NSWindow.Level(
rawValue: Int(CGWindowLevelForKey(.floatingWindow)))
newPanel.backgroundColor = .clear
newPanel.hasShadow = false
newPanel.collectionBehavior = [.canJoinAllSpaces]

panel = newPanel
let monitorMode = ConfigManager.shared.config.monitors.mode
let screens: [NSScreen]

switch monitorMode {
case .main:
if let mainScreen = NSScreen.main {
screens = [mainScreen]
} else {
return
}
case .all:
screens = NSScreen.screens
}

// Remove excess panels if screens were reduced
while panels.count > screens.count {
panels.removeLast().close()
}

// Create panels for each screen
for (index, screen) in screens.enumerated() {
let panelFrame = NSRect(
x: 0,
y: 0,
width: screen.visibleFrame.width,
height: screen.visibleFrame.height
)

if index < panels.count {
// Update existing panel
panels[index].setFrame(panelFrame, display: true)
} else {
// Create new panel
let newPanel = HidingPanel(
contentRect: panelFrame,
styleMask: [.nonactivatingPanel],
backing: .buffered,
defer: false
)

newPanel.level = NSWindow.Level(
rawValue: Int(CGWindowLevelForKey(.floatingWindow)))
newPanel.backgroundColor = .clear
newPanel.hasShadow = false
newPanel.collectionBehavior = [.canJoinAllSpaces]

panels.append(newPanel)
}
}
}

private static func getScreenIndex(for rect: CGRect) -> Int {
let monitorMode = ConfigManager.shared.config.monitors.mode

switch monitorMode {
case .main:
return 0
case .all:
let screens = NSScreen.screens

// Find which screen contains the rect (based on the midpoint)
let midX = rect.midX
let midY = rect.midY

for (index, screen) in screens.enumerated() {
let frame = screen.frame
if midX >= frame.minX && midX <= frame.maxX &&
midY >= frame.minY && midY <= frame.maxY {
return index
}
}

// Default to main screen (index 0) if not found
return 0
}
}
}
26 changes: 21 additions & 5 deletions Barik/MenuBarPopup/MenuBarPopupView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,32 @@ struct MenuBarPopupView<Content: View>: View {
}

var computedOffset: CGFloat {
let screenWidth = NSScreen.main?.frame.width ?? 0
// Get the screen for the current popup
let screens: [NSScreen]
let monitorMode = ConfigManager.shared.config.monitors.mode

switch monitorMode {
case .main:
screens = NSScreen.main.map { [$0] } ?? []
case .all:
screens = NSScreen.screens
}

let screenIndex = MenuBarPopup.currentScreenIndex ?? 0
guard screenIndex < screens.count else { return 0 }

let screen = screens[screenIndex]
let screenFrame = screen.frame

let W = viewFrame.width
let M = viewFrame.midX
let newLeft = (M - W / 2) - 20
let newRight = (M + W / 2) + 20

if newRight > screenWidth {
return screenWidth - newRight
} else if newLeft < 0 {
return -newLeft
if newRight > screenFrame.maxX {
return screenFrame.maxX - newRight
} else if newLeft < screenFrame.minX {
return screenFrame.minX - newLeft
}
return 0
}
Expand Down