Skip to content
Open

merge #1313

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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ DerivedData/

# Build output
build/
dist/
*.app
*.dSYM
*.dmg

# Temporary files
*.swp
Expand Down
74 changes: 74 additions & 0 deletions BoringNotchXPCHelper/BoringNotchXPCHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// BoringNotchXPCHelper
//
// Created by Alexander on 2025-11-16.
// Modified by Maksymilian Wójcik on 2026-06-09.
//

import Foundation
Expand Down Expand Up @@ -139,6 +140,79 @@ class BoringNotchXPCHelper: NSObject, BoringNotchXPCHelperProtocol {
reply(false)
}

// MARK: - CPU Temperature (IOHID thermal sensors; helper is unsandboxed)

private static let thermalReader = ThermalReader()

@objc func currentCPUTemperature(with reply: @escaping (NSNumber?) -> Void) {
reply(Self.thermalReader?.readAverage().map { NSNumber(value: $0) })
}

/// Averages on-die temperature sensors (usage page 0xFF00 / usage 5) via the
/// private IOHIDEventSystemClient API, resolved at runtime with dlsym.
private final class ThermalReader {
private typealias CreateFn = @convention(c) (CFAllocator?) -> Unmanaged<AnyObject>?
private typealias MatchFn = @convention(c) (AnyObject?, CFDictionary?) -> Int32
private typealias ServicesFn = @convention(c) (AnyObject?) -> Unmanaged<CFArray>?
private typealias EventFn = @convention(c) (AnyObject?, Int64, Int32, Int64) -> Unmanaged<AnyObject>?
private typealias FloatFn = @convention(c) (AnyObject?, Int32) -> Double

private let servicesFn: ServicesFn
private let eventFn: EventFn
private let floatFn: FloatFn
private let client: AnyObject
private let temperatureType: Int64 = 15
private let temperatureField: Int32 = 15 << 16

init?() {
let handle = dlopen(nil, RTLD_LAZY)
func load<T>(_ name: String) -> T? {
guard let sym = dlsym(handle, name) else { return nil }
return unsafeBitCast(sym, to: T.self)
}
guard let create: CreateFn = load("IOHIDEventSystemClientCreate"),
let match: MatchFn = load("IOHIDEventSystemClientSetMatching"),
let services: ServicesFn = load("IOHIDEventSystemClientCopyServices"),
let event: EventFn = load("IOHIDServiceClientCopyEvent"),
let float: FloatFn = load("IOHIDEventGetFloatValue")
else {
NSLog("🌡️ [helper] dlsym FAILED for one or more IOHID symbols")
return nil
}
guard let clientRef = create(kCFAllocatorDefault)?.takeRetainedValue() else {
NSLog("🌡️ [helper] IOHIDEventSystemClientCreate returned nil")
return nil
}

servicesFn = services
eventFn = event
floatFn = float
client = clientRef
_ = match(clientRef, ["PrimaryUsagePage": 0xff00, "PrimaryUsage": 5] as CFDictionary)
}

func readAverage() -> Double? {
guard let services = servicesFn(client)?.takeRetainedValue() as? [AnyObject],
!services.isEmpty
else {
return nil
}
var sum = 0.0
var count = 0
for service in services {
guard let event = eventFn(service, temperatureType, 0, 0)?.takeRetainedValue() else {
continue
}
let value = floatFn(event, temperatureField)
if value > 0, value < 130 {
sum += value
count += 1
}
}
return count > 0 ? sum / Double(count) : nil
}
}

// MARK: - Private helpers for DisplayServices / IOKit access
private func displayServicesGetBrightness(displayID: CGDirectDisplayID, out: inout Float) -> Bool {
guard let sym = dlsym(DisplayServicesHandle.handle, "DisplayServicesGetBrightness") else { return false }
Expand Down
130 changes: 117 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<br>
</h1>


<p align="center">
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/boring-notch"><img src="https://badges.crowdin.net/boring-notch/localized.svg"></a>
<img src="https://github.com/TheBoredTeam/boring.notch/actions/workflows/cicd.yml/badge.svg" alt="TheBoringNotch Build & Test" style="margin-right: 10px;" />
Expand All @@ -26,8 +25,107 @@ Say hello to **Boring Notch**, the coolest way to make your MacBook’s notch th
<img src="https://github.com/user-attachments/assets/2d5f69c1-6e7b-4bc2-a6f1-bb9e27cf88a8" alt="Demo GIF" />
</p>

<!--https://github.com/user-attachments/assets/19b87973-4b3a-4853-b532-7e82d1d6b040-->
## <!--https://github.com/user-attachments/assets/19b87973-4b3a-4853-b532-7e82d1d6b040-->

## 🍴 About This Fork

> This is a personal fork of [**TheBoredTeam/boring.notch**](https://github.com/TheBoredTeam/boring.notch), maintained by [**@maksiosmf**](https://github.com/maksiosmf), adding a set of extra widgets and quality-of-life features on top of the original.
>
> 🔗 **Fork:** [`github.com/maksiosmf/boring.notch`](https://github.com/maksiosmf/boring.notch) &nbsp;·&nbsp; ⬆️ **Upstream:** [`TheBoredTeam/boring.notch`](https://github.com/TheBoredTeam/boring.notch)

### ✨ What's new in this fork

| | Feature | Description |
| :-: | :------------------------------- | :--------------------------------------------------------------------------------------------------------- |
| 🎧 | **Bluetooth / AirPods popup** | iOS-style "device connected" pill on the notch, with battery level (AirPods, mouse, keyboard, headphones). |
| 🔋 | **iOS-style charging animation** | Pulsing bolt + percentage when you plug in the charger. |
| 🔊 | **Audio output switcher** | Switch the system output device (AirPods / speakers / HDMI) right from the open notch. |
| 🌡️ | **System monitor** | Live CPU, memory, disk and network usage — plus **real CPU temperature**. |
| ⛅ | **Weather widget** | Current weather via [wttr.in](https://wttr.in) (no API key) — auto-located by IP or a city of your choice. |
| 🏠 | **Customizable Home** | Pick small components (clock, weather, CPU temp, CPU / RAM / disk) to show on the Home tab. |
| 📐 | **Adaptive notch height** | The open notch grows downward to fit widgets instead of squishing them. |
| ⚡ | **Snappier battery popups** | Plug / unplug indicators appear instantly. |
| 🪫 | **Low battery alert** | iOS-style red pulsing alert on the notch when the battery drops to 10% and again at 5%. |
| 🎚️ | **Device batteries widget** | Battery levels of connected Bluetooth devices (AirPods, mouse, keyboard) on the Widgets tab. |
| 🌦️ | **3-day weather forecast** | Compact day/icon/max-min row under the current weather (same wttr.in request, no extra API). |
| 💱 | **Currency / crypto rates** | Configurable pairs (e.g. `USD/PLN, BTC/USD`) from ECB (frankfurter.dev) and CoinGecko — no API keys. |
| 🛠️ | **Helper build fix** | Restored the XPC helper compilation so Accessibility / HUD replacement work again. |

Everything new lives under **Settings → Home** and **Settings → Widgets**.

### 📁 Modified files

A full list of what this fork touches relative to upstream — useful if you'd like to cherry-pick any of it.

<details>
<summary><b>New files (17)</b></summary>

| File | What it does |
| :--- | :--- |
| `boringNotch/managers/BluetoothActivityManager.swift` | Watches IOBluetooth connect/disconnect notifications, reads device battery level, debounces duplicate events and skips nameless devices, then triggers the notch popup. Also exposes `BluetoothBatteryReader.allDevices()` (via `system_profiler SPBluetoothDataType`) for the device-batteries widget. |
| `boringNotch/components/Live activities/BoringBluetoothPopup.swift` | The iOS-style "device connected" pill shown on the closed notch (device icon, name, battery). |
| `boringNotch/components/Live activities/BoringChargingAnimation.swift` | iOS-style charging popup: pulsing bolt, battery percentage and time-to-full on the closed notch. |
| `boringNotch/managers/AudioDeviceManager.swift` | CoreAudio wrapper: lists output devices, observes default-device changes, switches the system output device. |
| `boringNotch/components/Notch/AudioDeviceMenu.swift` | Speaker menu in the open-notch header for picking the output device. |
| `boringNotch/managers/SystemMonitorManager.swift` | Samples CPU, RAM, disk and network usage on a configurable interval (Mach host APIs + `getifaddrs`). |
| `boringNotch/managers/CPUTemperatureReader.swift` | In-process CPU temperature via private IOHID thermal-sensor APIs resolved with `dlsym` (averages on-die sensors). |
| `boringNotch/components/Notch/SystemMonitorView.swift` | The system-monitor widget UI (CPU / RAM / disk / network / temperature rows). |
| `boringNotch/managers/WeatherManager.swift` | Fetches current weather **and a 3-day forecast** from [wttr.in](https://wttr.in) (no API key), with manual-city support and a configurable refresh interval. |
| `boringNotch/managers/LocationManager.swift` | IP-based auto-location (city lookup) for the weather widget. |
| `boringNotch/components/Notch/WeatherWidgetView.swift` | The weather widget UI (condition icon, temperature, city). |
| `boringNotch/components/Notch/WidgetsView.swift` | The new **Widgets** tab hosting the system monitor, weather, device batteries and rates widgets (scrolls horizontally when more than two are enabled). |
| `boringNotch/components/Notch/HomeWidgetsView.swift` | Row of small, individually-toggleable components (clock, weather, CPU temp, CPU / RAM / disk) under the music player on the Home tab. |
| `boringNotch/components/Live activities/BoringLowBatteryAlert.swift` | iOS-style red pulsing low-battery alert shown on the closed notch at 10% / 5% (battery power only). |
| `boringNotch/components/Notch/DeviceBatteriesView.swift` | Widgets-tab panel listing battery levels of connected Bluetooth devices (refreshes every 30 s off the main thread). |
| `boringNotch/managers/RatesManager.swift` | Fetches configurable currency pairs from frankfurter.dev (ECB, no key) and crypto prices from CoinGecko (no key); refresh interval setting. |
| `boringNotch/components/Notch/RatesWidgetView.swift` | Widgets-tab panel showing the configured fiat/crypto rates. |

</details>

<details>
<summary><b>Modified files (19)</b></summary>

| File | What changed |
| :--- | :--- |
| `boringNotch/models/Constants.swift` | New `Defaults` keys for all fork features (charging animation, audio switcher, Bluetooth popup, system monitor, weather + forecast, Home components, low-battery alerts, device batteries, disk popup, rates) + `WeatherUnit` enum. |
| `boringNotch/components/Settings/SettingsView.swift` | New settings UI: Home-tab component toggles and a **Widgets** section (system monitor, weather + forecast, Bluetooth popup, charging animation, audio switcher, low-battery alert, disk popup, device batteries, rates pairs). |
| `boringNotch/ContentView.swift` | Renders the new closed-notch popups (charging, Bluetooth, low battery), routes the new `.widgets` tab, and adds adaptive open-notch height (`openNotchContentHeight`) so the notch grows downward for widget content instead of squishing it. |
| `boringNotch/BoringViewCoordinator.swift` | New `SneakContentType` cases (`.bluetooth`, `.charging`, `.audioDevice`, `.lowBattery`) and per-type popup durations (4 s Bluetooth/charging/low-battery). |
| `boringNotch/enums/generic.swift` | Adds `.widgets` to `NotchViews`. |
| `boringNotch/components/Tabs/TabSelectionView.swift` | Adds the **Widgets** tab; tabs are now filtered by settings (Shelf hidden when disabled, Widgets shown only when a widget — monitor, weather, device batteries or rates — is enabled). |
| `boringNotch/components/Notch/BoringHeader.swift` | Shows tabs when the Widgets tab is enabled (even without Shelf) and adds the audio-device menu button. |
| `boringNotch/components/Notch/NotchHomeView.swift` | Wraps the Home layout in a `VStack` and appends `HomeWidgetsView` when any Home component is enabled. |
| `boringNotch/sizing/matters.swift` | Replaces fixed `openNotchSize` height with `openNotchBaseHeight` (190) + `openNotchMaxHeight` (235); the window is created at max height and the visible shape scales to content. |
| `boringNotch/models/BatteryStatusViewModel.swift` | Sends `.charging` (instead of `.battery`) popups when the iOS charging animation is enabled. Adds the low-battery threshold logic (alert once at 10%, once at 5%, re-armed when charging). |
| `boringNotch/managers/BatteryActivityManager.swift` | Reduces battery-event queue spacing from 1 s to 0.05 s so plug/unplug popups appear instantly. |
| `boringNotch/boringNotchApp.swift` | Starts `BluetoothActivityManager` on launch. |
| `BoringNotchXPCHelper/BoringNotchXPCHelper.swift` | Adds an IOHID-based CPU-temperature reader to the (unsandboxed) helper. |
| `boringNotch/XPCHelperClient/BoringNotchXPCHelperProtocol.swift` | New `currentCPUTemperature` XPC method. |
| `boringNotch/XPCHelperClient/XPCHelperClient.swift` | Async client wrapper for the new CPU-temperature XPC call. |
| `boringNotch.xcodeproj/project.pbxproj` | Registers all new files **and fixes the `BoringNotchXPCHelper` target, which had no Sources build phase** — the helper never compiled (empty `.xpc`), so Accessibility / HUD replacement were broken even on upstream. |
| `boringNotch/boringNotch.entitlements` | Disables the App Sandbox (for in-process CPU-temperature reads) and adds Bluetooth + location entitlements. |
| `boringNotch/Info.plist` | Adds location and Bluetooth usage descriptions. |
| `.gitignore` | Ignores `dist/` and `*.dmg`. |

</details>

> [!IMPORTANT]
> **For upstream maintainers:** the `project.pbxproj` fix above (missing Sources build phase on the XPC helper target) likely affects upstream builds too and is independent of the fork features.

> [!NOTE]
> To read CPU temperature sensors, the **App Sandbox is disabled** in this fork. That makes the build a little less locked-down than upstream — perfectly fine for personal use, but worth knowing.

> [!TIP]
> This fork is signed for personal use (no Apple notarization), so on first launch run:
>
> ```bash
> xattr -dr com.apple.quarantine /Applications/boringNotch.app
> ```
>
> then open it normally.

---

<!--## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
Expand All @@ -42,6 +140,7 @@ Say hello to **Boring Notch**, the coolest way to make your MacBook’s notch th
## Installation

**System Requirements:**

- macOS **14 Sonoma** or later
- Apple Silicon or Intel Mac

Expand Down Expand Up @@ -103,6 +202,7 @@ brew install --cask TheBoredTeam/boring-notch/boring-notch
- Click the star in your menu bar to customize your notch to your heart's content.

## 📋 Roadmap

- [x] Playback live activity 🎧
- [x] Calendar integration 📆
- [x] Reminders integration ☑️
Expand All @@ -112,16 +212,16 @@ brew install --cask TheBoredTeam/boring-notch/boring-notch
- [x] Shelf functionality with AirDrop 📚
- [x] Notch sizing customization, finetuning on different display sizes 🖥️
- [x] System HUD replacements (volume, brightness, backlight) 🎚️💡⌨️
- [ ] Bluetooth Live Activity (connect/disconnect for bluetooth devices)
- [ ] Weather integration ⛅️
- [x] Bluetooth Live Activity (connect/disconnect for bluetooth devices) — _added in this fork_
- [x] Weather integration ⛅️ — _added in this fork_
- [ ] Customizable Layout options 🛠️
- [ ] Lock Screen Widgets 🔒
- [ ] Extension system 🧩
- [ ] Notifications (under consideration) 🔔
<!-- - [ ] Clipboard history manager 📌 `Extension` -->
<!-- - [ ] Download indicator of different browsers (Safari, Chromium browsers, Firefox) 🌍 `Extension`-->
<!-- - [ ] Customizable function buttons 🎛️ -->
<!-- - [ ] App switcher 🪄 -->
<!-- - [ ] Clipboard history manager 📌 `Extension` -->
<!-- - [ ] Download indicator of different browsers (Safari, Chromium browsers, Firefox) 🌍 `Extension`-->
<!-- - [ ] Customizable function buttons 🎛️ -->
<!-- - [ ] App switcher 🪄 -->

<!-- ## 🧩 Extensions
> [!NOTE]
Expand All @@ -137,18 +237,20 @@ brew install --cask TheBoredTeam/boring-notch/boring-notch
### Installation

1. **Clone the Repository**:

```bash
git clone https://github.com/TheBoredTeam/boring.notch.git
cd boring.notch
```

2. **Open the Project in Xcode**:

```bash
open boringNotch.xcodeproj
```

3. **Build and Run**:
- Click the "Run" button or press `Cmd + R`. Watch the magic unfold!
- Click the "Run" button or press `Cmd + R`. Watch the magic unfold!

## 🤝 Contributing

Expand All @@ -169,23 +271,25 @@ We’re all about good vibes and awesome contributions! Read [CONTRIBUTING.md](C
</a>

## Support us on Ko-fi!

<!-- <a href="https://www.buymeacoffee.com/jfxh67wvfxq" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-red.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a> -->

<a href="https://www.ko-fi.com/alexander5015" target="_blank"><img src="https://github.com/user-attachments/assets//a76175ef-7e93-475a-8b67-4922ba5964c2" alt="Support us on Ko-fi" style="height: 70px !important;width: 346px !important;" ></a>

## 🎉 Acknowledgments

We would like to express our gratitude to the authors and maintainers of the open-source projects that made this possible.
We would like to express our gratitude to the authors and maintainers of the open-source projects that made this possible.

## Notable Projects
- **[MediaRemoteAdapter](https://github.com/ungive/mediaremote-adapter)** – An open-source project that allowed us to use the Now Playing source in macOS 15.4+

- **[MediaRemoteAdapter](https://github.com/ungive/mediaremote-adapter)** – An open-source project that allowed us to use the Now Playing source in macOS 15.4+
- **[NotchDrop](https://github.com/Lakr233/NotchDrop)** – An open-source project that has been instrumental in developing the first version of the "Shelf" feature in Boring Notch.

For a full list of licenses and attributions, please see the [Third-Party Licenses](./THIRD_PARTY_LICENSES.md) file.

### Icon credits: [@maxtron95](https://github.com/maxtron95)

### Website credits: [@himanshhhhuv](https://github.com/himanshhhhuv)

- **SwiftUI**: For making us look like coding wizards.
- **You**: For being awesome and checking out **boring.notch**!


Loading
Loading