mirror of
https://github.com/wibus-wee/InjectGUI.git
synced 2025-11-25 19:37:33 +08:00
feat: status view basic ui
This commit is contained in:
@@ -18,6 +18,8 @@
|
||||
38877A372C4A7294009F5910 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A362C4A7294009F5910 /* Configuration.swift */; };
|
||||
38877A3A2C4A730F009F5910 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A392C4A730F009F5910 /* Constants.swift */; };
|
||||
38877A3D2C4A9EB7009F5910 /* SetupApplicationSupportDirectory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A3C2C4A9EB7009F5910 /* SetupApplicationSupportDirectory.swift */; };
|
||||
38AD95EA2C58E70E0032E79F /* Injector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AD95E92C58E70E0032E79F /* Injector.swift */; };
|
||||
38AD95EE2C58F59C0032E79F /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AD95ED2C58F59C0032E79F /* StatusView.swift */; };
|
||||
38BC1F532C4B587A00C3B60E /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BC1F522C4B587900C3B60E /* SidebarView.swift */; };
|
||||
38BC1F552C4B622500C3B60E /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BC1F542C4B622500C3B60E /* WelcomeView.swift */; };
|
||||
38BC1F5A2C4B98A300C3B60E /* AppDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BC1F592C4B98A300C3B60E /* AppDetailView.swift */; };
|
||||
@@ -38,6 +40,8 @@
|
||||
38877A362C4A7294009F5910 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
||||
38877A392C4A730F009F5910 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||
38877A3C2C4A9EB7009F5910 /* SetupApplicationSupportDirectory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupApplicationSupportDirectory.swift; sourceTree = "<group>"; };
|
||||
38AD95E92C58E70E0032E79F /* Injector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Injector.swift; sourceTree = "<group>"; };
|
||||
38AD95ED2C58F59C0032E79F /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
|
||||
38BC1F522C4B587900C3B60E /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; };
|
||||
38BC1F542C4B622500C3B60E /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
|
||||
38BC1F592C4B98A300C3B60E /* AppDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailView.swift; sourceTree = "<group>"; };
|
||||
@@ -100,6 +104,7 @@
|
||||
38877A2D2C4A6FFA009F5910 /* InjectConfiguration.swift */,
|
||||
38877A2F2C4A70DB009F5910 /* SoftwareManager.swift */,
|
||||
38877A362C4A7294009F5910 /* Configuration.swift */,
|
||||
38AD95E92C58E70E0032E79F /* Injector.swift */,
|
||||
);
|
||||
path = Backend;
|
||||
sourceTree = "<group>";
|
||||
@@ -111,6 +116,7 @@
|
||||
38BC1F522C4B587900C3B60E /* SidebarView.swift */,
|
||||
38BC1F542C4B622500C3B60E /* WelcomeView.swift */,
|
||||
38BC1F592C4B98A300C3B60E /* AppDetailView.swift */,
|
||||
38AD95ED2C58F59C0032E79F /* StatusView.swift */,
|
||||
38BC1F5B2C4BB02200C3B60E /* SettingsView.swift */,
|
||||
);
|
||||
path = View;
|
||||
@@ -221,6 +227,7 @@
|
||||
38877A1D2C4A6F83009F5910 /* InjectGUIApp.swift in Sources */,
|
||||
38877A3D2C4A9EB7009F5910 /* SetupApplicationSupportDirectory.swift in Sources */,
|
||||
38877A372C4A7294009F5910 /* Configuration.swift in Sources */,
|
||||
38AD95EA2C58E70E0032E79F /* Injector.swift in Sources */,
|
||||
38877A302C4A70DB009F5910 /* SoftwareManager.swift in Sources */,
|
||||
38877A332C4A7222009F5910 /* PublishedStorage.swift in Sources */,
|
||||
38BC1F552C4B622500C3B60E /* WelcomeView.swift in Sources */,
|
||||
@@ -229,6 +236,7 @@
|
||||
38877A352C4A7254009F5910 /* ViewKit.swift in Sources */,
|
||||
38877A3A2C4A730F009F5910 /* Constants.swift in Sources */,
|
||||
38BC1F5C2C4BB02200C3B60E /* SettingsView.swift in Sources */,
|
||||
38AD95EE2C58F59C0032E79F /* StatusView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
struct Package: Identifiable {
|
||||
let id: String
|
||||
@@ -16,7 +17,6 @@ struct Package: Identifiable {
|
||||
struct InjectConfigurationModel: Codable {
|
||||
let project, author: String
|
||||
let version: Double
|
||||
let description: Description
|
||||
let basePublicConfig: BasePublicConfig
|
||||
let appList: [AppList]
|
||||
|
||||
@@ -24,7 +24,6 @@ struct InjectConfigurationModel: Codable {
|
||||
case project
|
||||
case author = "Author"
|
||||
case version = "Version"
|
||||
case description = "Description"
|
||||
case basePublicConfig
|
||||
case appList = "AppList"
|
||||
}
|
||||
@@ -43,11 +42,12 @@ struct AppList: Codable {
|
||||
let deepSignApp, noDeep: Bool?
|
||||
let entitlements: String?
|
||||
let useOptool, autoHandleSetapp: Bool?
|
||||
let keygen: Bool?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case packageName, appBaseLocate, bridgeFile, injectFile, needCopyToAppDir, noSignTarget, autoHandleHelper, helperFile, tccutil, forQiuChenly, onlysh, extraShell
|
||||
case smExtra = "SMExtra"
|
||||
case componentApp, deepSignApp, noDeep, entitlements, useOptool, autoHandleSetapp
|
||||
case componentApp, deepSignApp, noDeep, entitlements, useOptool, autoHandleSetapp, keygen
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,19 +159,11 @@ struct BasePublicConfig: Codable {
|
||||
let bridgeFile: String
|
||||
}
|
||||
|
||||
// MARK: - Description
|
||||
struct Description: Codable {
|
||||
let desc, bridgeFile, packageName, injectFile: String
|
||||
let supportVersion, supportSubVersion, extraShell, needCopyToAppDir: String
|
||||
let deepSignApp, disableLibraryValidate, entitlements, noSignTarget: String
|
||||
let noDeep, tccutil, autoHandleSetapp, autoHandleHelper: String
|
||||
let helperFile, componentApp, forQiuChenly: String
|
||||
}
|
||||
|
||||
class InjectConfiguration: ObservableObject {
|
||||
static let shared = InjectConfiguration()
|
||||
|
||||
var remoteConf = nil as InjectConfigurationModel?
|
||||
@Published var remoteConf = nil as InjectConfigurationModel?
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private init() {
|
||||
updateRemoteConf()
|
||||
|
||||
104
InjectGUI/Backend/Injector.swift
Normal file
104
InjectGUI/Backend/Injector.swift
Normal file
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// Injector.swift
|
||||
// InjectGUI
|
||||
//
|
||||
// Created by wibus on 2024/7/30.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum InjectStatus {
|
||||
case none
|
||||
case running
|
||||
case finished
|
||||
case error
|
||||
}
|
||||
|
||||
enum InjectStage {
|
||||
case start
|
||||
case checkVersionIsSupported
|
||||
case handleKeygen
|
||||
case handleDeepCodeSign
|
||||
case handleAutoHandleHelper
|
||||
case handleSubApps
|
||||
case handleTccutil
|
||||
case handleExtraShell
|
||||
case handleInjectLibInject
|
||||
case end
|
||||
}
|
||||
|
||||
extension InjectStage {
|
||||
static var allCases: [InjectStage] {
|
||||
return [.start, .checkVersionIsSupported, .handleKeygen, .handleDeepCodeSign, .handleAutoHandleHelper, .handleSubApps, .handleTccutil, .handleExtraShell, .handleInjectLibInject, .end]
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .start:
|
||||
return "Start Injecting"
|
||||
case .checkVersionIsSupported:
|
||||
return "Checking Version is supported"
|
||||
case .handleKeygen:
|
||||
return "Handling Keygen"
|
||||
case .handleDeepCodeSign:
|
||||
return "Handling Deep Code Sign"
|
||||
case .handleAutoHandleHelper:
|
||||
return "Handling Auto Handle Helper"
|
||||
case .handleSubApps:
|
||||
return "Handling Sub Apps"
|
||||
case .handleTccutil:
|
||||
return "Handling Tccutil"
|
||||
case .handleExtraShell:
|
||||
return "Handling Extra Shell"
|
||||
case .handleInjectLibInject:
|
||||
return "Handling Inject Lib Inject"
|
||||
case .end:
|
||||
return "Injecting Finished"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InjectRunningError {
|
||||
var error: String
|
||||
var stage: InjectStage
|
||||
}
|
||||
|
||||
struct InjectRunningStage {
|
||||
var stage: InjectStage
|
||||
var message: String
|
||||
var progress: Double
|
||||
var error: InjectRunningError?
|
||||
var status: InjectStatus
|
||||
}
|
||||
|
||||
struct InjectRunningStatus {
|
||||
var appId: String
|
||||
var appName: String
|
||||
var stages: [InjectRunningStage] = []
|
||||
var message: String
|
||||
var progress: Double
|
||||
var error: InjectRunningError?
|
||||
}
|
||||
|
||||
class Injector: ObservableObject {
|
||||
static let shared = Injector()
|
||||
|
||||
@Published var stage: InjectRunningStatus = .init(appId: "", appName: "", stages: [], message: "", progress: 0)
|
||||
|
||||
init() {
|
||||
self.stage = InjectRunningStatus(
|
||||
appId: "pl.maketheweb.cleanshotx",
|
||||
appName: "CleanShot X",
|
||||
stages: [
|
||||
.init(stage: .start, message: InjectStage.start.description, progress: 1, status: .finished),
|
||||
.init(stage: .checkVersionIsSupported, message: InjectStage.checkVersionIsSupported.description, progress: 1, error: .init(error: "Version is not supported", stage: .checkVersionIsSupported), status: .error),
|
||||
.init(stage: .handleKeygen, message: InjectStage.handleKeygen.description, progress: 1, status: .finished),
|
||||
.init(stage: .handleDeepCodeSign, message: InjectStage.handleDeepCodeSign.description, progress: 0.6, status: .running),
|
||||
],
|
||||
message: "Injecting",
|
||||
progress: 0.6
|
||||
)
|
||||
}
|
||||
|
||||
func startInjectApp(package: String) {}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ struct AppDetail {
|
||||
let identifier: String // -> CFBundleIdentifier
|
||||
let version: String // -> CFBundleVersion
|
||||
let path: String // -> path
|
||||
let executable: String // -> CFBundleExecutable
|
||||
let icon: NSImage
|
||||
}
|
||||
|
||||
@@ -45,6 +46,7 @@ class SoftwareManager: ObservableObject {
|
||||
let bundleName = plist["CFBundleName"] as? String,
|
||||
let bundleIdentifier = plist["CFBundleIdentifier"] as? String,
|
||||
let bundleVersion = plist["CFBundleVersion"] as? String,
|
||||
let bundleExecutable = plist["CFBundleExecutable"] as? String,
|
||||
let bundleShortVersion = plist["CFBundleShortVersionString"] as? String else {
|
||||
return nil
|
||||
}
|
||||
@@ -91,6 +93,7 @@ class SoftwareManager: ObservableObject {
|
||||
identifier: bundleIdentifier,
|
||||
version: bundleVersion,
|
||||
path: path,
|
||||
executable: bundleExecutable,
|
||||
icon: icon ?? NSImage()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -46,15 +46,21 @@ enum ViewKit {
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
content().frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
content()
|
||||
Divider()
|
||||
HStack {
|
||||
if !secondaryButton.isEmpty {
|
||||
Button { action(false) } label: { Text(secondaryButton) }
|
||||
Button {
|
||||
action(false)
|
||||
dismiss()
|
||||
} label: { Text(secondaryButton) }
|
||||
}
|
||||
Spacer()
|
||||
if !primaryButton.isEmpty {
|
||||
Button { action(true) } label: { Text(primaryButton) }
|
||||
Button {
|
||||
action(true)
|
||||
dismiss()
|
||||
} label: { Text(primaryButton) }
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ struct AppDetailView: View {
|
||||
if getAppDetailFromSoftwareManager != nil {
|
||||
self.appDetail = SoftwareManager.shared.appListCache[appId]!
|
||||
} else {
|
||||
self.appDetail = AppDetail(name: appInjectConfDetail?.packageName.allStrings.first ?? "", identifier: appInjectConfDetail?.packageName.allStrings.first ?? "", version: "", path: "", icon: NSImage())
|
||||
self.appDetail = AppDetail(name: appInjectConfDetail?.packageName.allStrings.first ?? "", identifier: appInjectConfDetail?.packageName.allStrings.first ?? "", version: "", path: "", executable: "", icon: NSImage())
|
||||
}
|
||||
// self._appDetail = State(wrappedValue: SoftwareManager.shared.appListCache[appId] ?? AppDetail(name: "", identifier: "", version: "", path: "", icon: NSImage()))
|
||||
self._compatibility = State(wrappedValue: Compatibility(id: appId, inInjectLibList: false))
|
||||
|
||||
@@ -8,6 +8,7 @@ import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@StateObject var softwareManager = SoftwareManager.shared
|
||||
@State var showStatusSheet = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
@@ -29,6 +30,19 @@ struct ContentView: View {
|
||||
Label("Toggle Sidebar", systemImage: "sidebar.leading")
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem {
|
||||
Button {
|
||||
showStatusSheet.toggle()
|
||||
} label: {
|
||||
Label("Status", systemImage: "list.bullet.rectangle")
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showStatusSheet) {
|
||||
StatusView()
|
||||
.background(.ultraThinMaterial)
|
||||
.interactiveDismissDisabled(true) // disable esc to dismiss
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,6 @@ struct SettingsView: View {
|
||||
SettingItemView("Download GenShineImpactStarter") {
|
||||
Button(action: {
|
||||
injectConfiguration.downloadGenShineImpactStarter()
|
||||
isGenShineImpactStarterExist = true
|
||||
}) {
|
||||
Text(isGenShineImpactStarterExist ? "Downloaded" : "Download")
|
||||
}
|
||||
@@ -217,18 +216,6 @@ struct SettingsView: View {
|
||||
}
|
||||
.padding(20)
|
||||
}
|
||||
.onAppear() {
|
||||
print("SettingsView onAppear")
|
||||
}
|
||||
.onDisappear() {
|
||||
print("SettingsView onDisappear")
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.willBecomeActiveNotification)) { _ in
|
||||
print("SettingsView NSApplication.willBecomeActiveNotification")
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||
print("SettingsView NSApplication.didBecomeActiveNotification")
|
||||
}
|
||||
.tabItem {
|
||||
Label("General", systemImage: "gear")
|
||||
}
|
||||
|
||||
168
InjectGUI/View/StatusView.swift
Normal file
168
InjectGUI/View/StatusView.swift
Normal file
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// StatusView.swift
|
||||
// InjectGUI
|
||||
//
|
||||
// Created by wibus on 2024/7/30.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct StatusView: View {
|
||||
@Environment(\.dismiss) var dismiss // dismiss the sheet
|
||||
|
||||
@StateObject var injector = Injector.shared
|
||||
@State var appDetail: AppDetail = .init(name: "", identifier: "", version: "", path: "", executable: "", icon: NSImage())
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
if appDetail.name.isEmpty {
|
||||
Text("Why you are seeing this?")
|
||||
.font(.title)
|
||||
.bold()
|
||||
.foregroundColor(.secondary)
|
||||
.padding()
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("This is a status view for the injector.")
|
||||
.font(.headline)
|
||||
Text("It will only show when the injector is running.")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Text("Please contact the developer if you see this view without running the injector.")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
} else {
|
||||
// MARK: - App Info Display Box
|
||||
|
||||
HStack(spacing: 20) {
|
||||
Image(nsImage: appDetail.icon)
|
||||
.resizable()
|
||||
.frame(width: 64, height: 64)
|
||||
.cornerRadius(4)
|
||||
Spacer()
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(appDetail.name)
|
||||
.font(.headline)
|
||||
Text(appDetail.identifier)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Text("Version: \(appDetail.version)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: 260)
|
||||
Divider()
|
||||
|
||||
|
||||
|
||||
// MARK: - Inject Stage Display box
|
||||
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
ForEach(InjectStage.allCases, id: \.self) { stage in
|
||||
HStack(spacing: 10) {
|
||||
if let index = injector.stage.stages.firstIndex(where: { $0.stage == stage }) {
|
||||
switch injector.stage.stages[index].status {
|
||||
case .none:
|
||||
Image(systemName: "questionmark.circle")
|
||||
.foregroundColor(.secondary)
|
||||
case .running:
|
||||
Image(systemName: "circle.dotted")
|
||||
case .finished:
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
case .error:
|
||||
Image(systemName: "exclamationmark.circle.fill")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
Text("\(stage.description)")
|
||||
.fontDesign(.rounded)
|
||||
.fontWeight(injector.stage.stages[index].status == .running ? .bold : .regular)
|
||||
// Text(injector.stage.stages[index].message)
|
||||
// .font(.subheadline)
|
||||
// .foregroundColor(.secondary)
|
||||
Spacer()
|
||||
Text("\(Int(injector.stage.stages[index].progress * 100))%")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
Image(systemName: "questionmark.circle.fill")
|
||||
.foregroundColor(.secondary)
|
||||
Text("\(stage.description)")
|
||||
.fontDesign(.rounded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
|
||||
// MARK: - Progress Bar
|
||||
ProgressBar(value: $injector.stage.progress)
|
||||
.frame(height: 10)
|
||||
HStack {
|
||||
Text("Progress: \(Int(injector.stage.progress * 100))%")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
if let error = injector.stage.error {
|
||||
Text("Error: \(error.error)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Buttons
|
||||
Button(injector.stage.progress == 1 ? "Finished. Close" : "Stop Injecting") {
|
||||
|
||||
dismiss()
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.controlSize(.large)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
.onChange(of: injector.stage.appId) { appId in
|
||||
guard let appDetail = SoftwareManager.shared.appListCache[appId] else {
|
||||
return
|
||||
}
|
||||
self.appDetail = appDetail
|
||||
}
|
||||
.frame(minWidth: 350, minHeight: appDetail.name.isEmpty ? 200 : 380)
|
||||
.padding()
|
||||
.onAppear {
|
||||
guard let appDetail = SoftwareManager.shared.appListCache[injector.stage.appId] else {
|
||||
return
|
||||
}
|
||||
self.appDetail = appDetail
|
||||
}
|
||||
.background(Color(.windowBackgroundColor))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ProgressBar: View {
|
||||
@Binding var value: Double
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack(alignment: .leading) {
|
||||
Rectangle()
|
||||
.foregroundColor(Color(.systemGray))
|
||||
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||
.cornerRadius(geometry.size.height / 2)
|
||||
Rectangle()
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(width: min(CGFloat(value) * geometry.size.width, geometry.size.width), height: geometry.size.height)
|
||||
.cornerRadius(geometry.size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user