mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Merge remote-tracking branch 'origin/main' into feature/chinese-localization
This commit is contained in:
commit
c09be0b12b
12 changed files with 5469 additions and 5172 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +1,12 @@
|
|||
/*
|
||||
* Atoll (DynamicIsland)
|
||||
* Copyright (C) 2024-2026 Atoll Contributors
|
||||
* Copyright (C) 2024-2026 Atoll Contributors and zeph
|
||||
* Adapted from rtaudio (https://github.com/ZephyrCodesStuff/rtaudio)
|
||||
*
|
||||
* Objective-C bridge to expose C++ AudioProcessor to Swift.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
/*
|
||||
* Atoll (DynamicIsland)
|
||||
* Copyright (C) 2024-2026 Atoll Contributors
|
||||
* Copyright (C) 2024-2026 Atoll Contributors and zeph
|
||||
* Adapted from rtaudio (https://github.com/ZephyrCodesStuff/rtaudio)
|
||||
*
|
||||
* Objective-C++ implementation bridging AudioProcessor to Swift.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
/*
|
||||
* Atoll (DynamicIsland)
|
||||
* Copyright (C) 2024-2026 Atoll Contributors
|
||||
* Copyright (C) 2024-2026 Atoll Contributors and zeph
|
||||
*
|
||||
* Real-time audio processor using Accelerate framework for efficient FFT
|
||||
* and band filtering. Adapted from rtaudio project.
|
||||
* and band filtering. Adapted from rtaudio project (https://github.com/ZephyrCodesStuff/rtaudio)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
/*
|
||||
* Atoll (DynamicIsland)
|
||||
* Copyright (C) 2024-2026 Atoll Contributors
|
||||
* Copyright (C) 2024-2026 Atoll Contributors and zeph
|
||||
*
|
||||
* CoreAudio tap for capturing real-time audio from music applications.
|
||||
* Uses macOS 14.2+ Process Tap API for efficient audio capture.
|
||||
* Adapted from rtaudio project.
|
||||
* Adapted from rtaudio project (https://github.com/ZephyrCodesStuff/rtaudio)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
/*
|
||||
* Atoll (DynamicIsland)
|
||||
* Copyright (C) 2024-2026 Atoll Contributors
|
||||
* Copyright (C) 2024-2026 Atoll Contributors and zeph
|
||||
*
|
||||
* Real-time audio spectrum visualization using CoreAudio tap data.
|
||||
* Adapted from rtaudio project.
|
||||
* Adapted from rtaudio project (https://github.com/ZephyrCodesStuff/rtaudio)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1010,6 +1010,16 @@ enum ConferenceProvider: CaseIterable {
|
|||
}
|
||||
}
|
||||
|
||||
var nativeURLScheme: String? {
|
||||
switch self {
|
||||
case .zoom: return "zoommtg"
|
||||
case .teams: return "msteams"
|
||||
case .facetime: return "facetime"
|
||||
case .discord: return "discord"
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
static func detect(from url: URL) -> ConferenceProvider {
|
||||
let host = url.host?.lowercased() ?? ""
|
||||
return allCases.first { provider in
|
||||
|
|
@ -1035,7 +1045,17 @@ struct ConferenceJoinButton: View {
|
|||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: { openURL(url) }) {
|
||||
Button(action: {
|
||||
if let scheme = provider.nativeURLScheme,
|
||||
var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
|
||||
components.scheme = scheme
|
||||
if let nativeURL = components.url {
|
||||
openURL(nativeURL)
|
||||
return
|
||||
}
|
||||
}
|
||||
openURL(url)
|
||||
}) {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "video.fill")
|
||||
.font(.system(size: 9))
|
||||
|
|
|
|||
|
|
@ -2074,7 +2074,15 @@ private struct ExternalDisplayIntegrationsSection: View {
|
|||
if enableThirdPartyDDCIntegration {
|
||||
Picker("Provider", selection: $thirdPartyDDCProvider) {
|
||||
ForEach(ThirdPartyDDCProvider.allCases) { provider in
|
||||
Text(provider.displayName).tag(provider)
|
||||
HStack {
|
||||
AppIconImage(
|
||||
bundleIdentifiers: provider.bundleIdentifiers,
|
||||
symbolFallback: "display",
|
||||
symbolColor: .secondary
|
||||
)
|
||||
Text(provider.displayName)
|
||||
}
|
||||
.tag(provider)
|
||||
}
|
||||
}
|
||||
.settingsHighlight(id: highlightID("Third-party DDC provider"))
|
||||
|
|
@ -2752,6 +2760,9 @@ struct CalendarSettings: View {
|
|||
@Default(.lockScreenCalendarSelectionMode) private var lockScreenCalendarSelectionMode
|
||||
@Default(.lockScreenSelectedCalendarIDs) private var lockScreenSelectedCalendarIDs
|
||||
@Default(.lockScreenShowCalendarEventAfterStartEnabled) private var lockScreenShowCalendarEventAfterStartEnabled
|
||||
@Default(.enableThirdPartyCalendarApp) private var enableThirdPartyCalendarApp
|
||||
@Default(.selectedCalendarApp) private var selectedCalendarApp
|
||||
@Default(.fantasticalDefaultView) private var fantasticalDefaultView
|
||||
|
||||
private func highlightID(_ title: String) -> String {
|
||||
SettingsTab.calendar.highlightID(for: title)
|
||||
|
|
@ -3046,6 +3057,49 @@ struct CalendarSettings: View {
|
|||
.settingsHighlight(id: highlightID("Show start time after event begins"))
|
||||
}
|
||||
|
||||
// MARK: - Third-party Calendar Integration
|
||||
Section {
|
||||
Defaults.Toggle(key: .enableThirdPartyCalendarApp) {
|
||||
HStack {
|
||||
Image(systemName: "ellipsis.calendar")
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(width: 20, height: 20)
|
||||
Text("Enable third-party calendar app launch")
|
||||
}
|
||||
}
|
||||
.settingsHighlight(id: highlightID("Enable third-party calendar app launch"))
|
||||
|
||||
if enableThirdPartyCalendarApp {
|
||||
Picker("Calendar App", selection: $selectedCalendarApp) {
|
||||
ForEach(ThirdPartyCalendarApp.allCases) { app in
|
||||
HStack {
|
||||
AppIconImage(
|
||||
bundleIdentifiers: app.bundleIdentifiers,
|
||||
symbolFallback: app.fallbackIconName,
|
||||
symbolColor: app.fallbackIconColor
|
||||
)
|
||||
Text(app.displayName)
|
||||
}
|
||||
.tag(app)
|
||||
}
|
||||
}
|
||||
.settingsHighlight(id: highlightID("Calendar App"))
|
||||
|
||||
if selectedCalendarApp == .fantastical {
|
||||
Picker("Default View", selection: $fantasticalDefaultView) {
|
||||
ForEach(FantasticalViewStyle.allCases, id: \.self) { style in
|
||||
Text(style.displayName).tag(style)
|
||||
}
|
||||
}
|
||||
.settingsHighlight(id: highlightID("Fantastical Default View"))
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Third-party Calendar Integration")
|
||||
} footer: {
|
||||
Text("When enabled, clicking on calendar events will open the selected third-party calendar app instead of Apple Calendar.")
|
||||
}
|
||||
|
||||
Section(header: Text("Select Calendars")) {
|
||||
let grouped = Dictionary(grouping: calendarManager.allCalendars, by: \.accountName)
|
||||
let sortedAccounts = grouped.keys.sorted()
|
||||
|
|
@ -3462,19 +3516,7 @@ struct Shelf: View {
|
|||
Picker("Quick Share Service", selection: $quickShareProvider) {
|
||||
ForEach(quickShareService.availableProviders, id: \.id) { provider in
|
||||
HStack {
|
||||
Group {
|
||||
if let imgData = provider.imageData, let nsImg = NSImage(data: imgData) {
|
||||
Image(nsImage: nsImg)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 16, height: 16)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 3))
|
||||
} else {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
.frame(width: 16, height: 16)
|
||||
}
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
QuickShareProviderIconImage(provider: provider, size: 16)
|
||||
Text(provider.id)
|
||||
}
|
||||
.tag(provider.id)
|
||||
|
|
@ -3485,19 +3527,7 @@ struct Shelf: View {
|
|||
|
||||
if let selectedProvider {
|
||||
HStack {
|
||||
Group {
|
||||
if let imgData = selectedProvider.imageData, let nsImg = NSImage(data: imgData) {
|
||||
Image(nsImage: nsImg)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 16, height: 16)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 3))
|
||||
} else {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
.frame(width: 16, height: 16)
|
||||
}
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
QuickShareProviderIconImage(provider: selectedProvider, size: 16)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Currently selected: \(selectedProvider.id)")
|
||||
.font(.caption)
|
||||
|
|
@ -7933,3 +7963,109 @@ struct TerminalSettings: View {
|
|||
.navigationTitle("Terminal")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Reusable App Icon View
|
||||
|
||||
/// Fetches the real app icon from the system using bundle identifiers,
|
||||
/// falling back to an asset catalog image or an SF Symbol.
|
||||
struct AppIconImage: View {
|
||||
let bundleIdentifiers: [String]
|
||||
var assetFallback: String? = nil
|
||||
var symbolFallback: String = "app.fill"
|
||||
var symbolColor: Color = .accentColor
|
||||
var size: CGFloat = 16
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let nsImage = resolvedIcon() {
|
||||
Image(nsImage: nsImage)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipShape(RoundedRectangle(cornerRadius: size * 0.2))
|
||||
} else if let assetFallback, let nsImage = NSImage(named: NSImage.Name(assetFallback)) {
|
||||
Image(nsImage: nsImage)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipShape(RoundedRectangle(cornerRadius: size * 0.2))
|
||||
} else {
|
||||
Image(systemName: symbolFallback)
|
||||
.foregroundColor(symbolColor)
|
||||
}
|
||||
}
|
||||
.frame(width: size, height: size)
|
||||
}
|
||||
|
||||
private func resolvedIcon() -> NSImage? {
|
||||
for bundleID in bundleIdentifiers {
|
||||
if let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID) {
|
||||
let icon = NSWorkspace.shared.icon(forFile: appURL.path)
|
||||
// NSWorkspace returns a valid icon even for generic apps;
|
||||
// resize to keep memory low.
|
||||
let thumb = NSImage(size: NSSize(width: 32, height: 32))
|
||||
thumb.lockFocus()
|
||||
icon.draw(in: NSRect(origin: .zero, size: NSSize(width: 32, height: 32)),
|
||||
from: NSRect(origin: .zero, size: icon.size),
|
||||
operation: .copy, fraction: 1.0)
|
||||
thumb.unlockFocus()
|
||||
return thumb
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private struct QuickShareProviderIconImage: View {
|
||||
let provider: QuickShareProvider
|
||||
var size: CGFloat = 16
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let imgData = provider.imageData, let nsImg = NSImage(data: imgData) {
|
||||
Image(nsImage: nsImg)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipShape(RoundedRectangle(cornerRadius: size * 0.2))
|
||||
} else {
|
||||
AppIconImage(
|
||||
bundleIdentifiers: provider.bundleIdentifiersFallback,
|
||||
assetFallback: provider.assetFallbackName,
|
||||
symbolFallback: provider.symbolFallbackName,
|
||||
symbolColor: .accentColor,
|
||||
size: size
|
||||
)
|
||||
}
|
||||
}
|
||||
.frame(width: size, height: size)
|
||||
}
|
||||
}
|
||||
|
||||
private extension QuickShareProvider {
|
||||
var bundleIdentifiersFallback: [String] {
|
||||
switch id {
|
||||
case "LocalSend":
|
||||
return ["org.localsend.localsend_app", "org.localsend.localsend"]
|
||||
case "AirDrop":
|
||||
return ["com.apple.finder"]
|
||||
case "Mail":
|
||||
return ["com.apple.mail"]
|
||||
case "Messages":
|
||||
return ["com.apple.MobileSMS", "com.apple.iChat"]
|
||||
case "Notes":
|
||||
return ["com.apple.Notes"]
|
||||
case "Reminders":
|
||||
return ["com.apple.reminders"]
|
||||
case "Add to Safari Reading List":
|
||||
return ["com.apple.Safari"]
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
var assetFallbackName: String? {
|
||||
id == "LocalSend" ? "LocalSend" : nil
|
||||
}
|
||||
|
||||
var symbolFallbackName: String {
|
||||
id == "System Share Menu" ? "square.and.arrow.up.on.square" : "square.and.arrow.up"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Atoll (DynamicIsland)
|
||||
* Copyright (C) 2024-2026 Atoll Contributors
|
||||
* Adapted from SwiftTerm (https://github.com/migueldeicaza/SwiftTerm)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
|||
|
|
@ -256,6 +256,54 @@ enum CalendarSelectionState: Codable, Defaults.Serializable {
|
|||
case selected(Set<String>)
|
||||
}
|
||||
|
||||
enum FantasticalViewStyle: String, CaseIterable, Codable, Defaults.Serializable {
|
||||
case mini = "mini"
|
||||
case calendar = "calendar"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .mini: return "Mini View"
|
||||
case .calendar: return "Full Calendar"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ThirdPartyCalendarApp: String, CaseIterable, Codable, Defaults.Serializable, Identifiable {
|
||||
case fantastical = "fantastical"
|
||||
case notionCalendar = "notionCalendar"
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .fantastical: return "Fantastical"
|
||||
case .notionCalendar: return "Notion Calendar"
|
||||
}
|
||||
}
|
||||
|
||||
/// Bundle identifiers to try when looking up the app icon (first match wins).
|
||||
var bundleIdentifiers: [String] {
|
||||
switch self {
|
||||
case .fantastical: return ["com.flexibits.fantastical2.mac", "com.flexibits.fantastical"]
|
||||
case .notionCalendar: return ["com.cron.electron"]
|
||||
}
|
||||
}
|
||||
|
||||
var fallbackIconName: String {
|
||||
switch self {
|
||||
case .fantastical: return "calendar.badge.clock"
|
||||
case .notionCalendar: return "calendar.badge.plus"
|
||||
}
|
||||
}
|
||||
|
||||
var fallbackIconColor: Color {
|
||||
switch self {
|
||||
case .fantastical: return .red
|
||||
case .notionCalendar: return .blue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ClipboardDisplayMode: String, CaseIterable, Codable, Defaults.Serializable {
|
||||
case popover = "popover" // Traditional popover attached to button
|
||||
case panel = "panel" // Floating panel near notch
|
||||
|
|
@ -330,6 +378,14 @@ enum ThirdPartyDDCProvider: String, CaseIterable, Codable, Defaults.Serializable
|
|||
return "Lunar"
|
||||
}
|
||||
}
|
||||
|
||||
/// Bundle identifiers to try when looking up the app icon.
|
||||
var bundleIdentifiers: [String] {
|
||||
switch self {
|
||||
case .betterDisplay: return ["pro.betterdisplay.BetterDisplay"]
|
||||
case .lunar: return ["fyi.lunar.Lunar"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum HideNotchOption: String, Defaults.Serializable {
|
||||
|
|
@ -854,6 +910,11 @@ extension Defaults.Keys {
|
|||
static let lockScreenShowCalendarStartTimeAfterBegins = Key<Bool>("lockScreenShowCalendarStartTimeAfterBegins", default: true)
|
||||
static let lockScreenWeatherWidgetRowOrder = Key<String>("lockScreenWeatherWidgetRowOrder", default: "weather_calendar_focus")
|
||||
|
||||
// MARK: Third-party Calendar Integration
|
||||
static let enableThirdPartyCalendarApp = Key<Bool>("enableThirdPartyCalendarApp", default: false)
|
||||
static let selectedCalendarApp = Key<ThirdPartyCalendarApp>("selectedCalendarApp", default: .fantastical)
|
||||
static let fantasticalDefaultView = Key<FantasticalViewStyle>("fantasticalDefaultView", default: .mini)
|
||||
|
||||
// MARK: Battery
|
||||
static let showPowerStatusNotifications = Key<Bool>("showPowerStatusNotifications", default: true)
|
||||
static let showBatteryIndicator = Key<Bool>("showBatteryIndicator", default: BatteryActivityManager.shared.hasBattery())
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
*/
|
||||
|
||||
import Foundation
|
||||
import Defaults
|
||||
import AppKit
|
||||
|
||||
struct EventModel: Equatable, Identifiable {
|
||||
let id: String
|
||||
|
|
@ -97,7 +99,20 @@ extension EventModel {
|
|||
var isMeeting: Bool { !participants.isEmpty }
|
||||
|
||||
func calendarAppURL() -> URL? {
|
||||
// Check if a third-party calendar app is enabled
|
||||
if Defaults[.enableThirdPartyCalendarApp] {
|
||||
switch Defaults[.selectedCalendarApp] {
|
||||
case .fantastical:
|
||||
return fantasticalURL()
|
||||
case .notionCalendar:
|
||||
return notionCalendarURL()
|
||||
}
|
||||
}
|
||||
return appleCalendarURL()
|
||||
}
|
||||
|
||||
/// Returns URL to open event in Apple Calendar
|
||||
private func appleCalendarURL() -> URL? {
|
||||
guard let id = id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -123,6 +138,76 @@ extension EventModel {
|
|||
}
|
||||
return URL(string: "ical://ekevent\(date)/\(id)?method=show&options=more")
|
||||
}
|
||||
|
||||
/// Returns URL to open date in Fantastical
|
||||
private func fantasticalURL() -> URL? {
|
||||
// Reminders still use Apple's Reminders app
|
||||
guard !type.isReminder else {
|
||||
return URL(string: "x-apple-reminderkit://remcdreminder/\(id)")
|
||||
}
|
||||
|
||||
let viewStyle = Defaults[.fantasticalDefaultView]
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd"
|
||||
let dateString = dateFormatter.string(from: start)
|
||||
|
||||
// x-fantastical3://show/mini/yyyy-MM-dd or x-fantastical3://show/calendar/yyyy-MM-dd
|
||||
return URL(string: "x-fantastical3://show/\(viewStyle.rawValue)/\(dateString)")
|
||||
}
|
||||
|
||||
/// Returns URL to open Fantastical at current date (for general calendar access)
|
||||
static func fantasticalShowURL(for date: Date? = nil) -> URL? {
|
||||
let viewStyle = Defaults[.fantasticalDefaultView]
|
||||
if let date = date {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd"
|
||||
let dateString = dateFormatter.string(from: date)
|
||||
return URL(string: "x-fantastical3://show/\(viewStyle.rawValue)/\(dateString)")
|
||||
}
|
||||
return URL(string: "x-fantastical3://show/\(viewStyle.rawValue)")
|
||||
}
|
||||
|
||||
/// Returns URL to open event in Notion Calendar (formerly Cron)
|
||||
private func notionCalendarURL() -> URL? {
|
||||
// Reminders still use Apple's Reminders app
|
||||
guard !type.isReminder else {
|
||||
return URL(string: "x-apple-reminderkit://remcdreminder/\(id)")
|
||||
}
|
||||
|
||||
let formatter = ISO8601DateFormatter()
|
||||
|
||||
var components = URLComponents()
|
||||
components.scheme = "cron"
|
||||
components.host = "showEvent"
|
||||
components.queryItems = [
|
||||
URLQueryItem(name: "accountEmail", value: calendar.accountName),
|
||||
URLQueryItem(name: "iCalUID", value: id),
|
||||
URLQueryItem(name: "startDate", value: formatter.string(from: start)),
|
||||
URLQueryItem(name: "endDate", value: formatter.string(from: end)),
|
||||
URLQueryItem(name: "title", value: title)
|
||||
]
|
||||
|
||||
if let url = components.url {
|
||||
return url
|
||||
}
|
||||
|
||||
// Fallback: just launch Notion Calendar app
|
||||
Self.launchNotionCalendar()
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Launches Notion Calendar app directly (fallback or general access)
|
||||
static func launchNotionCalendar() {
|
||||
// Launch via bundle identifier (Notion Calendar's bundle ID is "com.cron.electron")
|
||||
if let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.cron.electron") {
|
||||
let config = NSWorkspace.OpenConfiguration()
|
||||
NSWorkspace.shared.openApplication(at: url, configuration: config) { _, error in
|
||||
if let error = error {
|
||||
print("Error launching Notion Calendar: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Participant: Hashable {
|
||||
|
|
|
|||
|
|
@ -112,7 +112,12 @@ Atoll builds upon the work of several open-source projects and draws inspiration
|
|||
|
||||
- [**SkyLightWindow**](https://github.com/Lakr233/SkyLightWindow) - window rendering for Lock Screen Widgets
|
||||
|
||||
- [**rtaudio**](https://github.com/ZephyrCodesStuff/rtaudio) - Live music visualizer using C++ was adapted from this project
|
||||
|
||||
- [**SwiftTerm**](https://github.com/migueldeicaza/SwiftTerm) - Terminal tab implementation in the standard mode was adapted from this project
|
||||
|
||||
- Wick - Thanks Nate for allowing us to replicate the iOS like Timer design for the Lock Screen Widget
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/Ebullioscopic/Atoll/graphs/contributors">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue