Skip to content

Commit

Permalink
Add Ephemeral plugins support #357
Browse files Browse the repository at this point in the history
  • Loading branch information
melonamin committed Apr 14, 2023
1 parent d725684 commit 6e5d16b
Show file tree
Hide file tree
Showing 10 changed files with 908 additions and 579 deletions.
25 changes: 16 additions & 9 deletions SwiftBar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@
FAA14A192728C7FC0052FDB8 /* AppDelegate+Intents.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAA14A182728C7FC0052FDB8 /* AppDelegate+Intents.swift */; };
FAC3F2ED2915D88300D8F346 /* ShortcutPluginsPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC3F2EC2915D88300D8F346 /* ShortcutPluginsPreferencesView.swift */; };
FAC3F2F12916A61800D8F346 /* AboutSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC3F2F02916A61800D8F346 /* AboutSettingsView.swift */; };
FAC4AE4929722CD2000BED63 /* EphemeralPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC4AE4829722CD2000BED63 /* EphemeralPlugin.swift */; };
FAC4AE4B297304F4000BED63 /* SetEphemeralPluginIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC4AE4A297304F4000BED63 /* SetEphemeralPluginIntentHandler.swift */; };
FAD14C052585AE1800CB7BBE /* String+ANSIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD14C042585AE1800CB7BBE /* String+ANSIColor.swift */; };
FAD1BC9B25D22E9400B761E8 /* PluginDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD1BC9A25D22E9400B761E8 /* PluginDetailsView.swift */; };
FAD1BC9E25D22EEA00B761E8 /* AnimatableWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD1BC9D25D22EEA00B761E8 /* AnimatableWindow.swift */; };
Expand Down Expand Up @@ -219,6 +221,8 @@
FAA14A182728C7FC0052FDB8 /* AppDelegate+Intents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Intents.swift"; sourceTree = "<group>"; };
FAC3F2EC2915D88300D8F346 /* ShortcutPluginsPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutPluginsPreferencesView.swift; sourceTree = "<group>"; };
FAC3F2F02916A61800D8F346 /* AboutSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutSettingsView.swift; sourceTree = "<group>"; };
FAC4AE4829722CD2000BED63 /* EphemeralPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralPlugin.swift; sourceTree = "<group>"; };
FAC4AE4A297304F4000BED63 /* SetEphemeralPluginIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetEphemeralPluginIntentHandler.swift; sourceTree = "<group>"; };
FAD14C042585AE1800CB7BBE /* String+ANSIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+ANSIColor.swift"; sourceTree = "<group>"; };
FAD1BC9A25D22E9400B761E8 /* PluginDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginDetailsView.swift; sourceTree = "<group>"; };
FAD1BC9D25D22EEA00B761E8 /* AnimatableWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatableWindow.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -362,10 +366,11 @@
39AF776E25461277001D76E5 /* Plugin.swift */,
FAD571A225A0071700623B57 /* StreamablePlugin.swift */,
39AF7777254613FB001D76E5 /* ExecutablePlugin.swift */,
FAC4AE4829722CD2000BED63 /* EphemeralPlugin.swift */,
FA6E13852915B70E00BA4594 /* ShortcutPlugin.swift */,
39AF7771254612BF001D76E5 /* PluginMetadata.swift */,
39AF7774254613C9001D76E5 /* PluginManger.swift */,
3990DCE02602BB5B002CDCD5 /* PluginDebugInfo.swift */,
FA6E13852915B70E00BA4594 /* ShortcutPlugin.swift */,
);
path = Plugin;
sourceTree = "<group>";
Expand Down Expand Up @@ -429,6 +434,7 @@
FAA14A122728C6F20052FDB8 /* ReloadPluginIntentHandler.swift */,
FAA14A142728C6FC0052FDB8 /* DisablePluginIntentHandler.swift */,
FAA14A162728C79B0052FDB8 /* GetPluginsIntentHandler.swift */,
FAC4AE4A297304F4000BED63 /* SetEphemeralPluginIntentHandler.swift */,
);
path = Intents;
sourceTree = "<group>";
Expand Down Expand Up @@ -512,9 +518,8 @@
3920747225460FD000213DBE /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1200;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1410;
TargetAttributes = {
3920747925460FD000213DBE = {
CreatedOnToolsVersion = 12.0.1;
Expand Down Expand Up @@ -657,7 +662,7 @@
398DAE76255373B700747D90 /* Launch At Login */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 8;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Expand All @@ -669,7 +674,7 @@
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 1;
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\"${BUILT_PRODUCTS_DIR}/LaunchAtLogin_LaunchAtLogin.bundle/Contents/Resources/copy-helper-swiftpm.sh\"\n";
};
Expand All @@ -695,7 +700,7 @@
FAD14BF62583256A00CB7BBE /* Codesign Sparkle */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 8;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Expand All @@ -707,7 +712,7 @@
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 1;
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "
";
Expand Down Expand Up @@ -752,6 +757,7 @@
39AF7793254B5834001D76E5 /* NSColor.swift in Sources */,
FA136A542917EE0700A7D712 /* PluginUtilities.swift in Sources */,
398B86C4254DA85300DEA027 /* URL+Extension.swift in Sources */,
FAC4AE4929722CD2000BED63 /* EphemeralPlugin.swift in Sources */,
FAA14A192728C7FC0052FDB8 /* AppDelegate+Intents.swift in Sources */,
39AF778D2548EBA3001D76E5 /* MenuLineParameters.swift in Sources */,
FAD1BC9E25D22EEA00B761E8 /* AnimatableWindow.swift in Sources */,
Expand All @@ -772,6 +778,7 @@
39641EA5254E096200713DAF /* PreferencesView.swift in Sources */,
39AF7772254612BF001D76E5 /* PluginMetadata.swift in Sources */,
39B84A1F257D436200FA012E /* RunScript.swift in Sources */,
FAC4AE4B297304F4000BED63 /* SetEphemeralPluginIntentHandler.swift in Sources */,
FAA14A152728C6FC0052FDB8 /* DisablePluginIntentHandler.swift in Sources */,
FA64C9DF25CF691D00C4E5C5 /* GeneralPreferencesView.swift in Sources */,
FAD14C052585AE1800CB7BBE /* String+ANSIColor.swift in Sources */,
Expand Down Expand Up @@ -985,7 +992,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 461;
CURRENT_PROJECT_VERSION = 479;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"SwiftBar/Preview Content\"";
DEVELOPMENT_TEAM = X93LWC49WV;
Expand Down Expand Up @@ -1013,7 +1020,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 461;
CURRENT_PROJECT_VERSION = 479;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"SwiftBar/Preview Content\"";
DEVELOPMENT_TEAM = X93LWC49WV;
Expand Down
2 changes: 2 additions & 0 deletions SwiftBar/AppDelegate+Intents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ extension AppDelegate {
return DisablePluginIntentHandler()
case is ReloadPluginIntent:
return ReloadPluginIntentHandler()
case is SetEphemeralPluginIntent:
return SetEphemeralPluginIntentHandler()
default:
return nil
}
Expand Down
31 changes: 31 additions & 0 deletions SwiftBar/Intents/SetEphemeralPluginIntentHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Intents

@available(macOS 11.0, *)
public class SetEphemeralPluginIntentHandler: NSObject, SetEphemeralPluginIntentHandling {
@MainActor
public func handle(intent: SetEphemeralPluginIntent) async -> SetEphemeralPluginIntentResponse {
guard let id = intent.name, let content = intent.content, let exitAfter = intent.exitAfter else {
return SetEphemeralPluginIntentResponse()
}
delegate.pluginManager.setEphemeralPlugin(pluginId: id, content: content, exitAfter: Double(truncating: exitAfter))
return SetEphemeralPluginIntentResponse()
}

public func resolveName(for intent: SetEphemeralPluginIntent) async -> INStringResolutionResult {
guard let name = intent.name, !name.isEmpty else {
return INStringResolutionResult.needsValue()
}
return INStringResolutionResult.success(with: name)
}

public func resolveContent(for intent: SetEphemeralPluginIntent) async -> INStringResolutionResult {
guard let content = intent.content, !content.isEmpty else {
return INStringResolutionResult.needsValue()
}
return INStringResolutionResult.success(with: content)
}

public func resolveExitAfter(for intent: SetEphemeralPluginIntent) async -> INTimeIntervalResolutionResult {
INTimeIntervalResolutionResult.success(with: TimeInterval(truncating: intent.exitAfter ?? 0))
}
}
26 changes: 21 additions & 5 deletions SwiftBar/MenuBar/MenuBarItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class MenubarItem: NSObject {
let runInTerminalItem = NSMenuItem(title: Localizable.MenuBar.RunInTerminal.localized, action: #selector(runInTerminal), keyEquivalent: "")
let disablePluginItem = NSMenuItem(title: Localizable.MenuBar.DisablePlugin.localized, action: #selector(disablePlugin), keyEquivalent: "")
let debugPluginItem = NSMenuItem(title: Localizable.MenuBar.DebugPlugin.localized, action: #selector(debugPlugin), keyEquivalent: "")
let terminatePluginItem = NSMenuItem(title: Localizable.MenuBar.TerminateEphemeralPlugin.localized, action: #selector(terminateEphemeralPlugin), keyEquivalent: "")
let swiftBarItem = NSMenuItem(title: Localizable.MenuBar.SwiftBar.localized, action: nil, keyEquivalent: "")
var isDefault = false
var isOpen = false
Expand Down Expand Up @@ -212,7 +213,7 @@ extension MenubarItem {
let aboutSwiftbarItem = NSMenuItem(title: Localizable.MenuBar.AboutPlugin.localized, action: #selector(aboutSwiftBar), keyEquivalent: "")
let quitItem = NSMenuItem(title: Localizable.App.Quit.localized, action: #selector(quit), keyEquivalent: "q")
let showErrorItem = NSMenuItem(title: Localizable.MenuBar.ShowError.localized, action: #selector(showErrorPopover), keyEquivalent: "")
[refreshAllItem, enableAllItem, disableAllItem, preferencesItem, openPluginFolderItem, changePluginFolderItem, getPluginsItem, quitItem, disablePluginItem, debugPluginItem, aboutItem, aboutSwiftbarItem, runInTerminalItem, showErrorItem, sendFeedbackItem].forEach { item in
[refreshAllItem, enableAllItem, disableAllItem, preferencesItem, openPluginFolderItem, changePluginFolderItem, getPluginsItem, quitItem, disablePluginItem, debugPluginItem, terminatePluginItem, aboutItem, aboutSwiftbarItem, runInTerminalItem, showErrorItem, sendFeedbackItem].forEach { item in
item.target = self
item.attributedTitle = NSAttributedString(string: item.title, attributes: [.font: NSFont.menuBarFont(ofSize: 0)])
}
Expand Down Expand Up @@ -245,11 +246,21 @@ extension MenubarItem {
if plugin?.error != nil {
statusBarMenu.addItem(showErrorItem)
}
statusBarMenu.addItem(runInTerminalItem)
statusBarMenu.addItem(disablePluginItem)
if PreferencesStore.shared.pluginDebugMode {
statusBarMenu.addItem(debugPluginItem)
if let pluginType = plugin?.type {
if PluginType.runnableInTerminal.contains(pluginType) {
statusBarMenu.addItem(runInTerminalItem)
}
if PreferencesStore.shared.pluginDebugMode, PluginType.debugable.contains(pluginType) {
statusBarMenu.addItem(debugPluginItem)
}
if PluginType.disableable.contains(pluginType) {
statusBarMenu.addItem(disablePluginItem)
}
if pluginType == .Ephemeral {
statusBarMenu.addItem(terminatePluginItem)
}
}

if plugin?.metadata?.isEmpty == false {
statusBarMenu.addItem(aboutItem)
}
Expand Down Expand Up @@ -319,6 +330,11 @@ extension MenubarItem {
AppShared.showPluginDebug(plugin: plugin)
}

@objc func terminateEphemeralPlugin() {
guard let plugin = plugin else { return }
plugin.terminate()
}

@objc func showErrorPopover() {
guard let plugin = plugin, plugin.error != nil else { return }
errorPopover.behavior = .transient
Expand Down
89 changes: 89 additions & 0 deletions SwiftBar/Plugin/EphemeralPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import Combine
import Foundation
import os

class EphemeralPlugin: Plugin {
var id: PluginID
let type: PluginType = .Ephemeral
let name: String = "Ephemeral"
let file: String = "none"

var updateInterval: Double = 60 * 60 * 24 * 100 {
didSet {
cancellable.forEach { $0.cancel() }
cancellable.removeAll()

guard updateInterval != 0 else { return }
updateTimerPublisher
.autoconnect()
.receive(on: RunLoop.main)
.sink(receiveValue: { [weak self] _ in
self?.terminate()
}).store(in: &cancellable)
}
}

var metadata: PluginMetadata?
var lastUpdated: Date?
var lastState: PluginState
var lastRefreshReason: PluginRefreshReason = .FirstLaunch
var contentUpdatePublisher = PassthroughSubject<String?, Never>()
var operation: RunPluginOperation<ExecutablePlugin>?

var content: String? = "..." {
didSet {
guard content != oldValue else { return }
lastUpdated = Date()
contentUpdatePublisher.send(content)
}
}

var error: Error?
var debugInfo = PluginDebugInfo()

lazy var invokeQueue: OperationQueue = delegate.pluginManager.pluginInvokeQueue

var updateTimerPublisher: Timer.TimerPublisher {
Timer.TimerPublisher(interval: updateInterval, runLoop: .main, mode: .default)
}

var cronTimer: Timer?

var cancellable: Set<AnyCancellable> = []

let prefs = PreferencesStore.shared

init(id: PluginID, content: String, exitAfter: Double) {
self.id = id
lastState = .Success
os_log("Initialized ephemeral plugin\n%{public}@", log: Log.plugin, description)
refresh(reason: .FirstLaunch)
self.content = content
lastUpdated = Date()
if exitAfter != 0 {
updateInterval = exitAfter
updateTimerPublisher
.autoconnect()
.receive(on: invokeQueue)
.sink(receiveValue: { [weak self] _ in
self?.terminate()
}).store(in: &cancellable)
}
}

func disable() {}

func terminate() {
delegate.pluginManager.setEphemeralPlugin(pluginId: id, content: "")
}

func enable() {}

func start() {}

func refresh(reason _: PluginRefreshReason) {}

func invoke() -> String? {
nil
}
}
13 changes: 13 additions & 0 deletions SwiftBar/Plugin/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ enum PluginType: String {
case Executable
case Streamable
case Shortcut
case Ephemeral

static var debugable: [Self] {
[.Executable, .Streamable]
}

static var runnableInTerminal: [Self] {
[.Executable, .Streamable]
}

static var disableable: [Self] {
[.Executable, .Streamable, .Shortcut]
}
}

enum PluginState {
Expand Down
19 changes: 19 additions & 0 deletions SwiftBar/Plugin/PluginManger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class PluginManager: ObservableObject {
@Published var plugins: [Plugin] = [] {
didSet {
shortcutPlugins = plugins.filter { $0.type == .Shortcut }.compactMap { $0 as? ShortcutPlugin }

pluginsDidChange()
}
}
Expand All @@ -27,6 +28,10 @@ class PluginManager: ObservableObject {
plugins.filter { $0.type == .Streamable || $0.type == .Executable }
}

var ephemeralPlugins: [EphemeralPlugin] {
plugins.filter { $0.type == .Ephemeral }.compactMap { $0 as? EphemeralPlugin }
}

var enabledPlugins: [Plugin] {
plugins.filter(\.enabled)
}
Expand Down Expand Up @@ -237,6 +242,20 @@ class PluginManager: ObservableObject {
loadPlugins()
}

func setEphemeralPlugin(pluginId: PluginID, content: String, exitAfter: Double = 0) {
if let plugin = ephemeralPlugins.first(where: { $0.id == pluginId }) {
guard !content.isEmpty else {
plugins.removeAll(where: { $0.id == pluginId && $0.type == .Ephemeral })
return
}
plugin.content = content
plugin.updateInterval = exitAfter
return
}

plugins.append(EphemeralPlugin(id: pluginId, content: content, exitAfter: exitAfter))
}

enum ImportPluginError: Error {
case badURL
case importFail
Expand Down
Loading

0 comments on commit 6e5d16b

Please sign in to comment.