mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 08:29:07 +00:00
v3.2.0
This commit is contained in:
@@ -37,9 +37,10 @@
|
|||||||
C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D492AFEB26700C7ED9E /* AppListItems.swift */; };
|
C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D492AFEB26700C7ED9E /* AppListItems.swift */; };
|
||||||
C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D502AFF00F300C7ED9E /* Locations.swift */; };
|
C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D502AFF00F300C7ED9E /* Locations.swift */; };
|
||||||
C7DC1FEE2B9F7D3D009AC317 /* features.json in Resources */ = {isa = PBXBuildFile; fileRef = C7DC1FED2B9F7D3D009AC317 /* features.json */; };
|
C7DC1FEE2B9F7D3D009AC317 /* features.json in Resources */ = {isa = PBXBuildFile; fileRef = C7DC1FED2B9F7D3D009AC317 /* features.json */; };
|
||||||
C7DE67272BA61E8D00EB1633 /* MenuBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE67262BA61E8D00EB1633 /* MenuBar.swift */; };
|
|
||||||
C7DE672B2BA6343D00EB1633 /* MenuBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */; };
|
C7DE672B2BA6343D00EB1633 /* MenuBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */; };
|
||||||
C7DE672D2BA6356500EB1633 /* MenuBarMiniAppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE672C2BA6356500EB1633 /* MenuBarMiniAppView.swift */; };
|
C7DE672D2BA6356500EB1633 /* MenuBarMiniAppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE672C2BA6356500EB1633 /* MenuBarMiniAppView.swift */; };
|
||||||
|
C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B02BA8A35500F13DE4 /* Interface.swift */; };
|
||||||
|
C7ED86B32BA8ED8800F13DE4 /* Trash.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B22BA8ED8800F13DE4 /* Trash.swift */; };
|
||||||
C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F539372AF60865007DF1B2 /* Utilities.swift */; };
|
C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F539372AF60865007DF1B2 /* Utilities.swift */; };
|
||||||
C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7FB173A2B96321300B96F9A /* AppsListView.swift */; };
|
C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7FB173A2B96321300B96F9A /* AppsListView.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
@@ -100,9 +101,10 @@
|
|||||||
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListItems.swift; sourceTree = "<group>"; };
|
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListItems.swift; sourceTree = "<group>"; };
|
||||||
C7D31D502AFF00F300C7ED9E /* Locations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locations.swift; sourceTree = "<group>"; };
|
C7D31D502AFF00F300C7ED9E /* Locations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locations.swift; sourceTree = "<group>"; };
|
||||||
C7DC1FED2B9F7D3D009AC317 /* features.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = features.json; sourceTree = SOURCE_ROOT; };
|
C7DC1FED2B9F7D3D009AC317 /* features.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = features.json; sourceTree = SOURCE_ROOT; };
|
||||||
C7DE67262BA61E8D00EB1633 /* MenuBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBar.swift; sourceTree = "<group>"; };
|
|
||||||
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarItem.swift; sourceTree = "<group>"; };
|
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarItem.swift; sourceTree = "<group>"; };
|
||||||
C7DE672C2BA6356500EB1633 /* MenuBarMiniAppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarMiniAppView.swift; sourceTree = "<group>"; };
|
C7DE672C2BA6356500EB1633 /* MenuBarMiniAppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarMiniAppView.swift; sourceTree = "<group>"; };
|
||||||
|
C7ED86B02BA8A35500F13DE4 /* Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interface.swift; sourceTree = "<group>"; };
|
||||||
|
C7ED86B22BA8ED8800F13DE4 /* Trash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trash.swift; sourceTree = "<group>"; };
|
||||||
C7F539372AF60865007DF1B2 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
|
C7F539372AF60865007DF1B2 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
|
||||||
C7FB173A2B96321300B96F9A /* AppsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsListView.swift; sourceTree = "<group>"; };
|
C7FB173A2B96321300B96F9A /* AppsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsListView.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@@ -132,6 +134,8 @@
|
|||||||
C7045A312B068AD700376976 /* NewWin.swift */,
|
C7045A312B068AD700376976 /* NewWin.swift */,
|
||||||
C7045A332B068B2900376976 /* UpdateView.swift */,
|
C7045A332B068B2900376976 /* UpdateView.swift */,
|
||||||
C7045A352B068DD700376976 /* PermView.swift */,
|
C7045A352B068DD700376976 /* PermView.swift */,
|
||||||
|
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */,
|
||||||
|
C7CF47232B3B3F1700979C5F /* WindowSettings.swift */,
|
||||||
);
|
);
|
||||||
path = Windows;
|
path = Windows;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -140,16 +144,15 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
C77B90192AF1938F009CC655 /* Logic.swift */,
|
C77B90192AF1938F009CC655 /* Logic.swift */,
|
||||||
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */,
|
|
||||||
C77B90172AF19382009CC655 /* AppCommands.swift */,
|
C77B90172AF19382009CC655 /* AppCommands.swift */,
|
||||||
C77B90152AF19377009CC655 /* AppState.swift */,
|
C77B90152AF19377009CC655 /* AppState.swift */,
|
||||||
C7F539372AF60865007DF1B2 /* Utilities.swift */,
|
C7F539372AF60865007DF1B2 /* Utilities.swift */,
|
||||||
C72893042AFD42E600C8C1CD /* DeepLink.swift */,
|
C72893042AFD42E600C8C1CD /* DeepLink.swift */,
|
||||||
C77B901B2AF193A3009CC655 /* Styles.swift */,
|
C77B901B2AF193A3009CC655 /* Styles.swift */,
|
||||||
C7D31D502AFF00F300C7ED9E /* Locations.swift */,
|
C7D31D502AFF00F300C7ED9E /* Locations.swift */,
|
||||||
C7CF47232B3B3F1700979C5F /* WindowSettings.swift */,
|
|
||||||
C7A9CE462B89164700EB6E78 /* Authorization.swift */,
|
C7A9CE462B89164700EB6E78 /* Authorization.swift */,
|
||||||
C7ACADE52B92A737000B5845 /* Features.swift */,
|
C7ACADE52B92A737000B5845 /* Features.swift */,
|
||||||
|
C7ED86B22BA8ED8800F13DE4 /* Trash.swift */,
|
||||||
);
|
);
|
||||||
path = Logic;
|
path = Logic;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -175,7 +178,7 @@
|
|||||||
children = (
|
children = (
|
||||||
C77B901D2AF1A9DB009CC655 /* SettingsWindow.swift */,
|
C77B901D2AF1A9DB009CC655 /* SettingsWindow.swift */,
|
||||||
C76D08492AF83C6E00D07867 /* General.swift */,
|
C76D08492AF83C6E00D07867 /* General.swift */,
|
||||||
C7DE67262BA61E8D00EB1633 /* MenuBar.swift */,
|
C7ED86B02BA8A35500F13DE4 /* Interface.swift */,
|
||||||
C76D08472AF83C3F00D07867 /* Update.swift */,
|
C76D08472AF83C3F00D07867 /* Update.swift */,
|
||||||
C76D08502AF850F300D07867 /* About.swift */,
|
C76D08502AF850F300D07867 /* About.swift */,
|
||||||
);
|
);
|
||||||
@@ -357,11 +360,13 @@
|
|||||||
C77B90162AF19377009CC655 /* AppState.swift in Sources */,
|
C77B90162AF19377009CC655 /* AppState.swift in Sources */,
|
||||||
C7045A322B068AD700376976 /* NewWin.swift in Sources */,
|
C7045A322B068AD700376976 /* NewWin.swift in Sources */,
|
||||||
C7DE672D2BA6356500EB1633 /* MenuBarMiniAppView.swift in Sources */,
|
C7DE672D2BA6356500EB1633 /* MenuBarMiniAppView.swift in Sources */,
|
||||||
|
C7ED86B32BA8ED8800F13DE4 /* Trash.swift in Sources */,
|
||||||
C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */,
|
C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */,
|
||||||
C77B901A2AF1938F009CC655 /* Logic.swift in Sources */,
|
C77B901A2AF1938F009CC655 /* Logic.swift in Sources */,
|
||||||
C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */,
|
C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */,
|
||||||
C77B901C2AF193A3009CC655 /* Styles.swift in Sources */,
|
C77B901C2AF193A3009CC655 /* Styles.swift in Sources */,
|
||||||
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */,
|
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */,
|
||||||
|
C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */,
|
||||||
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */,
|
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */,
|
||||||
C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */,
|
C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */,
|
||||||
C7DE672B2BA6343D00EB1633 /* MenuBarItem.swift in Sources */,
|
C7DE672B2BA6343D00EB1633 /* MenuBarItem.swift in Sources */,
|
||||||
@@ -378,7 +383,6 @@
|
|||||||
C76D08552AF89CDE00D07867 /* RegularMode.swift in Sources */,
|
C76D08552AF89CDE00D07867 /* RegularMode.swift in Sources */,
|
||||||
C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */,
|
C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */,
|
||||||
C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */,
|
C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */,
|
||||||
C7DE67272BA61E8D00EB1633 /* MenuBar.swift in Sources */,
|
|
||||||
C77B90232AF2D616009CC655 /* FilesView.swift in Sources */,
|
C77B90232AF2D616009CC655 /* FilesView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -555,7 +559,7 @@
|
|||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 23;
|
CURRENT_PROJECT_VERSION = 24;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BK8443AXLU;
|
DEVELOPMENT_TEAM = BK8443AXLU;
|
||||||
@@ -573,7 +577,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 3.1.1;
|
MARKETING_VERSION = 3.2.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
|
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -590,7 +594,7 @@
|
|||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 23;
|
CURRENT_PROJECT_VERSION = 24;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BK8443AXLU;
|
DEVELOPMENT_TEAM = BK8443AXLU;
|
||||||
@@ -608,7 +612,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 3.1.1;
|
MARKETING_VERSION = 3.2.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
|
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|||||||
@@ -105,18 +105,14 @@ struct ZombieFile: Identifiable, Equatable, Hashable {
|
|||||||
enum CurrentTabView:Int
|
enum CurrentTabView:Int
|
||||||
{
|
{
|
||||||
case general
|
case general
|
||||||
case menubar
|
case interface
|
||||||
// case permissions
|
|
||||||
// case sentinel
|
|
||||||
case update
|
case update
|
||||||
case about
|
case about
|
||||||
|
|
||||||
var title: String {
|
var title: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .general: return "General"
|
case .general: return "General"
|
||||||
case .menubar: return "MenuBar"
|
case .interface: return "Interface"
|
||||||
// case .permissions: return "Permissions"
|
|
||||||
// case .sentinel: return "Sentinel"
|
|
||||||
case .update: return "Update"
|
case .update: return "Update"
|
||||||
case .about: return "About"
|
case .about: return "About"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,109 +5,109 @@
|
|||||||
// Created by Alin Lupascu on 2/23/24.
|
// Created by Alin Lupascu on 2/23/24.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
//import Foundation
|
||||||
import Swift
|
//import Swift
|
||||||
|
//
|
||||||
// https://github.com/x13a/authorization-swift
|
//// https://github.com/x13a/authorization-swift
|
||||||
|
//
|
||||||
public struct Authorization {
|
//public struct Authorization {
|
||||||
|
//
|
||||||
public enum Error: Swift.Error {
|
// public enum Error: Swift.Error {
|
||||||
case create(OSStatus)
|
// case create(OSStatus)
|
||||||
case copyRights(OSStatus)
|
// case copyRights(OSStatus)
|
||||||
case exec(OSStatus)
|
// case exec(OSStatus)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
public static func executeWithPrivileges(
|
// public static func executeWithPrivileges(
|
||||||
_ command: String
|
// _ command: String
|
||||||
) -> Result<FileHandle, Error> {
|
// ) -> Result<FileHandle, Error> {
|
||||||
|
//
|
||||||
let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2)
|
// let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2)
|
||||||
var fn: @convention(c) (
|
// var fn: @convention(c) (
|
||||||
AuthorizationRef,
|
// AuthorizationRef,
|
||||||
UnsafePointer<CChar>, // path
|
// UnsafePointer<CChar>, // path
|
||||||
AuthorizationFlags,
|
// AuthorizationFlags,
|
||||||
UnsafePointer<UnsafePointer<CChar>?>, // args
|
// UnsafePointer<UnsafePointer<CChar>?>, // args
|
||||||
UnsafeMutablePointer<UnsafeMutablePointer<FILE>>?
|
// UnsafeMutablePointer<UnsafeMutablePointer<FILE>>?
|
||||||
) -> OSStatus
|
// ) -> OSStatus
|
||||||
fn = unsafeBitCast(
|
// fn = unsafeBitCast(
|
||||||
dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges"),
|
// dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges"),
|
||||||
to: type(of: fn)
|
// to: type(of: fn)
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
var authorizationRef: AuthorizationRef? = nil
|
// var authorizationRef: AuthorizationRef? = nil
|
||||||
var err = AuthorizationCreate(nil, nil, [], &authorizationRef)
|
// var err = AuthorizationCreate(nil, nil, [], &authorizationRef)
|
||||||
guard err == errAuthorizationSuccess else {
|
// guard err == errAuthorizationSuccess else {
|
||||||
return .failure(.create(err))
|
// return .failure(.create(err))
|
||||||
}
|
// }
|
||||||
defer { AuthorizationFree(authorizationRef!, [.destroyRights]) }
|
// defer { AuthorizationFree(authorizationRef!, [.destroyRights]) }
|
||||||
|
//
|
||||||
var components = command.components(separatedBy: " ")
|
// var components = command.components(separatedBy: " ")
|
||||||
var path = components.remove(at: 0).cString(using: .utf8)!
|
// var path = components.remove(at: 0).cString(using: .utf8)!
|
||||||
let name = kAuthorizationRightExecute.cString(using: .utf8)!
|
// let name = kAuthorizationRightExecute.cString(using: .utf8)!
|
||||||
|
//
|
||||||
var items: AuthorizationItem = name.withUnsafeBufferPointer { nameBuf in
|
// var items: AuthorizationItem = name.withUnsafeBufferPointer { nameBuf in
|
||||||
path.withUnsafeBufferPointer { pathBuf in
|
// path.withUnsafeBufferPointer { pathBuf in
|
||||||
let pathPtr =
|
// let pathPtr =
|
||||||
UnsafeMutableRawPointer(mutating: pathBuf.baseAddress!)
|
// UnsafeMutableRawPointer(mutating: pathBuf.baseAddress!)
|
||||||
return AuthorizationItem(
|
// return AuthorizationItem(
|
||||||
name: nameBuf.baseAddress!,
|
// name: nameBuf.baseAddress!,
|
||||||
valueLength: path.count,
|
// valueLength: path.count,
|
||||||
value: pathPtr,
|
// value: pathPtr,
|
||||||
flags: 0
|
// flags: 0
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
var rights: AuthorizationRights =
|
// var rights: AuthorizationRights =
|
||||||
withUnsafeMutablePointer(to: &items) { items in
|
// withUnsafeMutablePointer(to: &items) { items in
|
||||||
return AuthorizationRights(count: 1, items: items)
|
// return AuthorizationRights(count: 1, items: items)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
let flags: AuthorizationFlags = [
|
// let flags: AuthorizationFlags = [
|
||||||
.interactionAllowed,
|
// .interactionAllowed,
|
||||||
.preAuthorize,
|
// .preAuthorize,
|
||||||
.extendRights,
|
// .extendRights,
|
||||||
]
|
// ]
|
||||||
|
//
|
||||||
err = AuthorizationCopyRights(
|
// err = AuthorizationCopyRights(
|
||||||
authorizationRef!,
|
// authorizationRef!,
|
||||||
&rights,
|
// &rights,
|
||||||
nil,
|
// nil,
|
||||||
flags,
|
// flags,
|
||||||
nil
|
// nil
|
||||||
)
|
// )
|
||||||
guard err == errAuthorizationSuccess else {
|
// guard err == errAuthorizationSuccess else {
|
||||||
return .failure(.copyRights(err))
|
// return .failure(.copyRights(err))
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
let rest = components.map { $0.cString(using: .utf8)! }
|
// let rest = components.map { $0.cString(using: .utf8)! }
|
||||||
var args = Array<UnsafePointer<CChar>?>(
|
// var args = Array<UnsafePointer<CChar>?>(
|
||||||
repeating: nil,
|
// repeating: nil,
|
||||||
count: rest.count + 1
|
// count: rest.count + 1
|
||||||
)
|
// )
|
||||||
for (idx, arg) in rest.enumerated() {
|
// for (idx, arg) in rest.enumerated() {
|
||||||
args[idx] = UnsafePointer<CChar>?(arg)
|
// args[idx] = UnsafePointer<CChar>?(arg)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
var file = FILE()
|
// var file = FILE()
|
||||||
let fh: FileHandle?
|
// let fh: FileHandle?
|
||||||
|
//
|
||||||
(err, fh) = withUnsafeMutablePointer(to: &file) { file in
|
// (err, fh) = withUnsafeMutablePointer(to: &file) { file in
|
||||||
var pipe = file
|
// var pipe = file
|
||||||
let err = fn(authorizationRef!, &path, [], &args, &pipe)
|
// let err = fn(authorizationRef!, &path, [], &args, &pipe)
|
||||||
guard err == errAuthorizationSuccess else {
|
// guard err == errAuthorizationSuccess else {
|
||||||
return (err, nil)
|
// return (err, nil)
|
||||||
}
|
// }
|
||||||
let fh = FileHandle(
|
// let fh = FileHandle(
|
||||||
fileDescriptor: fileno(pipe),
|
// fileDescriptor: fileno(pipe),
|
||||||
closeOnDealloc: true
|
// closeOnDealloc: true
|
||||||
)
|
// )
|
||||||
return (err, fh)
|
// return (err, fh)
|
||||||
}
|
// }
|
||||||
guard err == errAuthorizationSuccess else {
|
// guard err == errAuthorizationSuccess else {
|
||||||
return .failure(.exec(err))
|
// return .failure(.exec(err))
|
||||||
}
|
// }
|
||||||
return .success(fh!)
|
// return .success(fh!)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|||||||
@@ -640,30 +640,6 @@ func reversePathsSearch(appState: AppState, locations: Locations, completion: @e
|
|||||||
// Move files to trash using applescript/Finder so it asks for user password if needed
|
// Move files to trash using applescript/Finder so it asks for user password if needed
|
||||||
func moveFilesToTrash(at fileURLs: [URL], completion: @escaping () -> Void = {}) {
|
func moveFilesToTrash(at fileURLs: [URL], completion: @escaping () -> Void = {}) {
|
||||||
@AppStorage("settings.sentinel.enable") var sentinel: Bool = false
|
@AppStorage("settings.sentinel.enable") var sentinel: Bool = false
|
||||||
// var filesFinder = fileURLs
|
|
||||||
// var filesSudo: [URL] = []
|
|
||||||
//
|
|
||||||
// for file in fileURLs {
|
|
||||||
// if isSocketFile(at: file) {
|
|
||||||
// if let index = filesFinder.firstIndex(of: file) {
|
|
||||||
// filesFinder.remove(at: index)
|
|
||||||
// filesSudo.insert(file, at: 0)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if !filesSudo.isEmpty {
|
|
||||||
// // Remove socket files with rm
|
|
||||||
// let filesSudoPaths = filesSudo.map { $0.path }
|
|
||||||
// do {
|
|
||||||
// let fileHandler = try Authorization.executeWithPrivileges("/bin/rm -f \(filesSudoPaths.joined(separator: " "))").get()
|
|
||||||
// printOS(String(bytes: fileHandler.readDataToEndOfFile(), encoding: .utf8)!)
|
|
||||||
// } catch {
|
|
||||||
// printOS("Failed to remove socket file/s with privileges: \(error)")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
updateOnBackground {
|
updateOnBackground {
|
||||||
let posixFiles = fileURLs.map { "POSIX file \"\($0.path)\", " }.joined().dropLast(3)
|
let posixFiles = fileURLs.map { "POSIX file \"\($0.path)\", " }.joined().dropLast(3)
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// Trash.swift
|
||||||
|
// Pearcleaner
|
||||||
|
//
|
||||||
|
// Created by Alin Lupascu on 3/18/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
//class TrashManager {
|
||||||
|
// private var fileLocations: [URL: URL] = [:] // Maps original location to trash location
|
||||||
|
//
|
||||||
|
// // Function to move files to Trash and track their original locations
|
||||||
|
// func delete(fileURLs: [URL]) {
|
||||||
|
// let fileManager = FileManager.default
|
||||||
|
// let trashURL = try! fileManager.url(for: .trashDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
|
||||||
|
//
|
||||||
|
// for originalURL in fileURLs {
|
||||||
|
// let destinationURL = trashURL.appendingPathComponent(originalURL.lastPathComponent)
|
||||||
|
//
|
||||||
|
// do {
|
||||||
|
// // The resultingItemURL is the new location of the item in the trash.
|
||||||
|
// var resultingItemURL: NSURL?
|
||||||
|
// try fileManager.trashItem(at: originalURL, resultingItemURL: &resultingItemURL)
|
||||||
|
// if let trashPath = resultingItemURL as URL? {
|
||||||
|
// // Track the original URL and its corresponding location in the Trash
|
||||||
|
// fileLocations[originalURL] = trashPath
|
||||||
|
// }
|
||||||
|
// } catch {
|
||||||
|
// print("Error moving file to Trash: \(error)")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Function to undo the deletion of files, moving them back from Trash to their original locations
|
||||||
|
// func undoDelete() {
|
||||||
|
// let fileManager = FileManager.default
|
||||||
|
//
|
||||||
|
// for (originalURL, trashURL) in fileLocations {
|
||||||
|
// do {
|
||||||
|
// // Attempt to move the item back to its original location
|
||||||
|
// try fileManager.moveItem(at: trashURL, to: originalURL)
|
||||||
|
// } catch {
|
||||||
|
// print("Error moving file back from Trash: \(error)")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Clear the tracking dictionary after undoing the deletions
|
||||||
|
// fileLocations.removeAll()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//import Foundation
|
||||||
|
//
|
||||||
|
//func moveToTrash(fileURLs: [URL], completion: @escaping () -> Void = {}) {
|
||||||
|
// for fileURL in fileURLs {
|
||||||
|
// do {
|
||||||
|
// try _ = fileURL.checkResourceIsReachable()
|
||||||
|
// } catch {
|
||||||
|
// printOS(error.localizedDescription)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// let target: NSAppleEventDescriptor = .init(bundleIdentifier: "com.apple.finder")
|
||||||
|
// let event: NSAppleEventDescriptor = .init(eventClass: kAECoreSuite,
|
||||||
|
// eventID: AEEventID(kAEDelete),
|
||||||
|
// targetDescriptor: target,
|
||||||
|
// returnID: AEReturnID(kAutoGenerateReturnID),
|
||||||
|
// transactionID: AETransactionID(kAnyTransactionID))
|
||||||
|
// let fileList: NSAppleEventDescriptor = fileURLs.enumerated().reduce(into: .init(listDescriptor: ())) {
|
||||||
|
// (result: inout NSAppleEventDescriptor, element: (offset: Int, element: URL)) in
|
||||||
|
// if let nativePath: NSAppleEventDescriptor = .init(
|
||||||
|
// descriptorType: typeFileURL,
|
||||||
|
// data: element.element.absoluteString.data(using: .utf8)
|
||||||
|
// ) {
|
||||||
|
// result.insert(nativePath, at: element.offset + 1)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// event.setParam(fileList, forKeyword: keyDirectObject)
|
||||||
|
//
|
||||||
|
// do {
|
||||||
|
// try event.sendEvent(options: .noReply, timeout: TimeInterval(kAEDefaultTimeout))
|
||||||
|
// } catch let error as NSError {
|
||||||
|
// if case -600 = error.code {
|
||||||
|
// printOS("Finder is not running.")
|
||||||
|
// } else {
|
||||||
|
// printOS(error.description)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// completion()
|
||||||
|
//}
|
||||||
|
|
||||||
@@ -156,6 +156,31 @@ func isDarkModeEnabled() -> Bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if Pearcleaner has any windows open
|
||||||
|
func hasWindowOpen() -> Bool {
|
||||||
|
for window in NSApp.windows where window.title == "Pearcleaner" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and hide/show main app window when using menubar item
|
||||||
|
func findAndHideWindows(named titles: [String]) {
|
||||||
|
for title in titles {
|
||||||
|
if let window = NSApp.windows.first(where: { $0.title == title }) {
|
||||||
|
window.orderOut(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAndShowWindows(named titles: [String]) {
|
||||||
|
for title in titles {
|
||||||
|
if let window = NSApp.windows.first(where: { $0.title == title }) {
|
||||||
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Check if appearance is dark mode
|
// Check if appearance is dark mode
|
||||||
//func getCasks() -> [String] {
|
//func getCasks() -> [String] {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ struct PearcleanerApp: App {
|
|||||||
@AppStorage("settings.general.brew") private var brew: Bool = false
|
@AppStorage("settings.general.brew") private var brew: Bool = false
|
||||||
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
||||||
@AppStorage("settings.menubar.mainWin") private var mainWinEnabled: Bool = false
|
@AppStorage("settings.menubar.mainWin") private var mainWinEnabled: Bool = false
|
||||||
|
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "trash"
|
||||||
|
|
||||||
@State private var search = ""
|
@State private var search = ""
|
||||||
@State private var showPopover: Bool = false
|
@State private var showPopover: Bool = false
|
||||||
@@ -41,10 +42,8 @@ struct PearcleanerApp: App {
|
|||||||
ZStack() {
|
ZStack() {
|
||||||
if !mini {
|
if !mini {
|
||||||
RegularMode(search: $search, showPopover: $showPopover)
|
RegularMode(search: $search, showPopover: $showPopover)
|
||||||
.environmentObject(locations)
|
|
||||||
} else {
|
} else {
|
||||||
MiniMode(search: $search, showPopover: $showPopover)
|
MiniMode(search: $search, showPopover: $showPopover)
|
||||||
.environmentObject(locations)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if showFeature {
|
if showFeature {
|
||||||
@@ -57,8 +56,8 @@ struct PearcleanerApp: App {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
|
.environmentObject(locations)
|
||||||
.preferredColorScheme(displayMode.colorScheme)
|
.preferredColorScheme(displayMode.colorScheme)
|
||||||
// .alert(isPresented: $appState.showAlert) { presentAlert(appState: appState) }
|
|
||||||
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*"))
|
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*"))
|
||||||
.onOpenURL(perform: { url in
|
.onOpenURL(perform: { url in
|
||||||
let deeplinkManager = DeeplinkManager(showPopover: $showPopover)
|
let deeplinkManager = DeeplinkManager(showPopover: $showPopover)
|
||||||
@@ -107,13 +106,12 @@ struct PearcleanerApp: App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if menubarEnabled {
|
if menubarEnabled {
|
||||||
MenuBarExtraManager.shared.addMenuBarExtra {
|
MenuBarExtraManager.shared.addMenuBarExtra(withView: {
|
||||||
MenuBarMiniAppView(search: $search, showPopover: $showPopover)
|
MenuBarMiniAppView(search: $search, showPopover: $showPopover)
|
||||||
.environmentObject(locations)
|
.environmentObject(locations)
|
||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
|
}, icon: selectedMenubarIcon)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
@@ -175,7 +173,7 @@ struct PearcleanerApp: App {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
||||||
|
|
||||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
|
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
|
||||||
@@ -183,16 +181,50 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
||||||
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
|
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
|
func windowShouldClose(_ sender: NSWindow) -> Bool {
|
||||||
|
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
|
||||||
|
// let alert = NSAlert.init()
|
||||||
|
// alert.addButton(withTitle: "Return")
|
||||||
|
// alert.addButton(withTitle: "Quit")
|
||||||
|
// alert.informativeText = "Quit or return to application?"
|
||||||
|
// let response = alert.runModal()
|
||||||
|
// if response == NSApplication.ModalResponse.alertFirstButtonReturn {
|
||||||
|
// return false
|
||||||
|
// } else {
|
||||||
|
// NSApplication.shared.terminate(self)
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
if menubarEnabled {
|
if menubarEnabled {
|
||||||
NSApp.windows.first?.close()
|
findAndHideWindows(named: ["Pearcleaner"])
|
||||||
NSApplication.shared.setActivationPolicy(.accessory)
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
|
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
|
||||||
|
// let dockEnabled = UserDefaults.standard.bool(forKey: "settings.dock.enabled")
|
||||||
|
|
||||||
|
if menubarEnabled {
|
||||||
|
#if !DEBUG
|
||||||
|
findAndHideWindows(named: ["Pearcleaner"])
|
||||||
|
// NSApp.windows.first?.close()
|
||||||
|
NSApplication.shared.setActivationPolicy(.accessory)
|
||||||
|
#endif
|
||||||
|
// if dockEnabled {
|
||||||
|
// NSApplication.shared.setActivationPolicy(.regular)
|
||||||
|
// } else {
|
||||||
|
// NSApplication.shared.setActivationPolicy(.accessory)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link window to delegate
|
||||||
|
let mainWindow = NSApp.windows[0]
|
||||||
|
mainWindow.delegate = self
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "pear-1.5.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 561 B |
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "pear-1.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 565 B |
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "pear-2.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "pear-3.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "pear-4.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -152,7 +152,7 @@ struct AboutSettingsTab: View {
|
|||||||
Text("Made with ❤️ by Alin Lupascu (dev@itsalin.com)").font(.footnote).padding(.bottom)
|
Text("Made with ❤️ by Alin Lupascu (dev@itsalin.com)").font(.footnote).padding(.bottom)
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: 650)
|
.frame(width: 500, height: 600)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,32 +35,11 @@ struct GeneralSettingsTab: View {
|
|||||||
VStack {
|
VStack {
|
||||||
|
|
||||||
HStack() {
|
HStack() {
|
||||||
Text("Appearance").font(.title2)
|
Text("Functionality").font(.title2)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: glass ? "cube.transparent" : "cube.transparent.fill")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
|
||||||
Text("\(glass ? "Transparent material enabled" : "Transparent material disabled")")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
}
|
|
||||||
// InfoButton(text: "When transparent material is enabled, sticky section headers (User/System) in app list are disabled to keep from showing app name text overlayed under the section header text with no background to separate the two.", color: nil)
|
|
||||||
Spacer()
|
|
||||||
Toggle(isOn: $glass, label: {
|
|
||||||
})
|
|
||||||
.toggleStyle(.switch)
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Image(systemName: instantSearch ? "bolt" : "bolt.slash")
|
Image(systemName: instantSearch ? "bolt" : "bolt.slash")
|
||||||
@@ -110,146 +89,6 @@ struct GeneralSettingsTab: View {
|
|||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: displayMode.colorScheme == .dark ? "moon.fill" : "sun.max.fill")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
|
||||||
Text("Set application color mode")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
Picker("", selection: $selectedTheme) {
|
|
||||||
ForEach(themes, id: \.self) { theme in
|
|
||||||
Text(theme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pickerStyle(SegmentedPickerStyle())
|
|
||||||
.frame(width: 200)
|
|
||||||
.onChange(of: selectedTheme) { newTheme in
|
|
||||||
switch newTheme {
|
|
||||||
case "Auto":
|
|
||||||
displayMode.colorScheme = nil
|
|
||||||
if isDarkModeEnabled() {
|
|
||||||
displayMode.colorScheme = .dark
|
|
||||||
} else {
|
|
||||||
displayMode.colorScheme = .light
|
|
||||||
}
|
|
||||||
case "Dark":
|
|
||||||
displayMode.colorScheme = .dark
|
|
||||||
case "Light":
|
|
||||||
displayMode.colorScheme = .light
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
// === Mini =================================================================================================
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
HStack() {
|
|
||||||
Text("Mini Configuration").font(.title2)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: mini ? "square.resize.up" : "square.resize.down")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
|
||||||
Text("\(mini ? "Mini window mode selected" : "Full size window mode selected")")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
Toggle(isOn: $mini, label: {
|
|
||||||
})
|
|
||||||
.toggleStyle(.switch)
|
|
||||||
.onChange(of: mini) { newVal in
|
|
||||||
if mini {
|
|
||||||
appState.currentView = miniView ? .apps : .empty
|
|
||||||
showPopover = false
|
|
||||||
resizeWindowAuto(windowSettings: windowSettings)
|
|
||||||
} else {
|
|
||||||
resizeWindowAuto(windowSettings: windowSettings)
|
|
||||||
if appState.appInfo.appName.isEmpty {
|
|
||||||
appState.currentView = .empty
|
|
||||||
} else {
|
|
||||||
appState.currentView = .files
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: miniView ? "square.grid.3x3.square" : "plus.square.dashed")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
|
||||||
Text("\(miniView ? "Show apps list on startup" : "Show drop target on startup")")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
}
|
|
||||||
|
|
||||||
InfoButton(text: "In mini window mode, you can have Pearcleaner startup to the Apps List view or the Drop Target view.", color: nil, label: "")
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
Toggle(isOn: $miniView, label: {
|
|
||||||
})
|
|
||||||
.toggleStyle(.switch)
|
|
||||||
.onChange(of: miniView) { newVal in
|
|
||||||
appState.currentView = newVal ? .apps : .empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: popoverStay ? "pin" : "pin.slash")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
|
||||||
Text("\(popoverStay ? "Popover window will stay on top" : "Popover window will not stay on top")")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
}
|
|
||||||
|
|
||||||
InfoButton(text: "In mini window mode, if you pin the Files popover on top, clicking away from the window will not dismiss it. Otherwise, it will dismiss by clicking anywhere outside the popover.", color: nil, label: "")
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
Toggle(isOn: $popoverStay, label: {
|
|
||||||
})
|
|
||||||
.toggleStyle(.switch)
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
|
|
||||||
// === Perms ================================================================================================
|
// === Perms ================================================================================================
|
||||||
|
|
||||||
|
|
||||||
@@ -369,7 +208,7 @@ struct GeneralSettingsTab: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: 690)
|
.frame(width: 500, height: 420)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,396 @@
|
|||||||
|
//
|
||||||
|
// Interface.swift
|
||||||
|
// Pearcleaner
|
||||||
|
//
|
||||||
|
// Created by Alin Lupascu on 3/18/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
import ServiceManagement
|
||||||
|
|
||||||
|
struct InterfaceSettingsTab: View {
|
||||||
|
@EnvironmentObject var appState: AppState
|
||||||
|
@EnvironmentObject var locations: Locations
|
||||||
|
@State private var windowSettings = WindowSettings()
|
||||||
|
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
||||||
|
// @AppStorage("settings.dock.enabled") private var dockEnabled: Bool = false
|
||||||
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
|
@AppStorage("displayMode") var displayMode: DisplayMode = .system
|
||||||
|
@AppStorage("settings.general.glass") private var glass: Bool = true
|
||||||
|
@AppStorage("settings.general.dark") var isDark: Bool = true
|
||||||
|
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
|
||||||
|
@AppStorage("settings.general.miniview") private var miniView: Bool = true
|
||||||
|
@AppStorage("settings.general.selectedTheme") var selectedTheme: String = "Auto"
|
||||||
|
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "pear-4"
|
||||||
|
private let themes = ["Auto", "Dark", "Light"]
|
||||||
|
@State private var isLaunchAtLoginEnabled: Bool = false
|
||||||
|
let icons = ["externaldrive", "trash", "folder", "pear-1", "pear-1.5", "pear-2", "pear-3", "pear-4"]
|
||||||
|
|
||||||
|
@Binding var showPopover: Bool
|
||||||
|
@Binding var search: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Form {
|
||||||
|
VStack {
|
||||||
|
|
||||||
|
HStack() {
|
||||||
|
Text("Appearance").font(.title2)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: glass ? "cube.transparent" : "cube.transparent.fill")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("\(glass ? "Transparent material enabled" : "Transparent material disabled")")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Toggle(isOn: $glass, label: {
|
||||||
|
})
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: displayMode.colorScheme == .dark ? "moon.fill" : "sun.max.fill")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("Application color mode")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Picker("", selection: $selectedTheme) {
|
||||||
|
ForEach(themes, id: \.self) { theme in
|
||||||
|
Text(theme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(SegmentedPickerStyle())
|
||||||
|
.frame(width: 200)
|
||||||
|
.onChange(of: selectedTheme) { newTheme in
|
||||||
|
switch newTheme {
|
||||||
|
case "Auto":
|
||||||
|
displayMode.colorScheme = nil
|
||||||
|
if isDarkModeEnabled() {
|
||||||
|
displayMode.colorScheme = .dark
|
||||||
|
} else {
|
||||||
|
displayMode.colorScheme = .light
|
||||||
|
}
|
||||||
|
case "Dark":
|
||||||
|
displayMode.colorScheme = .dark
|
||||||
|
case "Light":
|
||||||
|
displayMode.colorScheme = .light
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// === Mini =================================================================================================
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HStack() {
|
||||||
|
Text("Mini Configuration").font(.title2)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: mini ? "square.resize.up" : "square.resize.down")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("\(mini ? "Mini window mode" : "Full size window mode")")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Toggle(isOn: $mini, label: {
|
||||||
|
})
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
.onChange(of: mini) { newVal in
|
||||||
|
if mini {
|
||||||
|
appState.currentView = miniView ? .apps : .empty
|
||||||
|
showPopover = false
|
||||||
|
resizeWindowAuto(windowSettings: windowSettings)
|
||||||
|
} else {
|
||||||
|
resizeWindowAuto(windowSettings: windowSettings)
|
||||||
|
if appState.appInfo.appName.isEmpty {
|
||||||
|
appState.currentView = .empty
|
||||||
|
} else {
|
||||||
|
appState.currentView = .files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: miniView ? "square.grid.3x3.square" : "plus.square.dashed")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("\(miniView ? "Show apps list on startup" : "Show drop target on startup")")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoButton(text: "In mini window mode, you can have Pearcleaner startup to the Apps List view or the Drop Target view.", color: nil, label: "")
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
Toggle(isOn: $miniView, label: {
|
||||||
|
})
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
.onChange(of: miniView) { newVal in
|
||||||
|
appState.currentView = newVal ? .apps : .empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: popoverStay ? "pin" : "pin.slash")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("\(popoverStay ? "Popover window will stay on top" : "Popover window will not stay on top")")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoButton(text: "In mini window mode, if you pin the Files popover on top, clicking away from the window will not dismiss it. Otherwise, it will dismiss by clicking anywhere outside the popover.", color: nil, label: "")
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
Toggle(isOn: $popoverStay, label: {
|
||||||
|
})
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
// === MenuBar===============================================================================================
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
HStack() {
|
||||||
|
Text("Menubar").font(.title2)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: "menubar.rectangle")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("\(menubarEnabled ? "Menubar icon enabled" : "Menubar icon disabled")")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
InfoButton(text: "When menubar icon is enabled, the main app window and dock icon will be hidden. You can still pop-out the main app window from the menubar icon temporarily if you'd like.", color: nil, label: "")
|
||||||
|
Spacer()
|
||||||
|
Toggle(isOn: $menubarEnabled, label: {
|
||||||
|
})
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
.onChange(of: menubarEnabled) { newVal in
|
||||||
|
if newVal {
|
||||||
|
MenuBarExtraManager.shared.addMenuBarExtra(withView: {
|
||||||
|
MenuBarMiniAppView(search: $search, showPopover: $showPopover)
|
||||||
|
.environmentObject(locations)
|
||||||
|
.environmentObject(appState)
|
||||||
|
}, icon: selectedMenubarIcon)
|
||||||
|
NSApplication.shared.setActivationPolicy(.accessory)
|
||||||
|
findAndShowWindows(named: ["Pearcleaner", "Interface"])
|
||||||
|
} else {
|
||||||
|
MenuBarExtraManager.shared.removeMenuBarExtra()
|
||||||
|
// dockEnabled = true
|
||||||
|
NSApplication.shared.setActivationPolicy(.regular)
|
||||||
|
if !hasWindowOpen() {
|
||||||
|
if mini {
|
||||||
|
windowSettings.newWindow {
|
||||||
|
MiniMode(search: $search, showPopover: $showPopover)
|
||||||
|
.environmentObject(locations)
|
||||||
|
.environmentObject(appState)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
windowSettings.newWindow {
|
||||||
|
RegularMode(search: $search, showPopover: $showPopover)
|
||||||
|
.environmentObject(locations)
|
||||||
|
.environmentObject(appState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: isLaunchAtLoginEnabled ? "person" : "person.slash")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("\(isLaunchAtLoginEnabled ? "Launch at login enabled" : "Launch at login disabled")")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
InfoButton(text: "This setting will affect Pearcleaner whether you're running in menubar mode or regular mode. If you disable menubar icon, you might want to disable this as well so Pearcleaner doesn't start on login.", color: nil, label: "")
|
||||||
|
Spacer()
|
||||||
|
Toggle(isOn: $isLaunchAtLoginEnabled, label: {
|
||||||
|
})
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
.onAppear {
|
||||||
|
isLaunchAtLoginEnabled = SMAppService.mainApp.status == .enabled
|
||||||
|
}
|
||||||
|
.onChange(of: isLaunchAtLoginEnabled) { newValue in
|
||||||
|
do {
|
||||||
|
if newValue {
|
||||||
|
if SMAppService.mainApp.status == .enabled {
|
||||||
|
try? SMAppService.mainApp.unregister()
|
||||||
|
}
|
||||||
|
|
||||||
|
try SMAppService.mainApp.register()
|
||||||
|
} else {
|
||||||
|
try SMAppService.mainApp.unregister()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
printOS("Failed to \(newValue ? "enable" : "disable") launch at login: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: "paintbrush")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("Menubar icon")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Picker("", selection: $selectedMenubarIcon) {
|
||||||
|
ForEach(icons, id: \.self) { icon in
|
||||||
|
HStack {
|
||||||
|
if icon.contains("pear") {
|
||||||
|
Image(icon)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
} else {
|
||||||
|
Image(systemName: icon)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tag(icon)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: 60)
|
||||||
|
.onChange(of: selectedMenubarIcon) { newValue in
|
||||||
|
MenuBarExtraManager.shared.swapMenuBarIcon(icon: newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
// HStack(spacing: 0) {
|
||||||
|
// Image(systemName: "dock.rectangle")
|
||||||
|
// .resizable()
|
||||||
|
// .scaledToFit()
|
||||||
|
// .frame(width: 20, height: 20)
|
||||||
|
// .padding(.trailing)
|
||||||
|
// .foregroundStyle(.gray)
|
||||||
|
// VStack(alignment: .leading, spacing: 5) {
|
||||||
|
// Text("\(dockEnabled ? "Show dock icon" : "Hide dock icon")")
|
||||||
|
// .font(.callout)
|
||||||
|
// .foregroundStyle(.gray)
|
||||||
|
// }
|
||||||
|
// InfoButton(text: "This setting only affects Pearcleaner when the menubar icon is enabled, otherwise the dock icon will always show", color: nil, label: "")
|
||||||
|
// Spacer()
|
||||||
|
// Toggle(isOn: $dockEnabled, label: {
|
||||||
|
// })
|
||||||
|
// .toggleStyle(.switch)
|
||||||
|
// .onChange(of: dockEnabled) { newValue in
|
||||||
|
// if newValue {
|
||||||
|
// NSApplication.shared.setActivationPolicy(.regular)
|
||||||
|
// } else {
|
||||||
|
// if menubarEnabled {
|
||||||
|
// NSApplication.shared.setActivationPolicy(.accessory)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// .padding(5)
|
||||||
|
// .padding(.leading)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.padding(20)
|
||||||
|
.frame(width: 500, height: 520)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
//
|
|
||||||
// MenuBar.swift
|
|
||||||
// Pearcleaner
|
|
||||||
//
|
|
||||||
// Created by Alin Lupascu on 3/16/24.
|
|
||||||
//
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
import ServiceManagement
|
|
||||||
|
|
||||||
struct MenuBarSettingsTab: View {
|
|
||||||
@EnvironmentObject var appState: AppState
|
|
||||||
@EnvironmentObject var locations: Locations
|
|
||||||
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
|
||||||
@State private var isLaunchAtLoginEnabled: Bool = false
|
|
||||||
|
|
||||||
@Binding var showPopover: Bool
|
|
||||||
@Binding var search: String
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Form {
|
|
||||||
VStack {
|
|
||||||
|
|
||||||
HStack() {
|
|
||||||
Text("Configuration").font(.title2)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: "menubar.rectangle")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
|
||||||
Text("\(menubarEnabled ? "Menubar icon enabled" : "Menubar icon disabled")")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
Toggle(isOn: $menubarEnabled, label: {
|
|
||||||
})
|
|
||||||
.toggleStyle(.switch)
|
|
||||||
.onChange(of: menubarEnabled) { newVal in
|
|
||||||
if newVal {
|
|
||||||
MenuBarExtraManager.shared.addMenuBarExtra {
|
|
||||||
MenuBarMiniAppView(search: $search, showPopover: $showPopover)
|
|
||||||
.environmentObject(locations)
|
|
||||||
.environmentObject(appState)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MenuBarExtraManager.shared.removeMenuBarExtra()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: isLaunchAtLoginEnabled ? "person.crop.circle.badge.checkmark" : "person.crop.circle")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
|
||||||
Text("\(isLaunchAtLoginEnabled ? "Launch at login enabled" : "Launch at login disabled")")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(.gray)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
Toggle(isOn: $isLaunchAtLoginEnabled, label: {
|
|
||||||
})
|
|
||||||
.toggleStyle(.switch)
|
|
||||||
.onAppear {
|
|
||||||
isLaunchAtLoginEnabled = SMAppService.mainApp.status == .enabled
|
|
||||||
}
|
|
||||||
.onChange(of: isLaunchAtLoginEnabled) { newValue in
|
|
||||||
do {
|
|
||||||
if newValue {
|
|
||||||
if SMAppService.mainApp.status == .enabled {
|
|
||||||
try? SMAppService.mainApp.unregister()
|
|
||||||
}
|
|
||||||
|
|
||||||
try SMAppService.mainApp.register()
|
|
||||||
} else {
|
|
||||||
try SMAppService.mainApp.unregister()
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
printOS("Failed to \(newValue ? "enable" : "disable") launch at login: \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(20)
|
|
||||||
.frame(width: 500, height: 690)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -23,11 +23,11 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
.tag(CurrentTabView.general)
|
.tag(CurrentTabView.general)
|
||||||
|
|
||||||
MenuBarSettingsTab(showPopover: $showPopover, search: $search)
|
InterfaceSettingsTab(showPopover: $showPopover, search: $search)
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label(CurrentTabView.menubar.title, systemImage: "menubar.rectangle")
|
Label(CurrentTabView.interface.title, systemImage: "macwindow")
|
||||||
}
|
}
|
||||||
.tag(CurrentTabView.menubar)
|
.tag(CurrentTabView.interface)
|
||||||
|
|
||||||
UpdateSettingsTab(showFeature: $showFeature)
|
UpdateSettingsTab(showFeature: $showFeature)
|
||||||
.tabItem {
|
.tabItem {
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ struct UpdateSettingsTab: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: 650)
|
.frame(width: 500, height: 520)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ struct MenuBarMiniAppView: View {
|
|||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var locations: Locations
|
@EnvironmentObject var locations: Locations
|
||||||
@State private var windowSettings = WindowSettings()
|
// @State private var windowSettings = WindowSettings()
|
||||||
@State private var animateGradient: Bool = false
|
@State private var animateGradient: Bool = false
|
||||||
@Binding var search: String
|
@Binding var search: String
|
||||||
@State private var showSys: Bool = true
|
@State private var showSys: Bool = true
|
||||||
@@ -56,6 +56,7 @@ struct MenuBarMiniAppView: View {
|
|||||||
|
|
||||||
AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps).padding(0)
|
AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps).padding(0)
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
|
|
||||||
if #available(macOS 14.0, *) {
|
if #available(macOS 14.0, *) {
|
||||||
SettingsLink()
|
SettingsLink()
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "gear", help: "Settings", color: Color("mode")))
|
.buttonStyle(SimpleButtonStyle(icon: "gear", help: "Settings", color: Color("mode")))
|
||||||
@@ -66,16 +67,51 @@ struct MenuBarMiniAppView: View {
|
|||||||
.buttonStyle(SimpleButtonStyle(icon: "gear", help: "Settings", color: Color("mode")))
|
.buttonStyle(SimpleButtonStyle(icon: "gear", help: "Settings", color: Color("mode")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button("") {
|
||||||
|
withAnimation(.easeInOut(duration: 0.5)) {
|
||||||
|
showPopover = false
|
||||||
|
updateOnMain {
|
||||||
|
appState.appInfo = .empty
|
||||||
|
appState.selectedZombieItems = []
|
||||||
|
if appState.zombieFile.fileSize.keys.count == 0 {
|
||||||
|
appState.currentView = .zombie
|
||||||
|
appState.showProgress.toggle()
|
||||||
|
showPopover.toggle()
|
||||||
|
if instantSearch {
|
||||||
|
reversePathsSearch(appState: appState, locations: locations)
|
||||||
|
} else {
|
||||||
|
loadAllPaths(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
appState.currentView = .zombie
|
||||||
|
showPopover.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files", color: Color("mode")))
|
||||||
|
|
||||||
SearchBarMiniBottom(search: $search)
|
SearchBarMiniBottom(search: $search)
|
||||||
|
|
||||||
Button("Main") {
|
Button("Main") {
|
||||||
windowSettings.newWindow {
|
findAndShowWindows(named: ["Pearcleaner"])
|
||||||
MiniMode(search: $search, showPopover: $showPopover)
|
// if mini {
|
||||||
.environmentObject(locations)
|
// windowSettings.newWindow {
|
||||||
.environmentObject(appState)
|
// MiniMode(search: $search, showPopover: $showPopover)
|
||||||
|
// .environmentObject(locations)
|
||||||
|
// .environmentObject(appState)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// windowSettings.newWindow {
|
||||||
|
// RegularMode(search: $search, showPopover: $showPopover)
|
||||||
|
// .environmentObject(locations)
|
||||||
|
// .environmentObject(appState)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
.buttonStyle(SimpleButtonStyle(icon: "macwindow.on.rectangle", help: "Pop Out Window", color: Color("mode")))
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "macwindow", help: "Show Main Window", color: Color("mode")))
|
|
||||||
|
|
||||||
Button("Kill") {
|
Button("Kill") {
|
||||||
NSApp.terminate(nil)
|
NSApp.terminate(nil)
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ struct MiniMode: View {
|
|||||||
// MARK: Background for whole app
|
// MARK: Background for whole app
|
||||||
// .background(Color("bg").opacity(1))
|
// .background(Color("bg").opacity(1))
|
||||||
// .background(VisualEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all))
|
// .background(VisualEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class MenuBarExtraManager {
|
|||||||
private var statusItem: NSStatusItem?
|
private var statusItem: NSStatusItem?
|
||||||
private var popover = NSPopover()
|
private var popover = NSPopover()
|
||||||
|
|
||||||
func addMenuBarExtra<V: View>(withView view: @escaping () -> V) {
|
func addMenuBarExtra<V: View>(withView view: @escaping () -> V, icon: String) {
|
||||||
guard statusItem == nil else { return }
|
guard statusItem == nil else { return }
|
||||||
|
|
||||||
// Initialize the status item
|
// Initialize the status item
|
||||||
@@ -21,7 +21,12 @@ class MenuBarExtraManager {
|
|||||||
|
|
||||||
// Set up the status item's button
|
// Set up the status item's button
|
||||||
if let button = statusItem?.button {
|
if let button = statusItem?.button {
|
||||||
button.image = NSImage(systemSymbolName: "menubar.rectangle", accessibilityDescription: "Pearcleaner")
|
if NSImage(systemSymbolName: icon, accessibilityDescription: nil) != nil {
|
||||||
|
button.image = NSImage(systemSymbolName: icon, accessibilityDescription: "Pearcleaner")
|
||||||
|
} else {
|
||||||
|
button.image = NSImage(named: icon)
|
||||||
|
|
||||||
|
}
|
||||||
button.action = #selector(togglePopover(_:))
|
button.action = #selector(togglePopover(_:))
|
||||||
button.target = self
|
button.target = self
|
||||||
}
|
}
|
||||||
@@ -40,14 +45,23 @@ class MenuBarExtraManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatus() -> Bool {
|
func swapMenuBarIcon(icon: String) {
|
||||||
if let item = statusItem {
|
guard let button = statusItem?.button else { return }
|
||||||
return true
|
if let image = NSImage(systemSymbolName: icon, accessibilityDescription: nil) {
|
||||||
|
button.image = image
|
||||||
} else {
|
} else {
|
||||||
return false
|
button.image = NSImage(named: icon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func getStatus() -> Bool {
|
||||||
|
// if let item = statusItem {
|
||||||
|
// return true
|
||||||
|
// } else {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
@objc func togglePopover(_ sender: AnyObject?) {
|
@objc func togglePopover(_ sender: AnyObject?) {
|
||||||
if let button = statusItem?.button {
|
if let button = statusItem?.button {
|
||||||
if popover.isShown {
|
if popover.isShown {
|
||||||
@@ -15,7 +15,7 @@ class WindowSettings {
|
|||||||
private let windowXKey = "windowXKey"
|
private let windowXKey = "windowXKey"
|
||||||
private let windowYKey = "windowYKey"
|
private let windowYKey = "windowYKey"
|
||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
var window: NSWindow?
|
var windows: [NSWindow] = []
|
||||||
|
|
||||||
func saveWindowSettings(frame: NSRect) {
|
func saveWindowSettings(frame: NSRect) {
|
||||||
|
|
||||||
@@ -37,7 +37,6 @@ class WindowSettings {
|
|||||||
func newWindow<V: View>(withView view: @escaping () -> V) {
|
func newWindow<V: View>(withView view: @escaping () -> V) {
|
||||||
let contentView = view
|
let contentView = view
|
||||||
let frame = self.loadWindowSettings()
|
let frame = self.loadWindowSettings()
|
||||||
// Create the window and set the content view
|
|
||||||
let newWindow = NSWindow(
|
let newWindow = NSWindow(
|
||||||
contentRect: frame,
|
contentRect: frame,
|
||||||
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
|
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
|
||||||
@@ -47,7 +46,8 @@ class WindowSettings {
|
|||||||
newWindow.center()
|
newWindow.center()
|
||||||
newWindow.setFrameAutosaveName("Main Window")
|
newWindow.setFrameAutosaveName("Main Window")
|
||||||
newWindow.contentView = NSHostingView(rootView: contentView())
|
newWindow.contentView = NSHostingView(rootView: contentView())
|
||||||
self.window = newWindow
|
// self.window = newWindow
|
||||||
|
self.windows.append(newWindow)
|
||||||
newWindow.makeKeyAndOrderFront(nil)
|
newWindow.makeKeyAndOrderFront(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"3.2.0": "- Menubar Item: You can choose to access Pearcleaner from the menubar. This will show a slightly modified Mini Mode view.",
|
||||||
"3.1.0": "- Homebrew Cleanup: If you install apps using brew, you can now enable homebrew cleanup in the settings to have Pearcleaner alert Homebrew that the app was removed externally from Homebrew and keep the brew app list synced.",
|
"3.1.0": "- Homebrew Cleanup: If you install apps using brew, you can now enable homebrew cleanup in the settings to have Pearcleaner alert Homebrew that the app was removed externally from Homebrew and keep the brew app list synced.",
|
||||||
"3.0.0": "- Instant Search: Enable in settings to load app files on startup|- Semantic Versioning: Going forward will use semver (Ex. v0.0.0)|-Feature Alert: For each version, a feature alert will popup once on startup to show details|- Leftover File Cleaning: Search your mac for files leftover by uninstalled apps|- Sidebar Drag Handle: Resize the sidebar in regular mode|- Redesign UI/Settings/Icon: Some new buttons, layout changes, new official app icon and pear color theme accents|- File Sort: Sort files alphabetically or by size|- Socket File Removal: Finds socket files that aren't even visible in Finder with show hidden files enabled|- OSLog Output: Will print errors to the Console app for easier troubleshooting"
|
"3.0.0": "- Instant Search: Enable in settings to load app files on startup|- Semantic Versioning: Going forward will use semver (Ex. v0.0.0)|-Feature Alert: For each version, a feature alert will popup once on startup to show details|- Leftover File Cleaning: Search your mac for files leftover by uninstalled apps|- Sidebar Drag Handle: Resize the sidebar in regular mode|- Redesign UI/Settings/Icon: Some new buttons, layout changes, new official app icon and pear color theme accents|- File Sort: Sort files alphabetically or by size|- Socket File Removal: Finds socket files that aren't even visible in Finder with show hidden files enabled|- OSLog Output: Will print errors to the Console app for easier troubleshooting"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user