Skip to content

Commit 9800b3b

Browse files
committed
Lot's of new stuff
1 parent 5fef725 commit 9800b3b

11 files changed

+1359
-179
lines changed

StikJIT/StikJIT-Bridging-Header.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//
44

55
#include "idevice/JITEnableContext.h"
6+
#import "Utilities/ProcessInspectorBridge.h"
67
#include "idevice/idevice.h"
78
#include "idevice/heartbeat.h"
89
#include "JSSupport/JSSupport.h"

StikJIT/StikJITApp.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ class TunnelManager: ObservableObject {
155155
static var shared = TunnelManager()
156156

157157
private var vpnManager: NETunnelProviderManager?
158+
private var pendingStopRequest = false
158159
private var tunnelDeviceIp: String {
159160
UserDefaults.standard.string(forKey: "TunnelDeviceIP") ?? "10.7.0.0"
160161
}
@@ -205,6 +206,10 @@ class TunnelManager: ObservableObject {
205206
self.updateTunnelStatus(from: firstManager.connection.status)
206207
VPNLogger.shared.log("Using existing tunnel configuration")
207208
}
209+
if self.pendingStopRequest, let manager = self.vpnManager {
210+
VPNLogger.shared.log("Pending stop request detected during preference load")
211+
self.stopVPN(with: manager)
212+
}
208213
} else {
209214
VPNLogger.shared.log("No existing tunnel configuration found")
210215
}
@@ -326,7 +331,16 @@ class TunnelManager: ObservableObject {
326331
}
327332

328333
func stopVPN() {
329-
guard let manager = vpnManager else { return }
334+
guard let manager = vpnManager else {
335+
pendingStopRequest = true
336+
loadTunnelPreferences()
337+
return
338+
}
339+
pendingStopRequest = false
340+
stopVPN(with: manager)
341+
}
342+
343+
private func stopVPN(with manager: NETunnelProviderManager) {
330344
tunnelStatus = .disconnecting
331345
manager.connection.stopVPNTunnel()
332346
VPNLogger.shared.log("Network tunnel stop initiated")
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// ProcessInspectorBridge.h
3+
// StikJIT
4+
//
5+
6+
#import "../idevice/JITEnableContext.h"
7+
8+
NS_ASSUME_NONNULL_BEGIN
9+
10+
FOUNDATION_EXPORT NSArray<NSDictionary*> * _Nullable FetchDeviceProcessList(NSError **error);
11+
FOUNDATION_EXPORT BOOL KillDeviceProcess(int pid, NSError **error);
12+
13+
NS_ASSUME_NONNULL_END
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// ProcessInspectorBridge.m
3+
// StikJIT
4+
//
5+
6+
#import "ProcessInspectorBridge.h"
7+
8+
NSArray<NSDictionary*> * _Nullable FetchDeviceProcessList(NSError **error) {
9+
@try {
10+
return [[JITEnableContext shared] fetchProcessListWithError:error];
11+
} @catch (NSException *exception) {
12+
if (error) {
13+
NSString *message = [NSString stringWithFormat:@"Process fetch failed: %@", exception.reason ?: @"Unknown error"];
14+
*error = [NSError errorWithDomain:@"ProcessInspectorBridge"
15+
code:-500
16+
userInfo:@{NSLocalizedDescriptionKey: message}];
17+
}
18+
return nil;
19+
}
20+
}
21+
22+
BOOL KillDeviceProcess(int pid, NSError **error) {
23+
@try {
24+
return [[JITEnableContext shared] killProcessWithPID:pid error:error];
25+
} @catch (NSException *exception) {
26+
if (error) {
27+
NSString *message = [NSString stringWithFormat:@"Process kill failed: %@", exception.reason ?: @"Unknown error"];
28+
*error = [NSError errorWithDomain:@"ProcessInspectorBridge"
29+
code:-501
30+
userInfo:@{NSLocalizedDescriptionKey: message}];
31+
}
32+
return NO;
33+
}
34+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Foundation
2+
3+
enum TabConfiguration {
4+
static let storageKey = "enabledTabIdentifiers"
5+
static let maxSelectableTabs = 4
6+
static let allowedIDs: [String] = ["home", "console", "scripts", "profiles", "processes", "deviceinfo", "location"]
7+
static let defaultIDs: [String] = ["home", "console", "scripts", "profiles"]
8+
static let defaultRawValue = serialize(defaultIDs)
9+
10+
static func sanitize(raw: String) -> [String] {
11+
let ids = raw
12+
.split(separator: ",")
13+
.map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
14+
return sanitize(ids: ids)
15+
}
16+
17+
static func sanitize(ids: [String]) -> [String] {
18+
var result: [String] = []
19+
for id in ids {
20+
guard allowedIDs.contains(id) else { continue }
21+
if !result.contains(id) {
22+
result.append(id)
23+
}
24+
if result.count == maxSelectableTabs {
25+
break
26+
}
27+
}
28+
if result.isEmpty {
29+
result = defaultIDs
30+
}
31+
return result
32+
}
33+
34+
static func serialize(_ ids: [String]) -> String {
35+
sanitize(ids: ids).joined(separator: ",")
36+
}
37+
}

StikJIT/Views/MainTabView.swift

Lines changed: 103 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,25 @@
77

88
import SwiftUI
99

10+
private struct TabDescriptor: Identifiable {
11+
let id: String
12+
let title: String
13+
let systemImage: String
14+
let builder: () -> AnyView
15+
}
16+
17+
extension Notification.Name {
18+
static let switchToTab = Notification.Name("MainTabSwitchNotification")
19+
}
20+
1021
struct MainTabView: View {
1122
@AppStorage("customAccentColor") private var customAccentColorHex: String = ""
1223
@AppStorage("appTheme") private var appThemeRaw: String = AppTheme.system.rawValue
13-
@State private var selection: Int = 0
24+
@AppStorage(TabConfiguration.storageKey) private var enabledTabIdentifiers: String = TabConfiguration.defaultRawValue
25+
@AppStorage("primaryTabSelection") private var selection: String = TabConfiguration.defaultIDs.first ?? "home"
26+
@State private var switchObserver: Any?
27+
@State private var detachedTab: TabDescriptor?
28+
@State private var didSetInitialHome = false
1429

1530
// Update checking
1631
@State private var showForceUpdate: Bool = false
@@ -27,41 +42,109 @@ struct MainTabView: View {
2742
}
2843

2944
private var isAppStoreBuild: Bool {
30-
themeExpansion?.isAppStoreBuild ?? true
45+
#if APPSTORE
46+
return true
47+
#else
48+
return false
49+
#endif
3150
}
3251

52+
private let configurableTabs: [TabDescriptor] = [
53+
TabDescriptor(id: "home", title: "Home", systemImage: "house") { AnyView(HomeView()) },
54+
TabDescriptor(id: "console", title: "Console", systemImage: "terminal") { AnyView(ConsoleLogsView()) },
55+
TabDescriptor(id: "scripts", title: "Scripts", systemImage: "scroll") { AnyView(ScriptListView()) },
56+
TabDescriptor(id: "profiles", title: "Profiles", systemImage: "magazine.fill") { AnyView(ProfileView()) },
57+
TabDescriptor(id: "processes", title: "Processes", systemImage: "rectangle.stack.person.crop") { AnyView(ProcessInspectorView()) },
58+
TabDescriptor(id: "deviceinfo", title: "Device Info", systemImage: "iphone.and.arrow.forward") { AnyView(DeviceInfoView()) },
59+
TabDescriptor(id: "location", title: "Location", systemImage: "location") { AnyView(LocationSimulationView()) }
60+
]
61+
62+
private var availableTabs: [TabDescriptor] {
63+
configurableTabs.filter { descriptor in
64+
descriptor.id != "location" || !isAppStoreBuild
65+
}
66+
}
67+
68+
private let settingsTab = TabDescriptor(id: "settings", title: "Settings", systemImage: "gearshape.fill") {
69+
AnyView(SettingsView())
70+
}
71+
72+
private var selectedTabDescriptors: [TabDescriptor] {
73+
let ids = TabConfiguration.sanitize(raw: enabledTabIdentifiers)
74+
return ids.compactMap { id in
75+
availableTabs.first(where: { $0.id == id })
76+
}
77+
}
78+
79+
private func ensureSelectionIsValid() {
80+
let ids = selectedTabDescriptors.map { $0.id }
81+
if ids.contains(selection) || selection == settingsTab.id {
82+
return
83+
}
84+
selection = ids.first ?? settingsTab.id
85+
}
86+
3387
var body: some View {
3488
ZStack {
3589
// Allow global themed background to show
3690
Color.clear.ignoresSafeArea()
3791

3892
// Main tabs
3993
TabView(selection: $selection) {
40-
HomeView()
41-
.tabItem { Label("Home", systemImage: "house") }
42-
.tag(0)
43-
44-
ConsoleLogsView()
45-
.tabItem { Label("Console", systemImage: "terminal") }
46-
.tag(1)
47-
48-
ScriptListView()
49-
.tabItem { Label("Scripts", systemImage: "scroll") }
50-
.tag(2)
94+
ForEach(selectedTabDescriptors) { descriptor in
95+
descriptor.builder()
96+
.tabItem { Label(descriptor.title, systemImage: descriptor.systemImage) }
97+
.tag(descriptor.id)
98+
}
5199

52-
ProfileView()
53-
.tabItem { Label("Profiles", systemImage: "magazine.fill") }
54-
.tag(3)
55-
56-
SettingsView()
57-
.tabItem { Label("Settings", systemImage: "gearshape.fill") }
58-
.tag(4)
100+
settingsTab.builder()
101+
.tabItem { Label(settingsTab.title, systemImage: settingsTab.systemImage) }
102+
.tag(settingsTab.id)
59103
}
60104
.id((themeExpansion?.hasThemeExpansion == true) ? customAccentColorHex : "default-accent")
61105
.tint(accentColor)
62106
.preferredColorScheme(preferredScheme)
63107
.onAppear {
108+
enabledTabIdentifiers = TabConfiguration.serialize(TabConfiguration.sanitize(raw: enabledTabIdentifiers))
109+
ensureSelectionIsValid()
110+
if !didSetInitialHome {
111+
if selectedTabDescriptors.contains(where: { $0.id == "home" }) {
112+
selection = "home"
113+
} else if let descriptor = availableTabs.first(where: { $0.id == "home" }) {
114+
detachedTab = descriptor
115+
}
116+
didSetInitialHome = true
117+
}
64118
checkForUpdate()
119+
switchObserver = NotificationCenter.default.addObserver(forName: .switchToTab, object: nil, queue: .main) { note in
120+
guard let id = note.object as? String else { return }
121+
if selectedTabDescriptors.contains(where: { $0.id == id }) {
122+
selection = id
123+
} else if let descriptor = availableTabs.first(where: { $0.id == id }) {
124+
detachedTab = descriptor
125+
}
126+
}
127+
}
128+
.onDisappear {
129+
if let observer = switchObserver {
130+
NotificationCenter.default.removeObserver(observer)
131+
switchObserver = nil
132+
}
133+
}
134+
.onChange(of: enabledTabIdentifiers) { _ in
135+
ensureSelectionIsValid()
136+
}
137+
.sheet(item: $detachedTab) { descriptor in
138+
NavigationStack {
139+
descriptor.builder()
140+
.toolbar {
141+
ToolbarItem(placement: .cancellationAction) {
142+
Button("Close") {
143+
detachedTab = nil
144+
}
145+
}
146+
}
147+
}
65148
}
66149

67150
if showForceUpdate {

0 commit comments

Comments
 (0)