Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d995119198 | |||
| b4d7c06432 | |||
| a7b95a4379 | |||
| 3efe25f804 | |||
| 0a3e28b28f | |||
| 009fab95be |
@@ -82,8 +82,8 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}.startAnimation()
|
||||
grabberMode = false
|
||||
|
||||
UserDefaults.standard.set(consoleView.center.x, forKey: "LocalConsole_X")
|
||||
UserDefaults.standard.set(consoleView.center.y, forKey: "LocalConsole_Y")
|
||||
UserDefaults.standard.set(consoleView.center.x, forKey: "LocalConsole.X")
|
||||
UserDefaults.standard.set(consoleView.center.y, forKey: "LocalConsole.Y")
|
||||
}), for: .touchUpInside)
|
||||
|
||||
consoleView.addSubview(button)
|
||||
@@ -131,8 +131,8 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
// TODO: Snap to nearest position.
|
||||
|
||||
UserDefaults.standard.set(consoleSize.width, forKey: "LocalConsole_Width")
|
||||
UserDefaults.standard.set(consoleSize.height, forKey: "LocalConsole_Height")
|
||||
UserDefaults.standard.set(consoleSize.width, forKey: "LocalConsole.Width")
|
||||
UserDefaults.standard.set(consoleSize.height, forKey: "LocalConsole.Height")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,8 +267,8 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
lazy var initialViewLocation: CGPoint = .zero
|
||||
|
||||
func configureConsole() {
|
||||
consoleSize = CGSize(width: UserDefaults.standard.object(forKey: "LocalConsole_Width") as? CGFloat ?? consoleSize.width,
|
||||
height: UserDefaults.standard.object(forKey: "LocalConsole_Height") as? CGFloat ?? consoleSize.height)
|
||||
consoleSize = CGSize(width: UserDefaults.standard.object(forKey: "LocalConsole.Width") as? CGFloat ?? consoleSize.width,
|
||||
height: UserDefaults.standard.object(forKey: "LocalConsole.Height") as? CGFloat ?? consoleSize.height)
|
||||
|
||||
|
||||
consoleView.layer.shadowRadius = 16
|
||||
@@ -312,7 +312,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
let tapRecognizer = UITapStartEndGestureRecognizer(target: self, action: #selector(consolePiPTapStartEnd(recognizer:)))
|
||||
tapRecognizer.delegate = self
|
||||
|
||||
longPressRecognizer.minimumPressDuration = 0.1
|
||||
longPressRecognizer.minimumPressDuration = 0.3
|
||||
|
||||
consoleView.addGestureRecognizer(panRecognizer)
|
||||
consoleView.addGestureRecognizer(tapRecognizer)
|
||||
@@ -383,9 +383,11 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
.first
|
||||
|
||||
if let windowScene = windowScene as? UIWindowScene {
|
||||
|
||||
windowSceneFound = true
|
||||
|
||||
UIWindow.swizzleStatusBarAppearanceOverride()
|
||||
SwizzleTool().swizzleContextMenuReverseOrder()
|
||||
|
||||
consoleWindow = ConsoleWindow(windowScene: windowScene)
|
||||
consoleWindow?.frame = UIScreen.main.bounds
|
||||
consoleWindow?.windowLevel = UIWindow.Level.statusBar
|
||||
@@ -397,8 +399,6 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
viewController.view.addSubview(consoleView)
|
||||
|
||||
UIWindow.swizzleStatusBarAppearanceOverride
|
||||
|
||||
updateConsoleOrigin()
|
||||
}
|
||||
}
|
||||
@@ -424,8 +424,8 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
func snapToCachedEndpoint() {
|
||||
let cachedConsolePosition = CGPoint(x: UserDefaults.standard.object(forKey: "LocalConsole_X") as? CGFloat ?? possibleEndpoints.first!.x,
|
||||
y: UserDefaults.standard.object(forKey: "LocalConsole_Y") as? CGFloat ?? possibleEndpoints.first!.y)
|
||||
let cachedConsolePosition = CGPoint(x: UserDefaults.standard.object(forKey: "LocalConsole.X") as? CGFloat ?? possibleEndpoints.first!.x,
|
||||
y: UserDefaults.standard.object(forKey: "LocalConsole.Y") as? CGFloat ?? possibleEndpoints.first!.y)
|
||||
|
||||
consoleView.center = cachedConsolePosition // Update console center so possibleEndpoints are calculated correctly.
|
||||
consoleView.center = nearestTargetTo(cachedConsolePosition, possibleTargets: possibleEndpoints)
|
||||
@@ -637,15 +637,23 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
var dynamicReportTimer: Timer? {
|
||||
willSet { dynamicReportTimer?.invalidate() }
|
||||
willSet {
|
||||
timerInvalidationCounter = 0
|
||||
dynamicReportTimer?.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
var timerInvalidationCounter = 0
|
||||
|
||||
func systemReport() {
|
||||
DispatchQueue.main.async { [self] in
|
||||
|
||||
if currentText != "" { print("\n") }
|
||||
|
||||
dynamicReportTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
|
||||
|
||||
guard consoleTextView.panGestureRecognizer.numberOfTouches == 0 else { return }
|
||||
|
||||
var _currentText = currentText
|
||||
|
||||
// To optimize performance, only scan the last 2500 characters of text for system report changes.
|
||||
@@ -667,10 +675,19 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
if currentText != _currentText {
|
||||
currentText = _currentText
|
||||
|
||||
timerInvalidationCounter = 0
|
||||
|
||||
} else {
|
||||
|
||||
// Invalidate the timer if there is no longer anything to update.
|
||||
timer.invalidate()
|
||||
timerInvalidationCounter += 1
|
||||
|
||||
// It has been 2 seconds and values have not changed.
|
||||
if timerInvalidationCounter == 2 {
|
||||
|
||||
// Invalidate the timer if there is no longer anything to update.
|
||||
dynamicReportTimer = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,11 +759,16 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
consoleTextView.attributedText = NSAttributedString(string: string, attributes: attributes)
|
||||
}
|
||||
|
||||
// Displays all UserDefaults keys, including unneeded keys that are included by default.
|
||||
public var showAllUserDefaultsKeys = false
|
||||
|
||||
func makeMenu() -> UIMenu {
|
||||
|
||||
let copy = UIAction(title: "Copy",
|
||||
image: UIImage(systemName: "doc.on.doc"), handler: { _ in
|
||||
self.copy()
|
||||
let share = UIAction(title: "Share Text...",
|
||||
image: UIImage(systemName: "square.and.arrow.up"), handler: { _ in
|
||||
let activityViewController = UIActivityViewController(activityItems: [self.consoleTextView.text ?? ""],
|
||||
applicationActivities: nil)
|
||||
self.viewController.present(activityViewController, animated: true)
|
||||
})
|
||||
|
||||
let resize = UIAction(title: "Resize Console",
|
||||
@@ -773,10 +795,127 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
let consoleActions = UIMenu(title: "", options: .displayInline, children: [clear, resize])
|
||||
|
||||
var frameSymbol = "rectangle.3.offgrid"
|
||||
|
||||
var debugActions: [UIMenuElement] = []
|
||||
|
||||
if #available(iOS 15, *) {
|
||||
frameSymbol = "square.inset.filled"
|
||||
|
||||
let deferredUserDefaultsList = UIDeferredMenuElement.uncached { completion in
|
||||
var actions: [UIAction] = []
|
||||
|
||||
let keys: [String] = {
|
||||
|
||||
if self.showAllUserDefaultsKeys {
|
||||
return UserDefaults.standard.dictionaryRepresentation().map { $0.key }
|
||||
}
|
||||
|
||||
// Show keys the developer has added to the app (+ LocalConsole keys), excluding all of Apple's keys.
|
||||
if let bundle: String = Bundle.main.bundleIdentifier {
|
||||
let preferencePath: String = NSHomeDirectory() + "/Library/Preferences/\(bundle).plist"
|
||||
|
||||
let _keys = NSDictionary(contentsOfFile: preferencePath)?.allKeys as! [String]
|
||||
|
||||
return _keys.filter {
|
||||
!$0.contains("LocalConsole.")
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
}()
|
||||
|
||||
if keys.isEmpty {
|
||||
actions.append(UIAction(title: "No Entries",
|
||||
image: nil, attributes: .disabled, handler: { _ in }
|
||||
))
|
||||
} else {
|
||||
for key in keys.sorted(by: { $0.lowercased() < $1.lowercased() }) {
|
||||
|
||||
// Old LocalConsole Key Cleanup
|
||||
guard !key.contains("LocalConsole_") else {
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
continue
|
||||
}
|
||||
|
||||
if let value = UserDefaults.standard.value(forKey: key) {
|
||||
let action = UIAction(title: key, image: nil) { _ in
|
||||
let alertController = UIAlertController(title: key,
|
||||
message: nil,
|
||||
preferredStyle: .alert)
|
||||
|
||||
let headerParagraphStyle = NSMutableParagraphStyle()
|
||||
headerParagraphStyle.paragraphSpacing = 6
|
||||
let contentParagraphStyle = NSMutableParagraphStyle()
|
||||
|
||||
let attributes: [NSAttributedString.Key: Any] = [
|
||||
.paragraphStyle: contentParagraphStyle,
|
||||
.foregroundColor: UIColor.label,
|
||||
.font: UIFont.systemFont(ofSize: 13, weight: .semibold, design: .monospaced)
|
||||
]
|
||||
|
||||
let attributedTitle: NSMutableAttributedString = {
|
||||
|
||||
let attributedString = NSMutableAttributedString(string: "Key\n" + key, attributes: attributes)
|
||||
attributedString.addAttributes(
|
||||
[NSAttributedString.Key.foregroundColor : UIColor.label.withAlphaComponent(0.5),
|
||||
NSAttributedString.Key.paragraphStyle : headerParagraphStyle],
|
||||
range: NSRange(location: 0, length: 3))
|
||||
|
||||
return attributedString
|
||||
}()
|
||||
|
||||
let attributedMessage: NSMutableAttributedString = {
|
||||
|
||||
let attributedString = NSMutableAttributedString(string: "\nValue\n" + "\(value)", attributes: attributes)
|
||||
attributedString.addAttributes(
|
||||
[NSAttributedString.Key.foregroundColor : UIColor.label.withAlphaComponent(0.5),
|
||||
NSAttributedString.Key.paragraphStyle : headerParagraphStyle],
|
||||
range: NSRange(location: 0, length: 7))
|
||||
|
||||
return attributedString
|
||||
}()
|
||||
|
||||
alertController.setValue(attributedTitle, forKey: "attributedTitle")
|
||||
alertController.setValue(attributedMessage, forKey: "attributedMessage")
|
||||
|
||||
alertController.addAction(UIAlertAction(title: "Copy Value", style: .default, handler: { _ in
|
||||
UIPasteboard.general.string = "\(value)"
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: "Clear Value", style: .destructive, handler: { _ in
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
|
||||
}))
|
||||
|
||||
self.viewController.present(alertController,
|
||||
animated: true)
|
||||
}
|
||||
action.subtitle = "\(value)"
|
||||
actions.append(action)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
actions.append(
|
||||
UIAction(title: "Clear Defaults",
|
||||
image: UIImage(systemName: "trash"), attributes: .destructive, handler: { _ in
|
||||
keys.forEach {
|
||||
UserDefaults.standard.removeObject(forKey: $0)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
completion(actions)
|
||||
}
|
||||
|
||||
let userDefaults = UIMenu(title: "UserDefaults", image: UIImage(systemName: "doc.badge.gearshape"), children: [deferredUserDefaultsList])
|
||||
|
||||
debugActions.append(userDefaults)
|
||||
}
|
||||
|
||||
|
||||
let viewFrames = UIAction(title: debugBordersEnabled ? "Hide View Frames" : "Show View Frames",
|
||||
image: UIImage(systemName: frameSymbol), handler: { _ in
|
||||
self.debugBordersEnabled.toggle()
|
||||
@@ -840,22 +979,26 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
})
|
||||
|
||||
let debugActions = UIMenu(title: "", options: .displayInline,
|
||||
debugActions.append(contentsOf: [viewFrames, systemReport, displayReport, respring])
|
||||
|
||||
let debugMenu = UIMenu(title: "", options: .displayInline,
|
||||
children: [UIMenu(title: "Debug", image: UIImage(systemName: "ant"),
|
||||
children: [viewFrames, systemReport, displayReport, respring])])
|
||||
children: debugActions)])
|
||||
|
||||
var menuContent: [UIMenuElement] = []
|
||||
|
||||
if consoleTextView.text != "" {
|
||||
menuContent.append(contentsOf: [copy, consoleActions])
|
||||
menuContent.append(contentsOf: [share, consoleActions])
|
||||
} else {
|
||||
menuContent.append(resize)
|
||||
}
|
||||
menuContent.append(debugActions)
|
||||
menuContent.append(debugMenu)
|
||||
|
||||
return UIMenu(title: "", children: menuContent)
|
||||
}
|
||||
|
||||
var consolePiPPopAnimator: UIViewPropertyAnimator?
|
||||
|
||||
@objc func longPressAction(recognizer: UILongPressGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
@@ -866,8 +1009,12 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
scrollLocked = false
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
consolePiPPopAnimator = UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
consoleView.transform = .init(scaleX: 1.04, y: 1.04)
|
||||
}
|
||||
consolePiPPopAnimator?.startAnimation()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
consoleTextView.alpha = 0.5
|
||||
menuButton.alpha = 0.5
|
||||
}.startAnimation()
|
||||
@@ -875,7 +1022,7 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
if !grabberMode { scrollLocked = true }
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.8, dampingRatio: 0.5) { [self] in
|
||||
UIViewPropertyAnimator(duration: 0.8, dampingRatio: 0.6) { [self] in
|
||||
consoleView.transform = .identity
|
||||
}.startAnimation()
|
||||
|
||||
@@ -902,7 +1049,10 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
initialViewLocation = consoleView.center
|
||||
}
|
||||
|
||||
guard !scrollLocked else { return }
|
||||
guard !scrollLocked else {
|
||||
isPressed = false
|
||||
return
|
||||
}
|
||||
|
||||
let translation = recognizer.translation(in: consoleView.superview)
|
||||
let velocity = recognizer.velocity(in: consoleView.superview)
|
||||
@@ -948,8 +1098,8 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
positionAnimator.startAnimation()
|
||||
|
||||
UserDefaults.standard.set(nearestTargetPosition.x, forKey: "LocalConsole_X")
|
||||
UserDefaults.standard.set(nearestTargetPosition.y, forKey: "LocalConsole_Y")
|
||||
UserDefaults.standard.set(nearestTargetPosition.x, forKey: "LocalConsole.X")
|
||||
UserDefaults.standard.set(nearestTargetPosition.y, forKey: "LocalConsole.Y")
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
|
||||
self.reassessGrabberMode()
|
||||
@@ -968,29 +1118,37 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// Animate touch down.
|
||||
func consolePiPTouchDown() {
|
||||
guard !grabberMode else { return }
|
||||
|
||||
UIViewPropertyAnimator(duration: 1.25, dampingRatio: 0.5) { [self] in
|
||||
consoleView.transform = .init(scaleX: 0.95, y: 0.95)
|
||||
}.startAnimation()
|
||||
}
|
||||
var consolePiPTouchDownAnimator: UIViewPropertyAnimator?
|
||||
|
||||
// Animate touch up.
|
||||
func consolePiPTouchUp() {
|
||||
UIViewPropertyAnimator(duration: 0.8, dampingRatio: 0.4) { [self] in
|
||||
consoleView.transform = .init(scaleX: 1, y: 1)
|
||||
}.startAnimation()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
if !grabberMode {
|
||||
consoleTextView.alpha = 1
|
||||
if !ResizeController.shared.isActive {
|
||||
menuButton.alpha = 1
|
||||
var isPressed: Bool = false {
|
||||
didSet {
|
||||
guard oldValue != isPressed else { return }
|
||||
|
||||
if isPressed {
|
||||
guard !grabberMode else { return }
|
||||
|
||||
consolePiPTouchDownAnimator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { [self] in
|
||||
consoleView.transform = .init(scaleX: 0.96, y: 0.96)
|
||||
}
|
||||
consolePiPTouchDownAnimator?.startAnimation(afterDelay: 0.1)
|
||||
} else {
|
||||
consolePiPTouchDownAnimator?.stopAnimation(true)
|
||||
consolePiPPopAnimator?.stopAnimation(true)
|
||||
|
||||
UIViewPropertyAnimator(duration: scrollLocked ? 0.4 : 0.7, dampingRatio: scrollLocked ? 1 : 0.45) { [self] in
|
||||
consoleView.transform = .identity
|
||||
}.startAnimation()
|
||||
|
||||
UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in
|
||||
if !grabberMode {
|
||||
consoleTextView.alpha = 1
|
||||
if !ResizeController.shared.isActive {
|
||||
menuButton.alpha = 1
|
||||
}
|
||||
}
|
||||
}.startAnimation()
|
||||
}
|
||||
}.startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
// Simulataneously listen to all gesture recognizers.
|
||||
@@ -1001,11 +1159,11 @@ public class LCManager: NSObject, UIGestureRecognizerDelegate {
|
||||
@objc func consolePiPTapStartEnd(recognizer: UITapStartEndGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
consolePiPTouchDown()
|
||||
isPressed = true
|
||||
case .changed:
|
||||
break
|
||||
case .ended, .cancelled, .possible, .failed:
|
||||
consolePiPTouchUp()
|
||||
isPressed = false
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
@@ -1072,21 +1230,49 @@ extension UIView {
|
||||
extension UIWindow {
|
||||
|
||||
/// Make sure this window does not have control over the status bar appearance.
|
||||
static let swizzleStatusBarAppearanceOverride: Void = {
|
||||
guard let originalMethod = class_getInstanceMethod(UIWindow.self, NSSelectorFromString("_can" + "Affect" + "Sta" + "tus" + "Bar" + "Appe" + "arance")),
|
||||
static func swizzleStatusBarAppearanceOverride() {
|
||||
guard let originalMethod = class_getInstanceMethod(UIWindow.self, NSSelectorFromString("_can" + "Affect" + "Status" + "Bar" + "Appearance")),
|
||||
let swizzledMethod = class_getInstanceMethod(UIWindow.self, #selector(swizzled_statusBarAppearance))
|
||||
else { return }
|
||||
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod)
|
||||
}()
|
||||
}
|
||||
|
||||
@objc func swizzled_statusBarAppearance() -> Bool {
|
||||
return isKeyWindow
|
||||
}
|
||||
}
|
||||
|
||||
class SwizzleTool: NSObject {
|
||||
|
||||
/// Ensure context menus always show in a non reversed order.
|
||||
func swizzleContextMenuReverseOrder() {
|
||||
guard let originalMethod = class_getInstanceMethod(NSClassFromString("_" + "UI" + "Context" + "Menu" + "List" + "View").self, NSSelectorFromString("reverses" + "Action" + "Order")),
|
||||
let swizzledMethod = class_getInstanceMethod(SwizzleTool.self, #selector(swizzled_reverses_Action_Order))
|
||||
else { Swift.print("Swizzle Error Occurred"); return }
|
||||
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod)
|
||||
}
|
||||
|
||||
@objc func swizzled_reverses_Action_Order() -> Bool {
|
||||
|
||||
if let menu = self.value(forKey: "displayed" + "Menu") as? UIMenu,
|
||||
menu.title == "Debug" || menu.title == "User" + "Defaults" {
|
||||
return false
|
||||
}
|
||||
|
||||
if let orig = self.value(forKey: "_" + "reverses" + "Action" + "Order") as? Bool {
|
||||
return orig
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LumaView: UIView {
|
||||
lazy var visualEffectView: UIView = {
|
||||
Bundle(path: "/Sys" + "tem/Lib" + "rary/Private" + "Frameworks/Material" + "Kit." + "framework")!.load()
|
||||
Bundle(path: "/Sys" + "tem/Lib" + "rary/Private" + "Framework" + "s/Material" + "Kit." + "framework")!.load()
|
||||
|
||||
let Pill = NSClassFromString("MT" + "Luma" + "Dodge" + "Pill" + "View") as! UIView.Type
|
||||
|
||||
@@ -1218,7 +1404,6 @@ fileprivate func _debugPrint(_ items: Any) {
|
||||
// Support for auto-rotate.
|
||||
class ConsoleViewController: UIViewController {
|
||||
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
|
||||
// Cancel the panner console is being panned to allow for location manipulation.
|
||||
|
||||
Reference in New Issue
Block a user