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: 3 additions & 1 deletion Sources/EdgeControl/App/EdgeControlApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ enum EdgeControlExecutable {
widgetBridge.start()
model.widgetDataBridge = widgetBridge

// Plugin desktop widget renderer: headless WKWebView snapshots
// Plugin desktop widget renderer: headless WKWebView snapshots.
// PluginWidgetManifest.write() now runs its disk I/O on a
// background queue so this call no longer gates start().
let pluginRenderer = PluginWidgetRenderer(pluginManager: pluginManager, model: model)
pluginRenderer.start()
model.pluginWidgetRenderer = pluginRenderer
Expand Down
16 changes: 13 additions & 3 deletions Sources/EdgeControl/Models/WidgetData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,20 @@ public struct PluginWidgetManifest: Codable, Sendable {

public func write() {
guard let dir = Self.containerDirectory else { return }
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
let url = dir.appendingPathComponent("plugins.json")
guard let data = try? JSONEncoder.widgetEncoder.encode(self) else { return }
try? data.write(to: url, options: .atomic)
// App-group container writes have been observed hanging the calling
// thread on __open inside Foundation's atomic-write path (sandbox /
// container-manager state we don't control). Move the entire dance
// — directory creation, freshness check, atomic write — to a
// background queue with a fire-and-forget contract so the main
// thread never blocks on this call. Worst case the manifest is a
// tick stale, which is fine for desktop widgets that already poll.
DispatchQueue.global(qos: .utility).async {
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
let url = dir.appendingPathComponent("plugins.json")
if let existing = try? Data(contentsOf: url), existing == data { return }
try? data.write(to: url, options: .atomic)
}
}

/// Get snapshot image path for a plugin at a given size
Expand Down