Skip to content

Commit f1b9e40

Browse files
committed
No more pip and widget fix
1 parent 42142c8 commit f1b9e40

10 files changed

Lines changed: 304 additions & 105 deletions

File tree

DebugWidget/DebugWidgetBundle.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import SwiftUI
1111
@main
1212
struct StikDebugWidgetBundle: WidgetBundle {
1313
var body: some Widget {
14-
// Both widgets enabled: Favorites uses enable-jit URL scheme (with PiP/script handled in-app),
14+
// Both widgets enabled: Favorites uses enable-jit URL scheme (with continued processing handled in-app),
1515
// System Apps uses launch-app URL scheme for non-debug launch behavior.
1616
FavoritesWidget()
1717
SystemAppsWidget()

StikDebug.xcodeproj/project.pbxproj

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10-
17C744F02E20BED000834F17 /* Pipify in Frameworks */ = {isa = PBXBuildFile; productRef = 17C744EF2E20BED000834F17 /* Pipify */; };
1110
68D1FA402E847E4A0028A0EA /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68D1FA3F2E847E4A0028A0EA /* StoreKit.framework */; };
1211
68D569BE2E1B415700A5BA36 /* CodeEditorView in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BD2E1B415700A5BA36 /* CodeEditorView */; };
1312
68D569C02E1B415700A5BA36 /* LanguageSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BF2E1B415700A5BA36 /* LanguageSupport */; };
@@ -155,7 +154,6 @@
155154
buildActionMask = 2147483647;
156155
files = (
157156
DCBA85862E3897BD00E88C06 /* StikImporter in Frameworks */,
158-
17C744F02E20BED000834F17 /* Pipify in Frameworks */,
159157
68D569C02E1B415700A5BA36 /* LanguageSupport in Frameworks */,
160158
68D1FA402E847E4A0028A0EA /* StoreKit.framework in Frameworks */,
161159
68D569BE2E1B415700A5BA36 /* CodeEditorView in Frameworks */,
@@ -272,7 +270,6 @@
272270
packageProductDependencies = (
273271
68D569BD2E1B415700A5BA36 /* CodeEditorView */,
274272
68D569BF2E1B415700A5BA36 /* LanguageSupport */,
275-
17C744EF2E20BED000834F17 /* Pipify */,
276273
DCBA85852E3897BD00E88C06 /* StikImporter */,
277274
68E714E52E6AA2B00025610F /* ZIPFoundation */,
278275
);
@@ -390,7 +387,6 @@
390387
minimizedProjectReferenceProxies = 1;
391388
packageReferences = (
392389
68D569BC2E1B415700A5BA36 /* XCRemoteSwiftPackageReference "CodeEditorView" */,
393-
17C744EE2E20BED000834F17 /* XCRemoteSwiftPackageReference "swiftui-pipify" */,
394390
DCBA85842E3897BD00E88C06 /* XCRemoteSwiftPackageReference "StikImporter" */,
395391
68E714E42E6AA2B00025610F /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
396392
);
@@ -997,14 +993,6 @@
997993
/* End XCConfigurationList section */
998994

999995
/* Begin XCRemoteSwiftPackageReference section */
1000-
17C744EE2E20BED000834F17 /* XCRemoteSwiftPackageReference "swiftui-pipify" */ = {
1001-
isa = XCRemoteSwiftPackageReference;
1002-
repositoryURL = "https://github.com/hugeBlack/swiftui-pipify";
1003-
requirement = {
1004-
branch = main;
1005-
kind = branch;
1006-
};
1007-
};
1008996
68D569BC2E1B415700A5BA36 /* XCRemoteSwiftPackageReference "CodeEditorView" */ = {
1009997
isa = XCRemoteSwiftPackageReference;
1010998
repositoryURL = "https://github.com/mchakravarty/CodeEditorView";
@@ -1032,11 +1020,6 @@
10321020
/* End XCRemoteSwiftPackageReference section */
10331021

10341022
/* Begin XCSwiftPackageProductDependency section */
1035-
17C744EF2E20BED000834F17 /* Pipify */ = {
1036-
isa = XCSwiftPackageProductDependency;
1037-
package = 17C744EE2E20BED000834F17 /* XCRemoteSwiftPackageReference "swiftui-pipify" */;
1038-
productName = Pipify;
1039-
};
10401023
68D569BD2E1B415700A5BA36 /* CodeEditorView */ = {
10411024
isa = XCSwiftPackageProductDependency;
10421025
package = 68D569BC2E1B415700A5BA36 /* XCRemoteSwiftPackageReference "CodeEditorView" */;

StikDebug.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

StikJIT/Info.plist

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@
1515
</array>
1616
</dict>
1717
</array>
18+
<key>BGTaskSchedulerPermittedIdentifiers</key>
19+
<array>
20+
<string>$(PRODUCT_BUNDLE_IDENTIFIER).continuedProcessingTask.script</string>
21+
</array>
1822
<key>UIBackgroundModes</key>
1923
<array>
2024
<string>audio</string>
25+
<string>processing</string>
2126
</array>
2227
<key>UIFileSharingEnabled</key>
2328
<true/>

StikJIT/JSSupport/RunJSView.swift

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ class RunJSViewModel: ObservableObject {
1616
var pid: Int
1717
var debugProxy: OpaquePointer?
1818
var semaphore: dispatch_semaphore_t?
19+
private var progressTimer: DispatchSourceTimer?
20+
private var reportedProgress: Double = 0
1921

2022
init(pid: Int, debugProxy: OpaquePointer?, semaphore: dispatch_semaphore_t?) {
2123
self.pid = pid
@@ -30,6 +32,7 @@ class RunJSViewModel: ObservableObject {
3032
func runScript(data: Data, name: String? = nil) throws {
3133
let scriptContent = String(data: data, encoding: .utf8)
3234
scriptName = name ?? "Script"
35+
startContinuedProcessing(withTitle: scriptName)
3336

3437
let getPidFunction: @convention(block) () -> Int = {
3538
return self.pid
@@ -73,37 +76,48 @@ class RunJSViewModel: ObservableObject {
7376
if let semaphore {
7477
semaphore.signal()
7578
}
76-
79+
7780
DispatchQueue.main.async {
7881
if let exception = self.context?.exception {
7982
self.logs.append(exception.debugDescription)
8083
}
81-
84+
let success = self.context?.exception == nil && !self.executionInterrupted
85+
self.stopContinuedProcessing(success: success)
8286
self.logs.append("Script Execution Completed")
83-
self.logs.append("You are safe to close the PIP Window.")
87+
self.logs.append("Background processing finished. You can dismiss this view.")
8488
}
8589
}
86-
}
87-
88-
struct RunJSViewPiP: View {
89-
@Binding var model: RunJSViewModel?
90-
@State var logs: [String] = []
91-
let timer = Timer.publish(every: 0.034, on: .main, in: .common).autoconnect()
9290

93-
94-
var body: some View {
95-
VStack(alignment: .leading, spacing: 4) {
96-
ForEach(logs.suffix(6).indices, id: \.self) { index in
97-
Text(logs.suffix(6)[index])
98-
.font(.system(size: 12))
99-
.foregroundStyle(.white)
91+
private func startContinuedProcessing(withTitle title: String) {
92+
guard ContinuedProcessingManager.shared.isSupported,
93+
UserDefaults.standard.bool(forKey: UserDefaults.Keys.enableContinuedProcessing) else { return }
94+
stopProgressTimer()
95+
reportedProgress = 0.05
96+
ContinuedProcessingManager.shared.begin(title: title, subtitle: "Script execution in progress")
97+
ContinuedProcessingManager.shared.updateProgress(reportedProgress)
98+
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global(qos: .background))
99+
timer.schedule(deadline: .now() + 5, repeating: 5)
100+
timer.setEventHandler { [weak self] in
101+
guard let self else { return }
102+
self.reportedProgress = min(0.9, self.reportedProgress + 0.1)
103+
ContinuedProcessingManager.shared.updateProgress(self.reportedProgress)
104+
if self.reportedProgress >= 0.9 {
105+
self.stopProgressTimer()
100106
}
101107
}
102-
.padding()
103-
.onReceive(timer) { _ in
104-
self.logs = model?.logs ?? []
105-
}
106-
.frame(width: 300, height: 150)
108+
timer.resume()
109+
progressTimer = timer
110+
}
111+
112+
private func stopContinuedProcessing(success: Bool) {
113+
stopProgressTimer()
114+
ContinuedProcessingManager.shared.updateProgress(1.0)
115+
ContinuedProcessingManager.shared.finish(success: success)
116+
}
117+
118+
private func stopProgressTimer() {
119+
progressTimer?.cancel()
120+
progressTimer = nil
107121
}
108122
}
109123

StikJIT/StikJITApp.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@ private func registerAdvancedOptionsDefault() {
1515
let os = ProcessInfo.processInfo.operatingSystemVersion
1616
// Enable advanced options by default on iOS 19/26 and above
1717
let enabled = os.majorVersion >= 19
18-
UserDefaults.standard.register(defaults: ["enableAdvancedOptions": enabled])
19-
UserDefaults.standard.register(defaults: ["enablePiP": enabled])
20-
UserDefaults.standard.register(defaults: [UserDefaults.Keys.txmOverride: false])
18+
let defaults = UserDefaults.standard
19+
defaults.register(defaults: ["enableAdvancedOptions": enabled])
20+
defaults.register(defaults: [UserDefaults.Keys.txmOverride: false])
21+
if defaults.object(forKey: UserDefaults.Keys.enableContinuedProcessing) == nil {
22+
let legacyPiP = defaults.object(forKey: "enablePiP") as? Bool
23+
if let legacyPiP {
24+
defaults.set(legacyPiP, forKey: UserDefaults.Keys.enableContinuedProcessing)
25+
} else {
26+
defaults.register(defaults: [UserDefaults.Keys.enableContinuedProcessing: enabled])
27+
}
28+
}
2129
}
2230

2331
// MARK: - Welcome Sheet
@@ -519,6 +527,7 @@ struct HeartbeatApp: App {
519527

520528
init() {
521529
registerAdvancedOptionsDefault()
530+
ContinuedProcessingManager.shared.configureIfNeeded()
522531
newVerCheck()
523532
let fixMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.fix_init(forOpeningContentTypes:asCopy:)))!
524533
let origMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.init(forOpeningContentTypes:asCopy:)))!
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//
2+
// ContinuedProcessingManager.swift
3+
// StikJIT
4+
//
5+
// Created by Codex on 11/20/24.
6+
//
7+
8+
import Foundation
9+
import BackgroundTasks
10+
11+
final class ContinuedProcessingManager {
12+
static let shared = ContinuedProcessingManager()
13+
private let handler: ContinuedProcessingHandling
14+
15+
private init() {
16+
if #available(iOS 26.0, *) {
17+
handler = ModernContinuedProcessingHandler()
18+
} else {
19+
handler = NoopContinuedProcessingHandler()
20+
}
21+
}
22+
23+
var isSupported: Bool { handler.isSupported }
24+
25+
func configureIfNeeded() {
26+
handler.configureIfNeeded()
27+
}
28+
29+
func begin(title: String, subtitle: String) {
30+
handler.begin(title: title, subtitle: subtitle)
31+
}
32+
33+
func updateProgress(_ fraction: Double) {
34+
handler.updateProgress(fraction)
35+
}
36+
37+
func finish(success: Bool) {
38+
handler.finish(success: success)
39+
}
40+
}
41+
42+
private protocol ContinuedProcessingHandling: AnyObject {
43+
var isSupported: Bool { get }
44+
func configureIfNeeded()
45+
func begin(title: String, subtitle: String)
46+
func updateProgress(_ fraction: Double)
47+
func finish(success: Bool)
48+
}
49+
50+
private final class NoopContinuedProcessingHandler: ContinuedProcessingHandling {
51+
var isSupported: Bool { false }
52+
func configureIfNeeded() {}
53+
func begin(title: String, subtitle: String) {}
54+
func updateProgress(_ fraction: Double) {}
55+
func finish(success: Bool) {}
56+
}
57+
58+
@available(iOS 26.0, *)
59+
private final class ModernContinuedProcessingHandler: ContinuedProcessingHandling {
60+
private let scheduler = BGTaskScheduler.shared
61+
private let taskIdentifier: String
62+
private var didRegister = false
63+
private var activeTask: BGContinuedProcessingTask?
64+
private let queue = DispatchQueue(label: "com.stikdebug.continuedProcessing",
65+
qos: .utility)
66+
private var pendingMetadata: (title: String, subtitle: String)?
67+
68+
init() {
69+
let bundleID = Bundle.main.bundleIdentifier ?? "com.stik.sj"
70+
taskIdentifier = "\(bundleID).continuedProcessingTask.script"
71+
}
72+
73+
var isSupported: Bool { true }
74+
75+
func configureIfNeeded() {
76+
guard !didRegister else { return }
77+
scheduler.register(forTaskWithIdentifier: taskIdentifier, using: nil) { [weak self] task in
78+
guard let continuedTask = task as? BGContinuedProcessingTask else {
79+
task.setTaskCompleted(success: false)
80+
return
81+
}
82+
self?.handle(task: continuedTask)
83+
}
84+
didRegister = true
85+
}
86+
87+
func begin(title: String, subtitle: String) {
88+
guard UserDefaults.standard.bool(forKey: UserDefaults.Keys.enableContinuedProcessing) else { return }
89+
configureIfNeeded()
90+
var reserved = false
91+
queue.sync {
92+
if activeTask == nil && pendingMetadata == nil {
93+
pendingMetadata = (title: title, subtitle: subtitle)
94+
reserved = true
95+
}
96+
}
97+
guard reserved else { return }
98+
let request = BGContinuedProcessingTaskRequest(identifier: taskIdentifier,
99+
title: title,
100+
subtitle: subtitle)
101+
request.strategy = .queue
102+
do {
103+
try scheduler.submit(request)
104+
LogManager.shared.addInfoLog("Requested continued processing: \(title)")
105+
} catch {
106+
LogManager.shared.addWarningLog("Unable to request continued processing: \(error.localizedDescription)")
107+
queue.async { [weak self] in
108+
self?.pendingMetadata = nil
109+
}
110+
}
111+
}
112+
113+
func updateProgress(_ fraction: Double) {
114+
queue.async { [weak self] in
115+
guard let task = self?.activeTask else { return }
116+
let clamped = max(0.0, min(1.0, fraction))
117+
task.progress.totalUnitCount = max(task.progress.totalUnitCount, 100)
118+
task.progress.completedUnitCount = Int64(Double(task.progress.totalUnitCount) * clamped)
119+
}
120+
}
121+
122+
func finish(success: Bool) {
123+
queue.async { [weak self] in
124+
guard let self else { return }
125+
if let task = self.activeTask {
126+
task.progress.completedUnitCount = task.progress.totalUnitCount
127+
task.setTaskCompleted(success: success)
128+
self.activeTask = nil
129+
} else if pendingMetadata != nil {
130+
scheduler.cancel(taskRequestWithIdentifier: taskIdentifier)
131+
}
132+
pendingMetadata = nil
133+
}
134+
}
135+
136+
private func handle(task: BGContinuedProcessingTask) {
137+
queue.async { [weak self] in
138+
guard let self else { return }
139+
activeTask = task
140+
if let metadata = pendingMetadata {
141+
task.updateTitle(metadata.title, subtitle: metadata.subtitle)
142+
}
143+
if task.progress.totalUnitCount == 0 {
144+
task.progress.totalUnitCount = 100
145+
}
146+
task.progress.completedUnitCount = 1
147+
task.expirationHandler = { [weak self] in
148+
self?.handleExpiration()
149+
}
150+
}
151+
}
152+
153+
private func handleExpiration() {
154+
LogManager.shared.addWarningLog("Continued processing expired early")
155+
finish(success: false)
156+
}
157+
}

StikJIT/Utilities/Extensions.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,7 @@ extension UserDefaults {
2323
enum Keys {
2424
/// Forces the app to treat the current device as TXM-capable so scripts always run.
2525
static let txmOverride = "overrideTXMForScripts"
26+
/// Controls whether BGContinuedProcessingTask should be used to keep scripts alive in the background.
27+
static let enableContinuedProcessing = "enableContinuedProcessing"
2628
}
2729
}

0 commit comments

Comments
 (0)