Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 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
630186a
docs: add more improvements to README (#6)
bottlebrushes Jan 22, 2026
0455b69
Added support for changing the keybind of openning the notification c…
jarboer Jan 23, 2026
bc986fd
fix: restore now playing widget in default config
bottlebrushes Jan 30, 2026
3974bdd
Merge pull request #8 from bettercoderthanyou/bettercoderthanyou/fix-…
bottlebrushes Jan 30, 2026
91800ba
feat: add Claude Code usage tracking widget
bottlebrushes Feb 2, 2026
6ea8b6e
Merge pull request #9 from bettercoderthanyou/bettercoderthanyou/clau…
bottlebrushes Feb 2, 2026
03f7457
feat: drag-and-drop widget reordering in menu bar
bottlebrushes Feb 2, 2026
7f715e7
Merge pull request #10 from bettercoderthanyou/bettercoderthanyou/dra…
bottlebrushes Feb 2, 2026
521549d
Merge pull request #7 from jarboer/notification-center-keybind-config
bottlebrushes Feb 2, 2026
4a28e28
ci: auto-build on push + update README quickstart
bottlebrushes Feb 2, 2026
d7929dc
Merge pull request #11 from bettercoderthanyou/bettercoderthanyou/dra…
bottlebrushes Feb 2, 2026
2e4435c
docs: add drag-and-drop and Claude widget to improvements list
bottlebrushes Feb 2, 2026
049a410
Merge pull request #12 from bettercoderthanyou/bettercoderthanyou/dra…
bottlebrushes Feb 2, 2026
a69ef83
fix: hide claude widget when no data, count session messages accurately
bottlebrushes Feb 2, 2026
de321ce
feat: use Anthropic usage API with deferred keychain access
bottlebrushes Feb 2, 2026
639e855
Merge pull request #13 from bettercoderthanyou/bettercoderthanyou/hid…
bottlebrushes Feb 2, 2026
3e2644c
feat: add pomodoro timer widget
bottlebrushes Feb 3, 2026
00823ef
Merge pull request #14 from bettercoderthanyou/bettercoderthanyou/pom…
bottlebrushes Feb 3, 2026
040132a
fix: show pomodoro notifications while app is active
bottlebrushes Feb 3, 2026
4112a99
Merge pull request #15 from bettercoderthanyou/bettercoderthanyou/pom…
bottlebrushes Feb 3, 2026
642e63d
feat: add configurable default calendar app for opening events
bottlebrushes Feb 3, 2026
d927c48
Merge pull request #16 from bettercoderthanyou/bettercoderthanyou/not…
bottlebrushes Feb 3, 2026
28907d4
fix: ad-hoc sign app in CI to prevent macOS "damaged" error
bottlebrushes Feb 3, 2026
d99a5a7
Merge pull request #17 from bettercoderthanyou/bettercoderthanyou/fix…
bottlebrushes Feb 3, 2026
fe47cc8
feat: tomato icon for pomodoro widget, defer Claude keychain access
bottlebrushes Feb 3, 2026
7819ff8
Merge pull request #18 from bettercoderthanyou/bettercoderthanyou/fix…
bottlebrushes Feb 3, 2026
c3ab855
feat: add countdown widget with configurable target date
bottlebrushes Feb 4, 2026
6992bc5
feat: add fill icon mode for pomodoro timer widget
bottlebrushes Feb 4, 2026
d3944da
Merge pull request #19 from bettercoderthanyou/bettercoderthanyou/cou…
bottlebrushes Feb 4, 2026
72710ef
Right-click context menu & fix drag to leftmost position (#20)
bottlebrushes Feb 4, 2026
4483be9
fix: persist countdown widget settings and widen icon for 3-digit counts
bottlebrushes Feb 4, 2026
f915ccb
Merge pull request #21 from bettercoderthanyou/bettercoderthanyou/cou…
bottlebrushes Feb 4, 2026
e455793
fix: refresh Claude widget on wake from sleep (#22)
bottlebrushes Feb 5, 2026
789e7e6
fix: Claude widget auto-connects on launch and survives sleep
bottlebrushes Feb 23, 2026
57d5772
Merge pull request #23 from bottlebrushes/bettercoderthanyou/claude-w…
bottlebrushes Feb 23, 2026
fdb0191
fix: wifi widget shows connected when VPN is active
bottlebrushes Feb 23, 2026
836bd8c
Merge pull request #24 from bottlebrushes/bettercoderthanyou/fix-wifi…
bottlebrushes Feb 23, 2026
c318d44
fix: CI release step race condition with latest tag
bottlebrushes Feb 23, 2026
5ea82d1
Merge pull request #25 from bottlebrushes/bettercoderthanyou/fix-wifi…
bottlebrushes Feb 23, 2026
555fa54
Animate Claude usage icon drain
bottlebrushes Feb 24, 2026
2b0db50
Merge pull request #26 from bottlebrushes/bettercoderthanyou/icon-usa…
bottlebrushes Feb 24, 2026
beb43f3
Auto-commit: save work in progress
bottlebrushes Feb 25, 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
46 changes: 46 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Build and Release

on:
push:
branches: [main]

jobs:
build:
runs-on: macos-15
steps:
- uses: actions/checkout@v4

- name: Build Release
run: |
xcodebuild -scheme Barik -configuration Release \
-derivedDataPath build \
CODE_SIGNING_ALLOWED=NO

- name: Ad-hoc sign app
run: |
codesign --force --deep --sign - \
--entitlements Barik/Barik.entitlements \
--options runtime \
build/Build/Products/Release/Barik.app

- name: Zip app
run: ditto -c -k --keepParent build/Build/Products/Release/Barik.app Barik.zip

- name: Get version
id: version
run: |
VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" build/Build/Products/Release/Barik.app/Contents/Info.plist)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Update latest release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release delete latest --yes --cleanup-tag 2>/dev/null || true
gh release create latest Barik.zip \
--title "Latest Build (v${{ steps.version.outputs.version }})" \
--notes "Automated build from \`main\` branch ($(date -u +%Y-%m-%d)).

Built from commit ${{ github.sha }}." \
--target ${{ github.sha }} \
--prerelease
20 changes: 20 additions & 0 deletions Barik/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import SwiftUI
final class AppDelegate: NSObject, NSApplicationDelegate {
private var backgroundPanel: NSPanel?
private var menuBarPanel: NSPanel?
private let contextMenu = MenuBarContextMenu()
private var eventMonitor: Any?

func applicationDidFinishLaunching(_ notification: Notification) {
if let error = ConfigManager.shared.initError {
Expand All @@ -21,6 +23,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

MenuBarPopup.setup()
setupPanels()
setupContextMenuMonitor()

NotificationCenter.default.addObserver(
self,
Expand Down Expand Up @@ -72,6 +75,23 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
panel = newPanel
}

private func setupContextMenuMonitor() {
eventMonitor = NSEvent.addLocalMonitorForEvents(matching: .rightMouseDown) {
[weak self] event in
guard let self,
let panel = self.menuBarPanel,
event.window === panel,
let contentView = panel.contentView
else {
return event
}
let locationInView = contentView.convert(event.locationInWindow, from: nil)
self.contextMenu.popUp(
positioning: nil, at: locationInView, in: contentView)
return nil
}
}

private func showFatalConfigError(message: String) {
let alert = NSAlert()
alert.messageText = "Configuration Error"
Expand Down
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>
137 changes: 129 additions & 8 deletions Barik/Config/ConfigManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ final class ConfigManager: ObservableObject {
private var fileWatchSource: DispatchSourceFileSystemObject?
private var fileDescriptor: CInt = -1
private var configFilePath: String?
private var suppressNextReload = false

private init() {
loadOrCreateConfigIfNeeded()
Expand Down Expand Up @@ -72,8 +73,11 @@ final class ConfigManager: ObservableObject {
displayed = [ # widgets on menu bar
"default.spaces",
"spacer",
"default.claude-usage",
"default.nowplaying",
"default.network",
"default.battery",
"default.countdown",
"divider",
# { "default.time" = { time-zone = "America/Los_Angeles", format = "E d, hh:mm" } },
"default.time"
Expand All @@ -84,6 +88,11 @@ final class ConfigManager: ObservableObject {
window.show-title = true
window.title.max-length = 50

[widgets.default.claude-usage]
plan = "pro"
five-hour-limit = 80
weekly-limit = 500

[widgets.default.battery]
show-percentage = true
warning-level = 30
Expand Down Expand Up @@ -116,6 +125,10 @@ final class ConfigManager: ObservableObject {
guard let self = self, let path = self.configFilePath else {
return
}
if self.suppressNextReload {
self.suppressNextReload = false
return
}
self.parseConfigFile(at: path)
}
fileWatchSource?.setCancelHandler { [weak self] in
Expand All @@ -134,7 +147,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 +178,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 +201,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 +218,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 +228,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,20 +250,108 @@ 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")
}
}

func updateDisplayedWidgets(_ items: [TomlWidgetItem]) {
guard let path = configFilePath else {
print("Config file path is not set")
return
}
do {
let currentText = try String(contentsOfFile: path, encoding: .utf8)
let updatedText = replaceDisplayedArray(in: currentText, with: items)
suppressNextReload = true
try updatedText.write(toFile: path, atomically: true, encoding: .utf8)
DispatchQueue.main.async {
self.parseConfigFile(at: path)
}
} catch {
suppressNextReload = false
print("Error updating displayed widgets:", error)
}
}

private func replaceDisplayedArray(in original: String, with items: [TomlWidgetItem]) -> String {
let lines = original.components(separatedBy: "\n")
var inWidgetsSection = false
var arrayStartLine: Int?
var arrayEndLine: Int?
var bracketDepth = 0
var foundStart = false

for (lineIndex, line) in lines.enumerated() {
let trimmed = line.trimmingCharacters(in: .whitespaces)

if trimmed.hasPrefix("[") && trimmed.hasSuffix("]") {
inWidgetsSection = (trimmed == "[widgets]")
if foundStart && !inWidgetsSection {
break
}
continue
}

if inWidgetsSection && !foundStart {
if trimmed.hasPrefix("displayed") && trimmed.contains("=") {
arrayStartLine = lineIndex
foundStart = true
for char in trimmed {
if char == Character("[") { bracketDepth += 1 }
if char == Character("]") { bracketDepth -= 1 }
}
if bracketDepth == 0 {
arrayEndLine = lineIndex
break
}
}
} else if foundStart && arrayEndLine == nil {
for char in trimmed {
if char == Character("[") { bracketDepth += 1 }
if char == Character("]") { bracketDepth -= 1 }
}
if bracketDepth == 0 {
arrayEndLine = lineIndex
break
}
}
}

guard let start = arrayStartLine, let end = arrayEndLine else {
return original
}

let newArrayLines = "displayed = " + items.toTomlDisplayedArray()

var newLines = Array(lines[0..<start])
newLines.append(newArrayLines)
if end + 1 < lines.count {
newLines.append(contentsOf: lines[(end + 1)...])
}

return newLines.joined(separator: "\n")
}

func toggleWidget(_ widgetId: String) {
var items = config.rootToml.widgets.displayed
if let index = items.firstIndex(where: { $0.id == widgetId }) {
items.remove(at: index)
} else {
items.append(TomlWidgetItem(id: widgetId, inlineParams: [:]))
}
updateDisplayedWidgets(items)
}

func globalWidgetConfig(for widgetId: String) -> ConfigData {
config.rootToml.widgets.config(for: widgetId) ?? [:]
}
Expand Down
Loading