cleanup
This commit is contained in:
@@ -431,13 +431,14 @@
|
|||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = Gaze/Info.plist;
|
INFOPLIST_FILE = Gaze/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Gaze;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
MARKETING_VERSION = 0.4.1;
|
MARKETING_VERSION = 0.4.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.Gaze;
|
PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.Gaze;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -467,13 +468,14 @@
|
|||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = Gaze/Info.plist;
|
INFOPLIST_FILE = Gaze/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Gaze;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||||
MARKETING_VERSION = 0.4.1;
|
MARKETING_VERSION = 0.4.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.Gaze;
|
PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.Gaze;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// Thread-safe configuration holder for eye tracking thresholds.
|
/// Thread-safe configuration holder for eye tracking thresholds.
|
||||||
enum EyeTrackingConstants {
|
/// All properties are Sendable constants, safe for use in any concurrency context.
|
||||||
|
enum EyeTrackingConstants: Sendable {
|
||||||
// MARK: - Logging
|
// MARK: - Logging
|
||||||
/// Interval between log messages in seconds
|
/// Interval between log messages in seconds
|
||||||
static let logInterval: TimeInterval = 0.5
|
static let logInterval: TimeInterval = 0.5
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ struct GazeApp: App {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
OnboardingContainerView(settingsManager: settingsManager)
|
OnboardingContainerView(settingsManager: settingsManager)
|
||||||
.onChange(of: settingsManager.settings.hasCompletedOnboarding) {
|
.onChange(of: settingsManager.settings.hasCompletedOnboarding) { _, completed in
|
||||||
completed in
|
|
||||||
if completed {
|
if completed {
|
||||||
closeAllWindows()
|
closeAllWindows()
|
||||||
appDelegate.onboardingCompleted()
|
appDelegate.onboardingCompleted()
|
||||||
|
|||||||
@@ -68,6 +68,24 @@ class EyeTrackingService: NSObject, ObservableObject {
|
|||||||
var debugRightPupilRatio: Double?
|
var debugRightPupilRatio: Double?
|
||||||
var debugYaw: Double?
|
var debugYaw: Double?
|
||||||
var debugPitch: Double?
|
var debugPitch: Double?
|
||||||
|
|
||||||
|
nonisolated init(
|
||||||
|
faceDetected: Bool = false,
|
||||||
|
isEyesClosed: Bool = false,
|
||||||
|
userLookingAtScreen: Bool = true,
|
||||||
|
debugLeftPupilRatio: Double? = nil,
|
||||||
|
debugRightPupilRatio: Double? = nil,
|
||||||
|
debugYaw: Double? = nil,
|
||||||
|
debugPitch: Double? = nil
|
||||||
|
) {
|
||||||
|
self.faceDetected = faceDetected
|
||||||
|
self.isEyesClosed = isEyesClosed
|
||||||
|
self.userLookingAtScreen = userLookingAtScreen
|
||||||
|
self.debugLeftPupilRatio = debugLeftPupilRatio
|
||||||
|
self.debugRightPupilRatio = debugRightPupilRatio
|
||||||
|
self.debugYaw = debugYaw
|
||||||
|
self.debugPitch = debugPitch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startEyeTracking() async throws {
|
func startEyeTracking() async throws {
|
||||||
@@ -277,6 +295,20 @@ class EyeTrackingService: NSObject, ObservableObject {
|
|||||||
var rightPupilRatio: Double?
|
var rightPupilRatio: Double?
|
||||||
var yaw: Double?
|
var yaw: Double?
|
||||||
var pitch: Double?
|
var pitch: Double?
|
||||||
|
|
||||||
|
nonisolated init(
|
||||||
|
lookingAway: Bool = false,
|
||||||
|
leftPupilRatio: Double? = nil,
|
||||||
|
rightPupilRatio: Double? = nil,
|
||||||
|
yaw: Double? = nil,
|
||||||
|
pitch: Double? = nil
|
||||||
|
) {
|
||||||
|
self.lookingAway = lookingAway
|
||||||
|
self.leftPupilRatio = leftPupilRatio
|
||||||
|
self.rightPupilRatio = rightPupilRatio
|
||||||
|
self.yaw = yaw
|
||||||
|
self.pitch = pitch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Non-isolated gaze direction detection
|
/// Non-isolated gaze direction detection
|
||||||
|
|||||||
@@ -71,16 +71,23 @@ final class FullscreenDetectionService: ObservableObject {
|
|||||||
private let permissionManager: ScreenCapturePermissionManaging
|
private let permissionManager: ScreenCapturePermissionManaging
|
||||||
private let environmentProvider: FullscreenEnvironmentProviding
|
private let environmentProvider: FullscreenEnvironmentProviding
|
||||||
|
|
||||||
// This initializer is only for use within main actor contexts
|
|
||||||
init(
|
init(
|
||||||
permissionManager: ScreenCapturePermissionManaging = ScreenCapturePermissionManager.shared,
|
permissionManager: ScreenCapturePermissionManaging,
|
||||||
environmentProvider: FullscreenEnvironmentProviding = SystemFullscreenEnvironmentProvider()
|
environmentProvider: FullscreenEnvironmentProviding
|
||||||
) {
|
) {
|
||||||
self.permissionManager = permissionManager
|
self.permissionManager = permissionManager
|
||||||
self.environmentProvider = environmentProvider
|
self.environmentProvider = environmentProvider
|
||||||
setupObservers()
|
setupObservers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience initializer using default services
|
||||||
|
convenience init() {
|
||||||
|
self.init(
|
||||||
|
permissionManager: ScreenCapturePermissionManager.shared,
|
||||||
|
environmentProvider: SystemFullscreenEnvironmentProvider()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Factory method to safely create instances from non-main actor contexts
|
// Factory method to safely create instances from non-main actor contexts
|
||||||
static func create(
|
static func create(
|
||||||
permissionManager: ScreenCapturePermissionManaging? = nil,
|
permissionManager: ScreenCapturePermissionManaging? = nil,
|
||||||
@@ -109,8 +116,10 @@ final class FullscreenDetectionService: ObservableObject {
|
|||||||
object: workspace,
|
object: workspace,
|
||||||
queue: .main
|
queue: .main
|
||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
|
Task { @MainActor in
|
||||||
self?.checkFullscreenState()
|
self?.checkFullscreenState()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
observers.append(spaceObserver)
|
observers.append(spaceObserver)
|
||||||
|
|
||||||
let transitionObserver = notificationCenter.addObserver(
|
let transitionObserver = notificationCenter.addObserver(
|
||||||
@@ -118,8 +127,10 @@ final class FullscreenDetectionService: ObservableObject {
|
|||||||
object: nil,
|
object: nil,
|
||||||
queue: .main
|
queue: .main
|
||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
|
Task { @MainActor in
|
||||||
self?.checkFullscreenState()
|
self?.checkFullscreenState()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
observers.append(transitionObserver)
|
observers.append(transitionObserver)
|
||||||
|
|
||||||
let fullscreenObserver = notificationCenter.addObserver(
|
let fullscreenObserver = notificationCenter.addObserver(
|
||||||
@@ -127,8 +138,10 @@ final class FullscreenDetectionService: ObservableObject {
|
|||||||
object: nil,
|
object: nil,
|
||||||
queue: .main
|
queue: .main
|
||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
|
Task { @MainActor in
|
||||||
self?.checkFullscreenState()
|
self?.checkFullscreenState()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
observers.append(fullscreenObserver)
|
observers.append(fullscreenObserver)
|
||||||
|
|
||||||
let exitFullscreenObserver = notificationCenter.addObserver(
|
let exitFullscreenObserver = notificationCenter.addObserver(
|
||||||
@@ -136,8 +149,10 @@ final class FullscreenDetectionService: ObservableObject {
|
|||||||
object: nil,
|
object: nil,
|
||||||
queue: .main
|
queue: .main
|
||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
|
Task { @MainActor in
|
||||||
self?.checkFullscreenState()
|
self?.checkFullscreenState()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
observers.append(exitFullscreenObserver)
|
observers.append(exitFullscreenObserver)
|
||||||
|
|
||||||
frontmostAppObserver = NotificationCenter.default.publisher(
|
frontmostAppObserver = NotificationCenter.default.publisher(
|
||||||
|
|||||||
@@ -33,8 +33,9 @@ class IdleMonitoringService: ObservableObject {
|
|||||||
|
|
||||||
private func startMonitoring() {
|
private func startMonitoring() {
|
||||||
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
self?.checkIdleState()
|
self.checkIdleState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,13 +38,13 @@ final class PupilCalibration: @unchecked Sendable {
|
|||||||
private var thresholdsLeft: [Int] = []
|
private var thresholdsLeft: [Int] = []
|
||||||
private var thresholdsRight: [Int] = []
|
private var thresholdsRight: [Int] = []
|
||||||
|
|
||||||
var isComplete: Bool {
|
nonisolated var isComplete: Bool {
|
||||||
lock.lock()
|
lock.lock()
|
||||||
defer { lock.unlock() }
|
defer { lock.unlock() }
|
||||||
return thresholdsLeft.count >= targetFrames && thresholdsRight.count >= targetFrames
|
return thresholdsLeft.count >= targetFrames && thresholdsRight.count >= targetFrames
|
||||||
}
|
}
|
||||||
|
|
||||||
func threshold(forSide side: Int) -> Int {
|
nonisolated func threshold(forSide side: Int) -> Int {
|
||||||
lock.lock()
|
lock.lock()
|
||||||
defer { lock.unlock() }
|
defer { lock.unlock() }
|
||||||
let thresholds = side == 0 ? thresholdsLeft : thresholdsRight
|
let thresholds = side == 0 ? thresholdsLeft : thresholdsRight
|
||||||
@@ -52,7 +52,7 @@ final class PupilCalibration: @unchecked Sendable {
|
|||||||
return thresholds.reduce(0, +) / thresholds.count
|
return thresholds.reduce(0, +) / thresholds.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluate(eyeData: UnsafePointer<UInt8>, width: Int, height: Int, side: Int) {
|
nonisolated func evaluate(eyeData: UnsafePointer<UInt8>, width: Int, height: Int, side: Int) {
|
||||||
let bestThreshold = findBestThreshold(eyeData: eyeData, width: width, height: height)
|
let bestThreshold = findBestThreshold(eyeData: eyeData, width: width, height: height)
|
||||||
lock.lock()
|
lock.lock()
|
||||||
defer { lock.unlock() }
|
defer { lock.unlock() }
|
||||||
@@ -63,7 +63,7 @@ final class PupilCalibration: @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func findBestThreshold(eyeData: UnsafePointer<UInt8>, width: Int, height: Int) -> Int {
|
private nonisolated func findBestThreshold(eyeData: UnsafePointer<UInt8>, width: Int, height: Int) -> Int {
|
||||||
let averageIrisSize = 0.48
|
let averageIrisSize = 0.48
|
||||||
var bestThreshold = 50
|
var bestThreshold = 50
|
||||||
var bestDiff = Double.greatestFiniteMagnitude
|
var bestDiff = Double.greatestFiniteMagnitude
|
||||||
@@ -91,7 +91,7 @@ final class PupilCalibration: @unchecked Sendable {
|
|||||||
return bestThreshold
|
return bestThreshold
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func irisSize(data: UnsafePointer<UInt8>, width: Int, height: Int) -> Double {
|
private nonisolated static func irisSize(data: UnsafePointer<UInt8>, width: Int, height: Int) -> Double {
|
||||||
let margin = 5
|
let margin = 5
|
||||||
guard width > margin * 2, height > margin * 2 else { return 0 }
|
guard width > margin * 2, height > margin * 2 else { return 0 }
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ final class PupilCalibration: @unchecked Sendable {
|
|||||||
return totalCount > 0 ? Double(blackCount) / Double(totalCount) : 0
|
return totalCount > 0 ? Double(blackCount) / Double(totalCount) : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset() {
|
nonisolated func reset() {
|
||||||
lock.lock()
|
lock.lock()
|
||||||
defer { lock.unlock() }
|
defer { lock.unlock() }
|
||||||
thresholdsLeft.removeAll()
|
thresholdsLeft.removeAll()
|
||||||
@@ -143,46 +143,46 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
|
|
||||||
// MARK: - Configuration
|
// MARK: - Configuration
|
||||||
|
|
||||||
static var enableDebugImageSaving = false
|
nonisolated(unsafe) static var enableDebugImageSaving = false
|
||||||
static var enablePerformanceLogging = false
|
nonisolated(unsafe) static var enablePerformanceLogging = false
|
||||||
static var frameSkipCount = 10 // Process every Nth frame
|
nonisolated(unsafe) static var frameSkipCount = 10 // Process every Nth frame
|
||||||
|
|
||||||
// MARK: - State (protected by lock)
|
// MARK: - State (protected by lock)
|
||||||
|
|
||||||
private static var _debugImageCounter = 0
|
private nonisolated(unsafe) static var _debugImageCounter = 0
|
||||||
private static var _frameCounter = 0
|
private nonisolated(unsafe) static var _frameCounter = 0
|
||||||
private static var _lastPupilPositions: (left: PupilPosition?, right: PupilPosition?) = (
|
private nonisolated(unsafe) static var _lastPupilPositions: (left: PupilPosition?, right: PupilPosition?) = (
|
||||||
nil, nil
|
nil, nil
|
||||||
)
|
)
|
||||||
private static var _metrics = PupilDetectorMetrics()
|
private nonisolated(unsafe) static var _metrics = PupilDetectorMetrics()
|
||||||
|
|
||||||
static let calibration = PupilCalibration()
|
nonisolated(unsafe) static let calibration = PupilCalibration()
|
||||||
|
|
||||||
// MARK: - Convenience Properties
|
// MARK: - Convenience Properties
|
||||||
|
|
||||||
private static var debugImageCounter: Int {
|
private nonisolated static var debugImageCounter: Int {
|
||||||
get { _debugImageCounter }
|
get { _debugImageCounter }
|
||||||
set { _debugImageCounter = newValue }
|
set { _debugImageCounter = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static var frameCounter: Int {
|
private nonisolated static var frameCounter: Int {
|
||||||
get { _frameCounter }
|
get { _frameCounter }
|
||||||
set { _frameCounter = newValue }
|
set { _frameCounter = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static var lastPupilPositions: (left: PupilPosition?, right: PupilPosition?) {
|
private nonisolated static var lastPupilPositions: (left: PupilPosition?, right: PupilPosition?) {
|
||||||
get { _lastPupilPositions }
|
get { _lastPupilPositions }
|
||||||
set { _lastPupilPositions = newValue }
|
set { _lastPupilPositions = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static var metrics: PupilDetectorMetrics {
|
private nonisolated static var metrics: PupilDetectorMetrics {
|
||||||
get { _metrics }
|
get { _metrics }
|
||||||
set { _metrics = newValue }
|
set { _metrics = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Precomputed Tables
|
// MARK: - Precomputed Tables
|
||||||
|
|
||||||
private static let spatialWeightsLUT: [[Float]] = {
|
private nonisolated(unsafe) static let spatialWeightsLUT: [[Float]] = {
|
||||||
let d = 10
|
let d = 10
|
||||||
let radius = d / 2
|
let radius = d / 2
|
||||||
let sigmaSpace: Float = 15.0
|
let sigmaSpace: Float = 15.0
|
||||||
@@ -197,7 +197,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
return weights
|
return weights
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private static let colorWeightsLUT: [Float] = {
|
private nonisolated(unsafe) static let colorWeightsLUT: [Float] = {
|
||||||
let sigmaColor: Float = 15.0
|
let sigmaColor: Float = 15.0
|
||||||
var lut = [Float](repeating: 0, count: 256)
|
var lut = [Float](repeating: 0, count: 256)
|
||||||
for diff in 0..<256 {
|
for diff in 0..<256 {
|
||||||
@@ -209,12 +209,12 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
|
|
||||||
// MARK: - Reusable Buffers
|
// MARK: - Reusable Buffers
|
||||||
|
|
||||||
private static var grayscaleBuffer: UnsafeMutablePointer<UInt8>?
|
private nonisolated(unsafe) static var grayscaleBuffer: UnsafeMutablePointer<UInt8>?
|
||||||
private static var grayscaleBufferSize = 0
|
private nonisolated(unsafe) static var grayscaleBufferSize = 0
|
||||||
private static var eyeBuffer: UnsafeMutablePointer<UInt8>?
|
private nonisolated(unsafe) static var eyeBuffer: UnsafeMutablePointer<UInt8>?
|
||||||
private static var eyeBufferSize = 0
|
private nonisolated(unsafe) static var eyeBufferSize = 0
|
||||||
private static var tempBuffer: UnsafeMutablePointer<UInt8>?
|
private nonisolated(unsafe) static var tempBuffer: UnsafeMutablePointer<UInt8>?
|
||||||
private static var tempBufferSize = 0
|
private nonisolated(unsafe) static var tempBufferSize = 0
|
||||||
|
|
||||||
// MARK: - Public API
|
// MARK: - Public API
|
||||||
|
|
||||||
@@ -369,7 +369,9 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
|
|
||||||
// MARK: - Buffer Management
|
// MARK: - Buffer Management
|
||||||
|
|
||||||
private static func ensureBufferCapacity(frameSize: Int, eyeSize: Int) {
|
// MARK: - Buffer Management
|
||||||
|
|
||||||
|
private nonisolated static func ensureBufferCapacity(frameSize: Int, eyeSize: Int) {
|
||||||
if grayscaleBufferSize < frameSize {
|
if grayscaleBufferSize < frameSize {
|
||||||
grayscaleBuffer?.deallocate()
|
grayscaleBuffer?.deallocate()
|
||||||
grayscaleBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: frameSize)
|
grayscaleBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: frameSize)
|
||||||
@@ -388,7 +390,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
|
|
||||||
// MARK: - Optimized Grayscale Conversion (vImage)
|
// MARK: - Optimized Grayscale Conversion (vImage)
|
||||||
|
|
||||||
private static func extractGrayscaleDataOptimized(
|
private nonisolated static func extractGrayscaleDataOptimized(
|
||||||
from pixelBuffer: CVPixelBuffer,
|
from pixelBuffer: CVPixelBuffer,
|
||||||
to output: UnsafeMutablePointer<UInt8>,
|
to output: UnsafeMutablePointer<UInt8>,
|
||||||
width: Int,
|
width: Int,
|
||||||
@@ -476,7 +478,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
|
|
||||||
// MARK: - Optimized Eye Isolation
|
// MARK: - Optimized Eye Isolation
|
||||||
|
|
||||||
private static func isolateEyeWithMaskOptimized(
|
private nonisolated static func isolateEyeWithMaskOptimized(
|
||||||
frameData: UnsafePointer<UInt8>,
|
frameData: UnsafePointer<UInt8>,
|
||||||
frameWidth: Int,
|
frameWidth: Int,
|
||||||
frameHeight: Int,
|
frameHeight: Int,
|
||||||
@@ -526,7 +528,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
private static func pointInPolygonFast(
|
private nonisolated static func pointInPolygonFast(
|
||||||
px: Float, py: Float, edges: [(x1: Float, y1: Float, x2: Float, y2: Float)]
|
px: Float, py: Float, edges: [(x1: Float, y1: Float, x2: Float, y2: Float)]
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
var inside = false
|
var inside = false
|
||||||
@@ -542,7 +544,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
|
|
||||||
// MARK: - Optimized Image Processing
|
// MARK: - Optimized Image Processing
|
||||||
|
|
||||||
static func imageProcessingOptimized(
|
nonisolated static func imageProcessingOptimized(
|
||||||
input: UnsafePointer<UInt8>,
|
input: UnsafePointer<UInt8>,
|
||||||
output: UnsafeMutablePointer<UInt8>,
|
output: UnsafeMutablePointer<UInt8>,
|
||||||
width: Int,
|
width: Int,
|
||||||
@@ -569,7 +571,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func gaussianBlurOptimized(
|
private nonisolated static func gaussianBlurOptimized(
|
||||||
input: UnsafePointer<UInt8>,
|
input: UnsafePointer<UInt8>,
|
||||||
output: UnsafeMutablePointer<UInt8>,
|
output: UnsafeMutablePointer<UInt8>,
|
||||||
width: Int,
|
width: Int,
|
||||||
@@ -607,7 +609,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func erodeOptimized(
|
private nonisolated static func erodeOptimized(
|
||||||
input: UnsafePointer<UInt8>,
|
input: UnsafePointer<UInt8>,
|
||||||
output: UnsafeMutablePointer<UInt8>,
|
output: UnsafeMutablePointer<UInt8>,
|
||||||
width: Int,
|
width: Int,
|
||||||
@@ -668,7 +670,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
|
|
||||||
/// Optimized centroid-of-dark-pixels approach - much faster than union-find
|
/// Optimized centroid-of-dark-pixels approach - much faster than union-find
|
||||||
/// Returns the centroid of the largest dark region
|
/// Returns the centroid of the largest dark region
|
||||||
private static func findPupilFromContoursOptimized(
|
private nonisolated static func findPupilFromContoursOptimized(
|
||||||
data: UnsafePointer<UInt8>,
|
data: UnsafePointer<UInt8>,
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int
|
height: Int
|
||||||
@@ -722,7 +724,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
|
|
||||||
// MARK: - Helper Methods
|
// MARK: - Helper Methods
|
||||||
|
|
||||||
private static func landmarksToPixelCoordinates(
|
private nonisolated static func landmarksToPixelCoordinates(
|
||||||
landmarks: VNFaceLandmarkRegion2D,
|
landmarks: VNFaceLandmarkRegion2D,
|
||||||
faceBoundingBox: CGRect,
|
faceBoundingBox: CGRect,
|
||||||
imageSize: CGSize
|
imageSize: CGSize
|
||||||
@@ -736,7 +738,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func createEyeRegion(from points: [CGPoint], imageSize: CGSize) -> EyeRegion? {
|
private nonisolated static func createEyeRegion(from points: [CGPoint], imageSize: CGSize) -> EyeRegion? {
|
||||||
guard !points.isEmpty else { return nil }
|
guard !points.isEmpty else { return nil }
|
||||||
|
|
||||||
let margin: CGFloat = 5
|
let margin: CGFloat = 5
|
||||||
@@ -777,7 +779,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
|
|
||||||
// MARK: - Debug Helpers
|
// MARK: - Debug Helpers
|
||||||
|
|
||||||
private static func saveDebugImage(
|
private nonisolated static func saveDebugImage(
|
||||||
data: UnsafePointer<UInt8>, width: Int, height: Int, name: String
|
data: UnsafePointer<UInt8>, width: Int, height: Int, name: String
|
||||||
) {
|
) {
|
||||||
guard let cgImage = createCGImage(from: data, width: width, height: height) else { return }
|
guard let cgImage = createCGImage(from: data, width: width, height: height) else { return }
|
||||||
@@ -793,7 +795,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
print("💾 Saved debug image: \(url.path)")
|
print("💾 Saved debug image: \(url.path)")
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func createCGImage(from data: UnsafePointer<UInt8>, width: Int, height: Int)
|
private nonisolated static func createCGImage(from data: UnsafePointer<UInt8>, width: Int, height: Int)
|
||||||
-> CGImage?
|
-> CGImage?
|
||||||
{
|
{
|
||||||
let mutableData = UnsafeMutablePointer<UInt8>.allocate(capacity: width * height)
|
let mutableData = UnsafeMutablePointer<UInt8>.allocate(capacity: width * height)
|
||||||
@@ -817,7 +819,7 @@ final class PupilDetector: @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Clean up allocated buffers (call on app termination if needed)
|
/// Clean up allocated buffers (call on app termination if needed)
|
||||||
static func cleanup() {
|
nonisolated static func cleanup() {
|
||||||
grayscaleBuffer?.deallocate()
|
grayscaleBuffer?.deallocate()
|
||||||
grayscaleBuffer = nil
|
grayscaleBuffer = nil
|
||||||
grayscaleBufferSize = 0
|
grayscaleBufferSize = 0
|
||||||
|
|||||||
@@ -111,7 +111,12 @@ final class ServiceContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new container configured for testing with default mock settings
|
/// Creates a new container configured for testing with default mock settings
|
||||||
static func forTesting(settings: AppSettings = .defaults) -> ServiceContainer {
|
static func forTesting() -> ServiceContainer {
|
||||||
|
forTesting(settings: AppSettings())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new container configured for testing with custom settings
|
||||||
|
static func forTesting(settings: AppSettings) -> ServiceContainer {
|
||||||
let mockSettings = MockSettingsManager(settings: settings)
|
let mockSettings = MockSettingsManager(settings: settings)
|
||||||
return ServiceContainer(settingsManager: mockSettings)
|
return ServiceContainer(settingsManager: mockSettings)
|
||||||
}
|
}
|
||||||
@@ -138,7 +143,11 @@ final class MockSettingsManager: SettingsProviding {
|
|||||||
.posture: \.postureTimer,
|
.posture: \.postureTimer,
|
||||||
]
|
]
|
||||||
|
|
||||||
init(settings: AppSettings = .defaults) {
|
convenience init() {
|
||||||
|
self.init(settings: AppSettings())
|
||||||
|
}
|
||||||
|
|
||||||
|
init(settings: AppSettings) {
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self._settingsSubject = CurrentValueSubject(settings)
|
self._settingsSubject = CurrentValueSubject(settings)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,21 @@ class TimerEngine: ObservableObject {
|
|||||||
// Logging manager
|
// Logging manager
|
||||||
private let logger = LoggingManager.shared.timerLogger
|
private let logger = LoggingManager.shared.timerLogger
|
||||||
|
|
||||||
|
convenience init(
|
||||||
|
settingsManager: any SettingsProviding,
|
||||||
|
enforceModeService: EnforceModeService? = nil
|
||||||
|
) {
|
||||||
|
self.init(
|
||||||
|
settingsManager: settingsManager,
|
||||||
|
enforceModeService: enforceModeService,
|
||||||
|
timeProvider: SystemTimeProvider()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
init(
|
init(
|
||||||
settingsManager: any SettingsProviding,
|
settingsManager: any SettingsProviding,
|
||||||
enforceModeService: EnforceModeService? = nil,
|
enforceModeService: EnforceModeService?,
|
||||||
timeProvider: TimeProviding = SystemTimeProvider()
|
timeProvider: TimeProviding
|
||||||
) {
|
) {
|
||||||
self.settingsProvider = settingsManager
|
self.settingsProvider = settingsManager
|
||||||
self.enforceModeService = enforceModeService ?? EnforceModeService.shared
|
self.enforceModeService = enforceModeService ?? EnforceModeService.shared
|
||||||
|
|||||||
@@ -75,8 +75,9 @@ class UsageTrackingService: ObservableObject {
|
|||||||
|
|
||||||
private func startTracking() {
|
private func startTracking() {
|
||||||
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
self?.tick()
|
self.tick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,8 +86,10 @@ final class OnboardingWindowPresenter {
|
|||||||
object: window,
|
object: window,
|
||||||
queue: .main
|
queue: .main
|
||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
|
Task { @MainActor in
|
||||||
self?.windowController = nil
|
self?.windowController = nil
|
||||||
self?.removeCloseObserver()
|
self?.removeCloseObserver()
|
||||||
|
}
|
||||||
NotificationCenter.default.post(name: Notification.Name("OnboardingWindowDidClose"), object: nil)
|
NotificationCenter.default.post(name: Notification.Name("OnboardingWindowDidClose"), object: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ struct UserTimerEditSheet: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.segmented)
|
.pickerStyle(.segmented)
|
||||||
.onChange(of: type) { newType in
|
.onChange(of: type) { _, newType in
|
||||||
if newType == .subtle {
|
if newType == .subtle {
|
||||||
timeOnScreen = 3
|
timeOnScreen = 3
|
||||||
} else if timeOnScreen == 3 {
|
} else if timeOnScreen == 3 {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ final class ServiceContainerTests: XCTestCase {
|
|||||||
let mockSettings = EnhancedMockSettingsManager(settings: .shortIntervals)
|
let mockSettings = EnhancedMockSettingsManager(settings: .shortIntervals)
|
||||||
let customEngine = TimerEngine(
|
let customEngine = TimerEngine(
|
||||||
settingsManager: mockSettings,
|
settingsManager: mockSettings,
|
||||||
|
enforceModeService: nil,
|
||||||
timeProvider: MockTimeProvider()
|
timeProvider: MockTimeProvider()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ final class TimerEngineTests: XCTestCase {
|
|||||||
let timeProvider = MockTimeProvider()
|
let timeProvider = MockTimeProvider()
|
||||||
let engine = TimerEngine(
|
let engine = TimerEngine(
|
||||||
settingsManager: testEnv.settingsManager,
|
settingsManager: testEnv.settingsManager,
|
||||||
|
enforceModeService: nil,
|
||||||
timeProvider: timeProvider
|
timeProvider: timeProvider
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ final class TimerEngineTestabilityTests: XCTestCase {
|
|||||||
let timeProvider = MockTimeProvider()
|
let timeProvider = MockTimeProvider()
|
||||||
let timerEngine = TimerEngine(
|
let timerEngine = TimerEngine(
|
||||||
settingsManager: testEnv.settingsManager,
|
settingsManager: testEnv.settingsManager,
|
||||||
|
enforceModeService: nil,
|
||||||
timeProvider: timeProvider
|
timeProvider: timeProvider
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ final class TimerEngineTestabilityTests: XCTestCase {
|
|||||||
let timeProvider = MockTimeProvider(startTime: Date())
|
let timeProvider = MockTimeProvider(startTime: Date())
|
||||||
let timerEngine = TimerEngine(
|
let timerEngine = TimerEngine(
|
||||||
settingsManager: testEnv.settingsManager,
|
settingsManager: testEnv.settingsManager,
|
||||||
|
enforceModeService: nil,
|
||||||
timeProvider: timeProvider
|
timeProvider: timeProvider
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user