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
4 changes: 4 additions & 0 deletions boringNotch.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
B141C2412CA5F53F00AC8CC8 /* SparkleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B141C2402CA5F53E00AC8CC8 /* SparkleView.swift */; };
B1628B922CC260C0003D8DF3 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = B1628B912CC260C0003D8DF3 /* SwiftUIIntrospect */; };
B17266DF2C64DFA00031BA0D /* BundleInfos.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17266DE2C64DFA00031BA0D /* BundleInfos.swift */; };
442F87339A9A4131BE8A09B6 /* CalendarAppURLSchemes.plist in Resources */ = {isa = PBXBuildFile; fileRef = D94887D6942243249BCD46EC /* CalendarAppURLSchemes.plist */; };
B17266E12C6532560031BA0D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B17266E02C6532560031BA0D /* Localizable.xcstrings */; };
B172AAC02C95DA0B001623F1 /* InlineOSD.swift in Sources */ = {isa = PBXBuildFile; fileRef = B172AABF2C95DA0B001623F1 /* InlineOSD.swift */; };
B18654392C6F4990000B926A /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = B18654382C6F4990000B926A /* KeyboardShortcuts */; };
Expand Down Expand Up @@ -322,6 +323,7 @@
B141C2402CA5F53E00AC8CC8 /* SparkleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkleView.swift; sourceTree = "<group>"; };
B17266DE2C64DFA00031BA0D /* BundleInfos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleInfos.swift; sourceTree = "<group>"; };
B17266E02C6532560031BA0D /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
D94887D6942243249BCD46EC /* CalendarAppURLSchemes.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = CalendarAppURLSchemes.plist; sourceTree = "<group>"; };
B172AABF2C95DA0B001623F1 /* InlineOSD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineOSD.swift; sourceTree = "<group>"; };
B186543B2C6F49AE000B926A /* ShortcutConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutConstants.swift; sourceTree = "<group>"; };
B19016232CC15B4D00E3F12E /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -673,6 +675,7 @@
14CEF4152C5CAED300855D72 /* boringNotchApp.swift */,
14CEF4172C5CAED300855D72 /* ContentView.swift */,
B17266E02C6532560031BA0D /* Localizable.xcstrings */,
D94887D6942243249BCD46EC /* CalendarAppURLSchemes.plist */,
14CEF4192C5CAED400855D72 /* Assets.xcassets */,
14A7E5962C65FD31008C1BE9 /* boring.m4a */,
1443E7F42C609E650027C1FC /* Info.plist */,
Expand Down Expand Up @@ -972,6 +975,7 @@
112B0EB82E30DD0F00562D6C /* MediaRemoteAdapterTestClient in Resources */,
112B0EB92E30DD0F00562D6C /* mediaremote-adapter.pl in Resources */,
B17266E12C6532560031BA0D /* Localizable.xcstrings in Resources */,
442F87339A9A4131BE8A09B6 /* CalendarAppURLSchemes.plist in Resources */,
14A7E5972C65FD31008C1BE9 /* boring.m4a in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
30 changes: 30 additions & 0 deletions boringNotch/CalendarAppURLSchemes.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?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">
<!--
Maps a calendar app's bundle ID to a URL template for opening a specific event.

Placeholders:
{id} – percent-encoded EKEvent.calendarItemIdentifier
{date} – ISO-8601 recurrence date prefix used by Apple Calendar ("/yyyy-MM-dd'T'HH:mm:ssZ"),
empty string for non-recurring events; safe to omit from templates that don't need it

To add support for another calendar app:
1. Find its bundle ID (e.g. via: mdls -name kMDItemCFBundleIdentifier /Applications/YourApp.app)
2. Find its URL scheme (check the app's documentation or Info.plist CFBundleURLSchemes)
3. Add a new key/string pair below
-->
<dict>
<!-- Apple Calendar -->
<key>com.apple.iCal</key>
<string>ical://ekevent{date}/{id}?method=show&amp;options=more</string>

<!-- Fantastical (bundle ID unchanged across versions 2 and 3) -->
<key>com.flexibits.fantastical2.mac</key>
<string>x-fantastical2://show?eventIdentifier={id}</string>

<!-- BusyCal -->
<key>com.busymac.busycal3</key>
<string>busycal://open?calendarItemIdentifier={id}</string>
</dict>
</plist>
49 changes: 25 additions & 24 deletions boringNotch/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -2449,16 +2449,6 @@
}
}
},
"Changing the app language requires restarting Boring Notch." : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Changing the app language requires restarting Boring Notch."
}
}
}
},
"Change media with horizontal gestures" : {
"localizations" : {
"de" : {
Expand Down Expand Up @@ -2505,6 +2495,16 @@
}
}
},
"Changing the app language requires restarting Boring Notch." : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Changing the app language requires restarting Boring Notch."
}
}
}
},
"Charging" : {
"localizations" : {
"cs" : {
Expand Down Expand Up @@ -6893,6 +6893,16 @@
}
}
},
"Later" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Later"
}
}
}
},
"Launch at login" : {
"localizations" : {
"fr" : {
Expand All @@ -6915,16 +6925,6 @@
}
}
},
"Later" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Later"
}
}
}
},
"Layout Preview" : {
"localizations" : {
"de" : {
Expand Down Expand Up @@ -9559,6 +9559,7 @@
}
},
"OSD" : {
"extractionState" : "stale",
"localizations" : {
"fr" : {
"stringUnit" : {
Expand Down Expand Up @@ -10055,9 +10056,6 @@
},
"Real-time audio waveform" : {

},
"Requires macOS 14.2 or later. Update macOS to enable real-time audio waveform." : {

},
"Release name" : {
"localizations" : {
Expand Down Expand Up @@ -10441,6 +10439,9 @@
}
}
}
},
"Requires macOS 14.2 or later. Update macOS to enable real-time audio waveform." : {

},
"Reset to Defaults" : {
"localizations" : {
Expand Down Expand Up @@ -14153,4 +14154,4 @@
}
},
"version" : "1.1"
}
}
45 changes: 34 additions & 11 deletions boringNotch/models/EventModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// Modified by Alexander on 2025-05-18.
//

import AppKit
import Foundation

struct EventModel: Equatable, Identifiable {
Expand Down Expand Up @@ -83,31 +84,53 @@ extension EventModel {
var isMeeting: Bool { !participants.isEmpty }

func calendarAppURL() -> URL? {

guard let id = id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
guard let encodedId = id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
return nil
}

guard !type.isReminder else {
return URL(string: "x-apple-reminderkit://remcdreminder/\(id)")
return URL(string: "x-apple-reminderkit://remcdreminder/\(encodedId)")
}

let date: String
// Recurrence date fragment — only used by the Apple Calendar ical:// template.
// Other app templates omit {date} so this replacement is a harmless no-op for them.
let dateFragment: String
if hasRecurrenceRules {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
if !isAllDay {
formatter.timeZone = .init(secondsFromGMT: 0)
}
if let formattedDate = formatter.string(for: start) {
date = "/\(formattedDate)"
} else {
return nil
}
guard let formattedDate = formatter.string(for: start) else { return nil }
dateFragment = "/\(formattedDate)"
} else {
date = ""
dateFragment = ""
}

if let template = Self.urlTemplate(forDefaultCalendarApp: ()) {
let urlString = template
.replacingOccurrences(of: "{id}", with: encodedId)
.replacingOccurrences(of: "{date}", with: dateFragment)
return URL(string: urlString)
}
return URL(string: "ical://ekevent\(date)/\(id)?method=show&options=more")

// Fallback when the plist is missing or the default app has no entry.
return URL(string: "ical://ekevent\(dateFragment)/\(encodedId)?method=show&options=more")
}

// Reads CalendarAppURLSchemes.plist and returns the URL template for the current
// default calendar app (identified by which app handles webcal:// links).
private static func urlTemplate(forDefaultCalendarApp _: ()) -> String? {
guard
let webcalURL = URL(string: "webcal://x"),
let appURL = NSWorkspace.shared.urlForApplication(toOpen: webcalURL),
let bundleId = Bundle(url: appURL)?.bundleIdentifier,
let plistURL = Bundle.main.url(forResource: "CalendarAppURLSchemes", withExtension: "plist"),
let data = try? Data(contentsOf: plistURL),
let mapping = try? PropertyListDecoder().decode([String: String].self, from: data)
else { return nil }

return mapping[bundleId]
}
}

Expand Down
Loading