Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
873c46d
refactor: use event-driven notifications instead of polling for NowPl…
bottlebrushes Jan 3, 2026
6666de0
feat: add weather widget using Open-Meteo API
bottlebrushes Jan 3, 2026
fcd0fff
feat: add event-based yabai provider with Unix socket listener
bottlebrushes Jan 3, 2026
d20a3de
feat: add weather popup with hourly forecast
bottlebrushes Jan 3, 2026
59cc625
fix: move yabai calls off main thread to prevent crash
bottlebrushes Jan 3, 2026
b3776d4
feat: add click-action config option for time widget
bottlebrushes Jan 3, 2026
c78e49e
feat: add click-action config for time widget with accessibility prompt
bottlebrushes Jan 3, 2026
0e0a782
fix: use SOCK_STREAM for yabai socket communication
bottlebrushes Jan 3, 2026
ded70fb
feat: enhanced WiFi popup with macOS-style controls
bottlebrushes Jan 4, 2026
0171ff0
feat: use keyboard shortcut simulation for Notification Center
bottlebrushes Jan 4, 2026
f9ee373
fix: correct popup Y positioning to appear directly below menu bar
bottlebrushes Jan 4, 2026
2335adb
Fix popup positioning to appear directly below widgets
bottlebrushes Jan 4, 2026
25be29b
Fix popup positioning to appear just below menu bar
bottlebrushes Jan 4, 2026
713d115
feat: replace polling with event-driven notifications
bottlebrushes Jan 16, 2026
6ba00ab
Merge pull request #1 from bettercoderthanyou/bettercoderthanyou/remo…
bottlebrushes Jan 16, 2026
54aae17
fix: rewrite popup positioning for consistent Y level across all widgets
bottlebrushes Jan 19, 2026
1904b49
feat: add calendar day view popup with today/tomorrow layout (#2)
bottlebrushes Jan 20, 2026
23c28e8
feat: enhanced calendar popup with day view, selection, and event det…
bottlebrushes Jan 21, 2026
d1bcef6
feat: click album art or song info to open music player (#4)
bottlebrushes Jan 22, 2026
85892fb
docs: update README for barik-but-better fork (#5)
bottlebrushes Jan 22, 2026
40d41fb
docs: add more improvements to README
bottlebrushes Jan 22, 2026
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
2 changes: 2 additions & 0 deletions Barik/Barik.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
36 changes: 28 additions & 8 deletions Barik/Config/ConfigManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,26 @@ final class ConfigManager: ObservableObject {
do {
let currentText = try String(contentsOfFile: path, encoding: .utf8)
let updatedText = updatedTOMLString(
original: currentText, key: key, newValue: newValue)
original: currentText, key: key, newValue: newValue, quoteValue: true)
try updatedText.write(
toFile: path, atomically: false, encoding: .utf8)
DispatchQueue.main.async {
self.parseConfigFile(at: path)
}
} catch {
print("Error updating config:", error)
}
}

func updateConfigValueRaw(key: String, newValue: String) {
guard let path = configFilePath else {
print("Config file path is not set")
return
}
do {
let currentText = try String(contentsOfFile: path, encoding: .utf8)
let updatedText = updatedTOMLString(
original: currentText, key: key, newValue: newValue, quoteValue: false)
try updatedText.write(
toFile: path, atomically: false, encoding: .utf8)
DispatchQueue.main.async {
Expand All @@ -146,8 +165,9 @@ final class ConfigManager: ObservableObject {
}

private func updatedTOMLString(
original: String, key: String, newValue: String
original: String, key: String, newValue: String, quoteValue: Bool = true
) -> String {
let formattedValue = quoteValue ? "\"\(newValue)\"" : newValue
if key.contains(".") {
let components = key.split(separator: ".").map(String.init)
guard components.count >= 2 else {
Expand All @@ -168,7 +188,7 @@ final class ConfigManager: ObservableObject {
let trimmed = line.trimmingCharacters(in: .whitespaces)
if trimmed.hasPrefix("[") && trimmed.hasSuffix("]") {
if insideTargetTable && !updatedKey {
newLines.append("\(actualKey) = \"\(newValue)\"")
newLines.append("\(actualKey) = \(formattedValue)")
updatedKey = true
}
if trimmed == tableHeader {
Expand All @@ -185,7 +205,7 @@ final class ConfigManager: ObservableObject {
if line.range(of: pattern, options: .regularExpression)
!= nil
{
newLines.append("\(actualKey) = \"\(newValue)\"")
newLines.append("\(actualKey) = \(formattedValue)")
updatedKey = true
continue
}
Expand All @@ -195,13 +215,13 @@ final class ConfigManager: ObservableObject {
}

if foundTable && insideTargetTable && !updatedKey {
newLines.append("\(actualKey) = \"\(newValue)\"")
newLines.append("\(actualKey) = \(formattedValue)")
}

if !foundTable {
newLines.append("")
newLines.append("[\(tablePath)]")
newLines.append("\(actualKey) = \"\(newValue)\"")
newLines.append("\(actualKey) = \(formattedValue)")
}
return newLines.joined(separator: "\n")
} else {
Expand All @@ -217,15 +237,15 @@ final class ConfigManager: ObservableObject {
if line.range(of: pattern, options: .regularExpression)
!= nil
{
newLines.append("\(key) = \"\(newValue)\"")
newLines.append("\(key) = \(formattedValue)")
updatedAtLeastOnce = true
continue
}
}
newLines.append(line)
}
if !updatedAtLeastOnce {
newLines.append("\(key) = \"\(newValue)\"")
newLines.append("\(key) = \(formattedValue)")
}
return newLines.joined(separator: "\n")
}
Expand Down
66 changes: 66 additions & 0 deletions Barik/Helpers/SystemUIHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import AppKit
import Foundation

/// Helper for triggering macOS system UI elements
final class SystemUIHelper {

/// Opens the macOS Notification Center by simulating Ctrl+Option+N keypress
static func openNotificationCenter() {
// Simulate Ctrl+Option+N keyboard shortcut
let keyCode: CGKeyCode = 45 // 'n' key
let flags: CGEventFlags = [.maskControl, .maskAlternate]

// Create and post key down event
if let keyDown = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true) {
keyDown.flags = flags
keyDown.post(tap: .cghidEventTap)
}

// Create and post key up event
if let keyUp = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false) {
keyUp.flags = flags
keyUp.post(tap: .cghidEventTap)
}
}

/// Opens the macOS Weather menu bar dropdown
static func openWeatherDropdown() {
let script = """
tell application "System Events"
tell process "ControlCenter"
try
click menu bar item "Weather" of menu bar 1
on error
-- Weather might not be in menu bar, try to open Weather app instead
tell application "Weather" to activate
end try
end tell
end tell
"""
runAppleScript(script)
}

/// Opens the Weather app
static func openWeatherApp() {
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.Weather")!)
// Fallback to opening Weather app directly
if let weatherURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.apple.weather") {
NSWorkspace.shared.open(weatherURL)
}
}

/// Runs an AppleScript
@discardableResult
private static func runAppleScript(_ script: String) -> String? {
guard let appleScript = NSAppleScript(source: script) else {
return nil
}
var error: NSDictionary?
let result = appleScript.executeAndReturnError(&error)
if let error = error {
print("AppleScript Error: \(error)")
return nil
}
return result.stringValue
}
}
7 changes: 6 additions & 1 deletion Barik/Info.plist
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
<dict>
<key>NSLocationUsageDescription</key>
<string>Barik needs your location to show local weather conditions.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Barik needs your location to show local weather conditions.</string>
</dict>
</plist>
45 changes: 18 additions & 27 deletions Barik/MenuBarPopup/MenuBarPopup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI
private var panel: NSPanel?

class HidingPanel: NSPanel, NSWindowDelegate {
var hideTimer: Timer?
var hideWorkItem: DispatchWorkItem?

override var canBecomeKey: Bool {
return true
Expand All @@ -23,13 +23,12 @@ class HidingPanel: NSPanel, NSWindowDelegate {

func windowDidResignKey(_ notification: Notification) {
NotificationCenter.default.post(name: .willHideWindow, object: nil)
hideTimer = Timer.scheduledTimer(
withTimeInterval: TimeInterval(
Constants.menuBarPopupAnimationDurationInMilliseconds) / 1000.0,
repeats: false
) { [weak self] _ in
let workItem = DispatchWorkItem { [weak self] in
self?.orderOut(nil)
}
hideWorkItem = workItem
let duration = Double(Constants.menuBarPopupAnimationDurationInMilliseconds) / 1000.0
DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: workItem)
}
}

Expand Down Expand Up @@ -59,8 +58,8 @@ class MenuBarPopup {
lastContentIdentifier = id

if let hidingPanel = panel as? HidingPanel {
hidingPanel.hideTimer?.invalidate()
hidingPanel.hideTimer = nil
hidingPanel.hideWorkItem?.cancel()
hidingPanel.hideWorkItem = nil
}

if panel.isKeyWindow {
Expand All @@ -73,13 +72,10 @@ class MenuBarPopup {
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
panel.contentView = NSHostingView(
rootView:
ZStack {
MenuBarPopupView {
content()
}
.position(x: rect.midX)
MenuBarPopupView(widgetRect: rect) {
content()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.id(UUID())
)
panel.makeKeyAndOrderFront(nil)
Expand All @@ -91,13 +87,10 @@ class MenuBarPopup {
} else {
panel.contentView = NSHostingView(
rootView:
ZStack {
MenuBarPopupView {
content()
}
.position(x: rect.midX)
MenuBarPopupView(widgetRect: rect) {
content()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
)
panel.makeKeyAndOrderFront(nil)
DispatchQueue.main.async {
Expand All @@ -108,13 +101,11 @@ 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
)
guard let screen = NSScreen.main else { return }

// Use full screen frame so the panel covers the entire screen including menu bar area
// This ensures consistent positioning regardless of dock position or menu bar configuration
let panelFrame = screen.frame

let newPanel = HidingPanel(
contentRect: panelFrame,
Expand Down
14 changes: 13 additions & 1 deletion Barik/MenuBarPopup/MenuBarPopupVariantView.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import SwiftUI

enum MenuBarPopupVariant: String, Equatable {
case box, vertical, horizontal, settings
case box, vertical, horizontal, dayView, settings
}

struct MenuBarPopupVariantView: View {
private let box: AnyView?
private let vertical: AnyView?
private let horizontal: AnyView?
private let dayView: AnyView?
private let settings: AnyView?

var selectedVariant: MenuBarPopupVariant
Expand All @@ -22,6 +23,7 @@ struct MenuBarPopupVariantView: View {
@ViewBuilder box: () -> some View = { EmptyView() },
@ViewBuilder vertical: () -> some View = { EmptyView() },
@ViewBuilder horizontal: () -> some View = { EmptyView() },
@ViewBuilder dayView: () -> some View = { EmptyView() },
@ViewBuilder settings: () -> some View = { EmptyView() }
) {
self.selectedVariant = selectedVariant
Expand All @@ -30,13 +32,16 @@ struct MenuBarPopupVariantView: View {
let boxView = box()
let verticalView = vertical()
let horizontalView = horizontal()
let dayViewView = dayView()
let settingsView = settings()

self.box = (boxView is EmptyView) ? nil : AnyView(boxView)
self.vertical =
(verticalView is EmptyView) ? nil : AnyView(verticalView)
self.horizontal =
(horizontalView is EmptyView) ? nil : AnyView(horizontalView)
self.dayView =
(dayViewView is EmptyView) ? nil : AnyView(dayViewView)
self.settings =
(settingsView is EmptyView) ? nil : AnyView(settingsView)
}
Expand All @@ -63,6 +68,11 @@ struct MenuBarPopupVariantView: View {
variant: .horizontal,
systemImageName: "rectangle.inset.filled")
}
if dayView != nil {
variantButton(
variant: .dayView,
systemImageName: "calendar.day.timeline.left")
}
if settings != nil {
variantButton(
variant: .settings, systemImageName: "gearshape.fill")
Expand All @@ -89,6 +99,8 @@ struct MenuBarPopupVariantView: View {
if let view = vertical { view }
case .horizontal:
if let view = horizontal { view }
case .dayView:
if let view = dayView { view }
case .settings:
if let view = settings { view }
}
Expand Down
Loading