From 2d446196dbd2ca68859c750bc1fad2b3cdb960f3 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Tue, 28 Oct 2025 19:48:21 +0530 Subject: [PATCH 01/64] added new code for network extension and safari/chrome ext --- .../Constants.swift | 13 + ...ontentBlockerExtensionRequestHandler.swift | 92 +++ .../ContentBlockerRequestHandler.swift | 78 ++ .../SelfControl Safari Extension/Info.plist | 15 + .../SelfControl Safari Extension.entitlements | 10 + .../SelfControl Safari Extension/block.html | 33 + .../blockerList.json | 60 ++ .../SelfControl-Development-Setup.md | 112 +++ .../SelfControl.xcodeproj/project.pbxproj | 754 ++++++++++++++++++ .../xcschemes/SelfControl.xcscheme | 100 +++ .../xcschemes/SelfControlExtension.xcscheme | 77 ++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 58 ++ .../SelfControl/Assets.xcassets/Contents.json | 6 + .../SelfControl/SelfControl/Info.plist | 38 + .../SelfControl/Main/ContentView.swift | 94 +++ .../SelfControl/Main/DNSResolver.swift | 125 +++ .../SelfControl/Main/FilterViewModel.swift | 336 ++++++++ .../SelfControl/Main/SCDurationSlider.swift | 88 ++ .../SelfControl/SelfControl/Main/Status.swift | 39 + .../Preferences/PreferencesView.swift | 68 ++ .../Preferences/ProxyPreferences.swift | 27 + .../Preview Assets.xcassets/Contents.json | 6 + .../SelfControl/SelfControl/README.md | 9 + .../SafariExtension/BlockListManager.swift | 96 +++ .../SafariExtension/BlockListViewModel.swift | 37 + .../SafariExtension/SafariExtensionView.swift | 57 ++ .../SimpleRegexConverter.swift | 103 +++ .../SelfControl/SelfControl.entitlements | 17 + .../SelfControl/SelfControlApp.swift | 99 +++ .../BlockOrAllowList.swift | 147 ++++ .../SelfControlExtension/Constants.swift | 35 + .../DNSProxyProvider.swift | 108 +++ .../SelfControlExtension/DNSQuestion.swift | 72 ++ .../ExtensionsPreferencesManager.swift | 24 + .../FilterDataProvider.swift | 531 ++++++++++++ .../HostToIpMapping.swift | 82 ++ .../SelfControlExtension/IPCConnection.swift | 219 +++++ .../SelfControlExtension/Info.plist | 16 + .../SelfControlExtension/PlistListner.swift | 58 ++ .../SelfControlExtension/Process/Binary.swift | 175 ++++ .../Process/Process.swift | 330 ++++++++ .../Process/SourceAppAuditTokenBuilder.swift | 61 ++ .../Process/Utilities.swift | 714 +++++++++++++++++ .../SelfControlExtension/README.md | 8 + .../ReverseDomainMapper.swift | 57 ++ .../SelfControlExtension.entitlements | 14 + .../SelfControlExtension/String+URL.swift | 35 + .../SelfControlExtension/TLDURLToDomain.swift | 49 ++ .../SelfControlExtension/main.swift | 18 + .../dynamic-url-blocker/background.js | 199 +++++ .../dynamic-url-blocker/manifest.json | 23 + .../dynamic-url-blocker/popup.css | 52 ++ .../dynamic-url-blocker/popup.html | 24 + .../dynamic-url-blocker/popup.js | 73 ++ 55 files changed, 5782 insertions(+) create mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/Constants.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerExtensionRequestHandler.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerRequestHandler.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/Info.plist create mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/SelfControl Safari Extension.entitlements create mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/block.html create mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/blockerList.json create mode 100644 Self-Control-Extension/SelfControl/SelfControl-Development-Setup.md create mode 100644 Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj create mode 100644 Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme create mode 100644 Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlExtension.xcscheme create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/Contents.json create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Info.plist create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/DNSResolver.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SCDurationSlider.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/Status.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 Self-Control-Extension/SelfControl/SelfControl/README.md create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListManager.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListViewModel.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SimpleRegexConverter.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControl.entitlements create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/BlockOrAllowList.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/ExtensionsPreferencesManager.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/HostToIpMapping.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Info.plist create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/PlistListner.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Process/Binary.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Process/Process.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Process/SourceAppAuditTokenBuilder.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Process/Utilities.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/README.md create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/ReverseDomainMapper.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/SelfControlExtension.entitlements create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/String+URL.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/TLDURLToDomain.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/main.swift create mode 100644 Self-Control-Extension/dynamic-url-blocker/background.js create mode 100644 Self-Control-Extension/dynamic-url-blocker/manifest.json create mode 100644 Self-Control-Extension/dynamic-url-blocker/popup.css create mode 100644 Self-Control-Extension/dynamic-url-blocker/popup.html create mode 100644 Self-Control-Extension/dynamic-url-blocker/popup.js diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Constants.swift b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Constants.swift new file mode 100644 index 00000000..58dd76f3 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Constants.swift @@ -0,0 +1,13 @@ +// +// Constants.swift +// SelfControl +// +// Created by Satendra Singh on 09/10/25. +// + + + +enum Constants { + /// File name for the JSON file with Safari rules. + static let SAFARI_BLOCKER_FILE_NAME = "blockerList.json" +} diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerExtensionRequestHandler.swift b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerExtensionRequestHandler.swift new file mode 100644 index 00000000..b20bd5fc --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerExtensionRequestHandler.swift @@ -0,0 +1,92 @@ +// +// ContentBlockerExtensionRequestHandler.swift +// SelfControl +// +// Created by Satendra Singh on 09/10/25. +// + +import os.log +import Foundation + +public enum ContentBlockerExtensionRequestHandler { + /// Handles content blocking extension request for rules. + /// + /// This method loads the content blocker rules JSON file from the shared container + /// and attaches it to the extension context to be used by Safari. + /// + /// - Parameters: + /// - context: The extension context that initiated the request. + /// - groupIdentifier: The app group identifier used to access the shared container. + public static func handleRequest(with context: NSExtensionContext, groupIdentifier: String) { + os_log(.info, "[SC] 🔍] Safari Start loading the content blocker, %{public}@", context.inputItems.description) + + // Get the shared container URL using the provided group identifier + guard + let appGroupURL = FileManager.default.containerURL( + forSecurityApplicationGroupIdentifier: groupIdentifier + ) + else { + context.cancelRequest( + withError: createError(code: 1001, message: "Failed to access App Group container.") + ) + return + } + + // Construct the path to the shared blocker list file + let sharedFileURL = appGroupURL.appendingPathComponent(Constants.SAFARI_BLOCKER_FILE_NAME) + + // Determine which blocker list file to use + var blockerListFileURL = sharedFileURL + if !FileManager.default.fileExists(atPath: sharedFileURL.path) { + os_log(.info, "[SC] 🔍] Safari No blocker list file found. Using the default one.") + + // Fall back to the default blocker list included in the bundle + guard + let defaultURL = Bundle.main.url(forResource: "blockerList", withExtension: "json") + else { + context.cancelRequest( + withError: createError( + code: 1002, + message: "[SC] 🔍] Safari Failed to find default blocker list." + ) + ) + return + } + blockerListFileURL = defaultURL + } + + // Create an attachment with the blocker list file + guard let attachment = NSItemProvider(contentsOf: blockerListFileURL) else { + context.cancelRequest( + withError: createError(code: 1003, message: "Failed to create attachment.") + ) + return + } + + // Prepare and complete the extension request with the blocker list + let item = NSExtensionItem() + item.attachments = [attachment] +// item.attributedTitle = NSAttributedString(string: "Hellow world!") +// item.attributedContentText = NSAttributedString(string: "Hello Content of the world!") + + context.completeRequest( + returningItems: [item] + ) { _ in + os_log(.info, "[SC] 🔍] Safari Finished loading the content blocker") + } + } + + /// Creates an NSError with the specified code and message. + /// + /// - Parameters: + /// - code: The error code. + /// - message: The error message. + /// - Returns: An NSError object with the specified parameters. + private static func createError(code: Int, message: String) -> NSError { + return NSError( + domain: "extension request handler", + code: code, + userInfo: [NSLocalizedDescriptionKey: message] + ) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerRequestHandler.swift b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerRequestHandler.swift new file mode 100644 index 00000000..e2fb7c43 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerRequestHandler.swift @@ -0,0 +1,78 @@ +// +// ContentBlockerRequestHandler.swift +// SelfControl Safari Extension +// +// Created by Satendra Singh on 07/10/25. +// + +import Foundation +import UniformTypeIdentifiers +import os.log + +class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling { + // Must match the App Group used by the host app + private let appGroup = "group.com.application.SelfControl.corebits" + private let sharedFileName = "blockerList.json" + + func beginRequest(with context: NSExtensionContext) { + ContentBlockerExtensionRequestHandler.handleRequest(with: context, groupIdentifier: appGroup) + return + let item = NSExtensionItem() + + // Try to load rules from the shared App Group container first + if let sharedFileURL = sharedRulesFileURL(), FileManager.default.fileExists(atPath: sharedFileURL.path) { + let provider = NSItemProvider(contentsOf: sharedFileURL)! + item.attachments = [provider] + context.completeRequest(returningItems: [item], completionHandler: nil) + return + } + + // Fallback: load bundled blockerList.json from the extension resources + if let bundledURL = Bundle.main.url(forResource: "blockerList", withExtension: "json") { + let provider = NSItemProvider(contentsOf: bundledURL)! + item.attachments = [provider] + context.completeRequest(returningItems: [item], completionHandler: nil) + return + } + + // If neither exists, return an empty rules array to avoid errors +// let emptyRulesData = Data("[]".utf8) +// let tmpURL = writeTempData(emptyRulesData, suggestedName: sharedFileName) +// let provider = NSItemProvider(contentsOf: tmpURL)! +// item.attachments = [provider] +// context.completeRequest(returningItems: [item], completionHandler: nil) + } + + // MARK: - Helpers + + private func sharedRulesFileURL() -> URL? { + let fileManager = FileManager.default + guard let containerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { + NSLog("❌ App Group container not found for %@", appGroup) + os_log("[SC] 🔍] Safari ❌ App Group container not found for: %{public}@", appGroup) + return nil + } + return containerURL.appendingPathComponent(sharedFileName) + } + + private func writeTempData(_ data: Data, suggestedName: String) -> URL { + let tmpDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + let url = tmpDir.appendingPathComponent(suggestedName) + do { + try data.write(to: url, options: .atomic) + } catch { + NSLog("❌ Failed to write temporary rules file: \(error.localizedDescription)") + os_log("[SC] 🔍] Safari ❌ Failed to write temporary rules file: %{public}@", error.localizedDescription) + + } + return url + } + + + func cancelRequest(withError error: any Error) { + + os_log("[SC] 🔍] Safari ❌ cancelRequeste: %{public}@", error.localizedDescription) + + } + +} diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Info.plist b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Info.plist new file mode 100644 index 00000000..0d1cc768 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Info.plist @@ -0,0 +1,15 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.Safari.content-blocker + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler + + NSHumanReadableDescription + Add a description of what your extension does here. + + diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/SelfControl Safari Extension.entitlements b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/SelfControl Safari Extension.entitlements new file mode 100644 index 00000000..ab2dad3b --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/SelfControl Safari Extension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.application.SelfControl.corebits + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/block.html b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/block.html new file mode 100644 index 00000000..77a15722 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/block.html @@ -0,0 +1,33 @@ + + + + +Site Blocked + + + + +

🚫 Site Blocked

+

This website has been blocked by your Safari content blocker.

+

You can change this in Safari ▸ Extensions if you want to allow it.

+ Open Safari Extensions + + diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/blockerList.json b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/blockerList.json new file mode 100644 index 00000000..ea0ed75a --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/blockerList.json @@ -0,0 +1,60 @@ +[ + { + "action": { + "type": "block" + }, + "trigger": { + "url-filter": "facebook.com/friends" + } + }, + { + "action": { + "type": "block" + }, + "trigger": { + "url-filter": "facebook.com/marketplace" + } + }, + { + "action": { + "type": "block" + }, + "trigger": { + "url-filter": "facebook.com/watch" + } + } +] +//[ +// { +// "trigger" : { +// "url_filter" : "^https?:\/\/(www\\.)?facebook\\.com\/friends.*" +// }, +// "action" : { +// "type" : "block" +// } +// }, +// { +// "trigger" : { +// "url_filter" : "^https?:\/\/(www\\.)?linkedin\\.com\/mynetwork.*" +// }, +// "action" : { +// "type" : "block" +// } +// }, +// { +// "trigger" : { +// "url_filter" : "^https?:\/\/(www\\.)?linkedin\\.com\/jobs.*" +// }, +// "action" : { +// "type" : "block" +// } +// }, +// { +// "trigger" : { +// "url_filter" : "^https?:\/\/(www\\.)?facebook\\.com\/marketplace.*" +// }, +// "action" : { +// "type" : "block" +// } +// } +//] diff --git a/Self-Control-Extension/SelfControl/SelfControl-Development-Setup.md b/Self-Control-Extension/SelfControl/SelfControl-Development-Setup.md new file mode 100644 index 00000000..4d3e9888 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl-Development-Setup.md @@ -0,0 +1,112 @@ +# SelfControl Development Setup Guide + +This document outlines the exact setup needed for developing the SelfControl app that uses NetworkExtension for filtering network traffic. + +## Prerequisites + +- Xcode 16.0 or later +- macOS (15.0) or later +- Apple Developer account with Network Extension entitlements +- Administrator privileges on your Mac + +## Development Setup Steps + +### 1. Disable System Integrity Protection (SIP) + +For NetworkExtension development, SIP needs to be disabled to allow proper installation and uninstallation of system extensions during development: + +1. Boot into Recovery Mode (restart holding Cmd+R) +2. Open Terminal from Utilities menu +3. Run: `csrutil disable` +4. Restart your Mac + +> ⚠️ **IMPORTANT**: Disabling SIP reduces your system's security. Only do this on a development machine, not on your primary production machine. + +### 2. Configure Post-Build Actions + +To ensure fresh installs of the system extension during development, add two critical post-build actions to your Xcode project: + +1. Open your project in Xcode +2. Select your main app target +3. Go to "Build Phases" +4. Add a new "Run Script" phase (click the + button) +5. Add the following script: + +```bash +# Uninstall the previous system extension first +systemextensionsctl uninstall A4L93BSQEG com.application.SelfControl.SelfControlExtension + +# Copy the app to /Applications +ditto "${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}" "/Applications/${FULL_PRODUCT_NAME}" +``` + +> Note: Replace `A4L93BSQEG` with your actual Team ID and `com.application.SelfControl.SelfControlExtension` with your actual extension bundle ID if different. + +### 3. Bundle Identifier Setup + +Ensure your bundle identifiers are set up correctly: + +- Main App: `com.application.SelfControl` +- Extension: `com.application.SelfControl.SelfControlExtension` + +### 4. Configure Info.plist for Network Extension + +In your Network Extension's Info.plist, ensure proper configuration: + +```xml +NetworkExtension + + NEMachServiceName + $(TeamIdentifierPrefix)com.application.SelfControl.SelfControlExtension + NEProviderClasses + + com.apple.networkextension.filter-data + $(PRODUCT_MODULE_NAME).FilterDataProvider + + +``` + +### 5. Development Workflow + +With this setup, your development workflow becomes: + +1. Make code changes in Xcode +2. Build the project (⌘B) + - This will automatically uninstall the previous extension + - Then copy the new build to /Applications +3. Launch the app from /Applications (not from Xcode) +4. Check Console.app for logs with the prefix `[EADBUG]` + +### 6. Troubleshooting + +If you encounter issues: + +- Verify the app is properly copied to `/Applications` +- Check that the system extension uninstall command is working correctly +- Inspect Console.app for any error messages +- Verify your Team ID is correct in the post-build script +- Ensure bundle identifiers match between the app, extension, and post-build script + +### 7. Re-enabling SIP After Development + +When you're done with development and ready to deploy: + +1. Boot into Recovery Mode +2. Run: `csrutil enable` +3. Restart your Mac + +## Known Limitations + +- Each build requires a fresh installation due to NetworkExtension constraints +- The app must run from /Applications to function properly +- With SIP disabled, your development machine has reduced security protections +- NetworkExtension development generally requires more manual steps than typical app development + +## Conclusion + +This setup automates the most tedious parts of NetworkExtension development by: +1. Ensuring old extensions are uninstalled before new ones are installed +2. Automatically placing the app in the required location (/Applications) +3. Allowing for rapid iteration despite NetworkExtension's constraints + +By following these specific steps, you should be able to develop your NetworkExtension-based app more efficiently. \ No newline at end of file diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj new file mode 100644 index 00000000..2882203d --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -0,0 +1,754 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 77BA53092D9D5E2A00863476 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77BA53082D9D5E2A00863476 /* NetworkExtension.framework */; }; + 9A441CE32E36024E00A521CC /* com.application.SelfControl.corebits.network.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9A7FFB602E950837007D4A0D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A7FFA0B2E91950D007D4A0D /* Cocoa.framework */; }; + 9A7FFB692E950837007D4A0D /* SelfControl Safari Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9A7FFB5F2E950837007D4A0D /* SelfControl Safari Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 9A441CE12E36024300A521CC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 77BA52CE2D9D5D7700863476 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 77BA53052D9D5E2A00863476; + remoteInfo = SelfControlExtension; + }; + 9A7FFB672E950837007D4A0D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 77BA52CE2D9D5D7700863476 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A7FFB5E2E950837007D4A0D; + remoteInfo = "SelfControl Safari Extension"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9A7FFA162E91950D007D4A0D /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 9A7FFB692E950837007D4A0D /* SelfControl Safari Extension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + 9AFC129E2E355671003B5455 /* Embed System Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(SYSTEM_EXTENSIONS_FOLDER_PATH)"; + dstSubfolderSpec = 16; + files = ( + 9A441CE32E36024E00A521CC /* com.application.SelfControl.corebits.network.systemextension in Embed System Extensions */, + ); + name = "Embed System Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 77BA52D62D9D5D7700863476 /* SelfControl.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SelfControl.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */ = {isa = PBXFileReference; explicitFileType = "wrapper.system-extension"; includeInIndex = 0; path = com.application.SelfControl.corebits.network.systemextension; sourceTree = BUILT_PRODUCTS_DIR; }; + 77BA53082D9D5E2A00863476 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; + 9A7FFA0B2E91950D007D4A0D /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 9A7FFB5F2E950837007D4A0D /* SelfControl Safari Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SelfControl Safari Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 77BA53142D9D5E2A00863476 /* Exceptions for "SelfControlExtension" folder in "SelfControlExtension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + DNSProxyProvider.swift, + DNSQuestion.swift, + Info.plist, + ); + target = 77BA53052D9D5E2A00863476 /* SelfControlExtension */; + }; + 9A6873742E280E320054AD3F /* Exceptions for "SelfControl" folder in "SelfControlExtension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Preferences/ProxyPreferences.swift, + ); + target = 77BA53052D9D5E2A00863476 /* SelfControlExtension */; + }; + 9A6873762E2814B30054AD3F /* Exceptions for "SelfControlExtension" folder in "SelfControl" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + BlockOrAllowList.swift, + IPCConnection.swift, + PlistListner.swift, + "String+URL.swift", + ); + target = 77BA52D52D9D5D7700863476 /* SelfControl */; + }; + 9A7FFB6D2E950837007D4A0D /* Exceptions for "SelfControl Safari Extension" folder in "SelfControl Safari Extension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 9A7FFB5E2E950837007D4A0D /* SelfControl Safari Extension */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 77BA52D82D9D5D7700863476 /* SelfControl */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 9A6873742E280E320054AD3F /* Exceptions for "SelfControl" folder in "SelfControlExtension" target */, + ); + path = SelfControl; + sourceTree = ""; + }; + 77BA530A2D9D5E2A00863476 /* SelfControlExtension */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 9A6873762E2814B30054AD3F /* Exceptions for "SelfControlExtension" folder in "SelfControl" target */, + 77BA53142D9D5E2A00863476 /* Exceptions for "SelfControlExtension" folder in "SelfControlExtension" target */, + ); + path = SelfControlExtension; + sourceTree = ""; + }; + 9A7FFB612E950837007D4A0D /* SelfControl Safari Extension */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 9A7FFB6D2E950837007D4A0D /* Exceptions for "SelfControl Safari Extension" folder in "SelfControl Safari Extension" target */, + ); + path = "SelfControl Safari Extension"; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 77BA52D32D9D5D7700863476 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 77BA53032D9D5E2A00863476 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 77BA53092D9D5E2A00863476 /* NetworkExtension.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A7FFB5C2E950837007D4A0D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A7FFB602E950837007D4A0D /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 77BA52CD2D9D5D7700863476 = { + isa = PBXGroup; + children = ( + 77BA52D82D9D5D7700863476 /* SelfControl */, + 77BA530A2D9D5E2A00863476 /* SelfControlExtension */, + 9A7FFB612E950837007D4A0D /* SelfControl Safari Extension */, + 77BA53072D9D5E2A00863476 /* Frameworks */, + 77BA52D72D9D5D7700863476 /* Products */, + ); + sourceTree = ""; + }; + 77BA52D72D9D5D7700863476 /* Products */ = { + isa = PBXGroup; + children = ( + 77BA52D62D9D5D7700863476 /* SelfControl.app */, + 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */, + 9A7FFB5F2E950837007D4A0D /* SelfControl Safari Extension.appex */, + ); + name = Products; + sourceTree = ""; + }; + 77BA53072D9D5E2A00863476 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 77BA53082D9D5E2A00863476 /* NetworkExtension.framework */, + 9A7FFA0B2E91950D007D4A0D /* Cocoa.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 77BA52D52D9D5D7700863476 /* SelfControl */ = { + isa = PBXNativeTarget; + buildConfigurationList = 77BA52E52D9D5D7A00863476 /* Build configuration list for PBXNativeTarget "SelfControl" */; + buildPhases = ( + 77BA52D22D9D5D7700863476 /* Sources */, + 77BA52D32D9D5D7700863476 /* Frameworks */, + 77BA52D42D9D5D7700863476 /* Resources */, + 9AFC129E2E355671003B5455 /* Embed System Extensions */, + 9A7FFA162E91950D007D4A0D /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 9A441CE22E36024300A521CC /* PBXTargetDependency */, + 9A7FFB682E950837007D4A0D /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 77BA52D82D9D5D7700863476 /* SelfControl */, + ); + name = SelfControl; + packageProductDependencies = ( + ); + productName = SelfControl; + productReference = 77BA52D62D9D5D7700863476 /* SelfControl.app */; + productType = "com.apple.product-type.application"; + }; + 77BA53052D9D5E2A00863476 /* SelfControlExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 77BA53152D9D5E2A00863476 /* Build configuration list for PBXNativeTarget "SelfControlExtension" */; + buildPhases = ( + 77BA53022D9D5E2A00863476 /* Sources */, + 77BA53032D9D5E2A00863476 /* Frameworks */, + 77BA53042D9D5E2A00863476 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 77BA530A2D9D5E2A00863476 /* SelfControlExtension */, + ); + name = SelfControlExtension; + packageProductDependencies = ( + ); + productName = SelfControlExtension; + productReference = 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */; + productType = "com.apple.product-type.system-extension"; + }; + 9A7FFB5E2E950837007D4A0D /* SelfControl Safari Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A7FFB6A2E950837007D4A0D /* Build configuration list for PBXNativeTarget "SelfControl Safari Extension" */; + buildPhases = ( + 9A7FFB5B2E950837007D4A0D /* Sources */, + 9A7FFB5C2E950837007D4A0D /* Frameworks */, + 9A7FFB5D2E950837007D4A0D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 9A7FFB612E950837007D4A0D /* SelfControl Safari Extension */, + ); + name = "SelfControl Safari Extension"; + packageProductDependencies = ( + ); + productName = "SelfControl Safari Extension"; + productReference = 9A7FFB5F2E950837007D4A0D /* SelfControl Safari Extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 77BA52CE2D9D5D7700863476 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2600; + LastUpgradeCheck = 1620; + TargetAttributes = { + 77BA52D52D9D5D7700863476 = { + CreatedOnToolsVersion = 16.2; + }; + 77BA53052D9D5E2A00863476 = { + CreatedOnToolsVersion = 16.2; + }; + 9A7FFB5E2E950837007D4A0D = { + CreatedOnToolsVersion = 26.0; + }; + }; + }; + buildConfigurationList = 77BA52D12D9D5D7700863476 /* Build configuration list for PBXProject "SelfControl" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 77BA52CD2D9D5D7700863476; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 77BA52D72D9D5D7700863476 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 77BA52D52D9D5D7700863476 /* SelfControl */, + 77BA53052D9D5E2A00863476 /* SelfControlExtension */, + 9A7FFB5E2E950837007D4A0D /* SelfControl Safari Extension */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 77BA52D42D9D5D7700863476 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 77BA53042D9D5E2A00863476 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A7FFB5D2E950837007D4A0D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 77BA52D22D9D5D7700863476 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 77BA53022D9D5E2A00863476 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A7FFB5B2E950837007D4A0D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 9A441CE22E36024300A521CC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 77BA53052D9D5E2A00863476 /* SelfControlExtension */; + targetProxy = 9A441CE12E36024300A521CC /* PBXContainerItemProxy */; + }; + 9A7FFB682E950837007D4A0D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A7FFB5E2E950837007D4A0D /* SelfControl Safari Extension */; + targetProxy = 9A7FFB672E950837007D4A0D /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 77BA52E32D9D5D7A00863476 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 77BA52E42D9D5D7A00863476 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 77BA52E62D9D5D7A00863476 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = SelfControl/SelfControl.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"SelfControl/Preview Content\""; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; + ENABLE_APP_SANDBOX = NO; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = SelfControl/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Self Control app dev"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 77BA52E72D9D5D7A00863476 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = SelfControl/SelfControl.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"SelfControl/Preview Content\""; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; + ENABLE_APP_SANDBOX = NO; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = SelfControl/Info.plist; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Self Control app dev"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 77BA53162D9D5E2A00863476 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = SelfControlExtension/SelfControlExtension.entitlements; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = X6FQ433AWK; + "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; + ENABLE_APP_SANDBOX = NO; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SelfControlExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = SelfControlExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSLocalNetworkUsageDescription = "DNS lookup"; + INFOPLIST_KEY_NSSystemExtensionUsageDescription = "testing extension"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = "-lbsm"; + PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits.network; + PRODUCT_NAME = "$(inherited)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Self Control Dev ext"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 77BA53172D9D5E2A00863476 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = SelfControlExtension/SelfControlExtension.entitlements; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = X6FQ433AWK; + "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; + ENABLE_APP_SANDBOX = NO; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SelfControlExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = SelfControlExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSLocalNetworkUsageDescription = "DNS lookup"; + INFOPLIST_KEY_NSSystemExtensionUsageDescription = "testing extension"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = "-lbsm"; + PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits.network; + PRODUCT_NAME = "$(inherited)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Self Control Dev ext"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 9A7FFB6B2E950837007D4A0D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "SelfControl Safari Extension/SelfControl Safari Extension.entitlements"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = X6FQ433AWK; + "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "SelfControl Safari Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Safari Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.application.SelfControl.corebits.SelfControl-Safari-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "SelfControl Dev Net Ext"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 9A7FFB6C2E950837007D4A0D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "SelfControl Safari Extension/SelfControl Safari Extension.entitlements"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = X6FQ433AWK; + "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "SelfControl Safari Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Safari Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.application.SelfControl.corebits.SelfControl-Safari-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "SelfControl Dev Net Ext"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 77BA52D12D9D5D7700863476 /* Build configuration list for PBXProject "SelfControl" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 77BA52E32D9D5D7A00863476 /* Debug */, + 77BA52E42D9D5D7A00863476 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 77BA52E52D9D5D7A00863476 /* Build configuration list for PBXNativeTarget "SelfControl" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 77BA52E62D9D5D7A00863476 /* Debug */, + 77BA52E72D9D5D7A00863476 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 77BA53152D9D5E2A00863476 /* Build configuration list for PBXNativeTarget "SelfControlExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 77BA53162D9D5E2A00863476 /* Debug */, + 77BA53172D9D5E2A00863476 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9A7FFB6A2E950837007D4A0D /* Build configuration list for PBXNativeTarget "SelfControl Safari Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A7FFB6B2E950837007D4A0D /* Debug */, + 9A7FFB6C2E950837007D4A0D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 77BA52CE2D9D5D7700863476 /* Project object */; +} diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme new file mode 100644 index 00000000..37807739 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlExtension.xcscheme b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlExtension.xcscheme new file mode 100644 index 00000000..97fcc93b --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlExtension.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AccentColor.colorset/Contents.json b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/Contents.json b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..3f00db43 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/Contents.json b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Info.plist b/Self-Control-Extension/SelfControl/SelfControl/Info.plist new file mode 100644 index 00000000..b0aaa9df --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2025 SelfControl. All rights reserved. + NSLocalNetworkUsageDescription + DNS lookup + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + NSSupportsAutomaticTermination + + NSSupportsSuddenTermination + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift new file mode 100644 index 00000000..a8912aad --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift @@ -0,0 +1,94 @@ +// +// ContentView.swift +// SelfControl +// +// Created by Egzon Arifi on 02/04/2025. +// + +import SwiftUI +import NetworkExtension +import SystemExtensions +import os.log +import Cocoa + +struct ContentView: View { + @EnvironmentObject var viewModel: FilterViewModel + @Environment(\.openWindow) private var openWindow + @State private var newDomain = "" + + var body: some View { + VStack(spacing: 20) { + // Status indicator with image and text. + Button { + viewModel.activateExtension() + } label: { + Text("Install and Start Block") + } + HStack { + Spacer() + Slider(value: $viewModel.delay, in: 1...60) { + Text("Time: \(viewModel.delay, specifier: "%.1f") Minutes") + } + Spacer() + } + statusView + // Show a progress indicator when in the indeterminate state. + if viewModel.status == .indeterminate { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + } + + // Start/Stop buttons. + HStack { + if viewModel.status == .stopped { + Button("Start") { + viewModel.startFilter() + + } + } + if viewModel.status == .running { + Button("Stop") { + viewModel.stopFilter() + } + } + } + HStack { + TextField("Enter Url to test block", text: $newDomain) + Button("Test Url Blocking") { + viewModel.checkUrlRequest(url: newDomain) + } + } +// Button { +// viewModel.setBlockedUrls(urls: ProxyPreferences.getBlockedDomains()) +// } label: { +// Text("Enable Url Blocking") +// } + SafariExtensionView() + } + .padding() + .frame(minWidth: 150, minHeight: 150) + .onDisappear { +// let urls = ProxyPreferences.getBlockedDomains() + + } + } +} + +private extension ContentView { + var statusView: some View { + HStack { + viewModel.status.color + .clipShape(Circle()) + .frame(width: 20, height: 20) + Text("Status: \(viewModel.status.text)") + Button("Edit Blocklist") { + openWindow(id: "preferences") + + } + } + } +} + +#Preview { + ContentView() +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/DNSResolver.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/DNSResolver.swift new file mode 100644 index 00000000..be301c43 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/DNSResolver.swift @@ -0,0 +1,125 @@ +// +// DNSResolver.swift +// SelfControl +// +// Created by Satendra Singh on 21/10/25. +// + + +import Foundation +import Network + +actor DNSResolverActor { + var resolvedIPs: Set = [] + func resolve(hostURL: [String]) async -> Set { + var intendedHosts: Set = [] + for url in hostURL { + print("Resolving: \(url)") + if let host = DNSResolver.getHost(from: url) { + intendedHosts.insert(host) + } + } + + resolvedIPs = [] + for host in intendedHosts { + let ips = await DNSResolver.resolve(hostname: host) + print("host: \(host), ips: \(ips)") + resolvedIPs.formUnion(ips) + } + return resolvedIPs + } +} + +/// A modern async DNS resolver using Network.framework +final class DNSResolver { + static func getHost(from input: String) -> String? { + var string = input + if !string.contains("://") { + string = "https://" + string + } + return URL(string: string)?.host + } + + static func resolve(hostURL: String, timeout: TimeInterval = 5.0) async -> [String] { + guard let host = getHost(from: hostURL) else { return [] } + return await resolve(hostname: host, timeout: timeout) + } + /// Resolves IPv4/IPv6 addresses for a given domain asynchronously. + /// - Parameters: + /// - hostname: The domain name (e.g. "facebook.com"). + /// - timeout: Optional timeout in seconds (default 5s). + /// - Returns: Array of resolved IP addresses as strings. + static func resolve(hostname: String, timeout: TimeInterval = 5.0) async -> [String] { + await withCheckedContinuation { continuation in + let params = NWParameters.tcp + params.allowLocalEndpointReuse = true + + // We use the resolver service to resolve DNS asynchronously + let endpoint = NWEndpoint.hostPort(host: .name(hostname, nil), port: 80) + + let resolver = NWConnection(to: endpoint, using: params) + + var didResume = false + + @Sendable func finish(_ ips: [String]) { + guard !didResume else { return } + didResume = true + continuation.resume(returning: ips) + resolver.cancel() + } + + resolver.stateUpdateHandler = { state in + switch state { + case .ready: + // Once ready, we can extract resolved IPs from the endpoint + if let remote = resolver.currentPath?.remoteEndpoint, + case let .hostPort(host, _) = remote { + switch host { + case .ipv4(let addr): + finish([ipv4ToString(addr)]) + case .ipv6(let addr): + finish([ipv6ToString(addr)]) + default: + finish([]) + } + } else { + finish([]) + } + + case .failed(_): + finish([]) + default: + break + } + } + + // Start resolution + resolver.start(queue: .global(qos: .userInitiated)) + + // Timeout handler + DispatchQueue.global().asyncAfter(deadline: .now() + timeout) { [weak resolver] in + // If still not finished, cancel and return empty + resolver?.cancel() + finish([]) + } + } + } + + /// Converts IPv4 address to string. + nonisolated private static func ipv4ToString(_ addr: IPv4Address) -> String { + let bytes = [UInt8](addr.rawValue) + guard bytes.count == 4 else { return "" } + return bytes.map(String.init).joined(separator: ".") + } + + /// Converts IPv6 address to string. + nonisolated private static func ipv6ToString(_ addr: IPv6Address) -> String { + let bytes = [UInt8](addr.rawValue) + guard bytes.count == 16 else { return "" } + let segments = stride(from: 0, to: bytes.count, by: 2).map { + (UInt16(bytes[$0]) << 8) | UInt16(bytes[$0 + 1]) + } + // Basic hex formatting; does not perform zero-compression (::) + return segments.map { String(format: "%x", $0) }.joined(separator: ":") + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift new file mode 100644 index 00000000..a2d80514 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -0,0 +1,336 @@ +// +// FilterViewModel.swift +// SelfControl +// +// Created by Egzon Arifi on 02/04/2025. +// + +import SwiftUI +import NetworkExtension +import SystemExtensions +import os.log +import Cocoa + +final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionRequestDelegate, AppCommunication { + @Published var status: Status = .stopped + @State private var domains = ProxyPreferences.getBlockedDomains() + private let listner = PlistListner() + @Published var delay: Double = 0.0 + var blockedIPAddressed: [String] = [] + + // Date formatter used to log entries + lazy var dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + return formatter + }() + + // Observer for filter configuration changes + var observer: Any? + var extensionIdentifier: String? + + // Load the system extension bundle from the app’s Contents/Library/SystemExtensions folder. + lazy var extensionBundle: Bundle = { + let extensionsDirectoryURL = URL(fileURLWithPath: "Contents/Library/SystemExtensions", relativeTo: Bundle.main.bundleURL) + let extensionURLs: [URL] + do { + extensionURLs = try FileManager.default.contentsOfDirectory(at: extensionsDirectoryURL, + includingPropertiesForKeys: nil, + options: .skipsHiddenFiles) + } catch let error { + fatalError("Failed to get the contents of \(extensionsDirectoryURL.absoluteString): \(error.localizedDescription)") + } + guard let extensionURL = extensionURLs.first else { + fatalError("Failed to find any system extensions") + } + guard let extensionBundle = Bundle(url: extensionURL) else { + fatalError("Failed to create a bundle with URL \(extensionURL.absoluteString)") + } + return extensionBundle + }() + + override init() { + super.init() + onInit() + self.extensionIdentifier = extensionBundle.bundleIdentifier + self.listner.blockeddomainFetcher = { + return ProxyPreferences.getBlockedDomains() + } + self.listner.startListening() + } + + deinit { + if let observer = observer { + NotificationCenter.default.removeObserver(observer, name: .NEFilterConfigurationDidChange, object: NEFilterManager.shared()) + } + } + + func onInit() { + // On initialization load the filter configuration and register for changes. + loadFilterConfiguration { success in + guard success else { + self.status = .stopped + return + } + self.updateStatus() + self.observer = NotificationCenter.default.addObserver(forName: .NEFilterConfigurationDidChange, + object: NEFilterManager.shared(), + queue: .main) { [weak self] _ in + self?.updateStatus() + } + } + } + + // MARK: - UI and Filter Management + + func setBlockedUrls(urls: [String]) { + IPCConnection.shared.enableURLBlocking(urls) + Task { + let ips: Set = await DNSResolverActor().resolve(hostURL: urls) + print("Resolved app:\(ips)") + setIPAddressesToBlock(addresses: Array(ips)) + } + } + + func setIPAddressesToBlock(addresses: [String]) { + IPCConnection.shared.enableIPAddressesBlocking(addresses) + } + + private func refreshBlockedIPs() { + self.listner.blockeddomainFetcher = { + return ProxyPreferences.getBlockedDomains() + } + } + + func updateStatus() { + if NEFilterManager.shared().isEnabled { + registerWithProvider() + } else { + status = .stopped + } + } + + func logFlow(_ flowInfo: [String: String], at date: Date, userAllowed: Bool) { + guard let localPort = flowInfo[FlowInfoKey.localPort.rawValue], + let remoteAddress = flowInfo[FlowInfoKey.remoteAddress.rawValue] else { + return + } + let dateString = dateFormatter.string(from: date) + let message = "\(dateString) \(userAllowed ? "ALLOW" : "DENY") \(localPort) <-- \(remoteAddress)\n" + os_log("[SC] 🔍] %@", message) + } + + func loadFilterConfiguration(completionHandler: @escaping (Bool) -> Void) { + NEFilterManager.shared().loadFromPreferences { loadError in + DispatchQueue.main.async { + var success = true + if let error = loadError { + os_log("[SC] 🔍] Failed to load the filter configuration: %@", error.localizedDescription) + success = false + } + completionHandler(success) + } + } + } + + func enableFilterConfiguration() { + let filterManager = NEFilterManager.shared() + guard !filterManager.isEnabled else { + registerWithProvider() + return + } + loadFilterConfiguration { success in + guard success else { + self.status = .stopped + return + } + if filterManager.providerConfiguration == nil { + let providerConfiguration = NEFilterProviderConfiguration() + providerConfiguration.filterSockets = true + providerConfiguration.filterPackets = false + filterManager.providerConfiguration = providerConfiguration + if let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String { + filterManager.localizedDescription = appName + } + } + filterManager.isEnabled = true + filterManager.saveToPreferences { saveError in + DispatchQueue.main.async { + if let error = saveError { + os_log("[SC] 🔍] Failed to save the filter configuration: %@", error.localizedDescription) + self.status = .stopped + return + } else { +// self.enableDNSProxy() + } + self.registerWithProvider() + } + } + } + } + + func enableDNSProxy() { + let manager = NEDNSProxyManager.shared() + + manager.loadFromPreferences { error in + guard error == nil else { return } + + let proto = NEDNSProxyProviderProtocol() + proto.providerBundleIdentifier = "com.application.SelfControl.corebits.network" + proto.serverAddress = "127.0.0.1" // placeholder +// proto.filterSockets = true + manager.localizedDescription = "DNS Logger" + manager.providerProtocol = proto + manager.isEnabled = true + + manager.saveToPreferences { saveError in + if let saveError = saveError { + print("Failed to save: \(saveError)") + } else { + print("DNS proxy saved.") + } + } + } + } + + func registerWithProvider() { + // Assuming an IPCConnection singleton similar to the AppKit sample + IPCConnection.shared.register(withExtension: extensionBundle, delegate: self) { success in + DispatchQueue.main.async { + self.status = success ? .running : .stopped + self.setBlockedUrls(urls: ProxyPreferences.getBlockedDomains()) + } +// setBlockedURLs([]) + } + } + + func activateExtension() { + // Start by activating the system extension. + guard let extensionIdentifier = extensionIdentifier else { + self.status = .stopped + return + } +// let request = OSSystemExtensionRequest.propertiesRequest(forExtensionWithIdentifier: extensionIdentifier, queue: .main) +// request.delegate = self +// OSSystemExtensionManager.shared.submitRequest(request) + let activationRequest = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: extensionIdentifier, queue: .main) + activationRequest.delegate = self + OSSystemExtensionManager.shared.submitRequest(activationRequest) + } + // MARK: - UI Event Handlers. + + func startFilter() { + status = .indeterminate + guard !NEFilterManager.shared().isEnabled else { + registerWithProvider() + return + } +// guard let extensionIdentifier = extensionBundle.bundleIdentifier else { +// status = .stopped +// return +// } + activateExtension() + } + + func checkUrlRequest(url: String) { + URLSession.shared.dataTask(with: URL(string: url)!) { (data, response, error) in + print("Response: \(String(describing: response))") + print("Data: \(String(describing: data))") + print("Error: \(String(describing: error))") + }.resume() + } + + func stopFilter() { + let filterManager = NEFilterManager.shared() + status = .indeterminate + guard filterManager.isEnabled else { + status = .stopped + return + } + loadFilterConfiguration { success in + guard success else { + self.status = .running + return + } + // Disable the content filter configuration. + filterManager.isEnabled = false + filterManager.saveToPreferences { saveError in + DispatchQueue.main.async { + if let error = saveError { + os_log("[SC] 🔍] Failed to disable the filter configuration: %@", error.localizedDescription) + self.status = .running + return + } + self.status = .stopped + } + } + } + } + // MARK: - OSSystemExtensionRequestDelegate Methods + + func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) { + guard result == .completed else { + os_log("[SC] 🔍] Unexpected result %d for system extension request", result.rawValue) + status = .stopped + return + } + enableFilterConfiguration() + } + + func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) { + os_log("[SC] 🔍] System extension request failed: %@", error.localizedDescription) + status = .stopped + } + + func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { + os_log("[SC] 🔍] Extension %@ requires user approval", request.identifier) + } + + func request(_ request: OSSystemExtensionRequest, + actionForReplacingExtension existing: OSSystemExtensionProperties, + withExtension ext: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction { + os_log("[SC] 🔍] Replacing extension %@ version %@ with version %@", request.identifier, existing.bundleShortVersion, ext.bundleShortVersion) + return .replace + } + + func request(_ request: OSSystemExtensionRequest, foundProperties properties: [OSSystemExtensionProperties]) { + os_log("[SC] 🔍] foundProperties extension %@", properties) + } + + // MARK: - App Communication (Prompting the User) + + @objc func promptUser(aboutFlow flowInfo: [String: String], responseHandler: @escaping (Bool) -> Void) { + guard let localPort = flowInfo[FlowInfoKey.localPort.rawValue], + let remoteAddress = flowInfo[FlowInfoKey.remoteAddress.rawValue] else { + os_log("[SC] 🔍] Got a promptUser call without valid flow info: %@", flowInfo) + responseHandler(true) + return + } + let connectionDate = Date() + DispatchQueue.main.async { + // For SwiftUI on macOS, use NSAlert via the shared NSApplication window. + if let window = NSApplication.shared.windows.first { + let alert = NSAlert() + alert.alertStyle = .informational + alert.messageText = "New incoming connection" + alert.informativeText = "A new connection on port \(localPort) has been received from \(remoteAddress)." + alert.addButton(withTitle: "Allow") + alert.addButton(withTitle: "Deny") + alert.beginSheetModal(for: window) { response in + let userAllowed = (response == .alertFirstButtonReturn) + self.logFlow(flowInfo, at: connectionDate, userAllowed: userAllowed) + responseHandler(userAllowed) + } + } else { + // Fallback if no window is available. + self.logFlow(flowInfo, at: connectionDate, userAllowed: true) + responseHandler(true) + } + } + } + + func didSetUrls() { + print("didSetUrls+++++") + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SCDurationSlider.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SCDurationSlider.swift new file mode 100644 index 00000000..4b2487e0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SCDurationSlider.swift @@ -0,0 +1,88 @@ +// +// SCDurationSlider.swift +// SelfControl +// +// Created by Satendra Singh on 31/08/25. +// + +import Foundation + + +//import Foundation +//import AppKit + +//class SCTimeIntervalFormatter { +// +// func string(for obj: Any?) -> String { +// guard let number = obj as? NSNumber else { +// return "" +// } +// return formatSeconds(number.doubleValue) +// } +// +// func formatSeconds(_ seconds: TimeInterval) -> String { +// let useModernBehavior = NSAppKitVersion.current >= NSAppKitVersion.macOS10_8 +// if useModernBehavior { +// return formatSecondsUsingModernBehavior(seconds) +// } else { +// return formatSecondsUsingLegacyBehavior(seconds) +// } +// } +// +// private func formatSecondsUsingModernBehavior(_ seconds: TimeInterval) -> String { +// struct FormatterHolder { +// static let formatter: TTTTimeIntervalFormatter = { +// let formatter = NSDateFormatter() +// formatter.pastDeicticExpression = "" +// formatter.presentDeicticExpression = "" +// formatter.futureDeicticExpression = "" +// formatter.significantUnits = [.year, .month, .day, .hour, .minute] +// formatter.numberOfSignificantUnits = 0 +// formatter.leastSignificantUnit = .minute +// return formatter +// }() +// } +// +// var formatted = FormatterHolder.formatter.string(forTimeInterval: seconds) ?? "" +// if formatted.isEmpty { +// formatted = stringIndicatingZeroMinutes() +// } +// +// return formatted +// } +// +// private func formatSecondsUsingLegacyBehavior(_ seconds: TimeInterval) -> String { +// let numMinutes = Int(seconds / 60) +// let formatDays = numMinutes / 1440 +// let formatHours = (numMinutes % 1440) / 60 +// let formatMinutes = numMinutes % 60 +// +// var timeString = "" +// +// if numMinutes > 0 { +// if formatDays > 0 { +// timeString = "\(formatDays) " + (formatDays == 1 ? NSLocalizedString("day", comment: "Single day time string") : NSLocalizedString("days", comment: "Plural days time string")) +// } +// if formatHours > 0 { +// let hoursPart = "\(formatHours) " + (formatHours == 1 ? NSLocalizedString("hour", comment: "Single hour time string") : NSLocalizedString("hours", comment: "Plural hours time string")) +// timeString += (formatDays > 0 ? ", " : "") + hoursPart +// } +// if formatMinutes > 0 { +// let minutesPart = "\(formatMinutes) " + (formatMinutes == 1 ? NSLocalizedString("minute", comment: "Single minute time string") : NSLocalizedString("minutes", comment: "Plural minutes time string")) +// timeString += ((formatHours > 0 || formatDays > 0) ? ", " : "") + minutesPart +// } +// } else { +// timeString = stringIndicatingZeroMinutes() +// } +// +// return timeString +// } +// +// private func stringIndicatingZeroMinutes() -> String { +// return String( +// format: "0 %@ (%@)", +// NSLocalizedString("minutes", comment: "Plural minutes time string"), +// NSLocalizedString("disabled", comment: "Shows that SelfControl is disabled") +// ) +// } +//} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Status.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Status.swift new file mode 100644 index 00000000..cb8c962d --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/Status.swift @@ -0,0 +1,39 @@ +// +// Status.swift +// SelfControl +// +// Created by Egzon Arifi on 02/04/2025. +// + +import Foundation +import SwiftUI + +enum Status { + case stopped + case indeterminate + case running +} + +extension Status { + var text: String { + switch self { + case .stopped: + return "Stopped" + case .indeterminate: + return "Indeterminate" + case .running: + return "Running" + } + } + + var color: Color { + switch self { + case .stopped: + return .red + case .indeterminate: + return .yellow + case .running: + return .green + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift new file mode 100644 index 00000000..a57e5edf --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift @@ -0,0 +1,68 @@ +// +// PreferencesView.swift +// SelfControl +// +// Created by Satendra Singh on 12/07/25. +// + +import SwiftUI + +struct PreferencesView: View { + @State private var domains = ProxyPreferences.getBlockedDomains() + @State private var newDomain = "" + @EnvironmentObject var viewModel: FilterViewModel + + var body: some View { + VStack { + List { + ForEach(domains, id: \.self) { domain in + HStack { + Text(domain) + Spacer() + Button(action: { + deleteItem(domain: domain) + }) { + Image(systemName: "trash") + .foregroundColor(.red) + } + .buttonStyle(BorderlessButtonStyle()) // ensures button works inside List + } + } + .onDelete(perform: delete) // still supports swipe-to-delete + } + + HStack { + TextField("Add Domain", text: $newDomain) + Button("Add") { + addDomain() + } + } + + Button("Save Preferences") { + ProxyPreferences.setBlockedDomains(domains) + viewModel.setBlockedUrls(urls: domains) + } + } + .padding() + .frame(width: 400, height: 300) + } + + func addDomain() { + guard !newDomain.isEmpty else { return } +// guard let domainValue = newDomain.domainString else { return } + domains.append(newDomain) + newDomain = "" + ProxyPreferences.setBlockedDomains(domains) + } + + func delete(at offsets: IndexSet) { + domains.remove(atOffsets: offsets) + ProxyPreferences.setBlockedDomains(domains) + } + + func deleteItem(domain: String) { + if let index = domains.firstIndex(of: domain) { + domains.remove(at: index) + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift new file mode 100644 index 00000000..4ba2f0b7 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift @@ -0,0 +1,27 @@ +// +// ProxyPreferences.swift +// SelfControl +// +// Created by Satendra Singh on 12/07/25. +// + + +import Foundation + +struct ProxyPreferences { +// static let appGroup = "X6FQ433AWK.com.application.SelfControl.Extension" + static let blockedDomainsKey = "BlockedDomains" + + static func getBlockedDomains() -> [String] { + let defaults = UserDefaults.standard +// let defaults = UserDefaults(suiteName: appGroup) + + return defaults.stringArray(forKey: blockedDomainsKey) ?? [] + } + + static func setBlockedDomains(_ domains: [String]) { +// let defaults = UserDefaults(suiteName: appGroup) + let defaults = UserDefaults.standard + defaults.set(domains, forKey: blockedDomainsKey) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preview Content/Preview Assets.xcassets/Contents.json b/Self-Control-Extension/SelfControl/SelfControl/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/README.md b/Self-Control-Extension/SelfControl/SelfControl/README.md new file mode 100644 index 00000000..dae091d0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/README.md @@ -0,0 +1,9 @@ +# SelfControlApp + +This directory contains the Swift-based macOS app for SelfControl. + +## Structure + +- `UI/` - User interface code +- `ViewControllers/` - View controllers +- `Resources/` - App-specific resources \ No newline at end of file diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListManager.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListManager.swift new file mode 100644 index 00000000..b12b12af --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListManager.swift @@ -0,0 +1,96 @@ +import Foundation +import SafariServices + +struct BlockRule: Codable { + struct Trigger: Codable { + let urlFilter: String + + enum CodingKeys: String, CodingKey { + case urlFilter = "url-filter" + } + } + + struct Action: Codable { + let type: String + } + + let trigger: Trigger + let action: Action +} + + +enum BlockListManager { + + static func updateSafariBlockList(blockedPaths: [String], appGroup: String, extensionIdentifier: String) { + var rules: [BlockRule] = [] + + for path in blockedPaths { +// let components = path.split(separator: "/", maxSplits: 1, omittingEmptySubsequences: false) +// let domain = String(components[0]) +// let pathPart = components.count == 2 ? String(components[1]) : "" +// +// let escapedDomain = NSRegularExpression.escapedPattern(for: domain) +// var pattern = "^https?://(www\\.)?" + escapedDomain +// if !pathPart.isEmpty { +// let escapedPath = NSRegularExpression.escapedPattern(for: pathPart) +// pattern += "/" + escapedPath +// } +// pattern += ".*" + +// rules.append(BlockRule( +// trigger: .init(urlFilter: SimpleRegexConverter.regexFromURL(path) ?? path), +// action: .init(type: "block") +// )) + rules.append(BlockRule( + trigger: .init(urlFilter: path), + action: .init(type: "block") + )) + } + + do { + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted] + let data = try encoder.encode(rules) + + let fileManager = FileManager.default + guard let containerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { + print("❌ App group container not found.") + return + } + + // Ensure folder exists before writing + try fileManager.createDirectory(at: containerURL, withIntermediateDirectories: true, attributes: nil) + + let fileURL = containerURL.appendingPathComponent("blockerList.json") + try data.write(to: fileURL, options: .atomic) + + print("✅ Wrote blockerList.json to: \(fileURL.path)") + + // Query current state for diagnostics; proceed to reload regardless. + SFContentBlockerManager.getStateOfContentBlocker(withIdentifier: extensionIdentifier) { state, error in + if let error = error as NSError? { + print("⚠️ State check error for \(extensionIdentifier): \(error.domain) code \(error.code) – \(error.localizedDescription)") + print("ℹ️ Tips: Ensure the content blocker extension bundle identifier matches exactly, the extension is installed and enabled in Safari, and the app/extension share the same App Group: \(appGroup)") + } else { + print("🔍 Current state of \(extensionIdentifier):", state?.description ?? "Unknown") + if state?.isEnabled == false { + print("⚠️ Extension not enabled in Safari. Please enable it in Settings → Safari → Extensions.") + } + } + + // Attempt reload regardless; this often yields a clearer error if the identifier is wrong. + SFContentBlockerManager.reloadContentBlocker(withIdentifier: extensionIdentifier) { error in + if let error = error as NSError? { + print("❌ Reload error for \(extensionIdentifier): \(error.domain) code \(error.code) – \(error.localizedDescription)") + print("ℹ️ If this persists: verify the extension target’s bundle ID, that the extension is enabled, and that it has access to the shared container.") + } else { + print("✅ Safari Content Blocker reloaded successfully.") + } + } + } + + } catch { + print("❌ Error writing blocker file:", error) + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListViewModel.swift new file mode 100644 index 00000000..b8b29527 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListViewModel.swift @@ -0,0 +1,37 @@ +import Foundation +import Combine +import SafariServices + +/// ViewModel to manage blocked paths and write the blockerList.json into the App Group container. +class BlockListViewModel: ObservableObject { + @Published var blockedPaths: [String] = [ + "facebook.com/friends", + "youtube.com/shorts/", + "example.com/private/" + ] + private let appGroup = "group.com.application.SelfControl.corebits" + private let extensionIdentifier = "com.application.SelfControl.corebits.SelfControl-Safari-Extension" + + func addSample() { + blockedPaths.append("example.com/private/") + } + + func remove(at index: Int) { + guard index >= 0 && index < blockedPaths.count else { return } + blockedPaths.remove(at: index) + } + + func resetToDefaults() { + blockedPaths = [ + "facebook.com/friends", + "youtube.com/shorts/", + "example.com/private/" + ] + } + + func updateBlocker() { + let urls = ProxyPreferences.getBlockedDomains() + print("URLS: \(urls)") + BlockListManager.updateSafariBlockList(blockedPaths: urls, appGroup: appGroup, extensionIdentifier: extensionIdentifier) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionView.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionView.swift new file mode 100644 index 00000000..47080944 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionView.swift @@ -0,0 +1,57 @@ +// +// SafariExtensionView.swift +// SelfControl +// +// Created by Satendra Singh on 05/10/25. +// + +import SwiftUI + +struct SafariExtensionView: View { + @StateObject private var vm = BlockListViewModel() + + var body: some View { + VStack { +// HStack { +// Text("Blocked paths (domain/path)").font(.headline) +// Spacer() +// Button(action: vm.addSample) { +// Text("Add sample") +// } +// }.padding(.horizontal) +// +// List { +// ForEach(vm.blockedPaths.indices, id: \ .self) { idx in +// HStack { +// TextField("domain/path", text: $vm.blockedPaths[idx]) +// Button(action: { vm.remove(at: idx) }) { +// Image(systemName: "minus.circle") +// }.buttonStyle(BorderlessButtonStyle()) +// } +// } +// }.frame(minHeight: 200) + + HStack { + Button(action: vm.updateBlocker) { + Text("Update Blocker") + } + Spacer() + Button(action: vm.resetToDefaults) { + Text("Reset Defaults") + } + }.padding() + } + .onAppear { + vm.updateBlocker() + } + .padding() +// .frame(minWidth: 600, minHeight: 320) + } + //1550, 10300, soles, 250, + //50, sanitry, 1, 4, 2 ,6, 5 + //solems, 45, +} + +#Preview { + SafariExtensionView() +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SimpleRegexConverter.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SimpleRegexConverter.swift new file mode 100644 index 00000000..24ffd6cd --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SimpleRegexConverter.swift @@ -0,0 +1,103 @@ +// +// SimpleRegexConverter.swift +// SelfControl +// +// Created by Satendra Singh on 09/10/25. +// + +import Foundation + +final class SimpleRegexConverter { + /// Converts a full URL string into a regex domain pattern suitable for Safari content blocker rules. + /// Example: "https://www.facebook.com/friends" → "https?://(www\\.)?facebook\\.com/.*" + static func regexPattern(from urlString: String) -> String? { + guard let url = buildValidURL(from: urlString), + let host = url.host else { + return nil + } + + // Escape regex special characters in domain + let escapedHost = NSRegularExpression.escapedPattern(for: host) + + // Detect common "www" prefix and make it optional + let pattern: String + if host.hasPrefix("www.") { + let domainWithoutWWW = String(host.dropFirst(4)) + let escapedDomain = NSRegularExpression.escapedPattern(for: domainWithoutWWW) + pattern = "https?://(www\\.)?\(escapedDomain)/.*" + } else { + pattern = "https?://(www\\.)?\(escapedHost)/.*" + } + + return pattern + } + + static func buildValidURL(from path: String) -> URL? { + var trimmedPath = path.trimmingCharacters(in: .whitespacesAndNewlines) + + // If it's already a valid URL with scheme + if let url = URL(string: trimmedPath), url.scheme != nil { + return url + } + + // Add missing scheme (default to https) + if !trimmedPath.lowercased().hasPrefix("http://") && !trimmedPath.lowercased().hasPrefix("https://") { + trimmedPath = "https://" + trimmedPath + } + + // Try building again + guard var components = URLComponents(string: trimmedPath) else { + return nil + } + + // Fix missing host (e.g., if user entered "example.com/test") + if components.host == nil { + let parts = trimmedPath + .replacingOccurrences(of: "https://", with: "") + .replacingOccurrences(of: "http://", with: "") + .split(separator: "/", maxSplits: 1) + + if let hostPart = parts.first { + components.host = String(hostPart) + components.path = parts.count > 1 ? "/" + parts[1] : "" + } + } + + // Return final URL + return components.url + } + + + static func regexFromURL(_ input: String) -> String? { + var urlString = input.trimmingCharacters(in: .whitespacesAndNewlines) + + // Add a scheme if missing (needed for URL parsing) + if !urlString.lowercased().hasPrefix("http://") && + !urlString.lowercased().hasPrefix("https://") { + urlString = "https://" + urlString + } + + guard let url = URL(string: urlString), let host = url.host else { + return nil + } + + // Escape host and path for regex + let escapedHost = NSRegularExpression.escapedPattern(for: host) + let escapedPath = NSRegularExpression.escapedPattern(for: url.path) + + // Build regex: + // ^[^:]+://+([^.]+\.)*facebook\.com(/friends|[/:]|$) + // - Matches any scheme (http/https) + // - Allows any subdomain levels + // - Matches the path if given + var regex = #"^[^:]+://+([^.]+\.)*\#(escapedHost)"# + + if !escapedPath.isEmpty && escapedPath != "/" { + regex += escapedPath + } + + regex += #"([/:]|$)"# + + return regex + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControl.entitlements b/Self-Control-Extension/SelfControl/SelfControl/SelfControl.entitlements new file mode 100644 index 00000000..9cc108e5 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControl.entitlements @@ -0,0 +1,17 @@ + + + + + com.apple.developer.networking.networkextension + + content-filter-provider-systemextension + + com.apple.developer.system-extension.install + + com.apple.security.application-groups + + group.com.application.SelfControl.corebits + $(TeamIdentifierPrefix)com.application.SelfControl.corebits + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift new file mode 100644 index 00000000..e7fa36f6 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift @@ -0,0 +1,99 @@ +// +// SelfControlApp.swift +// SelfControl +// +// Created by Egzon Arifi on 02/04/2025. +// + +import SwiftUI + +@main +struct SelfControlApp: App { + @StateObject var viewModel = FilterViewModel() + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(viewModel) // Inject the object into the environment + + Button("Test", action: { + let iPAddress = resolveIPToHostname(ipAddress: "8.8.8.8") + print("IP: \(iPAddress ?? "Unknown")") + // Example: + if let host = reverseDNS(ipAddress: "8.8.8.8") { + print("Hostname: \(host)") + } else { + print("Could not resolve.") + } + // Example + if let domain = reverseDNSUsingGetNameInfo(ipAddress: "163.70.145.35") { + print("Domain: \(domain)") + } else { + print("Reverse DNS lookup failed.") + } + + }) + + } + Window("Preferences View", id: "preferences") { + PreferencesView() // Your view to be presented in the new window + .environmentObject(viewModel) // Inject the object into the environment + } + .windowStyle(.automatic) + } + + func resolveIPToHostname(ipAddress: String) -> String? { + let hostRef = CFHostCreateWithName(nil, ipAddress as CFString).takeRetainedValue() + + var resolved: DarwinBoolean = false + if CFHostStartInfoResolution(hostRef, .names, nil) { + if let names = CFHostGetNames(hostRef, &resolved)?.takeUnretainedValue() as NSArray?, + let hostname = names.firstObject as? String { + return hostname + } + } + return nil + } + + func reverseDNS(ipAddress: String) -> String? { + let hostRef = CFHostCreateWithAddress(nil, ipAddressToData(ipAddress) as CFData).takeRetainedValue() + var resolved: DarwinBoolean = false + if CFHostStartInfoResolution(hostRef, .names, nil), + let names = CFHostGetNames(hostRef, &resolved)?.takeUnretainedValue() as NSArray?, + let hostname = names.firstObject as? String { + return hostname + } + return nil + } + + private func ipAddressToData(_ ipAddress: String) -> Data { + var addr = in_addr() + inet_pton(AF_INET, ipAddress, &addr) + return Data(bytes: &addr, count: MemoryLayout.size) + } + + func reverseDNSUsingGetNameInfo(ipAddress: String) -> String? { + var addr = sockaddr_in() + addr.sin_len = UInt8(MemoryLayout.size) + addr.sin_family = sa_family_t(AF_INET) + inet_pton(AF_INET, ipAddress, &addr.sin_addr) + + var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + + // Precompute length so we don't read from `addr` inside the closure. + let addrLen = socklen_t(addr.sin_len) + + let result: Int32 = withUnsafePointer(to: &addr) { ptr in + ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in + getnameinfo(saPtr, + addrLen, + &hostname, socklen_t(hostname.count), + nil, 0, + NI_NAMEREQD) + } + } + + guard result == 0 else { return nil } + return String(cString: hostname) + } + +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/BlockOrAllowList.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/BlockOrAllowList.swift new file mode 100644 index 00000000..5a1b0659 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/BlockOrAllowList.swift @@ -0,0 +1,147 @@ +// +// BlockOrAllowList.swift +// SelfControl +// +// Created by Satendra Singh on 24/09/25. +// + + +import Foundation +import os.log +import NetworkExtension + +class BlockOrAllowList: NSObject { + + // MARK: - Properties + + var items: Set +// var lastModified: Date? + + // Serial queue for thread safety (replace @synchronized) +// private let queue = DispatchQueue(label: "BlockOrAllowList.serial") + + // MARK: - Initializer + + init(items: [String]) { + self.items = Set(items) + super.init() + } + + // MARK: - Methods + +// var isRemote: Bool { +// return path.hasPrefix("http://") || path.hasPrefix("https://") +// } + +// func load(_ path: String) { +// queue.sync { +// self.path = path +// self.items.removeAll() +// +// guard !path.isEmpty else { +// os_log("no list specified...", log: .default, type: .debug) +// return +// } +// +// var listString: String? +// var error: Error? +// +// if isRemote { +// os_log("(re)loading (remote) list", log: .default, type: .debug) +// if let url = URL(string: path) { +// do { +// listString = try String(contentsOf: url, encoding: .utf8) +// } catch let e { +// error = e +// } +// } +// if let error = error { +// os_log("ERROR: failed to (re)load (remote) list, %{public}@ (error: %{public}@)", log: .default, type: .error, path, String(describing: error)) +// return +// } +// // (Re)load remote URL once a day +// DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 24*60*60) { +// self.load(self.path) +// } +// } else { +// os_log("(re)loading (local) list, %{public}@", log: .default, type: .debug, path) +// do { +// listString = try String(contentsOfFile: path, encoding: .utf8) +// } catch let e { +// error = e +// } +// if let error = error { +// os_log("ERROR: failed to (re)load (local) list, %{public}@ (error: %{public}@)", log: .default, type: .error, path, String(describing: error)) +// return +// } +// if let attrs = try? FileManager.default.attributesOfItem(atPath: path), +// let fileModified = attrs[.modificationDate] as? Date { +// self.lastModified = fileModified +// } +// } +// +// if let listString = listString { +// let lines = listString.components(separatedBy: .newlines) +// let filtered = lines.map { $0.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() } +// .filter { !$0.isEmpty && !$0.hasPrefix("#") } +// self.items = Set(filtered) +// os_log("SC] 🔍(re)loaded %lu list items", log: .default, type: .debug, self.items.count) +// } +// } +// } + + /// Check if flow matches item on block or allow list + func isMatch(_ flow: NEFilterSocketFlow) -> Bool { + // Only access properties and perform mutation in the queue (thread safety) +// return queue.sync { + + var endpointNames = Set() + + // Get remote endpoint host + if let url = flow.url, let urlString = url.absoluteString.lowercased() as String? { + endpointNames.insert(urlString) + } + if let url = flow.url, let host = url.host?.lowercased() { + endpointNames.insert(host) + } + if let remoteEndpoint = flow.remoteEndpoint as? NWHostEndpoint { + endpointNames.insert(remoteEndpoint.hostname.lowercased()) + } + + // macOS 11+ specific property + if #available(macOS 11, *) { + if let remoteHostname = flow.remoteHostname?.lowercased() { + endpointNames.insert(remoteHostname) + if remoteHostname.hasPrefix("www.") { + let noWWW = String(remoteHostname.dropFirst(4)) + endpointNames.insert(noWWW) + } + } + } + os_log("[SC] 🔍] BlockList endpoint names : %{public}@", log: OSLog.default, type: .info, endpointNames.debugDescription) + + // Find matches + let matches = items.intersection(endpointNames) + if !matches.isEmpty { + os_log("SC] 🔍 endpoint names %{public}@ matched the following list items %{public}@", log: .default, type: .debug, String(describing: endpointNames), String(describing: matches)) + return true + } + + return false +// } + } +} + +// MARK: - NEFilterSocketFlow Extensions + +//extension NEFilterSocketFlow { +// /// You must implement these extensions if your Objective-C code provides them! +// @objc public override var url: URL? { +// // Implement according to your codebase (category/extension in Objective-C) +// return nil // Placeholder +// } +// @objc var remoteHostname: String? { +// // Implement according to your codebase (category/extension in Objective-C) +// return nil // Placeholder +// } +//} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift new file mode 100644 index 00000000..0db08d63 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift @@ -0,0 +1,35 @@ +// +// Constants.swift +// SelfControl +// +// Created by Satendra Singh on 11/10/25. +// + +enum Constants { + /// File name for the JSON file with Safari rules. + static let KEY_UUID = "uuid" + static let KEY_PROCESS_ID = "pid" + static let KEY_PROCESS_ARGS = "args" + static let KEY_PROCESS_NAME = "name" + static let KEY_PROCESS_PATH = "path" + static let KEY_INDEX = "index" + static let KEY_PATH = "paths" + static let KEY_CS_SIGNER = "signatureSigner" + static let KEY_CS_ID = "signatureIdentifier" + static let KEY_CS_INFO = "signingInfo" + static let KEY_CS_AUTHS = "signatureAuthorities" +} + +enum AllowedProcess: String { + case safari = "com.apple.Safari" + case chrome = "com.google.Chrome" + static var allCases: [AllowedProcess] = [.chrome, .safari] + static func isAllowed(_ bundleID: String) -> Bool { + allCases.contains(where: { $0.rawValue == bundleID }) + } + + static func isAllowedProcess(_ process: Process) -> Bool { + let ancestors: [String] = process.ancestors?.value(forKey: "name") as? [String] ?? [] + return allCases.contains(where: { $0.rawValue == process.bundleID }) || ancestors.contains(where: { $0.lowercased().contains("chrome") || $0.lowercased().contains("safari") }) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift new file mode 100644 index 00000000..15a0ab3a --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift @@ -0,0 +1,108 @@ +// +// DNSProxyProvider.swift +// SelfControl +// +// Created by Satendra Singh on 02/08/25. +// + + +import NetworkExtension +import os.log + +class DNSProxyProvider: NEDNSProxyProvider { + + // Cache for queried domains (thread-safe) + var observedDomains = Set() + let queue = DispatchQueue(label: "dns.sync.queue") + + override func startProxy(options: [String : Any]? = nil, completionHandler: @escaping (Error?) -> Void) { + NSLog("DNS Proxy started") + os_log("SC] 🔍 DNS Proxy started") + completionHandler(nil) + } + + override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool { + if let tcpFlow = flow as? NEAppProxyTCPFlow { + os_log("SC] 🔍 DNS Proxy NEAppProxyTCPFlow") + readFromTCP(flow: tcpFlow) + return true + } + + guard let udpFlow = flow as? NEAppProxyUDPFlow else { + return true + } + os_log("SC] 🔍 DNS Proxy NEAppProxyUDPFlow") + udpFlow.readDatagrams { datagrams, remoteEndpoints, error in + guard let datagrams = datagrams else { return } + + for data in datagrams { + if let message = DNSParser.parseMessage(data) { + for question in message.questions { + os_log("SC] 🔍 DNS Query for domain: %{public}@", question.name) + NSLog("🔍 DNS Query for domain: \(question.name)") + // Save domain name, blocklist, etc. + } + } + } + + // Respond or forward if you're proxying +// udpFlow.closeReadWithError(nil) +// udpFlow.closeWriteWithError(nil) + } + + return true + } + + override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { + NSLog("DNS Proxy stopped") + os_log("SC] 🔍 DNS Proxy stopped") + completionHandler() + } + + func readFromTCP(flow: NEAppProxyTCPFlow) { + flow.readData { [weak self] data, error in + guard let data = data, !data.isEmpty else { + os_log("SC] 🔍 No data or error: : %{public}@", error?.localizedDescription ?? "Empty") + flow.closeReadWithError(nil) + flow.closeWriteWithError(nil) + return + } + + var offset = 0 58540 + + while offset + 2 <= data.count { + // DNS over TCP starts with 2-byte length prefix + let length = Int(data.uint16(at: offset)) + offset += 2 + + guard offset + length <= data.count else { + NSLog("Incomplete DNS message") + os_log("SC] 🔍 Incomplete DNS message") + break + } + + let dnsData = data.subdata(in: offset..<(offset + length)) + if let message = DNSParser.parseMessage(dnsData) { + for q in message.questions { + NSLog("🌐 DNS (TCP) Query: \(q.name)") + os_log("SC] 🔍 🌐 (TCP) Query: %{public}@", q.name) + } + } + + offset += length + } + + // Optionally respond or forward, or keep reading + flow.closeReadWithError(nil) + flow.closeWriteWithError(nil) + } + } + +} + +extension Data { + public func uint16(at offset: Int) -> UInt16 { + guard offset + 1 < self.count else { return 0 } + return UInt16(self[offset]) << 8 | UInt16(self[offset + 1]) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift new file mode 100644 index 00000000..8f00f1d1 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift @@ -0,0 +1,72 @@ +// +// DNSQuestion.swift +// SelfControl +// +// Created by Satendra Singh on 02/08/25. +// + +import Foundation + +struct DNSQuestion { + let name: String + let type: UInt16 + let qclass: UInt16 +} + +struct DNSMessage { + let id: UInt16 + let isQuery: Bool + let opcode: UInt8 + let questions: [DNSQuestion] +} + +class DNSParser { + static func parseMessage(_ data: Data) -> DNSMessage? { + guard data.count >= 12 else { return nil } // DNS header size + + let id = data.uint16(at: 0) + let flags = data.uint16(at: 2) + + let isQuery = (flags & 0x8000) == 0 + let opcode = UInt8((flags & 0x7800) >> 11) + let qdcount = data.uint16(at: 4) + + var offset = 12 + + var questions: [DNSQuestion] = [] + + for _ in 0..= offset + 4 else { return nil } + let type = data.uint16(at: offset) + let qclass = data.uint16(at: offset + 2) + offset += 4 + questions.append(DNSQuestion(name: name, type: type, qclass: qclass)) + } + + return DNSMessage(id: id, isQuery: isQuery, opcode: opcode, questions: questions) + } + + private static func parseDomainName(_ data: Data, offset: Int) -> (String, Int)? { + var labels: [String] = [] + var index = offset + while index < data.count { + let length = Int(data[index]) + if length == 0 { + index += 1 + break + } + guard index + length < data.count else { return nil } + let labelData = data.subdata(in: index+1..<(index+1+length)) + if let label = String(data: labelData, encoding: .utf8) { + labels.append(label) + } else { + return nil + } + index += length + 1 + } + return (labels.joined(separator: "."), index) + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/ExtensionsPreferencesManager.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/ExtensionsPreferencesManager.swift new file mode 100644 index 00000000..99580098 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/ExtensionsPreferencesManager.swift @@ -0,0 +1,24 @@ +// +// ExtensionsPreferencesManager.swift +// SelfControl +// +// Created by Satendra Singh on 12/07/25. +// + +import Foundation +import os.log + +final class ExtensionsPreferencesManager { + let appGroup = "X6FQ433AWK.com.application.SelfControl.Extension" + var blockedDomains: [String] = [] + lazy var defaults = UserDefaults(suiteName: appGroup) + + init(blockedDomains: [String]) { + self.blockedDomains = blockedDomains + } + + init () { + blockedDomains = defaults?.stringArray(forKey: "BlockedDomains") ?? [] + os_log("SC] 🔍 DNS Blocked Domains Loaded: %{public}@", blockedDomains) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift new file mode 100644 index 00000000..3cd3f4c3 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift @@ -0,0 +1,531 @@ +import NetworkExtension +import os.log +import dnssd + +/// FilterDataProvider is a NEFilterDataProvider subclass that intercepts flows and applies a test rule. +class FilterDataProvider: NEFilterDataProvider { + // MARK: - Properties +// private let listner = PlistListner() + /// A dictionary for storing flows related to the same process. + private var relatedFlows: [String: [NEFilterSocketFlow]] = [:] + + // MARK: - Initialization + + override init() { + os_log("[SC] 🔍] FilterDataProvider: init") + super.init() +// listner.startListening() + } + + // MARK: - Filter Lifecycle + static let localPort = "8888" + + override func startFilter(completionHandler: @escaping (Error?) -> Void) { + os_log("[SC] 🔍] FilterDataProvider: Starting filter", log: OSLog.default, type: .info) + os_log("[SC] 🔍] captrued Domains: %{public}@", log: OSLog.default, type: .error, ExtensionsPreferencesManager().blockedDomains) +// os_log("[SC] 🔍] captrued blockedUrls: %{public}@", log: OSLog.default, type: .error, IPCConnection.shared.blockedUrls) + + // Filter incoming TCP connections on port 8888 + let blockedHosts = IPCConnection.shared.blockedUrls + +// let filterRules = blockedHosts.map { address -> NEFilterRule in +// // let localNetwork = NWHostEndpoint(hostname: address as! String, port: FilterDataProvider.localPort) +// let inboundNetworkRule = NENetworkRule(remoteNetwork: address, +// remotePrefix: 0, +// localNetwork: nil, +// localPrefix: 0, +// protocol: .any, +// direction: .outbound) +// return NEFilterRule(networkRule: inboundNetworkRule, action: .filterData) +// } + // Filter incoming TCP connections on port 8888 +// let filterRules = ["0.0.0.0", "::"].map { address -> NEFilterRule in +// let filterRules = ["*"].map { address -> NEFilterRule in +// let localNetwork = NWHostEndpoint(hostname: address, port: "*") +// let inboundNetworkRule = NENetworkRule(remoteNetwork: nil, +// remotePrefix: 0, +// localNetwork: localNetwork, +// localPrefix: 0, +// protocol: .any, +// direction: .outbound) +// return NEFilterRule(networkRule: inboundNetworkRule, action: .filterData) +// } +// + let filterRules = blockedHosts.map { address -> NEFilterRule in + let localNetwork = NWHostEndpoint(hostname: address, port: "*") + let inboundNetworkRule = NENetworkRule(remoteNetwork: nil, + remotePrefix: 0, + localNetwork: localNetwork, + localPrefix: 0, + protocol: .any, + direction: .outbound) + return NEFilterRule(networkRule: inboundNetworkRule, action: .filterData) + } + + + // Create a rule matching all outbound traffic. +// let networkRule = NENetworkRule(remoteNetwork: nil, +// remotePrefix: 0, +// localNetwork: nil, +// localPrefix: 0, +// protocol: .any, +// direction: .outbound) +// let filterRule = NEFilterRule(networkRule: networkRule, action: .filterData) +// let filterSettings = NEFilterSettings(rules: [filterRule], defaultAction: .allow) + let filterSettings = NEFilterSettings(rules: filterRules, defaultAction: .allow) + + apply(filterSettings) { error in + if let error = error { + os_log("[SC] 🔍] Error applying filter settings: %@", log: OSLog.default, type: .error, error.localizedDescription) + } + completionHandler(error) + } + } + + override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { + os_log("[SC] 🔍] FilterDataProvider: Stopping filter with reason %d", log: OSLog.default, type: .info, reason.rawValue) + completionHandler() + } + + // MARK: - Flow Handlings + + + func reverseDNSLookup(ip: String) -> String? { + var hints = addrinfo(ai_flags: AI_NUMERICHOST, ai_family: AF_UNSPEC, + ai_socktype: SOCK_STREAM, ai_protocol: IPPROTO_TCP, + ai_addrlen: 0, ai_canonname: nil, + ai_addr: nil, ai_next: nil) + var res: UnsafeMutablePointer? + + if getaddrinfo(ip, nil, &hints, &res) == 0, let addr = res?.pointee.ai_addr { + var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + if getnameinfo(addr, socklen_t(addr.pointee.sa_len), + &hostBuffer, socklen_t(hostBuffer.count), + nil, 0, NI_NAMEREQD) == 0 { + return String(cString: hostBuffer) + } + } + return nil + } + + func extractHost(from request: String) -> String? { + for line in request.split(separator: "\r\n") { + if line.lowercased().hasPrefix("host:") { + return line.replacingOccurrences(of: "Host:", with: "", options: .caseInsensitive) + .trimmingCharacters(in: .whitespaces) + } + } + return nil + } + + func extractPath(from request: String) -> String? { + guard let firstLine = request.split(separator: "\r\n").first else { return nil } + let comps = firstLine.split(separator: " ") + return comps.count > 1 ? String(comps[1]) : nil + } + + func extractSNI(fromTLSData data: Data) -> String? { + // TLS record starts with 0x16 (Handshake), version bytes, then length + guard data.count > 5, data[0] == 0x16 else { return nil } + + var offset = 5 // Skip record header + + // Verify handshake type = ClientHello (0x01) + guard data.count > offset, data[offset] == 0x01 else { return nil } + offset += 4 // Skip type + length (3 bytes) + + // Skip protocol version (2), random (32), session id length + session id + guard data.count > offset + 34 else { return nil } + offset += 34 + guard data.count > offset else { return nil } + + // Skip session ID + if offset < data.count { + let sessionIDLength = Int(data[offset]) + offset += 1 + sessionIDLength + } + + // Skip cipher suites + guard offset + 2 <= data.count else { return nil } + let cipherSuiteLength = Int(data[offset]) << 8 | Int(data[offset + 1]) + offset += 2 + cipherSuiteLength + + // Skip compression methods + guard offset < data.count else { return nil } + let compressionLength = Int(data[offset]) + offset += 1 + compressionLength + + // Skip extensions length + guard offset + 2 <= data.count else { return nil } + let extensionsLength = Int(data[offset]) << 8 | Int(data[offset + 1]) + offset += 2 + guard offset + extensionsLength <= data.count else { return nil } + + var extOffset = offset + while extOffset + 4 <= offset + extensionsLength { + let extType = Int(data[extOffset]) << 8 | Int(data[extOffset + 1]) + let extLen = Int(data[extOffset + 2]) << 8 | Int(data[extOffset + 3]) + extOffset += 4 + + // Extension type 0 = Server Name + if extType == 0 { + // Parse Server Name extension + var nameOffset = extOffset + 2 // skip list length + while nameOffset + 3 < extOffset + extLen { + let nameType = data[nameOffset] + let nameLen = Int(data[nameOffset + 1]) << 8 | Int(data[nameOffset + 2]) + nameOffset += 3 + if nameType == 0, nameOffset + nameLen <= data.count { + let nameData = data.subdata(in: nameOffset ..< nameOffset + nameLen) + return String(data: nameData, encoding: .utf8) + } + nameOffset += nameLen + } + } + + extOffset += extLen + } + + return nil + } + + override func handleOutboundData( + from flow: NEFilterFlow, + readBytesStartOffset offset: Int, + readBytes data: Data + ) -> NEFilterDataVerdict { + + + let process = processFromflow(flow: flow) + // os_log(" FilterDataProvider: appIdAndName %{public}@, %{public}@", log: OSLog.default, type: .debug, process?.name ?? "", process?.path ?? "") + os_log(" FilterDataProvider: app %{public}@", log: OSLog.default, type: .debug, process?.description ?? "") + + if let process = process, AllowedProcess.isAllowedProcess(process) { + os_log("[SC] 🔍] FilterDataProvider: allowing as it is in allowed list.") + return .allow() + } + guard let socketFlow = flow as? NEFilterSocketFlow else { + os_log("[SC] 🔍] Not a socket flow. Allowing.", log: OSLog.default, type: .info) + return .allow() + } + if socketFlow.direction != .outbound { + os_log("[SC] 🔍] Not a inbound socket flow. Allowing.", log: OSLog.default, type: .info) + return .allow() + } + +// guard let socketFlow = flow as? NEFilterSocketFlow else { +// return .allow() +// } + let requestString = String(data: data, encoding: .utf8) + if let requestString { + os_log("[SC] 🔍] data HTTP Request: %{public}@", requestString) + } + + // Try HTTP detection + if let requestString = requestString, + requestString.hasPrefix("GET ") || requestString.hasPrefix("POST ") { + + if let host = extractHost(from: requestString), + let path = extractPath(from: requestString) { + let urlString = "http://\(host)\(path)" + os_log("[SC] 🔍] data HTTP Request: %{public}@", urlString) + } + + } else if let sni = extractSNI(fromTLSData: data) { + os_log("[SC] 🔍] data TLS SNI Host: %{public}@", sni) + guard let hostDomain = TLDURLToDomain.getURLDomain(from: sni) else { + return .allow() + } + os_log("[SC] 🔍] data hostDomain: %{public}@", hostDomain) + + for url in IPCConnection.shared.blockedUrls { + if url.contains(hostDomain) { + os_log("[SC] 🔍] data Blocking flow Host match SNI:%{public}@, host:%{public}@", sni, hostDomain) + return .drop() + } + } + } + + return .allow() + } + + // Called for each new flow. + override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { + // Provide peek sizes required by this overload + return .filterDataVerdict(withFilterInbound: false, + peekInboundBytes: 0, + filterOutbound: true, + peekOutboundBytes: Int.max) + + os_log("[SC] 🔍] FilterDataProvider: handleNewFlow invoked", log: OSLog.default, type: .debug) +// if let appID = flow.sourceAppIdentifier { +// print("App making the request: \(appID)") +// } +// let appIdAndName = SourceAppAuditTokenBuilder.getAppInfo(from: flow) +// os_log("[SC] 🔍] FilterDataProvider: appIdAndName %@, %@", log: OSLog.default, type: .debug, appIdAndName.appName ?? "", appIdAndName.bundleID ?? "") + guard IPCConnection.shared.blockedUrls.count > 0 else { //No signinficant urls to block + return .allow() + } + + let process = processFromflow(flow: flow) +// os_log(" FilterDataProvider: appIdAndName %{public}@, %{public}@", log: OSLog.default, type: .debug, process?.name ?? "", process?.path ?? "") + os_log(" FilterDataProvider: app %{public}@", log: OSLog.default, type: .debug, process?.description ?? "") + + if let process = process, AllowedProcess.isAllowedProcess(process) { + os_log("[SC] 🔍] FilterDataProvider: allowing as it is in allowed list.") + return .allow() + } + guard let socketFlow = flow as? NEFilterSocketFlow else { + os_log("[SC] 🔍] Not a socket flow. Allowing.", log: OSLog.default, type: .info) + return .allow() + } + if socketFlow.direction != .outbound { + os_log("[SC] 🔍] Not a inbound socket flow. Allowing.", log: OSLog.default, type: .info) + return .allow() + } + var remoteHost = "" + if let flowURL = flow.url { + remoteHost = flowURL.absoluteString + os_log("[SC] 🔍] flow.url: \(remoteHost)") + } else { + if let remoteEndpoint = socketFlow.remoteEndpoint as? NWHostEndpoint { + remoteHost = remoteEndpoint.hostname + let port = remoteEndpoint.port + os_log("[SC] 🔍] NWEndpoint to host: %{public}@, URL: %{public}@, url: %{public}@", log: OSLog.default, type: .debug, remoteHost, port, flow.url?.host() ?? "") + } + } + + + //todo: check host name, work on reverse rule if domain match and if path is nil or blocked, drop connection. +// +// if IPCConnection.shared.blockedList.isMatch(flow as! NEFilterSocketFlow) { +// os_log("[SC] 🔍] Dropped", log: OSLog.default, type: .info) +// return .drop() +// } +// os_log("[SC] 🔍] Allowed", log: OSLog.default, type: .info) +// +// return .allow() + + os_log("[SC] 🔍] Flow from remote endpoint: %{public}@, URL: %{public}@", log: OSLog.default, type: .debug, socketFlow.remoteEndpoint.debugDescription, flow.url?.description ?? "nil") + + // Extract remote endpoint (if available). +// guard let remoteEndpoint = socketFlow.remoteEndpoint as? NWHostEndpoint else { +// os_log("[SC] 🔍] No valid remote endpoint. Allowing flow.", log: OSLog.default, type: .error) +// return .allow() +// } + + if remoteHost.isEmpty { //Unable to get host + return .allow() + } + if remoteHost.isValidIpAddress { + if IPCConnection.shared.blockedIPAddresses.contains(remoteHost) { + os_log("[SC] 🔍] Blocking flow IP match: %{public}@", remoteHost) + return .drop() + } + if let host = ReverseDomainMapper.reverseDNSUsingGetNameInfo(ipAddress: remoteHost) { + os_log("[SC] 🔍] Converted IP: %{public}@ to: %{public}@", remoteHost, host) + remoteHost = host + } else { //unable to convert, simple ignore + return .allow() + } + } else { + return .allow() + } + guard let hostDomain = TLDURLToDomain.getURLDomain(from: remoteHost) else { + return .allow() + } + for url in IPCConnection.shared.blockedUrls { + if url.contains(hostDomain) { + os_log("[SC] 🔍] Blocking flow Host match %{public}@, %{public}@", remoteHost, hostDomain) + return .drop() + } + } +// if var urlString = flow.url?.absoluteString { +// os_log("[SC] 🔍] Checking URL path: %{public}@", urlString) +//// let blockedHosts = ["google.com/mail", "google.com/news", "facebook.com"] +// let blockedHosts = IPCConnection.shared.blockedUrls +// os_log("[SC] 🔍] BlockedList: %{public}@ checking:%{public}@",blockedHosts, urlString) +// if urlString.hasPrefix("www.") { +// let noWWW = String(urlString.dropFirst(4)) +// urlString = noWWW +// } +// +// for host in blockedHosts { +// if urlString.contains(host) { +// os_log("[SC] 🔍] Blocking flow to handleNewFlow %{public}@", urlString) +// return .drop() +//// return .allow() +// } +//// flow.url?.host() == url +// } +// } + // if blockedHosts.contains(flow.url?.path() ?? "") { + // os_log("Blocking flow to %@", remoteEndpoint.hostname) + // return .drop() + // } + // Only process outbound traffic. +// if socketFlow.direction != .outbound { +// os_log("[SC] 🔍] Non-outbound traffic. Allowing.", log: OSLog.default, type: .info) +// return .allow() +// } + + return .allow() + + // Process the flow and decide a verdict. + let verdict = processEvent(for: socketFlow) + os_log("[SC] 🔍] Verdict for flow: %{public}@", log: OSLog.default, type: .debug, verdict.debugDescription) + return verdict + } + +// override func handleInboundDataComplete(for flow: NEFilterFlow) -> NEFilterDataVerdict { +// if let socketFlow = flow as? NEFilterSocketFlow { +// if let data = socketFlow.readData { +// if let requestString = String(data: data, encoding: .utf8) { +// if requestString.hasPrefix("GET") || requestString.hasPrefix("POST") { +// print("Request: \(requestString)") +// // Parse the path here +// } +// } +// } +// } +// return .allow() +// } +// +// override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { +// return .filterDataVerdict(withFilterInbound: true, +// peekInboundBytes: 1024, +// filterOutbound: true, +// peekOutboundBytes: 1024) +// } + + + override func handleInboundData(from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes: Data) -> NEFilterDataVerdict { + if let requestString = String(data: readBytes, encoding: .utf8) { + print("Outbound data: \(requestString)") + os_log("[SC] 🔍] handleInboundData: %{public}@", requestString) + + if requestString.contains("facebook.com/friends") { + return .drop() + } + } + return .allow() + } + + override func displayMessage(_ message: String, completionHandler: @escaping (Bool) -> Void) { + return completionHandler(true) + } + +// override func handleOutboundData(from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes: Data) -> NEFilterDataVerdict { +// if let urlString = flow.url?.absoluteString { +// os_log("[SC] 🔍] handleOutboundData URL: %{public}@", urlString) +// if urlString.contains("facebook.com/friends") { +// return .drop() +// } +// } +// +// if let requestString = String(data: readBytes, encoding: .utf8) { +// print("Outbound data: \(requestString)") +// os_log("[SC] 🔍] handleOutboundData: %{public}@", requestString) +// +// if requestString.contains("facebook.com/friends") { +// return .drop() +// } +// } +// return .allow() +// } + +// override func handleOutboundData(from flow: NEFilterFlow, +// readBytesStartOffset offset: Int, +// readBytes: Data, +// completionHandler: @escaping (NEFilterDataVerdict) -> Void) { +// +// if let requestString = String(data: readBytes, encoding: .utf8) { +// print("Outbound data: \(requestString)") +// +// if requestString.contains("facebook.com") { +// completionHandler(.drop()) +// return +// } +// } +// +// // If undecided, ask for more data +// completionHandler(.allow()) +// } + + /// Processes the flow and returns a verdict. + /// This is a simplified test rule that blocks flows destined for "example.com". + private func processEvent(for flow: NEFilterSocketFlow) -> NEFilterNewFlowVerdict { + guard let endpoint = flow.remoteEndpoint as? NWHostEndpoint else { + return .allow() + } + os_log("[SC] 🔍] processEvent endpoint.hostname: %{public}@ ", endpoint.hostname) + os_log("[SC] 🔍] processEvent remoteHostname: %{public}@ ", flow.remoteHostname ?? "NOTHING") + guard let host = flow.remoteHostname?.lowercased().domainString else { + os_log("[SC] 🔍] processEvent No Host") + return .allow() + } +// os_log("[SC] 🔍] This is localFlowEndpoint: %{public}@ ", flow.localFlowEndpoint?.debugDescription ?? "NOTHING") + let blockedHosts = IPCConnection.shared.blockedUrls + +// if host == "google.com" || host == "8.8.8.8" { + if blockedHosts.contains(host) { + os_log("[SC] 🔍] processEvent: Blocking flow to processEvent %{public}@ ", host) + return .drop() +// return .allow() + } + // Optionally log other flows for debugging + os_log("[SC] 🔍] processEvent: Allowing flow to %{public}@", log: OSLog.default, type: .info, host) + return .allow() + } + + + // MARK: - (Optional) Handling Related Flows & Alerts + + /// Adds a flow to a list of related flows for a given key. + private func addRelatedFlow(forKey key: String, flow: NEFilterSocketFlow) { + os_log("[SC] 🔍] Adding related flow for key: %@", log: OSLog.default, type: .debug, key) + if relatedFlows[key] == nil { + relatedFlows[key] = [] + } + relatedFlows[key]?.append(flow) + } + + /// Processes related flows once a decision is made for a given key. + private func processRelatedFlows(forKey key: String) { + guard let flows = relatedFlows[key] else { + os_log("[SC] 🔍] No related flows for key: %@", log: OSLog.default, type: .debug, key) + return + } + for flow in flows { + let verdict = processEvent(for: flow) + resumeFlow(flow, with: verdict) + } + relatedFlows[key] = nil + } + + /// A stub method for resuming a flow with a verdict. + private func resumeFlow(_ flow: NEFilterSocketFlow, with verdict: NEFilterNewFlowVerdict) { + // In a complete implementation, this would resume the paused flow with the provided verdict. + os_log("[SC] 🔍] Resuming flow %@ with verdict %@", log: OSLog.default, type: .info, flow.debugDescription, verdict.debugDescription) + } + + /// A stub method to simulate alerting the user. + /// In a complete implementation, this might trigger an IPC to your app for user intervention. + private func alertUser(for flow: NEFilterSocketFlow) { + os_log("[SC] 🔍] Alert: User decision needed for flow %@", log: OSLog.default, type: .info, flow.debugDescription) + } + + func processFromflow(flow: NEFilterFlow) -> Process? { + guard let auditTokenData = flow.sourceAppAuditToken else { + return nil + } + + // Convert NSData → audit_token_t + var auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in + return ptr.load(as: audit_token_t.self) + } + return Process.init(&auditToken) + } + } +//https://developer.chrome.com/docs/extensions/how-to/distribute/install-extensions + diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/HostToIpMapping.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/HostToIpMapping.swift new file mode 100644 index 00000000..8857b4f0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/HostToIpMapping.swift @@ -0,0 +1,82 @@ +// +// HostToIpMapping.swift +// SelfControl +// +// Created by Satendra Singh on 20/10/25. +// + +import Foundation + +class HostToIpMapping { + static func string(for addressData: Data) throws -> String { + var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + + let result = addressData.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> Int32 in + guard let addrPtr = ptr.baseAddress else { + return EINVAL + } + return getnameinfo( + addrPtr.assumingMemoryBound(to: sockaddr.self), + socklen_t(addressData.count), + &hostBuffer, + socklen_t(hostBuffer.count), + nil, + 0, + NI_NUMERICHOST + ) + } + + if result != 0 { + let message = String(cString: gai_strerror(result)) + throw NSError(domain: "HostToIpMappingError", code: Int(result), userInfo: [ + NSLocalizedDescriptionKey: message + ]) + } + + return String(cString: hostBuffer) + } + + static func ipAddresses(for domainName: String) -> [String] { + let startTime = Date() + + // ✅ Correct initialization + let cfHostOpt: CFHost? = CFHostCreateWithName(kCFAllocatorDefault, domainName as CFString).takeRetainedValue() + guard let cfHost = cfHostOpt else { + print("HostToIpMapping: Failed to create CFHost for \(domainName)") + return [] + } + + var streamError = CFStreamError() + let success = CFHostStartInfoResolution(cfHost, .addresses, &streamError) + + if !success || streamError.error != 0 { + print("HostToIpMapping: Warning: failed to resolve addresses for \(domainName) with stream error \(streamError.error)") + return [] + } + + // ✅ Use takeUnretainedValue safely + guard let addressArray = CFHostGetAddressing(cfHost, nil)? + .takeUnretainedValue() as? [Data] else { + print("HostToIpMapping: Warning: failed to resolve addresses for \(domainName)") + return [] + } + + var stringAddresses: [String] = [] + + for addrData in addressArray { + do { + let ip = try string(for: addrData) + stringAddresses.append(ip) + } catch { + print("HostToIpMapping: Warning: Failed to parse IP struct for \(domainName) with error: \(error.localizedDescription)") + } + } + + let elapsed = Date().timeIntervalSince(startTime) + if elapsed > 2.5 { + print("HostToIpMapping: Warning: took \(elapsed) seconds to resolve \(domainName)") + } + + return stringAddresses + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift new file mode 100644 index 00000000..286fbf67 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift @@ -0,0 +1,219 @@ +/* + See the LICENSE.txt file for this sample’s licensing information. + + Abstract: + This file contains the implementation of the app <-> provider IPC connection + */ + +import Foundation +import os.log +import Network + +/// App --> Provider IPC +@objc protocol ProviderCommunication { + func register(_ completionHandler: @escaping (Bool) -> Void) + func setBlockedURLs(_ urls: [String]) + func setBlockedIPAddresses(_ ips: [String]) +} + +/// Provider --> App IPC +@objc protocol AppCommunication { + + func promptUser(aboutFlow flowInfo: [String: String], responseHandler: @escaping (Bool) -> Void) + func didSetUrls() +} + +enum FlowInfoKey: String { + case localPort + case remoteAddress +} + +/// The IPCConnection class is used by both the app and the system extension to communicate with each other +class IPCConnection: NSObject { + + // MARK: Properties + + var listener: NSXPCListener? + var currentConnection: NSXPCConnection? + weak var delegate: AppCommunication? + static let shared = IPCConnection() +// var blockedUrls: [String] = ProxyPreferences.getBlockedDomains() + var blockedUrls: [String] = [String]() + var blockedList = BlockOrAllowList(items: []) + var blockedIPAddresses: Set = [] + + // MARK: Methods + + /** + The NetworkExtension framework registers a Mach service with the name in the system extension's NEMachServiceName Info.plist key. + The Mach service name must be prefixed with one of the app groups in the system extension's com.apple.security.application-groups entitlement. + Any process in the same app group can use the Mach service to communicate with the system extension. + */ + private func extensionMachServiceName(from bundle: Bundle) -> String { + + guard let networkExtensionKeys = bundle.object(forInfoDictionaryKey: "NetworkExtension") as? [String: Any], + let machServiceName = networkExtensionKeys["NEMachServiceName"] as? String else { + fatalError("Mach service name is missing from the Info.plist") + } + + return machServiceName + } + + func startListener() { + + let machServiceName = extensionMachServiceName(from: Bundle.main) + os_log("[SC] 🔍] Starting XPC listener for mach service %@", machServiceName) + + let newListener = NSXPCListener(machServiceName: machServiceName) + newListener.delegate = self + newListener.resume() + listener = newListener + } + + /// This method is called by the app to register with the provider running in the system extension. + func register(withExtension bundle: Bundle, delegate: AppCommunication, completionHandler: @escaping (Bool) -> Void) { + + self.delegate = delegate + + guard currentConnection == nil else { + os_log("[SC] 🔍] Already registered with the provider") + completionHandler(true) + return + } + + let machServiceName = extensionMachServiceName(from: bundle) + let newConnection = NSXPCConnection(machServiceName: machServiceName, options: []) + + // The exported object is the delegate. + newConnection.exportedInterface = NSXPCInterface(with: AppCommunication.self) + newConnection.exportedObject = delegate + + // The remote object is the provider's IPCConnection instance. + newConnection.remoteObjectInterface = NSXPCInterface(with: ProviderCommunication.self) + + currentConnection = newConnection + newConnection.resume() + + guard let providerProxy = newConnection.remoteObjectProxyWithErrorHandler({ registerError in + os_log("[SC] 🔍] Failed to register with the provider: %@", registerError.localizedDescription) + self.currentConnection?.invalidate() + self.currentConnection = nil + completionHandler(false) + }) as? ProviderCommunication else { + fatalError("Failed to create a remote object proxy for the provider") + } + providerProxy.register(completionHandler) +// providerProxy.setBlockedURLs(blockedUrls) + } + + /** + This method is called by the provider to cause the app (if it is registered) to display a prompt to the user asking + for a decision about a connection. + */ + func promptUser(aboutFlow flowInfo: [String: String], responseHandler:@escaping (Bool) -> Void) -> Bool { + + guard let connection = currentConnection else { + os_log("[SC] 🔍] Cannot prompt user because the app isn't registered") + return false + } + + guard let appProxy = connection.remoteObjectProxyWithErrorHandler({ promptError in + os_log("[SC] 🔍] Failed to prompt the user: %@", promptError.localizedDescription) + self.currentConnection = nil + responseHandler(true) + }) as? AppCommunication else { + fatalError("Failed to create a remote object proxy for the app") + } + + appProxy.promptUser(aboutFlow: flowInfo, responseHandler: responseHandler) + + return true + } +} + +extension IPCConnection: NSXPCListenerDelegate { + + // MARK: NSXPCListenerDelegate + + func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { + + // The exported object is this IPCConnection instance. + newConnection.exportedInterface = NSXPCInterface(with: ProviderCommunication.self) + newConnection.exportedObject = self + + // The remote object is the delegate of the app's IPCConnection instance. + newConnection.remoteObjectInterface = NSXPCInterface(with: AppCommunication.self) + + newConnection.invalidationHandler = { + self.currentConnection = nil + } + + newConnection.interruptionHandler = { + self.currentConnection = nil + } + + currentConnection = newConnection + newConnection.resume() + + return true + } + + func enableURLBlocking(_ urls: [String]) { + os_log("[SC] 🔍] Enabling URL blocking") + guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in + os_log("[SC] 🔍] Failed to register with the provider: %@", registerError.localizedDescription) + }) as? ProviderCommunication else { + fatalError("Failed to create a remote object proxy for the provider") + } + providerProxy.setBlockedURLs(urls) + } + + func enableIPAddressesBlocking(_ urls: [String]) { + os_log("[SC] 🔍] Enabling URL blocking") + guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in + os_log("[SC] 🔍] Failed to register with the provider: %@", registerError.localizedDescription) + }) as? ProviderCommunication else { + fatalError("Failed to create a remote object proxy for the provider") + } + providerProxy.setBlockedIPAddresses(urls) + } +} + + +extension IPCConnection: ProviderCommunication { + + func setBlockedIPAddresses(_ ips: [String]) { + blockedIPAddresses = Set(ips) + os_log("[SC] 🔍] setBlockedIPAddresses: %{public}@", blockedIPAddresses) + } + + func setBlockedURLs(_ urls: [String]) { + os_log("[SC] 🔍] Blocking: %{public}@",urls) + blockedUrls = urls +// delegate?.didSetUrls() + blockedList = BlockOrAllowList(items: blockedUrls) + + guard let connection = currentConnection else { + print("[SC] 🔍] Cannot update blocked urls, app isn't registered") + return + } + + guard let appProxy = connection.remoteObjectProxyWithErrorHandler({ promptError in + os_log("[SC] 🔍] Failed to prompt the user: %@", promptError.localizedDescription) +// self.currentConnection = nil +// responseHandler(true) + }) as? AppCommunication else { + fatalError("Failed to create a remote object proxy for the app") + } + appProxy.didSetUrls() + } + + + // MARK: ProviderCommunication + + func register(_ completionHandler: @escaping (Bool) -> Void) { + + os_log("[SC] 🔍] App registered") + completionHandler(true) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Info.plist b/Self-Control-Extension/SelfControl/SelfControlExtension/Info.plist new file mode 100644 index 00000000..cf77feef --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/Info.plist @@ -0,0 +1,16 @@ + + + + + NetworkExtension + + NEMachServiceName + $(TeamIdentifierPrefix)com.application.SelfControl.corebits.network + NEProviderClasses + + com.apple.networkextension.filter-data + $(PRODUCT_MODULE_NAME).FilterDataProvider + + + + diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/PlistListner.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/PlistListner.swift new file mode 100644 index 00000000..d31f9467 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/PlistListner.swift @@ -0,0 +1,58 @@ +// +// PlistListner.swift +// SelfControlExtension +// +// Created by Satendra Singh on 16/08/25. +// + +import Foundation +import Network +import os.log + +final class PlistListner: NSObject, ObservableObject { + + var listener: NWListener? = try! NWListener(using: .tcp, on: 8080) + var blockeddomainFetcher: (() -> [String])? + + func startListening() { + os_log("[SC] 🔍] NW startListening") +// let objects: [[String: Any]] = [ +// ["id": 1, "name": "Alice", "status": "running"], +// ["id": 2, "name": "Bob", "status": "stopped"], +// ["id": 3, "name": "Charlie", "status": "idle"] +// ] + listener = try! NWListener(using: .tcp, on: 8080) + listener?.newConnectionHandler = { conn in + let blockedDomainList: [String] = self.blockeddomainFetcher?() ?? [] + let blockedUrls = ["blocked": blockedDomainList] + let jsonData = try! JSONSerialization.data(withJSONObject: blockedUrls, options: []) + let jsonString = String(data: jsonData, encoding: .utf8)! + + os_log("[SC] 🔍] NW newConnectionHandler") + conn.start(queue: DispatchQueue.global(qos: .userInitiated)) +// conn.receiveMessage { data, _, _, _ in + os_log("[SC] 🔍] NW conn.receiveMessage") + let response = """ + HTTP/1.1 200 OK\r + Content-Type: application/json\r + Access-Control-Allow-Origin: *\r + \r + \(jsonString) + """ + conn.send(content: response.data(using: .utf8), contentContext: .finalMessage , completion: .contentProcessed { error in + os_log("[SC] 🔍] NW Sent response:\(error)") +// conn.cancel() + + }) +// } + + conn.stateUpdateHandler = { state in + if state == .ready { + os_log("[SC] 🔍] NW stateUpdateHandler ready") + } + } + } + listener?.start(queue: DispatchQueue.global(qos: .userInitiated)) +// RunLoop.main.run() + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Binary.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Binary.swift new file mode 100644 index 00000000..3397339e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Binary.swift @@ -0,0 +1,175 @@ +// +// Binary.swift +// SelfControl +// +// Created by Satendra Singh on 10/10/25. +// + +import Foundation +import AppKit +import OSLog +import Security +import CoreServices // for MDItem + +@objcMembers +class Binary: NSObject { + + // MARK: - Properties (mirroring Binary.h) + + dynamic var path: String + dynamic var name: String = "" + dynamic var icon: NSImage = NSImage() + dynamic var attributes: NSDictionary? + dynamic var metadata: NSDictionary? + dynamic var bundle: Bundle? + dynamic var csInfo: NSMutableDictionary = NSMutableDictionary() + dynamic var sha256: NSMutableString = NSMutableString() + + // MARK: - Init + + init(_ path: String) { + self.path = (path as NSString).resolvingSymlinksInPath + super.init() + + // Try load app bundle (nil for non-apps) + self.getBundle() + + // Get name + self.getName() + + // File attributes + self.getAttributes() + + // Spotlight metadata + self.getMetadata() + } + + // MARK: - Bundle + + // Try load app bundle; nil for non-apps + func getBundle() { + // First try direct path + if let b = Bundle(path: self.path) { + self.bundle = b + return + } + // Else find dynamically + self.bundle = Utilities().findAppBundle(self.path) + } + + // MARK: - Name + + // Figure out binary's name via bundle CFBundleName or lastPathComponent + func getName() { + if let bundle = self.bundle, + let bundleName = bundle.infoDictionary?["CFBundleName"] as? String { + self.name = bundleName + } else { + self.name = (self.path as NSString).lastPathComponent + } + } + + // MARK: - Attributes + + func getAttributes() { + self.attributes = try? FileManager.default.attributesOfItem(atPath: self.path) as NSDictionary + } + + // MARK: - Spotlight metadata + + func getMetadata() { + // MDItemCreate wants CFString path + let cfPath = self.path as CFString + guard let mdItem = MDItemCreate(kCFAllocatorDefault, cfPath) else { + return + } + defer { } + + guard let attributeNames = MDItemCopyAttributeNames(mdItem) else { + return + } + defer { } + + if let attrs = MDItemCopyAttributes(mdItem, attributeNames) { + self.metadata = attrs as NSDictionary + } + } + + // MARK: - Icon + + // Get an icon for the process: app’s icon or system one + func getIcon() { + // Skip short/non-absolute paths if no bundle (system logs errors otherwise) + if !self.path.hasPrefix("/"), self.bundle == nil { + return + } + + // For apps, try CFBundleIconFile + if let bundle = self.bundle { + if let iconFile = bundle.infoDictionary?["CFBundleIconFile"] as? String { + let iconExt = (iconFile as NSString).pathExtension.isEmpty ? "icns" : (iconFile as NSString).pathExtension + let iconBase = (iconFile as NSString).deletingPathExtension + if let iconPath = bundle.path(forResource: iconBase, ofType: iconExt) { + if let img = NSImage(contentsOfFile: iconPath) { + self.icon = img + } + } + } + } + + // Fallback to workspace icon + if self.bundle == nil || self.icon.size == .zero { + self.icon = NSWorkspace.shared.icon(forFile: self.path) + } + + // Standard size 128x128 + self.icon.size = NSSize(width: 128, height: 128) + } + + // MARK: - Code signing (static) + + // You likely have this helper already, but defining its expected signature for clarity: + func extractSigningInfo(_ code: SecStaticCode?, _ path: String, _ flags: SecCSFlags) -> [String: Any]? { + var staticCode: SecStaticCode? + let url = URL(fileURLWithPath: path) as CFURL + let status = SecStaticCodeCreateWithPath(url, SecCSFlags(), &staticCode) + guard status == errSecSuccess, let codeRef = staticCode else { + return nil + } + + var signingInfo: CFDictionary? + let infoStatus = SecCodeCopySigningInformation(codeRef, flags, &signingInfo) + guard infoStatus == errSecSuccess, let info = signingInfo as? [String: Any] else { + return nil + } + return info + } + + // Your corrected function: + func generateSigningInfo(_ flags: SecCSFlags) { + // These constants are C macros in Security framework headers, not imported automatically: + let kSecCodeInfoStatus = "Status" // corresponds to kSecCodeInfoStatus in Security framework + let KEY_CS_STATUS = kSecCodeInfoStatus // for compatibility with your existing code + + // Instead of 'nil', explicitly pass an optional SecStaticCode? = nil + if let extracted = extractSigningInfo(nil as SecStaticCode?, self.path, flags), + let statusNum = extracted[KEY_CS_STATUS] as? NSNumber, + statusNum.intValue == Int(noErr) { + // Bridge [String: Any] to NSMutableDictionary + self.csInfo = NSMutableDictionary(dictionary: extracted) + } else { + // logging commented out in original code + } + } + + // MARK: - Description + + override var description: String { + return String(format: "name: %@\npath: %@\nattributes: %@\nsigning info: %@\nmetadata: %@", + self.name, + self.path, + self.attributes ?? [:], + self.csInfo, + self.metadata ?? [:]) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Process.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Process.swift new file mode 100644 index 00000000..823bb5b2 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Process.swift @@ -0,0 +1,330 @@ +// +// Process.swift +// SelfControl +// +// Created by Satendra Singh on 10/10/25. +// + +import Foundation +import OSLog +import Security +import Darwin +import NetworkExtension + +@objcMembers +class Process: NSObject { + + // MARK: - Properties (mirroring Process.h) + + dynamic var pid: pid_t = -1 + dynamic var uid: uid_t = UInt32(bitPattern: -1) + dynamic var type: UInt16 = 0 + dynamic var exit: UInt32 = UInt32(bitPattern: -1) + dynamic var deleted: Bool = false + + dynamic var name: String? + dynamic var path: String? + dynamic var arguments: NSMutableArray? = NSMutableArray() + dynamic var ancestors: NSMutableArray? = NSMutableArray() + dynamic var csInfo: NSMutableDictionary? + dynamic var key: String = "" + dynamic var bundleID: String = "" + dynamic var binary: Binary = Binary.init("") + dynamic var timestamp: Date = Date() + + // MARK: - Init + + override init() { + super.init() + self.arguments = NSMutableArray() + self.ancestors = NSMutableArray() + self.timestamp = Date() + self.pid = -1 + self.uid = UInt32(bitPattern: -1) + self.exit = UInt32(bitPattern: -1) + } + + // Init with audit token pointer + // Matches -(id)init:(audit_token_t*)token + convenience init?(_ token: UnsafePointer) { + self.init() + + // Save pid + let tokenVal = token.pointee + let procPID = audit_token_to_pid(tokenVal) + self.pid = procPID + if self.pid == 0 { +// os_log_error(logHandle, "ERROR: 'audit_token_to_pid' returned NULL") + return nil + } + + // Get path via SecCode (also sets deleted flag) + self.getPath(token) + if (self.path ?? "").isEmpty { +// os_log_error(logHandle, "ERROR: failed to find path for process %d", self.pid) + return nil + } + + // Set name + if self.deleted != true { + self.name = Utilities().getProcessName(0, self.path!) + } else { + self.name = Utilities().getProcessName(self.pid, self.path!) + } + + // Get user + self.uid = audit_token_to_euid(tokenVal) + self.bundleID = Utilities().getBundleID(self.path ?? "") ?? "" + // Generate (dynamic) code information + self.generateSigningInfo(token) + + // Generate key + self.key = self.generateKey() + + // Init binary + self.binary = Binary(self.path ?? "") + + // PID specific logic: args and ancestors + self.getArgs() + self.ancestors = Utilities().generateProcessHierarchy(self.pid) + + // Verify pid-version (avoid pid reuse) + if let currentTokenNSData = Utilities().tokenForPid(self.pid), + currentTokenNSData.length == MemoryLayout.size { + let currentTokenData = Data(referencing: currentTokenNSData) + currentTokenData.withUnsafeBytes { (rawPtr: UnsafeRawBufferPointer) in + if let curPtr = rawPtr.baseAddress?.assumingMemoryBound(to: audit_token_t.self) { + let origVersion = audit_token_to_pidversion(tokenVal) + let curVersion = audit_token_to_pidversion(curPtr.pointee) + if origVersion != curVersion { +// os_log_error(logHandle, "ERROR: audit token mismatch ...pid re-used?") + self.arguments = nil + self.ancestors = nil + } + } + } + } + + // Process alive check + if Utilities().isAlive(self.pid) != true { +// os_log_error(logHandle, "ERROR: process (%d)%{public}@ has already exited", self.pid, self.path ?? "") + return nil + } + } + + // MARK: - Key generation + + // Matches -(NSString*)generateKey + func generateKey() -> String { + var id: String = "" + var signer: Int = 0 + enum Signer: Int { + case None = 0 + case Apple + case AppStore + case DevID + case AdHoc + } + + if let cs = self.csInfo { + if let v = cs[Constants.KEY_CS_SIGNER] as? NSNumber { + signer = v.intValue + } + + // Apple/App Store: just use cs id + if signer == Signer.Apple.rawValue || signer == Signer.AppStore.rawValue { + if let csid = cs[Constants.KEY_CS_ID] as? String, !csid.isEmpty { + id = csid + } + } + // Dev ID: use cs id + leaf signer + else if signer == Int(Signer.DevID.rawValue) { + if let csid = cs[Constants.KEY_CS_ID] as? String, !csid.isEmpty, + let auths = cs[Constants.KEY_CS_AUTHS] as? [Any], let first = auths.first as? String, !first.isEmpty { + id = "\(csid):\(first)" + } + } + } + + if id.isEmpty { + id = self.path ?? "" + } + +// os_log_debug(logHandle, "generated process key: %{public}@", id) + return id + } + + // MARK: - Path + + // Matches -(void)getPath:(audit_token_t*)token + func getPath(_ token: UnsafePointer) { + var status: OSStatus = errSecParam + var code: SecCode? + var staticCode: SecStaticCode? + var pathURL: CFURL? + + // SecCodeCopyGuestWithAttributes using audit token + let attrs: [String: Any] = [kSecGuestAttributeAudit as String: Data(bytes: token, count: MemoryLayout.size)] + status = SecCodeCopyGuestWithAttributes(nil, attrs as CFDictionary, SecCSFlags(), &code) + if status == errSecSuccess, let code = code { + // Convert dynamic code (SecCode) to static code (SecStaticCode) + status = SecCodeCopyStaticCode(code, SecCSFlags(), &staticCode) + if status == errSecSuccess, let staticCode = staticCode { + status = SecCodeCopyPath(staticCode, SecCSFlags(), &pathURL) + if status == errSecSuccess, let url = pathURL as NSURL? { + self.path = (url as URL).path + } else { +// os_log_error(logHandle, "ERROR: 'SecCodeCopyPath' failed with': %#x", status) + } + } else { +// os_log_error(logHandle, "ERROR: 'SecCodeCopyStaticCode' failed with': %#x", status) + } + } else { +// os_log_error(logHandle, "ERROR: 'SecCodeCopyGuestWithAttributes' failed with': %#x", status) + } + + // Deleted binary? + if status == OSStatus(kPOSIXErrorENOENT) { +// os_log_debug(logHandle, "process %d's binary appears to be deleted", self.pid) + self.deleted = true + } + + // Fallback + if pathURL == nil { + self.path = Utilities().getProcessPath(self.pid) + } + + if let p = self.path { + self.path = (p as NSString).resolvingSymlinksInPath + } + + // No CFRelease in Swift ARC; CoreFoundation objects are memory-managed automatically. + } + + // MARK: - Args + + // Matches -(void)getArgs + func getArgs() { + var mib: [Int32] = [CTL_KERN, KERN_ARGMAX, 0] + var systemMaxArgs: Int32 = 0 + var size = MemoryLayout.size(ofValue: systemMaxArgs) + + // Get system arg max + if sysctl(&mib, 2, &systemMaxArgs, &size, nil, 0) == -1 { + return + } + + guard systemMaxArgs > 0 else { return } + let bufSize = Int(systemMaxArgs) + let processArgs = UnsafeMutablePointer.allocate(capacity: bufSize) + defer { processArgs.deallocate() } + + mib = [CTL_KERN, KERN_PROCARGS2, Int32(self.pid)] + size = bufSize + + if sysctl(&mib, 3, processArgs, &size, nil, 0) == -1 { + return + } + + if size <= MemoryLayout.size { + return + } + + // numberOfArgs at start + var numberOfArgs: Int32 = 0 + memcpy(&numberOfArgs, processArgs, MemoryLayout.size) + + // parser after numberOfArgs + var parser = processArgs.advanced(by: MemoryLayout.size) + let endPtr = processArgs.advanced(by: size) + + // Skip executable path (NULL-terminated) + while parser < endPtr { + if parser.pointee == 0 { break } + parser = parser.advanced(by: 1) + } + if parser == endPtr { return } + + // Skip trailing NULLs to argv[0] + while parser < endPtr { + if parser.pointee != 0 { break } + parser = parser.advanced(by: 1) + } + if parser == endPtr { return } + + // Now parse args until count reached + var argStart = parser + let argsArray = NSMutableArray() + while parser < endPtr { + if parser.pointee == 0 { + if let arg = String(validatingUTF8: UnsafePointer(argStart)) { + argsArray.add(arg) + } + parser = parser.advanced(by: 1) + argStart = parser + if argsArray.count == Int(numberOfArgs) { + break + } + continue + } + parser = parser.advanced(by: 1) + } + + self.arguments = argsArray + } + + // MARK: - Code signing + + // Local key mapping for Security's kSecCodeInfoStatus + private let KEY_CS_STATUS = "Status" + + // Helper to extract signing info from an audit token (dynamic code) + private func extractSigningInfo(_ token: UnsafeMutablePointer, + _ requirement: SecRequirement?, + _ flags: SecCSFlags) -> NSMutableDictionary? { + // Build attributes with audit token + let attrs: [String: Any] = [kSecGuestAttributeAudit as String: + Data(bytes: token, count: MemoryLayout.size)] + var code: SecCode? + var status = SecCodeCopyGuestWithAttributes(nil, attrs as CFDictionary, flags, &code) + guard status == errSecSuccess, let codeRef = code else { + return nil + } + + // Convert SecCode (dynamic) to SecStaticCode before querying signing info + var staticCode: SecStaticCode? + status = SecCodeCopyStaticCode(codeRef, flags, &staticCode) + guard status == errSecSuccess, let staticCodeRef = staticCode else { + return nil + } + + var info: CFDictionary? + status = SecCodeCopySigningInformation(staticCodeRef, flags, &info) + guard status == errSecSuccess, let dict = info as? [String: Any] else { + return nil + } + return NSMutableDictionary(dictionary: dict) + } + + // Matches -(void)generateSigningInfo:(audit_token_t*)token + func generateSigningInfo(_ token: UnsafePointer) { + // Pass typed nil for requirement to avoid "nil requires a contextual type" + let requirement: SecRequirement? = nil + if let extracted = extractSigningInfo(UnsafeMutablePointer(mutating: token), requirement, SecCSFlags()) { + if let statusNum = extracted[KEY_CS_STATUS] as? NSNumber, statusNum.intValue == Int(noErr) { + self.csInfo = extracted + } else { +// os_log_error(logHandle, "ERROR: invalid code signing information for %{public}@: %{public}@", self.path ?? "", extracted) + } + } else { +// os_log_error(logHandle, "ERROR: failed to extract code signing information for %{public}@", self.path ?? "") + } + } + + // MARK: - Description + + override var description: String { + return String(format: "pid: %d\npath: %@\nuser: %d\nargs: %@\nancestors: %@\n signing info: %@\n binary:\n%@", self.pid, self.path ?? "nil", self.uid, self.arguments ?? [], self.ancestors ?? [], self.csInfo ?? [:], self.binary.description) + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/SourceAppAuditTokenBuilder.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/SourceAppAuditTokenBuilder.swift new file mode 100644 index 00000000..68b96414 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/SourceAppAuditTokenBuilder.swift @@ -0,0 +1,61 @@ +// +// SourceAppAuditTokenBuilder.swift +// SelfControlExtension +// +// Created by Satendra Singh on 10/10/25. +// + +import NetworkExtension +import Security +import Foundation +import AppKit + +struct SourceAppAuditTokenBuilder { + + static func getAppInfo(from flow: NEFilterFlow) -> (bundleID: String?, appName: String?) { + guard let auditTokenData = flow.sourceAppAuditToken else { + return (nil, nil) + } + + // Convert NSData → audit_token_t + let auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in + return ptr.load(as: audit_token_t.self) + } + + // MARK: 1. Extract PID from audit token + let pid = audit_token_to_pid(auditToken) + + // MARK: 2. Use SecTask to get bundle identifier + var bundleID: String? = nil + if let secTask = SecTaskCreateWithAuditToken(nil, auditToken) { + // macOS doesn’t define kSecEntitlementApplicationIdentifier constant, + // so use the raw entitlement string instead: + let entitlementKey = "application-identifier" as CFString + bundleID = SecTaskCopyValueForEntitlement(secTask, entitlementKey, nil) as? String + } + + // MARK: 3. Try to get app name from bundle ID + var appName: String? = nil + if let bundleID = bundleID, + let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID) { + appName = appURL.deletingPathExtension().lastPathComponent + } + + // MARK: 4. Fallback: get executable name via proc_pidpath() + if appName == nil { + var pathBuffer = [CChar](repeating: 0, count: Int(PATH_MAX)) + let result = proc_pidpath(pid, &pathBuffer, UInt32(pathBuffer.count)) + if result > 0 { + let path = String(cString: pathBuffer) + appName = URL(fileURLWithPath: path).lastPathComponent + } + } + + return (bundleID, appName) + } + + // Helper: extract PID from audit_token_t + private static func audit_token_to_pid(_ token: audit_token_t) -> pid_t { + return pid_t(token.val.0) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Utilities.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Utilities.swift new file mode 100644 index 00000000..0ee82162 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Utilities.swift @@ -0,0 +1,714 @@ +// +// File.swift +// SelfControl +// +// Created by Satendra Singh on 10/10/25. +// + + +// Swift port of utilities.m +// +// Note: This file relies on C system APIs. Ensure your bridging header +// imports the needed headers: libproc.h, sys/sysctl.h, CommonCrypto/CommonCrypto.h, +// mach/mach.h, Security/Security.h, Carbon/Carbon.h, CoreServices/CoreServices.h, CFNetwork/CFHost.h +// + +import AppKit +import Foundation +import OSLog +import SystemConfiguration +import Darwin +import CoreServices // for MDItem +import Security +import os.log +import os + +// Define C macro from libproc.h (not imported automatically into Swift) +private let PROC_PIDPATHINFO_MAXSIZE: Int = 4096 + +// MARK: - Globals + +final class Utilities { + // Provided elsewhere in project + + let logger = Logger(subsystem: "com.example.myapp", category: "network") + + // MARK: - Version / Bundle + + @objc + public func getAppVersion() -> String? { + return Bundle.main.infoDictionary?["CFBundleVersion"] as? String + } + + @objc + public func getBundleExecutable(_ appPath: String) -> String? { + guard let bundle = Bundle(path: appPath) else { + logger.error("ERROR: failed to load app bundle for %{public}\(appPath)") + return nil + } + return (bundle.executablePath as NSString?)?.resolvingSymlinksInPath + } + + // MARK: - Parent process (Carbon/deprecated) + + @objc + public func getRealParent(_ pid: pid_t) -> NSDictionary? { + let PROC_PIDPATHINFO_MAXSIZE = 4096 + + // Step 1: Get parent PID + var info = kinfo_proc() + var size = MemoryLayout.stride + var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid] + + guard sysctl(&mib, u_int(mib.count), &info, &size, nil, 0) == 0 else { + return nil + } + + let ppid = info.kp_eproc.e_ppid + if ppid == 0 { return nil } + + // Step 2: Get parent process path + var pathBuffer = [CChar](repeating: 0, count: Int(PROC_PIDPATHINFO_MAXSIZE)) + let result = proc_pidpath(ppid, &pathBuffer, UInt32(pathBuffer.count)) + let path = (result > 0) ? String(cString: pathBuffer) : nil + + // Step 3: Try to get bundle info via NSRunningApplication + var appName: String? = nil + var bundleID: String? = nil + + if let app = NSRunningApplication(processIdentifier: ppid) { + appName = app.localizedName + bundleID = app.bundleIdentifier + } + + // Step 4: Construct NSDictionary for compatibility + let dict: NSDictionary = [ + "ParentPID": NSNumber(value: ppid), + "ParentPath": path ?? "", + "ParentAppName": appName ?? "", + "ParentBundleID": bundleID ?? "" + ] + + return dict + } + + + // MARK: - Process hierarchy + + @objc + public func generateProcessHierarchy(_ child: pid_t) -> NSMutableArray { + let ancestors = NSMutableArray() + typealias RPIDFunc = @convention(c) (pid_t) -> pid_t + let rpidSym = dlsym(dlopen(nil, RTLD_NOW), "responsibility_get_pid_responsible_for_pid") + let getRPID = rpidSym.map { unsafeBitCast($0, to: RPIDFunc.self) } + + var currentPID = child + + while true { + let currentPath = getProcessPath(currentPID) ?? NSLocalizedString("unknown", comment: "unknown") + let currentName = getProcessName(0, currentPath) ?? NSLocalizedString("unknown", comment: "unknown") + ancestors.insert([ + Constants.KEY_PROCESS_ID: NSNumber(value: currentPID), + Constants.KEY_PROCESS_PATH: currentPath, + Constants.KEY_PROCESS_NAME: currentName + ], at: 0) + + var parentPID: pid_t = 0 + + if getuid() != 0 { + if let parent = getRealParent(currentPID), let p = parent["pid"] as? NSNumber { + parentPID = pid_t(truncating: p) + } + } + + if parentPID == 0, let getRPID = getRPID { + parentPID = getRPID(currentPID) + } + + if parentPID <= 0 || parentPID == currentPID { + parentPID = getParent(Int32(currentPID)) + } + + if parentPID <= 0 || parentPID == currentPID { + break + } + + currentPID = parentPID + } + + // add KEY_INDEX for UI + for i in 0.. String? { + var uid: uid_t = 0 + var gid: gid_t = 0 + + // explicitly type `nil` as `SCDynamicStore?` + if let cfUser = SCDynamicStoreCopyConsoleUser(nil as SCDynamicStore?, &uid, &gid) { + return cfUser as String + } + return nil + } + + // MARK: - Process name + + @objc + public func getProcessName(_ pid: pid_t, _ path: String) -> String? { + + let PROC_PIDPATHINFO_MAXSIZE = 4096 // actual constant value from libproc.h + + if pid != 0 { + var nameBuf = [CChar](repeating: 0, count: Int(PROC_PIDPATHINFO_MAXSIZE)) + let status = proc_name(pid, &nameBuf, UInt32(nameBuf.count)) + if status >= 0 { + return String(cString: nameBuf) + } + } + + if let bundle = findAppBundle(path), + let name = bundle.infoDictionary?["CFBundleName"] as? String { + return name + } + + return (path as NSString).lastPathComponent + } + + @objc + public func getBundleID(_ path: String) -> String? { + if let bundle = findAppBundle(path), + let name = bundle.infoDictionary?["CFBundleIdentifier"] as? String { + return name + } + return nil + } + + // MARK: - Find app bundle + + @objc + public func findAppBundle(_ path: String) -> Bundle? { + let standardized = ((path as NSString).standardizingPath as NSString).resolvingSymlinksInPath + var appPath: NSString = standardized as NSString + + while true { + if let bundle = Bundle(path: appPath as String) { + if bundle.bundlePath == standardized { return bundle } + if bundle.executablePath == standardized { return bundle } + } + + let next = appPath.deletingLastPathComponent as NSString + if next.length == 0 || next as String == "/" { break } + appPath = next + } + + return nil + } + + // MARK: - Process path + + @objc + public func getProcessPath(_ pid: pid_t) -> String? { + var buf = [CChar](repeating: 0, count: Int(PROC_PIDPATHINFO_MAXSIZE)) + let status = proc_pidpath(pid, &buf, UInt32(buf.count)) + if status > 0 { + return String(cString: buf) + } else { +// os_log_error(logHandle, "ERROR: for process %d, 'proc_pidpath' failed with %d (errno: %d)", pid, status, errno) + + var mib = [CTL_KERN, KERN_ARGMAX, 0] + var argMax: Int = 0 + var size = MemoryLayout.size(ofValue: argMax) + if sysctl(&mib, 2, &argMax, &size, nil, 0) == -1 { return nil } + + let argBuf = UnsafeMutablePointer.allocate(capacity: argMax) + defer { argBuf.deallocate() } + + mib = [CTL_KERN, KERN_PROCARGS2, Int32(pid)] + size = argMax + if sysctl(&mib, 3, argBuf, &size, nil, 0) == -1 { return nil } + if size <= MemoryLayout.size { return nil } + + // argv0 path follows int argc + let pathPtr = argBuf.advanced(by: MemoryLayout.size) + let p = String(cString: pathPtr) + + if p.hasPrefix("./") { + let trimmed = String(p.dropFirst(2)) + if let cwd = getProcessCWD(pid) { + return (cwd as NSString).appendingPathComponent(trimmed) + } + } + + return p + } + } + + // MARK: - CWD + + @objc + public func getProcessCWD(_ pid: pid_t) -> String? { + var vpi = proc_vnodepathinfo() + let status = withUnsafeMutablePointer(to: &vpi) { ptr -> Int32 in + return proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, ptr, Int32(MemoryLayout.size)) + } + if status > 0 { + return withUnsafePointer(to: vpi.pvi_cdir.vip_path) { ptr in + return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)) + } + } + return nil + } + + // MARK: - PIDs for path/user + + @objc + public func getProcessIDs(_ processPath: String, _ userID: Int32) -> NSMutableArray { + let result = NSMutableArray() + + let count = proc_listallpids(nil, 0) + guard count > 0 else { return result } + + let pids = UnsafeMutablePointer.allocate(capacity: Int(count)) + defer { pids.deallocate() } + + let status = proc_listallpids(pids, count * Int32(MemoryLayout.size)) + guard status >= 0 else { return result } + + var mib = [CTL_KERN, KERN_PROC, KERN_PROC_PID, 0] + var kproc = kinfo_proc() + let kprocSize = MemoryLayout.size + + for i in 0.. NSImage? { + // invalid path -> generic application icon + guard FileManager.default.fileExists(atPath: path) else { + let icon = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kGenericApplicationIcon))) + icon.size = NSSize(width: 128, height: 128) + return icon + } + + if let bundle = findAppBundle(path) { + if let icon = NSWorkspace.shared.icon(forFile: bundle.bundlePath) as NSImage? { + return icon + } + + if let iconFile = bundle.infoDictionary?["CFBundleIconFile"] as? NSString { + var ext = iconFile.pathExtension + if ext.isEmpty { ext = "icns" } + if let iconPath = bundle.path(forResource: iconFile.deletingPathExtension, ofType: ext) { + return NSImage(contentsOfFile: iconPath) + } + } + } + + var icon = NSWorkspace.shared.icon(forFile: path) + // replace generic document icon with application icon + let documentIcon = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kGenericDocumentIcon))) + if icon == documentIcon { + icon = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kGenericApplicationIcon))) + } + icon.size = NSSize(width: 128, height: 128) + return icon + } + + // MARK: - Make modal + + @objc + public func makeModal(_ controller: NSWindowController) { + var window: NSWindow? + + for _ in 0..<20 { + DispatchQueue.main.sync { + window = controller.window + } + if window == nil { + Thread.sleep(forTimeInterval: 0.05) + continue + } + DispatchQueue.main.sync { + NSApplication.shared.runModal(for: controller.window!) + } + break + } + } + + // MARK: - Find processes by name + + @objc + public func findProcesses(_ processName: String) -> NSMutableArray { + let processes = NSMutableArray() + let count = proc_listpids(UInt32(PROC_ALL_PIDS), 0, nil, 0) + guard count > 0 else { return processes } + + let pids = UnsafeMutablePointer.allocate(capacity: Int(count)) + defer { pids.deallocate() } + + let status = proc_listpids(UInt32(PROC_ALL_PIDS), 0, pids, count * Int32(MemoryLayout.size)) + guard status >= 0 else { return processes } + + for i in 0.. Date? { +// os_log_debug(logHandle, "extracting 'kMDItemDateAdded' for %{public}@", file) +// +// let url: URL +// if let bundle = findAppBundle(file) { +// url = bundle.bundleURL +// } else { +// url = URL(fileURLWithPath: file) +// } +// +// guard let item = MDItemCreateWithURL(nil, url as CFURL) else { return nil } +// defer { CFRelease(item) } +// +// if let date = MDItemCopyAttribute(item, kMDItemDateAdded)?.takeRetainedValue() as? Date { +// os_log_debug(logHandle, "extacted date, %{public}@, for %{public}@", String(describing: date), file) +// return date +// } else { +// os_log_debug(logHandle, "'kMDItemDateAdded' is nil ...falling back to 'kMDItemFSCreationDate'") +// if let date = MDItemCopyAttribute(item, kMDItemFSCreationDate)?.takeRetainedValue() as? Date { +// os_log_debug(logHandle, "extacted date, %{public}@, for %{public}@", String(describing: date), file) +// return date +// } +// } +// return nil +// } + + // MARK: - Parent PID + + @objc + public func getParent(_ pid: Int32) -> pid_t { + var kproc = kinfo_proc() + var size = MemoryLayout.size + var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid] // 👈 consistent Int32s + let res = sysctl(&mib, u_int(mib.count), &kproc, &size, nil, 0) + if res == 0 && size != 0 { + let ppid = kproc.kp_eproc.e_ppid +// os_log_debug(logHandle, "extracted parent ID %d for process: %d", ppid, pid) + return ppid + } + return -1 + } + + // MARK: - Dark mode + + @objc + public func isDarkMode() -> Bool { + return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" + } + + // MARK: - String default + + @objc + public func valueForStringItem(_ item: String?) -> String { + return item ?? NSLocalizedString("unknown", comment: "unknown") + } + + // MARK: - Alerts + + @objc + public func showAlert(_ style: NSAlert.Style, _ messageText: String, _ informativeText: String?, _ buttons: [String]) -> NSApplication.ModalResponse { + let alert = NSAlert() + alert.alertStyle = style + alert.messageText = messageText + if let info = informativeText { + alert.informativeText = info + } + for title in buttons { + alert.addButton(withTitle: title) + } + if let first = alert.buttons.first { + first.keyEquivalent = "\r" + } + + NSApp.setActivationPolicy(.regular) + if #available(macOS 14.0, *) { + NSApp.activate() + } else { + NSApp.activate(ignoringOtherApps: true) + } + NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) + + alert.window.makeKeyAndOrderFront(nil) + alert.window.center() + + let response = alert.runModal() +// (NSApp.delegate as? AppDelegate)?.setActivationPolicy() + return response + } + + // MARK: - Audit token for pid + + // Swift doesn't expose this C macro — we define it manually. + let TASK_AUDIT_TOKEN_COUNT = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) + + @objc + public func tokenForPid(_ pid: pid_t) -> NSData? { + var task: mach_port_t = 0 + var token = audit_token_t() + var size = TASK_AUDIT_TOKEN_COUNT + +// os_log_debug(logHandle, "retrieving audit token for %d", pid) + + var kr = task_name_for_pid(mach_task_self_, pid, &task) + guard kr == KERN_SUCCESS else { +// os_log_error(logHandle, "ERROR: 'task_name_for_pid' failed with %x", kr) + return nil + } + defer { mach_port_deallocate(mach_task_self_, task) } + + kr = withUnsafeMutablePointer(to: &token) { ptr in + ptr.withMemoryRebound(to: integer_t.self, capacity: Int(size)) { intPtr in + return task_info(task, task_flavor_t(TASK_AUDIT_TOKEN), intPtr, &size) + } + } + + guard kr == KERN_SUCCESS else { +// os_log_error(logHandle, "ERROR: 'task_info' failed with %x", kr) + return nil + } + +// os_log_debug(logHandle, "retrieved audit token") + return NSData(bytes: &token, length: MemoryLayout.size) + } + + // MARK: - Reverse DNS resolve + + @objc + public func resolveAddress(_ ipAddr: String) -> NSArray? { +// os_log_debug(logHandle, "(attempting to) reverse resolve %{public}@", ipAddr) + + var hints = addrinfo(ai_flags: AI_NUMERICHOST, ai_family: PF_UNSPEC, ai_socktype: SOCK_STREAM, ai_protocol: 0, ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil) + var res: UnsafeMutablePointer? + guard getaddrinfo(ipAddr, nil, &hints, &res) == 0, let result = res else { + return nil + } + defer { freeaddrinfo(result) } + + guard let addrData = CFDataCreate(nil, UnsafePointer(OpaquePointer(result.pointee.ai_addr)), CFIndex(result.pointee.ai_addrlen)) else { + return nil + } + defer { /*addrData*/ } + + let host = CFHostCreateWithAddress(kCFAllocatorDefault, addrData).takeRetainedValue() + var streamErr = CFStreamError() + guard CFHostStartInfoResolution(host, .names, &streamErr) else { + return nil + } + guard let names = CFHostGetNames(host, nil)?.takeUnretainedValue() as NSArray? else { + return nil + } + return names + } + + // MARK: - Process alive + + @objc + public func isAlive(_ processID: pid_t) -> Bool { + errno = 0 + _ = kill(processID, 0) + if errno == ESRCH { return false } + + var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, Int32(processID)] + var info = kinfo_proc() + var size = MemoryLayout.size + if sysctl(&mib, 4, &info, &size, nil, 0) == 0 { + // SZOMB check + if (UInt8(info.kp_proc.p_stat) & UInt8(SZOMB)) == UInt8(SZOMB) { + return false + } + } + return true + } + + // MARK: - Simulator app? + + @objc + public func isSimulatorApp(_ path: String) -> Bool { +// os_log_debug(logHandle, "checking if %{public}@ is a simulator application", path) + guard let bundle = findAppBundle(path) else { return false } + guard let platforms = bundle.infoDictionary?["CFBundleSupportedPlatforms"] as? [String], !platforms.isEmpty else { + return false + } +// os_log_debug(logHandle, "supported platforms: %{public}@", String(describing: platforms)) + let set = Set(platforms) + return set.isSubset(of: Set(["iPhoneSimulator", "AppleTVSimulator"])) + } + + // MARK: - Launched by user? + + @objc + public func launchedByUser() -> Bool { + guard let parent = getRealParent(getpid()) else { return false } + let bid = parent["CFBundleIdentifier"] as? NSString + if bid == "com.apple.dock" || bid == "com.apple.finder" || bid == "com.apple.Terminal" { + return true + } + return false + } + + // MARK: - Fade out + + @objc + public func fadeOut(_ window: NSWindow, _ duration: Float) { + NSAnimationContext.runAnimationGroup { ctx in + ctx.duration = TimeInterval(duration) + window.animator().alphaValue = 0.0 + } completionHandler: { + window.close() + } + } + + // MARK: - Code-signing info match + +// @objc +// public func matchesCSInfo(_ csInfo1: NSDictionary?, _ csInfo2: NSDictionary?) -> Bool { +// var status1 = -1, status2 = -1 +// var signer1 = -1, signer2 = -1 +// var id1: String?, id2: String? +// var auths1: [Any]?, auths2: [Any]? +// +// if let n = csInfo1?[KEY_CS_STATUS] as? NSNumber { status1 = n.intValue } +// if let n = csInfo2?[KEY_CS_STATUS] as? NSNumber { status2 = n.intValue } +// if status1 != status2 { +// os_log_error(logHandle, "ERROR: code signing mismatch (signing status): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) +// return false +// } +// +// if let n = csInfo1?[KEY_CS_SIGNER] as? NSNumber { signer1 = n.intValue } +// if let n = csInfo2?[KEY_CS_SIGNER] as? NSNumber { signer2 = n.intValue } +// if signer1 != signer2 { +// if (signer1 == Apple && signer2 == AppStore) || (signer1 == AppStore && signer2 == Apple) { +// os_log_error(logHandle, "ignoring case where Apple App moved to/from Mac App Store: %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) +// } else { +// os_log_error(logHandle, "ERROR: code signing mismatch (signer): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) +// return false +// } +// } +// +// if let s = csInfo1?[KEY_CS_ID] as? String { id1 = s } +// if let s = csInfo2?[KEY_CS_ID] as? String { id2 = s } +// if (id1 != nil || id2 != nil), id1 != id2 { +// os_log_error(logHandle, "ERROR: code signing mismatch (signing ID): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) +// return false +// } +// +// if let a = csInfo1?[KEY_CS_AUTHS] as? [Any] { auths1 = a } +// if let a = csInfo2?[KEY_CS_AUTHS] as? [Any] { auths2 = a } +// if (auths1 != nil || auths2 != nil) && !(auths1 as NSArray? ?? []).isEqual(to: auths2 ?? []) { +// os_log_error(logHandle, "ERROR: code signing mismatch (signing auths): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) +// return false +// } +// +// return true +// } + + // MARK: - Escape JSON + + @objc + public func toEscapedJSON(_ input: String) -> String? { + do { + let data = try JSONSerialization.data(withJSONObject: input, options: .fragmentsAllowed) + return String(data: data, encoding: .utf8) + } catch { +// os_log_error(logHandle, "ERROR: failed to convert/escape %{public}@ to JSON (error: %{public}@)", input, error.localizedDescription) + return nil + } + } + + // MARK: - Absolute date from HH:mm (next 24h) + + @objc + public func absoluteDate(_ date: Date) -> Date { +// os_log_debug(logHandle, "function '%{public}s' invoked with %{public}@", #function, String(describing: date)) + + let now = Date() + let cal = Calendar.current + + let comps = cal.dateComponents([.hour, .minute], from: date) + var nowComps = cal.dateComponents([.year, .month, .day, .hour, .minute], from: now) + nowComps.hour = comps.hour + nowComps.minute = comps.minute + + var absDate = cal.date(from: nowComps) ?? now + if absDate < now { + absDate = cal.date(byAdding: .day, value: 1, to: absDate) ?? absDate + } + return absDate + } + + // MARK: - Internal volume? + + @objc + public func isInternalProcess(_ path: String) -> Bool { + var isInternal: AnyObject? + do { + var url = URL(fileURLWithPath: path) + try (url as NSURL).getResourceValue(&isInternal, forKey: URLResourceKey.volumeIsInternalKey) + return (isInternal as? NSNumber)?.boolValue ?? false + } catch { +// os_log_error(logHandle, "ERROR: 'getResourceValue'/'NSURLVolumeIsInternalKey' failed with %@", error.localizedDescription) + return false + } + } + + +} + diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/README.md b/Self-Control-Extension/SelfControl/SelfControlExtension/README.md new file mode 100644 index 00000000..234977c0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/README.md @@ -0,0 +1,8 @@ +# SelfControlExtension + +This directory contains the NetworkExtension implementation for SelfControl, responsible for URL and domain filtering. + +## Structure + +- `FilterProvider/` - NetworkExtension filter provider implementation +- `Configuration/` - Extension configuration code \ No newline at end of file diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/ReverseDomainMapper.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/ReverseDomainMapper.swift new file mode 100644 index 00000000..0610f54e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/ReverseDomainMapper.swift @@ -0,0 +1,57 @@ +// +// ReverseDomainMapper.swift +// SelfControlExtension +// +// Created by Satendra Singh on 14/10/25. +// + +import Foundation + +final class ReverseDomainMapper { + static func reverseDNSUsingGetNameInfo(ipAddress: String) -> String? { + var addr = sockaddr_in() + addr.sin_len = UInt8(MemoryLayout.size) + addr.sin_family = sa_family_t(AF_INET) + inet_pton(AF_INET, ipAddress, &addr.sin_addr) + + var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + + // Precompute length so we don't read from `addr` inside the closure. + let addrLen = socklen_t(addr.sin_len) + + let result: Int32 = withUnsafePointer(to: &addr) { ptr in + ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in + getnameinfo(saPtr, + addrLen, + &hostname, socklen_t(hostname.count), + nil, 0, + NI_NAMEREQD) + } + } + + guard result == 0 else { return nil } + return String(cString: hostname) + } +} + +enum Regex { + static let ipAddress = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" + static let hostname = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$" +} + +extension String { + var isValidIpAddress: Bool { + return self.matches(pattern: Regex.ipAddress) + } + + var isValidHostname: Bool { + return self.matches(pattern: Regex.hostname) + } + + private func matches(pattern: String) -> Bool { + return self.range(of: pattern, + options: .regularExpression, + range: nil, + locale: nil) != nil + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/SelfControlExtension.entitlements b/Self-Control-Extension/SelfControl/SelfControlExtension/SelfControlExtension.entitlements new file mode 100644 index 00000000..f6ded53d --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/SelfControlExtension.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.networking.networkextension + + content-filter-provider-systemextension + + com.apple.security.application-groups + + $(TeamIdentifierPrefix)com.application.SelfControl.corebits + + + diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/String+URL.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/String+URL.swift new file mode 100644 index 00000000..b2758df0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/String+URL.swift @@ -0,0 +1,35 @@ +// +// String+URL.swift +// SelfControlExtension +// +// Created by Satendra Singh on 05/08/25. +// + +import Foundation + +extension String { + var domainString: String? { + Self.extractDomain(from: self) + } + + static func extractDomain(from urlString: String) -> String? { + var formattedURLString = urlString + if !formattedURLString.lowercased().hasPrefix("http://") && + !formattedURLString.lowercased().hasPrefix("https://") { + formattedURLString = "https://" + formattedURLString + } + + guard let url = URL(string: formattedURLString), + let host = url.host else { + return nil + } + + // Optional: extract root domain (e.g., "facebook.com" from "sub.facebook.com") + let components = host.components(separatedBy: ".") + if components.count >= 2 { + return components.suffix(2).joined(separator: ".") + } else { + return host + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/TLDURLToDomain.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/TLDURLToDomain.swift new file mode 100644 index 00000000..f652a950 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/TLDURLToDomain.swift @@ -0,0 +1,49 @@ +// +// TLDURLToDomain.swift +// SelfControlExtension +// +// Created by Satendra Singh on 15/10/25. +// + +import Foundation + +final class TLDURLToDomain { + func domain(for tldURL: URL) -> String? { + return tldURL.host + } + + private func extractDomain(from url: URL) -> String? { + return url.host + } + + + func getDomain(from urlString: String) -> String? { + guard let url = URL(string: urlString) else { + print("Invalid URL string.") + return nil + } + + // Using URL.host + if let host = url.host { + return host + } + return nil + } + + static func getURLDomain(from input: String) -> String? { + // Normalize input to ensure it has a scheme + let formatted = input.contains("://") ? input : "https://\(input)" + guard let host = URL(string: formatted)?.host else { return nil } + + let parts = host.split(separator: ".") + guard parts.count >= 2 else { return host } + + // Return last two parts (e.g., facebook.com, google.co.uk) + if parts.count >= 3 && parts[parts.count - 2] == "co" { + return parts.suffix(3).joined(separator: ".") // handles e.g. google.co.uk + } else { + return parts.suffix(2).joined(separator: ".") + } + } + +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/main.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/main.swift new file mode 100644 index 00000000..ffadae74 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/main.swift @@ -0,0 +1,18 @@ +// +// main.swift +// SelfControlExtension +// +// Created by Egzon Arifi on 02/04/2025. +// + +import Foundation +import NetworkExtension +import os.log + +autoreleasepool { + os_log("[SC] 🔍] first light") + NEProvider.startSystemExtensionMode() + IPCConnection.shared.startListener() +} + +dispatchMain() diff --git a/Self-Control-Extension/dynamic-url-blocker/background.js b/Self-Control-Extension/dynamic-url-blocker/background.js new file mode 100644 index 00000000..3625f6a6 --- /dev/null +++ b/Self-Control-Extension/dynamic-url-blocker/background.js @@ -0,0 +1,199 @@ + +// Dynamic URL Blocker — background service worker (Manifest V3) + +// ---- Helpers ---- +function normalizeDomain(input) { + // strip protocol, path, query, hash + let domain = (input || "").trim().toLowerCase(); + domain = domain.replace(/^https?:\/\//, ""); + domain = domain.replace(/^www\./, ""); + domain = domain.split("/")[0].split("?")[0].split("#")[0]; + // allow punycode etc. Keep only allowed domain chars, dots, and dashes + domain = domain.replace(/[^a-z0-9\.\-]/g, ""); + return domain; +} + +function escapeRegex(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +// Build a regex that matches the domain and any subdomain, main-frame only. +function regexForDomain(domain) { + const d = escapeRegex(domain); + // ^https?://([sub.]* )?domain(/|:|$) + return `^https?:\\/\\/([a-z0-9-]+\\.)*${d}(\\/|:|$)`; +} + +// Simple deterministic hash for rule IDs (32-bit positive) +function hash32(str) { + let h = 5381; + for (let i = 0; i < str.length; i++) { + h = ((h << 5) + h) ^ str.charCodeAt(i); // djb2 xor + h = h | 0; // force 32-bit + } + // make positive and avoid 0 & small ids + h = Math.abs(h); + return (h % 2000000000) + 1000; // keep under Chrome's int32 max +} + +function ruleForDomain(domain) { + return { + id: hash32(domain), + priority: 1, + action: { type: "block" }, + condition: { + regexFilter: regexForDomain(domain), + resourceTypes: ["main_frame"] + } + }; +} + +async function fetchData() { + // try { + // const response = await fetch("http://127.0.0.1:8080/"); + // const data = await response.json(); + // console.log("Swift app data:", data); + // } catch (err) { + // console.error("Error fetching from Swift app:", err); + // } + try { + const response = await fetch("http://127.0.0.1:8080/"); + const data = await response.json(); + console.log("Array from Swift:", data); + + // // iterate + // data.forEach(item => { + // console.log(`${item. } is ${item.status}`); + // }); + // Parse into JS object +// const data = JSON.parse(jsonString); + +// Access blocked domain list +const blockedDomains = data.blocked; + +console.log(blockedDomains); +// 👉 ["domain1.com", "domain2.com", "domain3.com"] + +// Example: loop over them +blockedDomains.forEach(domain => { + console.log("Blocked:", domain); +}); + return blockedDomains; + } catch (err) { + console.error("Fetch failed:", err); + return []; + } +} + + +async function getDynamicRuleIds() { + const rules = await chrome.declarativeNetRequest.getDynamicRules(); + return rules.map(r => r.id); +} + +async function applyRulesFrom(blockedDomains) { + const desiredRules = blockedDomains.map(ruleForDomain); + const desiredIds = new Set(desiredRules.map(r => r.id)); + const currentIds = new Set(await getDynamicRuleIds()); + + // Remove any rules that shouldn't exist + const removeRuleIds = [...currentIds].filter(id => !desiredIds.has(id)); + + // Add rules that don't yet exist + const addRules = desiredRules.filter(r => !currentIds.has(r.id)); + + await chrome.declarativeNetRequest.updateDynamicRules({ + removeRuleIds, + addRules + }); +} + +async function loadBlockedDomains() { + const domains = await fetchData(); + return domains; + // return new Promise(resolve => { + // blockedDomains + // chrome.storage.local.get({ blockedSites: [] }, (data) => { + // resolve(data.blockedSites || []); + // }); + // }); +} + +async function saveBlockedDomains(domains) { + return new Promise(resolve => { + chrome.storage.local.set({ blockedSites: domains }, () => resolve()); + }); +} + +// ---- Lifecycle ---- +chrome.runtime.onInstalled.addListener(async () => { + const domains = await loadBlockedDomains(); + await applyRulesFrom(domains); + console.log("Dynamic URL Blocker installed. Domains:", domains); + // fetchData(); +}); + +chrome.tabs.onCreated.addListener((tab) => { + (async () => { + console.log("New tab:", tab.id); + const domains = await loadBlockedDomains(); + await applyRulesFrom(domains); + // Example: get tab info with async API + let tabInfo = await chrome.tabs.get(tab.id); + console.log("Fetched tab info:", tabInfo); + })(); +}); + +chrome.windows.onCreated.addListener((window) => { + (async () => { + const domains = await loadBlockedDomains(); + await applyRulesFrom(domains); + // Example: get tab info with async API + let windowInfo = await chrome.tabs.get(window.id); + console.log("New Window created:", windowInfo); + })(); +}); + +// ---- Messaging API for popup ---- +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + (async () => { + if (message?.action === "list") { + const domains = await loadBlockedDomains(); + sendResponse({ success: true, domains }); + return; + } + + if (message?.action === "add") { + let domain = normalizeDomain(message.site || ""); + if (!domain) { + sendResponse({ success: false, error: "Please enter a valid domain (e.g., example.com)." }); + return; + } + let domains = await loadBlockedDomains(); + if (!domains.includes(domain)) { + domains.push(domain); + await saveBlockedDomains(domains); + await applyRulesFrom(domains); + sendResponse({ success: true, domains }); + } else { + sendResponse({ success: false, error: "Already blocked." }); + } + return; + } + + if (message?.action === "remove") { + const domain = normalizeDomain(message.site || ""); + let domains = await loadBlockedDomains(); + domains = domains.filter(d => d !== domain); + await saveBlockedDomains(domains); + await applyRulesFrom(domains); + sendResponse({ success: true, domains }); + return; + } + + sendResponse({ success: false, error: "Unknown action." }); + })(); + + // Indicate we'll respond asynchronously + return true; +}); diff --git a/Self-Control-Extension/dynamic-url-blocker/manifest.json b/Self-Control-Extension/dynamic-url-blocker/manifest.json new file mode 100644 index 00000000..e64a8f3e --- /dev/null +++ b/Self-Control-Extension/dynamic-url-blocker/manifest.json @@ -0,0 +1,23 @@ +{ + "manifest_version": 3, + "name": "Dynamic URL Blocker", + "version": "1.0.0", + "description": "Block websites by typing them into a popup. Uses dynamic Declarative Net Request rules.", + "permissions": [ + "declarativeNetRequest", + "storage", + "scripting" + ], + "host_permissions": [ + "http://localhost/*", + "http://127.0.0.1/*" + ], + "background": { + "service_worker": "background.js", + "type": "module" + }, + "action": { + "default_popup": "popup.html", + "default_title": "Dynamic URL Blocker" + } +} \ No newline at end of file diff --git a/Self-Control-Extension/dynamic-url-blocker/popup.css b/Self-Control-Extension/dynamic-url-blocker/popup.css new file mode 100644 index 00000000..d8567226 --- /dev/null +++ b/Self-Control-Extension/dynamic-url-blocker/popup.css @@ -0,0 +1,52 @@ + +body { + font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; + padding: 12px; + width: 320px; +} + +h2 { margin: 0 0 8px; font-size: 16px; } +h3 { margin: 12px 0 6px; font-size: 14px; } + +.row { display: flex; gap: 8px; } + +input { + flex: 1; + padding: 8px; + border: 1px solid #ddd; + border-radius: 8px; +} + +button { + padding: 8px 12px; + border: none; + border-radius: 8px; + background: #111827; + color: white; + cursor: pointer; +} + +button:hover { opacity: 0.9; } + +ul { list-style: none; padding: 0; margin: 0; } + +li { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 0; + border-bottom: 1px solid #f0f0f0; +} + +li .domain { font-size: 13px; } + +li button.remove { + background: transparent; + color: #ef4444; + border: 1px solid #ef4444; + border-radius: 6px; + padding: 2px 8px; +} + +.hint { color: #666; font-size: 12px; } +.error { color: #b91c1c; font-size: 12px; margin-top: 6px; } diff --git a/Self-Control-Extension/dynamic-url-blocker/popup.html b/Self-Control-Extension/dynamic-url-blocker/popup.html new file mode 100644 index 00000000..abb3a07e --- /dev/null +++ b/Self-Control-Extension/dynamic-url-blocker/popup.html @@ -0,0 +1,24 @@ +\ + + + + + Dynamic URL Blocker + + + + + +

Blocks the domain and all its subdomains.

+ +

Blocked Domains

+
    + + + + diff --git a/Self-Control-Extension/dynamic-url-blocker/popup.js b/Self-Control-Extension/dynamic-url-blocker/popup.js new file mode 100644 index 00000000..89c26a83 --- /dev/null +++ b/Self-Control-Extension/dynamic-url-blocker/popup.js @@ -0,0 +1,73 @@ + +const siteInput = document.getElementById("siteInput"); +const addBtn = document.getElementById("addBtn"); +const siteList = document.getElementById("siteList"); + +function normalizeDomain(input) { + let domain = (input || "").trim().toLowerCase(); + domain = domain.replace(/^https?:\/\//, ""); + domain = domain.replace(/^www\./, ""); + domain = domain.split("/")[0].split("?")[0].split("#")[0]; + domain = domain.replace(/[^a-z0-9\.\-]/g, ""); + return domain; +} + +function render(list) { + siteList.innerHTML = ""; + if (!list || list.length === 0) { + const li = document.createElement("li"); + li.innerHTML = 'No blocked domains yet.'; + siteList.appendChild(li); + return; + } + + list.forEach(site => { + const li = document.createElement("li"); + + const span = document.createElement("span"); + span.className = "domain"; + span.textContent = site; + + // const removeBtn = document.createElement("button"); + // removeBtn.className = "remove"; + // removeBtn.textContent = "Unblock"; + // removeBtn.onclick = () => { + // chrome.runtime.sendMessage({ action: "remove", site }, (resp) => { + // render(resp.domains || []); + // }); + // }; + + li.appendChild(span); + // li.appendChild(removeBtn); + siteList.appendChild(li); + }); +} + +function load() { + chrome.runtime.sendMessage({ action: "list" }, (resp) => { + render(resp.domains || []); + }); +} + +// addBtn.onclick = () => { +// const raw = siteInput.value; +// const site = normalizeDomain(raw); +// if (!site) { +// alert("Please enter a valid domain, e.g. example.com"); +// return; +// } +// chrome.runtime.sendMessage({ action: "add", site }, (resp) => { +// if (!resp?.success) { +// alert(resp?.error || "Failed to add."); +// return; +// } +// siteInput.value = ""; +// render(resp.domains || []); +// }); +// }; + +// siteInput.addEventListener("keydown", (e) => { +// if (e.key === "Enter") addBtn.click(); +// }); + +document.addEventListener("DOMContentLoaded", load); From 9fe1046fe5cbf1981edad68c9e85d72d695ce54b Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sat, 29 Nov 2025 23:29:13 +0530 Subject: [PATCH 02/64] implemented extension with blocker customization --- .../SafariExtensionViewController.xib | 30 ++ .../Safari Rules Extension/Info.plist | 38 ++ .../Resources/ToolbarItemIcon.pdf | Bin 0 -> 4080 bytes .../Resources/blocked.html | 33 ++ .../Resources/script.js | 264 ++++++++++++ .../SafariExtensionHandler.swift | 124 ++++++ .../SafariExtensionViewController.swift | 18 + .../SafariExtensionViewController.xib | 30 ++ .../SelfControl Rules Extension/Info.plist | 38 ++ .../Resources/ToolbarItemIcon.pdf | Bin 0 -> 4080 bytes .../Resources/blocked.html | 33 ++ .../Resources/script.js | 264 ++++++++++++ .../SafariExtensionHandler.swift | 143 +++++++ .../SafariExtensionViewController.swift | 18 + .../SelfControl Rules Extension.entitlements | 10 + ...ontentBlockerExtensionRequestHandler.swift | 61 +++ .../SelfControl.xcodeproj/project.pbxproj | 375 +++++++++++++++++- .../SelfControl/Main/ContentView.swift | 2 + .../SelfControl/Main/FilterViewModel.swift | 54 ++- .../Main/NetworkExtensionState.swift | 54 +++ .../Main/SafariExtensionWebView.swift | 121 ++++++ .../Preferences/PreferencesView.swift | 1 + .../Preferences/ProxyPreferences.swift | 29 +- .../Resources/Base.lproj/Main.html | 19 + .../SelfControl/Resources/Icon.png | Bin 0 -> 100020 bytes .../SelfControl/Resources/Script.js | 22 + .../SelfControl/Resources/Style.css | 45 +++ .../SelfControl/Resources/en.lproj/Main.html | 19 + .../SafariExtension/BlockListManager.swift | 88 +++- .../SafariExtensionManager.swift | 37 ++ ...ft => ChromeExtensionRequestListner.swift} | 23 +- .../SelfControlExtension/Constants.swift | 24 ++ .../DNSProxyProvider.swift | 2 +- .../SelfControlExtension/DNSQuestion.swift | 2 +- .../FilterDataProvider.swift | 221 +++++------ .../SelfControlExtension/IPCConnection.swift | 102 +++-- .../RequestSourceAppValidator.swift | 38 ++ 37 files changed, 2189 insertions(+), 193 deletions(-) create mode 100644 Self-Control-Extension/SelfControl/Safari Rules Extension/Base.lproj/SafariExtensionViewController.xib create mode 100644 Self-Control-Extension/SelfControl/Safari Rules Extension/Info.plist create mode 100644 Self-Control-Extension/SelfControl/Safari Rules Extension/Resources/ToolbarItemIcon.pdf create mode 100644 Self-Control-Extension/SelfControl/Safari Rules Extension/Resources/blocked.html create mode 100644 Self-Control-Extension/SelfControl/Safari Rules Extension/Resources/script.js create mode 100644 Self-Control-Extension/SelfControl/Safari Rules Extension/SafariExtensionHandler.swift create mode 100644 Self-Control-Extension/SelfControl/Safari Rules Extension/SafariExtensionViewController.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl Rules Extension/Base.lproj/SafariExtensionViewController.xib create mode 100644 Self-Control-Extension/SelfControl/SelfControl Rules Extension/Info.plist create mode 100644 Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/ToolbarItemIcon.pdf create mode 100644 Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/blocked.html create mode 100644 Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js create mode 100644 Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionViewController.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl Rules Extension/SelfControl Rules Extension.entitlements create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/NetworkExtensionState.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Resources/Base.lproj/Main.html create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Resources/Icon.png create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Resources/Script.js create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Resources/Style.css create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Resources/en.lproj/Main.html create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionManager.swift rename Self-Control-Extension/SelfControl/SelfControlExtension/{PlistListner.swift => ChromeExtensionRequestListner.swift} (62%) create mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift diff --git a/Self-Control-Extension/SelfControl/Safari Rules Extension/Base.lproj/SafariExtensionViewController.xib b/Self-Control-Extension/SelfControl/Safari Rules Extension/Base.lproj/SafariExtensionViewController.xib new file mode 100644 index 00000000..55fd3ce6 --- /dev/null +++ b/Self-Control-Extension/SelfControl/Safari Rules Extension/Base.lproj/SafariExtensionViewController.xib @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Self-Control-Extension/SelfControl/Safari Rules Extension/Info.plist b/Self-Control-Extension/SelfControl/Safari Rules Extension/Info.plist new file mode 100644 index 00000000..d43b31dc --- /dev/null +++ b/Self-Control-Extension/SelfControl/Safari Rules Extension/Info.plist @@ -0,0 +1,38 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.Safari.extension + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).SafariExtensionHandler + SFSafariContentScript + + + Script + script.js + + + SFSafariToolbarItem + + Action + Command + Identifier + Button + Image + ToolbarItemIcon.pdf + Label + Your Button + + SFSafariWebsiteAccess + + Level + All + + + NSHumanReadableDescription + This is test Safari Ext Extension. You should tell us what your extension does here. + + diff --git a/Self-Control-Extension/SelfControl/Safari Rules Extension/Resources/ToolbarItemIcon.pdf b/Self-Control-Extension/SelfControl/Safari Rules Extension/Resources/ToolbarItemIcon.pdf new file mode 100644 index 0000000000000000000000000000000000000000..88405ddfe29891dccc415bf1c3b1530e53816384 GIT binary patch literal 4080 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 HPyzoRld}9& literal 0 HcmV?d00001 diff --git a/Self-Control-Extension/SelfControl/Safari Rules Extension/Resources/blocked.html b/Self-Control-Extension/SelfControl/Safari Rules Extension/Resources/blocked.html new file mode 100644 index 00000000..9fcc266c --- /dev/null +++ b/Self-Control-Extension/SelfControl/Safari Rules Extension/Resources/blocked.html @@ -0,0 +1,33 @@ + + + + +Site Blocked + + + + +

    🚫 Site Blocked

    +

    This website has been blocked by your Safari content blocker.

    + + + + diff --git a/Self-Control-Extension/SelfControl/Safari Rules Extension/Resources/script.js b/Self-Control-Extension/SelfControl/Safari Rules Extension/Resources/script.js new file mode 100644 index 00000000..a5a91526 --- /dev/null +++ b/Self-Control-Extension/SelfControl/Safari Rules Extension/Resources/script.js @@ -0,0 +1,264 @@ +//safari.extension.dispatchMessage("load") +//document.addEventListener("DOMContentLoaded", function(event) { +// console.log("DOMContentLoaded"); +// safari.extension.dispatchMessage("[SC] 🔍] Hello World!"); +//}); + +// +//window.addEventListener("load", function () { +// console.log("Page fully loaded"); +// safari.extension.dispatchMessage("[SC] 🔍] Hello World! Load"); +//}); +//document.addEventListener("click", function (event) { +// safari.extension.dispatchMessage("PAGE_CLICKED", { +// url: window.location.href, +// x: event.clientX, +// y: event.clientY +// }); +//}); +//function notifySwift(data) { +// safari.extension.dispatchMessage("REQUEST_DETECTED", data); +//} +// +//// Example usage: +//notifySwift({ url: location.href, time: Date.now() }); +//safari.self.addEventListener("message", function(event) { +// if (event.name === "SWIFT_ACK") { +// console.log("[SC] 🔍] Swift acknowledged:", event.message); +// } +//}); +//(function() { +// +// function checkURL() { +// console.log("[SC] 🔍] Swift acknowledged:", location.href); +// +// if (location.href.match(/facebook\.com\/friends/)) { +// // Stop page loading immediately +// window.stop(); +// +// // Notify Swift +// safari.extension.dispatchMessage("BLOCK_FRIENDS_PAGE", { +// url: location.href +// }); +// } +// } +// +// // Run on load + on history navigation +// checkURL(); +// document.addEventListener("DOMContentLoaded", checkURL); +// +// // For SPA router (Facebook uses React navigation) +// const pushState = history.pushState; +// history.pushState = function() { +// pushState.apply(history, arguments); +// checkURL(); +// }; +// +//})(); +// Injected script for Safari App Extension +// Blocks navigation to facebook.com/friends by stopping load and notifying the extension. +//(function() { +// +// function isFriendsPath(href) { +// try { +// // Normalize and check path+hash+query +// const url = new URL(href, location.origin); +// // Check hostname contains facebook.com and path begins with /friends +// return /(^|\.)facebook\.com$/.test(url.hostname) && url.pathname.startsWith('/friends'); +// } catch (e) { +// return false; +// } +// } +// +// function notifySwift() { +// console.log("[SC] 🔍] notifySwift:"); +// +// try { +// // Stop network activity for this page +// if (typeof window.stop === 'function') { +// window.stop(); +// } +// +// // Dispatch message to Safari App Extension +// if (window.safari && safari.extension && safari.extension.dispatchMessage) { +// console.log("[SC] 🔍] notifySwift:if (window.safari"); +// +// safari.extension.dispatchMessage("BLOCK_FRIENDS_PAGE", { +// url: location.href, +// time: Date.now() +// }); +// } else { +// console.warn("[SC] 🔍] Safari extension messaging API unavailable"); +// } +// } catch (err) { +// console.error("[SC] 🔍] notifySwift error:", err); +// } +// } +// +// function checkURL() { +// if (isFriendsPath(location.href)) { +// notifySwift(); +// } +// } +// +// // initial check +// checkURL(); +// +// // handle regular page load +// document.addEventListener("DOMContentLoaded", checkURL); +// +// // handle SPA navigation (monkey-patch history) +// (function(history) { +// const pushState = history.pushState; +// history.pushState = function() { +// const result = pushState.apply(this, arguments); +// setTimeout(checkURL, 10); +// return result; +// }; +// const replaceState = history.replaceState; +// history.replaceState = function() { +// const result = replaceState.apply(this, arguments); +// setTimeout(checkURL, 10); +// return result; +// }; +// })(window.history); +// +// // handle popstate (back/forward) +// window.addEventListener('popstate', function() { +// setTimeout(checkURL, 10); +// }); +// +// // observe clicks on anchors that may bypass history +// document.addEventListener('click', function(e) { +// setTimeout(checkURL, 10); +// }, true); +// +// // for good measure, observe mutations — some SPAs do their own routing +// const observer = new MutationObserver(function() { +// checkURL(); +// }); +// observer.observe(document.documentElement || document.body, { childList: true, subtree: true }); +// +//})(); + +//(function () { +// +// function notifySwift() { +// try { +// if (typeof window.stop === "function") { +// window.stop(); +// } +// +// if ( +// window.safari && +// safari.extension && +// typeof safari.extension.dispatchMessage === "function" +// ) { +// safari.extension.dispatchMessage("PAGE_VISIT", { +// url: location.href, +// time: Date.now(), +// }); +// } +// } catch (err) { +// console.error("[SC] notifySwift error:", err); +// } +// } +// +// function onURLChange() { +// console.log("[SC] 🔍 onURLChange:", location.href); +// notifySwift(); +// } +// +// // Initial load +// onURLChange(); +// +// // Observe SPA navigation (pushState, replaceState) +// (function (history) { +// ["pushState", "replaceState"].forEach(function (method) { +// const original = history[method]; +// history[method] = function () { +// const result = original.apply(this, arguments); +// setTimeout(onURLChange, 10); +// return result; +// }; +// }); +// })(window.history); +// +// window.addEventListener("popstate", () => setTimeout(onURLChange, 10)); +// document.addEventListener("click", () => setTimeout(onURLChange, 10), true); +// +// // ✅ Wait for DOM before attaching MutationObserver +// function setupObserver() { +// const target = document.body; +// if (!target) { +// // If body not yet available, retry shortly +// console.warn("[SC] document.body not ready, retrying..."); +// return setTimeout(setupObserver, 50); +// } +// +// try { +// const observer = new MutationObserver(() => onURLChange()); +// observer.observe(target, { childList: true, subtree: true }); +// console.log("[SC] ✅ MutationObserver attached"); +// } catch (err) { +// console.error("[SC] MutationObserver error:", err); +// } +// } +// +// if (document.readyState === "loading") { +// document.addEventListener("DOMContentLoaded", setupObserver); +// } else { +// setupObserver(); +// } +// +//})(); + +//if (safari.self && safari.self.addEventListener) { +// safari.self.addEventListener("message", function(event) { +// if (event.name === "REDIRECT_BLOCKED_URL" && event.message && event.message.redirect) { +// console.log("[SC] 🔍] REDIRECT_BLOCKED_URL:"); +// +// location.replace(event.message.redirect); +// } +// }); +//} + +(function () { + + // Listen for messages from the Swift extension + safari.self.addEventListener("message", (event) => { + if (event.name === "REDIRECT_BLOCKED_URL") { + const redirectURL = event.message.redirect; + console.log("[SC] 🚫 Redirecting to:", redirectURL); + + try { + window.stop(); // stop current load +// location.href = redirectURL; // redirect + const blockedPage = safari.extension.baseURI + "blocked.html"; + window.location.href = blockedPage; + } catch (err) { + console.error("[SC] redirect error:", err); + } + } + }); + + // Send PAGE_VISIT message + function notifySwift() { + try { + if (window.safari && safari.extension && safari.extension.dispatchMessage) { + safari.extension.dispatchMessage("PAGE_VISIT", { + url: location.href, + time: Date.now(), + }); + } + } catch (err) { + console.error("[SC] notifySwift error:", err); + } + } + + // Observe navigation changes (simplified) + window.addEventListener("load", notifySwift); + window.addEventListener("popstate", notifySwift); + document.addEventListener("click", () => setTimeout(notifySwift, 10), true); + +})(); diff --git a/Self-Control-Extension/SelfControl/Safari Rules Extension/SafariExtensionHandler.swift b/Self-Control-Extension/SelfControl/Safari Rules Extension/SafariExtensionHandler.swift new file mode 100644 index 00000000..a0448c3d --- /dev/null +++ b/Self-Control-Extension/SelfControl/Safari Rules Extension/SafariExtensionHandler.swift @@ -0,0 +1,124 @@ +// +// SafariExtensionHandler.swift +// test Safari Ext Extension +// +// Created by Satendra Singh on 30/10/25. +// + +import SafariServices +import os.log + +class SafariExtensionHandler: SFSafariExtensionHandler { + let blockedPatterns: [String] = [ + "facebook.com/friends", + "facebook.com/marketplace", + "instagram.com/explore", + "youtube.com/feed/subscriptions" + ] + + let redirectURL = "https://selfcontrol-blocked.local/blocked" // 🟢 Your custom page + + + override func beginRequest(with context: NSExtensionContext) { + let request = context.inputItems.first as? NSExtensionItem + + let profile: UUID? + if #available(iOS 17.0, macOS 14.0, *) { + profile = request?.userInfo?[SFExtensionProfileKey] as? UUID + } else { + profile = request?.userInfo?["profile"] as? UUID + } + + os_log(.default, "[SC] 🔍] The extension received a request for profile: %{public}@", profile?.uuidString ?? "none") + } + + override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) { + os_log("[SC] 🔍 The extension received a message: %{public}@", messageName) + + guard messageName == "PAGE_VISIT", + let urlString = userInfo?["url"] as? String else { return } + + os_log("[SC] 🔍 Received URL: %{public}@", urlString) + + // Match against blocked URLs + if blockedPatterns.contains(where: { urlString.contains($0) }) { + os_log("[SC] 🚫 Blocking and redirecting: %{public}@", urlString) + page.dispatchMessageToScript(withName: "REDIRECT_BLOCKED_URL", userInfo: ["redirect": redirectURL]) + } + } + + func messagexReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) { + os_log(.default, "[SC] 🔍] The extension messageReceived: %{public}@", messageName) + +// if messageName == "BLOCK_FRIENDS_PAGE" { +// +// page.getContainingTab { tab in +// tab.navigate(to: URL(string: "https://www.facebook.com/")!) +// } +// } + +// page.getPropertiesWithCompletionHandler { properties in +// os_log(.default, "[SC] 🔍] The extension received a message (%@) from a script injected into (%@) with userInfo (%@)", messageName, String(describing: properties?.url), userInfo ?? [:]) +// } +// page.getPropertiesWithCompletionHandler { properties in +// print("[SC] 🔍] ✅ Message Name:", messageName) +// print("[SC] 🔍] 🌍 Page URL:", properties?.url?.absoluteString ?? "Unknown") +// print("[SC] 🔍] 📦 Data Received:", userInfo ?? [:]) +// } +// // Example: Send response back to JS +// page.dispatchMessageToScript(withName: "SWIFT_ACK", userInfo: ["received": true]) + if messageName == "BLOCK_FRIENDS_PAGE" { + // Redirect the tab away from /friends + page.getContainingTab { tab in + // Redirect target — change as needed + if let url = URL(string: "https://www.facebook.com/") { + tab.navigate(to: url) + } + } + } + + page.getPropertiesWithCompletionHandler { properties in + NSLog("The extension received a message (\(messageName)) from a script injected into (\(String(describing: properties?.url))) with userInfo (\(userInfo ?? [:]))") + if #available(macOS 10.14, *) { + os_log(.default, "[SC] 🔍] The extension received a message: %{public}@", properties?.url?.description ?? "-") + } else { + // Fallback on earlier versions + } + if properties?.url?.description.contains("facebook.com/friends") == true { + page.getContainingTab(completionHandler: { tab in + // Fetch the active page for this tab to access its URL via page properties. + tab.navigate(to: URL(string: "https://www.corebitss.com")!) +// tab.getActivePage { activePage in +// guard let activePage = activePage else { +// NSLog("The extension received a message (\(messageName)) but the tab has no active page. userInfo (\(userInfo ?? [:]))") +// if #available(macOS 10.14, *) { +// os_log(.default, "[SC] 🔍] The extension received a message but the tab has no active page.") +// } +// return +// } +// activePage.getPropertiesWithCompletionHandler { tabPageProperties in +// let tabURLDesc = tabPageProperties?.url?.description ?? "-" +// NSLog("The extension received a message (\(messageName)) from a script injected into (\(tabURLDesc)) with userInfo (\(userInfo ?? [:]))") +// if #available(macOS 10.14, *) { +// os_log(.default, "[SC] 🔍] The extension received a message: %{public}@", tabURLDesc) +// } +// } +// } + }) + } + } + } + + override func toolbarItemClicked(in window: SFSafariWindow) { + os_log(.default, "[SC] 🔍] The extension's toolbar item was clicked") + } + + override func validateToolbarItem(in window: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) { + validationHandler(true, "") + } + + override func popoverViewController() -> SFSafariExtensionViewController { + return SafariExtensionViewController.shared + } + +} diff --git a/Self-Control-Extension/SelfControl/Safari Rules Extension/SafariExtensionViewController.swift b/Self-Control-Extension/SelfControl/Safari Rules Extension/SafariExtensionViewController.swift new file mode 100644 index 00000000..64669fba --- /dev/null +++ b/Self-Control-Extension/SelfControl/Safari Rules Extension/SafariExtensionViewController.swift @@ -0,0 +1,18 @@ +// +// SafariExtensionViewController.swift +// test Safari Ext Extension +// +// Created by Satendra Singh on 30/10/25. +// + +import SafariServices + +class SafariExtensionViewController: SFSafariExtensionViewController { + + static let shared: SafariExtensionViewController = { + let shared = SafariExtensionViewController() + shared.preferredContentSize = NSSize(width:320, height:240) + return shared + }() + +} diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Base.lproj/SafariExtensionViewController.xib b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Base.lproj/SafariExtensionViewController.xib new file mode 100644 index 00000000..559d8ba7 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Base.lproj/SafariExtensionViewController.xib @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Info.plist b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Info.plist new file mode 100644 index 00000000..9daffcc2 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Info.plist @@ -0,0 +1,38 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.Safari.extension + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).SafariExtensionHandler + SFSafariContentScript + + + Script + script.js + + + SFSafariToolbarItem + + Action + Command + Identifier + Button + Image + ToolbarItemIcon.pdf + Label + Your Button + + SFSafariWebsiteAccess + + Level + All + + + NSHumanReadableDescription + This is SelfControl Rules Extension. You should tell us what your extension does here. + + diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/ToolbarItemIcon.pdf b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/ToolbarItemIcon.pdf new file mode 100644 index 0000000000000000000000000000000000000000..88405ddfe29891dccc415bf1c3b1530e53816384 GIT binary patch literal 4080 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 HPyzoRld}9& literal 0 HcmV?d00001 diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/blocked.html b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/blocked.html new file mode 100644 index 00000000..d44aa0c0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/blocked.html @@ -0,0 +1,33 @@ + + + + +Site Blocked + + + + +

    🚫 Site Blocked

    +

    This website has been blocked by Self Control content blocker as per policy.

    + + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js new file mode 100644 index 00000000..73296b47 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js @@ -0,0 +1,264 @@ +//safari.extension.dispatchMessage("load") +//document.addEventListener("DOMContentLoaded", function(event) { +// console.log("DOMContentLoaded"); +// safari.extension.dispatchMessage("[SC] 🔍] Hello World!"); +//}); + +// +//window.addEventListener("load", function () { +// console.log("Page fully loaded"); +// safari.extension.dispatchMessage("[SC] 🔍] Hello World! Load"); +//}); +//document.addEventListener("click", function (event) { +// safari.extension.dispatchMessage("PAGE_CLICKED", { +// url: window.location.href, +// x: event.clientX, +// y: event.clientY +// }); +//}); +//function notifySwift(data) { +// safari.extension.dispatchMessage("REQUEST_DETECTED", data); +//} +// +//// Example usage: +//notifySwift({ url: location.href, time: Date.now() }); +//safari.self.addEventListener("message", function(event) { +// if (event.name === "SWIFT_ACK") { +// console.log("[SC] 🔍] Swift acknowledged:", event.message); +// } +//}); +//(function() { +// +// function checkURL() { +// console.log("[SC] 🔍] Swift acknowledged:", location.href); +// +// if (location.href.match(/facebook\.com\/friends/)) { +// // Stop page loading immediately +// window.stop(); +// +// // Notify Swift +// safari.extension.dispatchMessage("BLOCK_FRIENDS_PAGE", { +// url: location.href +// }); +// } +// } +// +// // Run on load + on history navigation +// checkURL(); +// document.addEventListener("DOMContentLoaded", checkURL); +// +// // For SPA router (Facebook uses React navigation) +// const pushState = history.pushState; +// history.pushState = function() { +// pushState.apply(history, arguments); +// checkURL(); +// }; +// +//})(); +// Injected script for Safari App Extension +// Blocks navigation to facebook.com/friends by stopping load and notifying the extension. +//(function() { +// +// function isFriendsPath(href) { +// try { +// // Normalize and check path+hash+query +// const url = new URL(href, location.origin); +// // Check hostname contains facebook.com and path begins with /friends +// return /(^|\.)facebook\.com$/.test(url.hostname) && url.pathname.startsWith('/friends'); +// } catch (e) { +// return false; +// } +// } +// +// function notifySwift() { +// console.log("[SC] 🔍] notifySwift:"); +// +// try { +// // Stop network activity for this page +// if (typeof window.stop === 'function') { +// window.stop(); +// } +// +// // Dispatch message to Safari App Extension +// if (window.safari && safari.extension && safari.extension.dispatchMessage) { +// console.log("[SC] 🔍] notifySwift:if (window.safari"); +// +// safari.extension.dispatchMessage("BLOCK_FRIENDS_PAGE", { +// url: location.href, +// time: Date.now() +// }); +// } else { +// console.warn("[SC] 🔍] Safari extension messaging API unavailable"); +// } +// } catch (err) { +// console.error("[SC] 🔍] notifySwift error:", err); +// } +// } +// +// function checkURL() { +// if (isFriendsPath(location.href)) { +// notifySwift(); +// } +// } +// +// // initial check +// checkURL(); +// +// // handle regular page load +// document.addEventListener("DOMContentLoaded", checkURL); +// +// // handle SPA navigation (monkey-patch history) +// (function(history) { +// const pushState = history.pushState; +// history.pushState = function() { +// const result = pushState.apply(this, arguments); +// setTimeout(checkURL, 10); +// return result; +// }; +// const replaceState = history.replaceState; +// history.replaceState = function() { +// const result = replaceState.apply(this, arguments); +// setTimeout(checkURL, 10); +// return result; +// }; +// })(window.history); +// +// // handle popstate (back/forward) +// window.addEventListener('popstate', function() { +// setTimeout(checkURL, 10); +// }); +// +// // observe clicks on anchors that may bypass history +// document.addEventListener('click', function(e) { +// setTimeout(checkURL, 10); +// }, true); +// +// // for good measure, observe mutations — some SPAs do their own routing +// const observer = new MutationObserver(function() { +// checkURL(); +// }); +// observer.observe(document.documentElement || document.body, { childList: true, subtree: true }); +// +//})(); + +//(function () { +// +// function notifySwift() { +// try { +// if (typeof window.stop === "function") { +// window.stop(); +// } +// +// if ( +// window.safari && +// safari.extension && +// typeof safari.extension.dispatchMessage === "function" +// ) { +// safari.extension.dispatchMessage("PAGE_VISIT", { +// url: location.href, +// time: Date.now(), +// }); +// } +// } catch (err) { +// console.error("[SC] notifySwift error:", err); +// } +// } +// +// function onURLChange() { +// console.log("[SC] 🔍 onURLChange:", location.href); +// notifySwift(); +// } +// +// // Initial load +// onURLChange(); +// +// // Observe SPA navigation (pushState, replaceState) +// (function (history) { +// ["pushState", "replaceState"].forEach(function (method) { +// const original = history[method]; +// history[method] = function () { +// const result = original.apply(this, arguments); +// setTimeout(onURLChange, 10); +// return result; +// }; +// }); +// })(window.history); +// +// window.addEventListener("popstate", () => setTimeout(onURLChange, 10)); +// document.addEventListener("click", () => setTimeout(onURLChange, 10), true); +// +// // ✅ Wait for DOM before attaching MutationObserver +// function setupObserver() { +// const target = document.body; +// if (!target) { +// // If body not yet available, retry shortly +// console.warn("[SC] document.body not ready, retrying..."); +// return setTimeout(setupObserver, 50); +// } +// +// try { +// const observer = new MutationObserver(() => onURLChange()); +// observer.observe(target, { childList: true, subtree: true }); +// console.log("[SC] ✅ MutationObserver attached"); +// } catch (err) { +// console.error("[SC] MutationObserver error:", err); +// } +// } +// +// if (document.readyState === "loading") { +// document.addEventListener("DOMContentLoaded", setupObserver); +// } else { +// setupObserver(); +// } +// +//})(); + +//if (safari.self && safari.self.addEventListener) { +// safari.self.addEventListener("message", function(event) { +// if (event.name === "REDIRECT_BLOCKED_URL" && event.message && event.message.redirect) { +// console.log("[SC] 🔍] REDIRECT_BLOCKED_URL:"); +// +// location.replace(event.message.redirect); +// } +// }); +//} +safari.extension.dispatchMessage("ready"); +(function () { + + // Listen for messages from the Swift extension + safari.self.addEventListener("message", (event) => { + if (event.name === "REDIRECT_BLOCKED_URL") { + const redirectURL = event.message.redirect; + console.log("[SC] 🚫 Redirecting to:", redirectURL); + + try { + window.stop(); // stop current load +// location.href = redirectURL; // redirect + const blockedPage = safari.extension.baseURI + "blocked.html"; + window.location.href = blockedPage; + } catch (err) { + console.error("[SC] redirect error:", err); + } + } + }); + + // Send PAGE_VISIT message + function notifySwift() { + try { + if (window.safari && safari.extension && safari.extension.dispatchMessage) { + safari.extension.dispatchMessage("PAGE_VISIT", { + url: location.href, + time: Date.now(), + }); + } + } catch (err) { + console.error("[SC] notifySwift error:", err); + } + } + + // Observe navigation changes (simplified) + window.addEventListener("load", notifySwift); + window.addEventListener("popstate", notifySwift); + document.addEventListener("click", () => setTimeout(notifySwift, 10), true); + +})(); diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift new file mode 100644 index 00000000..6706ffe6 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift @@ -0,0 +1,143 @@ +// +// SafariExtensionHandler.swift +// SelfControl Rules Extension +// +// Created by Satendra Singh on 16/11/25. +// + +import SafariServices +import os.log + +class SafariExtensionHandler: SFSafariExtensionHandler { + var blockedPatterns: [String] = [ + "facebook.com/friends", + "facebook.com/marketplace", + "instagram.com/explore", + "youtube.com/feed/subscriptions" + ] + + let redirectURL = "https://selfcontrol-blocked.local/blocked" // 🟢 Your custom page + private let appGroup = "group.com.application.SelfControl.corebits" + + override func beginRequest(with context: NSExtensionContext) { + let request = context.inputItems.first as? NSExtensionItem + refreshBlockedURLs() + let profile: UUID? + if #available(iOS 17.0, macOS 14.0, *) { + profile = request?.userInfo?[SFExtensionProfileKey] as? UUID + } else { + profile = request?.userInfo?["profile"] as? UUID + } + os_log(.default, "[SC] 🔍] The extension received a request for profile: %{public}@", profile?.uuidString ?? "none") + } + + override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) { + os_log("[SC] 🔍 The extension received a message: %{public}@", messageName) + if messageName == "reloadList" { + refreshBlockedURLs() + } + if messageName == "ready" { + pingHostApp() + } + + guard messageName == "PAGE_VISIT", + let urlString = userInfo?["url"] as? String else { return } + + os_log("[SC] 🔍 Received URL: %{public}@", urlString) + + // Match against blocked URLs + if blockedPatterns.contains(where: { urlString.contains($0) }) { + os_log("[SC] 🚫 Blocking and redirecting: %{public}@", urlString) + page.dispatchMessageToScript(withName: "REDIRECT_BLOCKED_URL", userInfo: ["redirect": redirectURL]) + } + } + + private func pingHostApp() { + let defaults = UserDefaults(suiteName: appGroup) + defaults?.set(true, forKey: "ready") + defaults?.synchronize() + } + + private func refreshBlockedURLs() { + let res = ContentBlockerExtensionRequestHandler.handleRequestList(groupIdentifier: appGroup) + os_log(.default, "[SC] 🔍] List of blocked URLs: %{public}@", res ?? "No data") + let domains: [String] = res?.map({ $0.trigger.urlFilter }) ?? [] + os_log(.default, "[SC] 🔍] List of blocked URLs: %{public}@", domains) + blockedPatterns = domains + } + + func messagexReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) { + os_log(.default, "[SC] 🔍] The extension messageReceived: %{public}@", messageName) + +// if messageName == "BLOCK_FRIENDS_PAGE" { +// +// page.getContainingTab { tab in +// tab.navigate(to: URL(string: "https://www.facebook.com/")!) +// } +// } + +// page.getPropertiesWithCompletionHandler { properties in +// os_log(.default, "[SC] 🔍] The extension received a message (%@) from a script injected into (%@) with userInfo (%@)", messageName, String(describing: properties?.url), userInfo ?? [:]) +// } +// page.getPropertiesWithCompletionHandler { properties in +// print("[SC] 🔍] ✅ Message Name:", messageName) +// print("[SC] 🔍] 🌍 Page URL:", properties?.url?.absoluteString ?? "Unknown") +// print("[SC] 🔍] 📦 Data Received:", userInfo ?? [:]) +// } +// // Example: Send response back to JS +// page.dispatchMessageToScript(withName: "SWIFT_ACK", userInfo: ["received": true]) + if messageName == "BLOCK_FRIENDS_PAGE" { + // Redirect the tab away from /friends + page.getContainingTab { tab in + // Redirect target — change as needed + if let url = URL(string: "https://www.facebook.com/") { + tab.navigate(to: url) + } + } + } + + page.getPropertiesWithCompletionHandler { properties in + NSLog("The extension received a message (\(messageName)) from a script injected into (\(String(describing: properties?.url))) with userInfo (\(userInfo ?? [:]))") + if #available(macOS 10.14, *) { + os_log(.default, "[SC] 🔍] The extension received a message: %{public}@", properties?.url?.description ?? "-") + } else { + // Fallback on earlier versions + } + if properties?.url?.description.contains("facebook.com/friends") == true { + page.getContainingTab(completionHandler: { tab in + // Fetch the active page for this tab to access its URL via page properties. + tab.navigate(to: URL(string: "https://www.corebitss.com")!) +// tab.getActivePage { activePage in +// guard let activePage = activePage else { +// NSLog("The extension received a message (\(messageName)) but the tab has no active page. userInfo (\(userInfo ?? [:]))") +// if #available(macOS 10.14, *) { +// os_log(.default, "[SC] 🔍] The extension received a message but the tab has no active page.") +// } +// return +// } +// activePage.getPropertiesWithCompletionHandler { tabPageProperties in +// let tabURLDesc = tabPageProperties?.url?.description ?? "-" +// NSLog("The extension received a message (\(messageName)) from a script injected into (\(tabURLDesc)) with userInfo (\(userInfo ?? [:]))") +// if #available(macOS 10.14, *) { +// os_log(.default, "[SC] 🔍] The extension received a message: %{public}@", tabURLDesc) +// } +// } +// } + }) + } + } + } + + override func toolbarItemClicked(in window: SFSafariWindow) { + os_log(.default, "[SC] 🔍] The extension's toolbar item was clicked") + } + + override func validateToolbarItem(in window: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) { + validationHandler(true, "") + } + + override func popoverViewController() -> SFSafariExtensionViewController { + return SafariExtensionViewController.shared + } + +} diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionViewController.swift b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionViewController.swift new file mode 100644 index 00000000..bf137567 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionViewController.swift @@ -0,0 +1,18 @@ +// +// SafariExtensionViewController.swift +// SelfControl Rules Extension +// +// Created by Satendra Singh on 16/11/25. +// + +import SafariServices + +class SafariExtensionViewController: SFSafariExtensionViewController { + + static let shared: SafariExtensionViewController = { + let shared = SafariExtensionViewController() + shared.preferredContentSize = NSSize(width:320, height:240) + return shared + }() + +} diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SelfControl Rules Extension.entitlements b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SelfControl Rules Extension.entitlements new file mode 100644 index 00000000..ab2dad3b --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SelfControl Rules Extension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.application.SelfControl.corebits + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerExtensionRequestHandler.swift b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerExtensionRequestHandler.swift index b20bd5fc..59e7c1c3 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerExtensionRequestHandler.swift +++ b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerExtensionRequestHandler.swift @@ -89,4 +89,65 @@ public enum ContentBlockerExtensionRequestHandler { userInfo: [NSLocalizedDescriptionKey: message] ) } + + public static func handleRequestList(groupIdentifier: String) -> [BlockRule]? { + os_log(.info, "[SC] 🔍] Safari Start loading the content blocker") + + // Get the shared container URL using the provided group identifier + guard + let appGroupURL = FileManager.default.containerURL( + forSecurityApplicationGroupIdentifier: groupIdentifier + ) + else { + return nil + } + + // Construct the path to the shared blocker list file + let sharedFileURL = appGroupURL.appendingPathComponent(Constants.SAFARI_BLOCKER_FILE_NAME) + + // Determine which blocker list file to use + var blockerListFileURL = sharedFileURL + if !FileManager.default.fileExists(atPath: sharedFileURL.path) { + os_log(.info, "[SC] 🔍] Safari No blocker list file found. Using the default one.") + + // Fall back to the default blocker list included in the bundle + guard + let defaultURL = Bundle.main.url(forResource: "blockerList", withExtension: "json") + else { + return nil + } + blockerListFileURL = defaultURL + } + + func handleBlockList(_ jsonData: Data) -> [BlockRule]? { + do { + let decoder = JSONDecoder() + let rules = try decoder.decode([BlockRule].self, from: jsonData) + NSLog("[SC] 🔍] Received Blocked URLs: \(rules)") + return rules + } catch { + NSLog("[SC] 🔍] Failed to decode block list: \(error)") + } + return nil + } + return handleBlockList(try! Data(contentsOf: blockerListFileURL)) + } +} + + +public struct BlockRule: Codable { + struct Trigger: Codable { + let urlFilter: String + + enum CodingKeys: String, CodingKey { + case urlFilter = "url-filter" + } + } + + struct Action: Codable { + let type: String + } + + let trigger: Trigger + let action: Action } diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index 2882203d..26dedbc3 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -10,7 +10,8 @@ 77BA53092D9D5E2A00863476 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77BA53082D9D5E2A00863476 /* NetworkExtension.framework */; }; 9A441CE32E36024E00A521CC /* com.application.SelfControl.corebits.network.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 9A7FFB602E950837007D4A0D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A7FFA0B2E91950D007D4A0D /* Cocoa.framework */; }; - 9A7FFB692E950837007D4A0D /* SelfControl Safari Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9A7FFB5F2E950837007D4A0D /* SelfControl Safari Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9AD524B22EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9AD524B12EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9AD524CE2EC9F15500AAD5BF /* SelfControl Rules Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9AD524B12EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -21,23 +22,41 @@ remoteGlobalIDString = 77BA53052D9D5E2A00863476; remoteInfo = SelfControlExtension; }; - 9A7FFB672E950837007D4A0D /* PBXContainerItemProxy */ = { + 9AD524B32EC9B0EA00AAD5BF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 77BA52CE2D9D5D7700863476 /* Project object */; proxyType = 1; - remoteGlobalIDString = 9A7FFB5E2E950837007D4A0D; - remoteInfo = "SelfControl Safari Extension"; + remoteGlobalIDString = 9AD524B02EC9B0EA00AAD5BF; + remoteInfo = "SelfControl Rules Extension"; + }; + 9AD524CF2EC9F15500AAD5BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 77BA52CE2D9D5D7700863476 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AD524B02EC9B0EA00AAD5BF; + remoteInfo = "SelfControl Rules Extension"; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 9A7FFA162E91950D007D4A0D /* Embed Foundation Extensions */ = { + 9AD524C72EC9B0EA00AAD5BF /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 9AD524B22EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + 9AD524D12EC9F15500AAD5BF /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( - 9A7FFB692E950837007D4A0D /* SelfControl Safari Extension.appex in Embed Foundation Extensions */, + 9AD524CE2EC9F15500AAD5BF /* SelfControl Rules Extension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -61,12 +80,15 @@ 77BA53082D9D5E2A00863476 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; 9A7FFA0B2E91950D007D4A0D /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 9A7FFB5F2E950837007D4A0D /* SelfControl Safari Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SelfControl Safari Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9AD524982EC9B0E900AAD5BF /* SelfControl Rules.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SelfControl Rules.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9AD524B12EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SelfControl Rules Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 77BA53142D9D5E2A00863476 /* Exceptions for "SelfControlExtension" folder in "SelfControlExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( + ChromeExtensionRequestListner.swift, DNSProxyProvider.swift, DNSQuestion.swift, Info.plist, @@ -84,8 +106,8 @@ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( BlockOrAllowList.swift, + ChromeExtensionRequestListner.swift, IPCConnection.swift, - PlistListner.swift, "String+URL.swift", ); target = 77BA52D52D9D5D7700863476 /* SelfControl */; @@ -97,6 +119,21 @@ ); target = 9A7FFB5E2E950837007D4A0D /* SelfControl Safari Extension */; }; + 9ACEF3412ECCEAEA00E2CAC8 /* Exceptions for "SelfControl Safari Extension" folder in "SelfControl Rules Extension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Constants.swift, + ContentBlockerExtensionRequestHandler.swift, + ); + target = 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */; + }; + 9AD524C32EC9B0EA00AAD5BF /* Exceptions for "SelfControl Rules Extension" folder in "SelfControl Rules Extension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */; + }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -121,10 +158,19 @@ isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( 9A7FFB6D2E950837007D4A0D /* Exceptions for "SelfControl Safari Extension" folder in "SelfControl Safari Extension" target */, + 9ACEF3412ECCEAEA00E2CAC8 /* Exceptions for "SelfControl Safari Extension" folder in "SelfControl Rules Extension" target */, ); path = "SelfControl Safari Extension"; sourceTree = ""; }; + 9AD524B52EC9B0EA00AAD5BF /* SelfControl Rules Extension */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 9AD524C32EC9B0EA00AAD5BF /* Exceptions for "SelfControl Rules Extension" folder in "SelfControl Rules Extension" target */, + ); + path = "SelfControl Rules Extension"; + sourceTree = ""; + }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -151,6 +197,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9AD524952EC9B0E900AAD5BF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AD524AE2EC9B0EA00AAD5BF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -160,6 +220,7 @@ 77BA52D82D9D5D7700863476 /* SelfControl */, 77BA530A2D9D5E2A00863476 /* SelfControlExtension */, 9A7FFB612E950837007D4A0D /* SelfControl Safari Extension */, + 9AD524B52EC9B0EA00AAD5BF /* SelfControl Rules Extension */, 77BA53072D9D5E2A00863476 /* Frameworks */, 77BA52D72D9D5D7700863476 /* Products */, ); @@ -171,6 +232,8 @@ 77BA52D62D9D5D7700863476 /* SelfControl.app */, 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */, 9A7FFB5F2E950837007D4A0D /* SelfControl Safari Extension.appex */, + 9AD524982EC9B0E900AAD5BF /* SelfControl Rules.app */, + 9AD524B12EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex */, ); name = Products; sourceTree = ""; @@ -195,13 +258,13 @@ 77BA52D32D9D5D7700863476 /* Frameworks */, 77BA52D42D9D5D7700863476 /* Resources */, 9AFC129E2E355671003B5455 /* Embed System Extensions */, - 9A7FFA162E91950D007D4A0D /* Embed Foundation Extensions */, + 9AD524D12EC9F15500AAD5BF /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( 9A441CE22E36024300A521CC /* PBXTargetDependency */, - 9A7FFB682E950837007D4A0D /* PBXTargetDependency */, + 9AD524D02EC9F15500AAD5BF /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( 77BA52D82D9D5D7700863476 /* SelfControl */, @@ -257,6 +320,49 @@ productReference = 9A7FFB5F2E950837007D4A0D /* SelfControl Safari Extension.appex */; productType = "com.apple.product-type.app-extension"; }; + 9AD524972EC9B0E900AAD5BF /* SelfControl Rules */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9AD524C82EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControl Rules" */; + buildPhases = ( + 9AD524942EC9B0E900AAD5BF /* Sources */, + 9AD524952EC9B0E900AAD5BF /* Frameworks */, + 9AD524962EC9B0E900AAD5BF /* Resources */, + 9AD524C72EC9B0EA00AAD5BF /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 9AD524B42EC9B0EA00AAD5BF /* PBXTargetDependency */, + ); + name = "SelfControl Rules"; + packageProductDependencies = ( + ); + productName = "SelfControl Rules"; + productReference = 9AD524982EC9B0E900AAD5BF /* SelfControl Rules.app */; + productType = "com.apple.product-type.application"; + }; + 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9AD524C42EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControl Rules Extension" */; + buildPhases = ( + 9AD524AD2EC9B0EA00AAD5BF /* Sources */, + 9AD524AE2EC9B0EA00AAD5BF /* Frameworks */, + 9AD524AF2EC9B0EA00AAD5BF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 9AD524B52EC9B0EA00AAD5BF /* SelfControl Rules Extension */, + ); + name = "SelfControl Rules Extension"; + packageProductDependencies = ( + ); + productName = "SelfControl Rules Extension"; + productReference = 9AD524B12EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -276,6 +382,12 @@ 9A7FFB5E2E950837007D4A0D = { CreatedOnToolsVersion = 26.0; }; + 9AD524972EC9B0E900AAD5BF = { + CreatedOnToolsVersion = 26.0; + }; + 9AD524B02EC9B0EA00AAD5BF = { + CreatedOnToolsVersion = 26.0; + }; }; }; buildConfigurationList = 77BA52D12D9D5D7700863476 /* Build configuration list for PBXProject "SelfControl" */; @@ -295,6 +407,8 @@ 77BA52D52D9D5D7700863476 /* SelfControl */, 77BA53052D9D5E2A00863476 /* SelfControlExtension */, 9A7FFB5E2E950837007D4A0D /* SelfControl Safari Extension */, + 9AD524972EC9B0E900AAD5BF /* SelfControl Rules */, + 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */, ); }; /* End PBXProject section */ @@ -321,6 +435,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9AD524962EC9B0E900AAD5BF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AD524AF2EC9B0EA00AAD5BF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -345,6 +473,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9AD524942EC9B0E900AAD5BF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AD524AD2EC9B0EA00AAD5BF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -353,10 +495,15 @@ target = 77BA53052D9D5E2A00863476 /* SelfControlExtension */; targetProxy = 9A441CE12E36024300A521CC /* PBXContainerItemProxy */; }; - 9A7FFB682E950837007D4A0D /* PBXTargetDependency */ = { + 9AD524B42EC9B0EA00AAD5BF /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 9A7FFB5E2E950837007D4A0D /* SelfControl Safari Extension */; - targetProxy = 9A7FFB672E950837007D4A0D /* PBXContainerItemProxy */; + target = 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */; + targetProxy = 9AD524B32EC9B0EA00AAD5BF /* PBXContainerItemProxy */; + }; + 9AD524D02EC9F15500AAD5BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */; + targetProxy = 9AD524CF2EC9F15500AAD5BF /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -650,7 +797,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.application.SelfControl.corebits.SelfControl-Safari-Extension"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "SelfControl Dev Net Ext"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "SelfControl Dev Net Ext"; REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; @@ -709,6 +856,190 @@ }; name = Release; }; + 9AD524C52EC9B0EA00AAD5BF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "SelfControl Rules Extension/SelfControl Rules Extension.entitlements"; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = X6FQ433AWK; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "SelfControl Rules Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Rules Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "-framework", + SafariServices, + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.application.SelfControl.corebits.SelfControl-Safari-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "SelfControl Dev Net Ext"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 9AD524C62EC9B0EA00AAD5BF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "SelfControl Rules Extension/SelfControl Rules Extension.entitlements"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = X6FQ433AWK; + "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "SelfControl Rules Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Rules Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "-framework", + SafariServices, + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.application.SelfControl.corebits.SelfControl-Safari-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "SelfControl Dev Net Ext"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 9AD524C92EC9B0EA00AAD5BF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = KWW448CAK7; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Rules"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMainStoryboardFile = Main; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 26.0; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "-framework", + SafariServices, + "-framework", + WebKit, + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.sample.SelfControl-Rules"; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 9AD524CA2EC9B0EA00AAD5BF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = KWW448CAK7; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Rules"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMainStoryboardFile = Main; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 26.0; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "-framework", + SafariServices, + "-framework", + WebKit, + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.sample.SelfControl-Rules"; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -748,6 +1079,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 9AD524C42EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControl Rules Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9AD524C52EC9B0EA00AAD5BF /* Debug */, + 9AD524C62EC9B0EA00AAD5BF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9AD524C82EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControl Rules" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9AD524C92EC9B0EA00AAD5BF /* Debug */, + 9AD524CA2EC9B0EA00AAD5BF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 77BA52CE2D9D5D7700863476 /* Project object */; diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift index a8912aad..ced47c9e 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift @@ -64,6 +64,8 @@ struct ContentView: View { // Text("Enable Url Blocking") // } SafariExtensionView() + SafariExtensionWebView() + .frame(minHeight: 100) } .padding() .frame(minWidth: 150, minHeight: 150) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index a2d80514..f38f4f3d 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -10,13 +10,19 @@ import NetworkExtension import SystemExtensions import os.log import Cocoa +import Combine +import SafariServices -final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionRequestDelegate, AppCommunication { +final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionRequestDelegate, ExtensionToApp { @Published var status: Status = .stopped @State private var domains = ProxyPreferences.getBlockedDomains() - private let listner = PlistListner() + private let listner = ChromeExtensionRequestListner() @Published var delay: Double = 0.0 var blockedIPAddressed: [String] = [] + private var cancellables = Set() + + // Safari extension identifier used to query state + private let safariExtensionIdentifier = "com.application.SelfControl.corebits.SelfControl-Safari-Extension" // Date formatter used to log entries lazy var dateFormatter: DateFormatter = { @@ -57,6 +63,17 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques return ProxyPreferences.getBlockedDomains() } self.listner.startListening() + + // Print status whenever it changes + $status + .sink { [weak self] newValue in + guard let self = self else { return } + print("[FilterViewModel] status changed to: \(newValue) (\(newValue.text))") + os_log("[SC] 🔍] status changed to: %{public}@ (%{public}@)", String(describing: newValue), newValue.text) + // Keep extension state in sync when status changes + self.refreshExtensionState() + } + .store(in: &cancellables) } deinit { @@ -70,6 +87,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques loadFilterConfiguration { success in guard success else { self.status = .stopped + self.refreshExtensionState() return } self.updateStatus() @@ -77,10 +95,26 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques object: NEFilterManager.shared(), queue: .main) { [weak self] _ in self?.updateStatus() + self?.refreshExtensionState() } + // Initial state refresh + self.refreshExtensionState() } } + // MARK: - NetworkExtensionStateProviding + + func refreshExtensionState() { + let isNEEnabled = NEFilterManager.shared().isEnabled + Task { @MainActor in + NetworkExtensionState.shared.isEnabled = isNEEnabled + if isNEEnabled == true { //Reset + NetworkExtensionState.shared.isSafariExtensionEnabled = false + NetworkExtensionState.shared.isChromeExtensionEnabled = false + } + } // We’ll query Safari extension state asynchronously for accuracy. + } + // MARK: - UI and Filter Management func setBlockedUrls(urls: [String]) { @@ -90,6 +124,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques print("Resolved app:\(ips)") setIPAddressesToBlock(addresses: Array(ips)) } + // State might change due to Safari integration } func setIPAddressesToBlock(addresses: [String]) { @@ -108,6 +143,8 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } else { status = .stopped } + // Keep extension state refreshed + refreshExtensionState() } func logFlow(_ flowInfo: [String: String], at date: Date, userAllowed: Bool) { @@ -142,6 +179,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques loadFilterConfiguration { success in guard success else { self.status = .stopped + self.refreshExtensionState() return } if filterManager.providerConfiguration == nil { @@ -159,9 +197,10 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques if let error = saveError { os_log("[SC] 🔍] Failed to save the filter configuration: %@", error.localizedDescription) self.status = .stopped + self.refreshExtensionState() return } else { -// self.enableDNSProxy() + self.enableDNSProxy() } self.registerWithProvider() } @@ -199,6 +238,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques DispatchQueue.main.async { self.status = success ? .running : .stopped self.setBlockedUrls(urls: ProxyPreferences.getBlockedDomains()) + self.refreshExtensionState() } // setBlockedURLs([]) } @@ -208,6 +248,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques // Start by activating the system extension. guard let extensionIdentifier = extensionIdentifier else { self.status = .stopped + self.refreshExtensionState() return } // let request = OSSystemExtensionRequest.propertiesRequest(forExtensionWithIdentifier: extensionIdentifier, queue: .main) @@ -245,11 +286,13 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques status = .indeterminate guard filterManager.isEnabled else { status = .stopped + refreshExtensionState() return } loadFilterConfiguration { success in guard success else { self.status = .running + self.refreshExtensionState() return } // Disable the content filter configuration. @@ -259,9 +302,11 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques if let error = saveError { os_log("[SC] 🔍] Failed to disable the filter configuration: %@", error.localizedDescription) self.status = .running + self.refreshExtensionState() return } self.status = .stopped + self.refreshExtensionState() } } } @@ -272,6 +317,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques guard result == .completed else { os_log("[SC] 🔍] Unexpected result %d for system extension request", result.rawValue) status = .stopped + refreshExtensionState() return } enableFilterConfiguration() @@ -280,6 +326,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) { os_log("[SC] 🔍] System extension request failed: %@", error.localizedDescription) status = .stopped + refreshExtensionState() } func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { @@ -331,6 +378,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func didSetUrls() { print("didSetUrls+++++") + // URLs updated; refresh extension state in case Safari side changed. } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/NetworkExtensionState.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/NetworkExtensionState.swift new file mode 100644 index 00000000..715d3c30 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/NetworkExtensionState.swift @@ -0,0 +1,54 @@ +// +// NetworkExtensionState.swift +// SelfControl +// +// Created by Satendra Singh on 26/11/25. +// + +import Foundation +import Combine + +@MainActor +final class NetworkExtensionState: ObservableObject { + // MARK: - Published mutable properties + @Published var isEnabled: Bool + @Published var isSafariExtensionEnabled: Bool + @Published var isChromeExtensionEnabled: Bool + + // Singleton instance, isolated to the MainActor + static let shared: NetworkExtensionState = NetworkExtensionState(isEnabled: false) + + // MARK: - Initializers + init(isEnabled: Bool, isSafariExtensionEnabled: Bool = false, isChromeExtensionEnabled: Bool = false) { + self.isEnabled = isEnabled + self.isSafariExtensionEnabled = isSafariExtensionEnabled + self.isChromeExtensionEnabled = isChromeExtensionEnabled + } + + // MARK: - Equatable + // Equatable conformance compares current values, not identity. + static func == (lhs: NetworkExtensionState, rhs: NetworkExtensionState) -> Bool { + lhs.isEnabled == rhs.isEnabled && + lhs.isSafariExtensionEnabled == rhs.isSafariExtensionEnabled && + lhs.isChromeExtensionEnabled == rhs.isChromeExtensionEnabled + } + + // MARK: - Focused setters (optional convenience) + func setIsEnabled(_ newValue: Bool) { + isEnabled = newValue + } + + func setSafariExtensionEnabled(_ newValue: Bool) { + isSafariExtensionEnabled = newValue + } + + func setChromeExtensionEnabled(_ newValue: Bool) { + isChromeExtensionEnabled = newValue + } + + func printAll() { + print("isEnabled: \(isEnabled)") + print("isSafariExtensionEnabled: \(isSafariExtensionEnabled)") + print("isChromeExtensionEnabled: \(isChromeExtensionEnabled)") + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift new file mode 100644 index 00000000..1920d47f --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift @@ -0,0 +1,121 @@ +// +// SafariExtensionWebView.swift +// SelfControl +// +// Created by Satendra Singh on 16/11/25. +// + +import SwiftUI +import WebKit +import SafariServices + +// A minimal NSViewRepresentable wrapper to show WKWebView in SwiftUI on macOS. +private struct WKWebViewRepresentable: NSViewRepresentable { + let webView: WKWebView + + func makeNSView(context: Context) -> WKWebView { + webView + } + + func updateNSView(_ nsView: WKWebView, context: Context) { + // No-op for now. You can update navigation or content here if needed. + } +} + +struct SafariExtensionWebView: View { + // Create a real WKWebView using WKWebViewConfiguration. + let vm = WebPageViewModel() + + var body: some View { + WKWebViewRepresentable(webView: vm.webView) + .frame(minHeight: 150) + .onAppear { + // Load something if desired, or leave it empty for now. + // Example: +// if let url = URL(string: "https://apple.com") { +// vm.webView.load(URLRequest(url: url)) +// } +// self.webView.navigationDelegate = self + +// self.webView.configuration.userContentController.add(self, name: "controller") + + self.vm.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!) + } + } +} + +final class WebPageViewModel: NSObject, WKNavigationDelegate, WKScriptMessageHandler { + private let extensionManager = SafariExtensionManager.shared + + let webView: WKWebView = { + let configuration = WKWebViewConfiguration() + // Configure as needed (userContentController, preferences, etc.) + return WKWebView(frame: .zero, configuration: configuration) + }() + + override init() { + super.init() + self.webView.navigationDelegate = self + self.webView.configuration.userContentController.add(self, name: "controller") + extensionManager.onChange = { [weak self] in + self?.updateSafariExtensionState(state: true) + } + } + + private let extensionIdentifier = "com.application.SelfControl.corebits.SelfControl-Safari-Extension" + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + + SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionIdentifier) { (state, error) in + if let error = error as NSError? { + print("⚠️ Safari State check error for \(self.extensionIdentifier): \(error.domain) code \(error.code) – \(error.localizedDescription)") + } else { + print("🔍 Safari Current state of \(self.extensionIdentifier):", state?.description ?? "Unknown") + print(state?.isEnabled ?? "Not sure") + if state?.isEnabled == false { + print("⚠️ Safari Extension not enabled in Safari. Please enable it in Settings → Safari → Extensions.") + } + } + + DispatchQueue.main.async { + if #available(macOS 13, *) { + webView.evaluateJavaScript("show(\(state?.isEnabled), true)") + } else { + webView.evaluateJavaScript("show(\(state?.isEnabled), false)") + } + self.updateSafariExtensionState(state: state?.isEnabled ?? false) + } + } + } + + + private func updateSafariExtensionState(state: Bool) { + ProxyPreferences.setSafariExtensionState(state) + IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: state) + if state == true { + updateBlocker() + } + } + + func updateBlocker() { + let urls = ProxyPreferences.getBlockedDomains() + print("URLS: \(urls)") + BlockListManager.updateSafariBlockList(blockedPaths: urls, appGroup: extensionIdentifier, extensionIdentifier: extensionIdentifier) + } + + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + if (message.body as! String != "open-preferences") { + return; + } + + SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionIdentifier) { error in + DispatchQueue.main.async { + NSApplication.shared.terminate(nil) + } + } + } +} + +#Preview { + SafariExtensionWebView() +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift index a57e5edf..b00abaa8 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift @@ -41,6 +41,7 @@ struct PreferencesView: View { Button("Save Preferences") { ProxyPreferences.setBlockedDomains(domains) viewModel.setBlockedUrls(urls: domains) + BlockListViewModel().updateBlocker() } } .padding() diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift index 4ba2f0b7..692462f0 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift @@ -10,18 +10,33 @@ import Foundation struct ProxyPreferences { // static let appGroup = "X6FQ433AWK.com.application.SelfControl.Extension" - static let blockedDomainsKey = "BlockedDomains" - + private static let blockedDomainsKey = "BlockedDomains" + private static let isSafariExtensionKey: String = "isSafariExtensionKey" + private static let isChromeExtensionKey: String = "isChromeExtensionKey" + private static let defaults = UserDefaults.standard static func getBlockedDomains() -> [String] { - let defaults = UserDefaults.standard -// let defaults = UserDefaults(suiteName: appGroup) - return defaults.stringArray(forKey: blockedDomainsKey) ?? [] } static func setBlockedDomains(_ domains: [String]) { -// let defaults = UserDefaults(suiteName: appGroup) - let defaults = UserDefaults.standard defaults.set(domains, forKey: blockedDomainsKey) } + + static func setSafariExtensionState(_ isEnabled: Bool) { + defaults.set(isEnabled, forKey: isSafariExtensionKey) + + } + + static func setChromeExtensionState(_ isEnabled: Bool) { + defaults.set(isEnabled, forKey: isChromeExtensionKey) + } + + static func safariExtensionState() { + defaults.value(forKey: isSafariExtensionKey) + + } + + static func chromeExtensionState() { + defaults.value(forKey: isChromeExtensionKey) + } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Resources/Base.lproj/Main.html b/Self-Control-Extension/SelfControl/SelfControl/Resources/Base.lproj/Main.html new file mode 100644 index 00000000..c5968810 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Resources/Base.lproj/Main.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + test Safari Ext Icon +

    You can turn on test Safari Ext’s extension in Safari Extensions preferences.

    +

    test Safari Ext’s extension is currently on. You can turn it off in Safari Extensions preferences.

    +

    test Safari Ext’s extension is currently off. You can turn it on in Safari Extensions preferences.

    + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl/Resources/Icon.png b/Self-Control-Extension/SelfControl/SelfControl/Resources/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..423b491de9f0c035d4c9d7736c535bebfe078c8e GIT binary patch literal 100020 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 V_?Dz(4+H(D^+fM+=_BiR{|z2&Zv_AV literal 0 HcmV?d00001 diff --git a/Self-Control-Extension/SelfControl/SelfControl/Resources/Script.js b/Self-Control-Extension/SelfControl/SelfControl/Resources/Script.js new file mode 100644 index 00000000..1f3d6705 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Resources/Script.js @@ -0,0 +1,22 @@ +function show(enabled, useSettingsInsteadOfPreferences) { + if (useSettingsInsteadOfPreferences) { + document.getElementsByClassName('state-on')[0].innerText = "test Safari Ext’s extension is currently on. You can turn it off in the Extensions section of Safari Settings."; + document.getElementsByClassName('state-off')[0].innerText = "test Safari Ext’s extension is currently off. You can turn it on in the Extensions section of Safari Settings."; + document.getElementsByClassName('state-unknown')[0].innerText = "You can turn on test Safari Ext’s extension in the Extensions section of Safari Settings."; + document.getElementsByClassName('open-preferences')[0].innerText = "Quit and Open Safari Settings…"; + } + + if (typeof enabled === "boolean") { + document.body.classList.toggle(`state-on`, enabled); + document.body.classList.toggle(`state-off`, !enabled); + } else { + document.body.classList.remove(`state-on`); + document.body.classList.remove(`state-off`); + } +} + +function openPreferences() { + webkit.messageHandlers.controller.postMessage("open-preferences"); +} + +document.querySelector("button.open-preferences").addEventListener("click", openPreferences); diff --git a/Self-Control-Extension/SelfControl/SelfControl/Resources/Style.css b/Self-Control-Extension/SelfControl/SelfControl/Resources/Style.css new file mode 100644 index 00000000..cbde9e69 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Resources/Style.css @@ -0,0 +1,45 @@ +* { + -webkit-user-select: none; + -webkit-user-drag: none; + cursor: default; +} + +:root { + color-scheme: light dark; + + --spacing: 20px; +} + +html { + height: 100%; +} + +body { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + + gap: var(--spacing); + margin: 0 calc(var(--spacing) * 2); + height: 100%; + + font: -apple-system-short-body; + text-align: center; +} + +body:not(.state-on, .state-off) :is(.state-on, .state-off) { + display: none; +} + +body.state-on :is(.state-off, .state-unknown) { + display: none; +} + +body.state-off :is(.state-on, .state-unknown) { + display: none; +} + +button { + font-size: 1em; +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Resources/en.lproj/Main.html b/Self-Control-Extension/SelfControl/SelfControl/Resources/en.lproj/Main.html new file mode 100644 index 00000000..c5968810 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Resources/en.lproj/Main.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + test Safari Ext Icon +

    You can turn on test Safari Ext’s extension in Safari Extensions preferences.

    +

    test Safari Ext’s extension is currently on. You can turn it off in Safari Extensions preferences.

    +

    test Safari Ext’s extension is currently off. You can turn it on in Safari Extensions preferences.

    + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListManager.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListManager.swift index b12b12af..2f9a5b69 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListManager.swift @@ -65,32 +65,82 @@ enum BlockListManager { try data.write(to: fileURL, options: .atomic) print("✅ Wrote blockerList.json to: \(fileURL.path)") +// SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionIdentifier) { (state, error) in +// guard let state = state, error == nil else { +// // Insert code to inform the user that something went wrong. +// return +// } +// if let error = error as NSError? { +// print("⚠️ State check error for \(extensionIdentifier): \(error.domain) code \(error.code) – \(error.localizedDescription)") +// print("ℹ️ Tips: Ensure the content blocker extension bundle identifier matches exactly, the extension is installed and enabled in Safari, and the app/extension share the same App Group: \(appGroup)") +// } else { +// print("🔍 Current state of \(extensionIdentifier):", state.description ?? "Unknown") +// if state.isEnabled == false { +// print("⚠️ Extension not enabled in Safari. Please enable it in Settings → Safari → Extensions.") +// } +// } +// +// DispatchQueue.main.async { +// if #available(macOS 13, *) { +//// webView.evaluateJavaScript("show(\(state.isEnabled), true)") +// } else { +//// webView.evaluateJavaScript("show(\(state.isEnabled), false)") +// } +// } +// } // Query current state for diagnostics; proceed to reload regardless. - SFContentBlockerManager.getStateOfContentBlocker(withIdentifier: extensionIdentifier) { state, error in - if let error = error as NSError? { - print("⚠️ State check error for \(extensionIdentifier): \(error.domain) code \(error.code) – \(error.localizedDescription)") - print("ℹ️ Tips: Ensure the content blocker extension bundle identifier matches exactly, the extension is installed and enabled in Safari, and the app/extension share the same App Group: \(appGroup)") - } else { - print("🔍 Current state of \(extensionIdentifier):", state?.description ?? "Unknown") - if state?.isEnabled == false { - print("⚠️ Extension not enabled in Safari. Please enable it in Settings → Safari → Extensions.") +// SFContentBlockerManager.getStateOfContentBlocker(withIdentifier: extensionIdentifier) { state, error in +// if let error = error as NSError? { +// print("⚠️ State check error for \(extensionIdentifier): \(error.domain) code \(error.code) – \(error.localizedDescription)") +// print("ℹ️ Tips: Ensure the content blocker extension bundle identifier matches exactly, the extension is installed and enabled in Safari, and the app/extension share the same App Group: \(appGroup)") +// } else { +// print("🔍 Current state of \(extensionIdentifier):", state?.description ?? "Unknown") +// if state?.isEnabled == false { +// print("⚠️ Extension not enabled in Safari. Please enable it in Settings → Safari → Extensions.") +// } +// } +// +// // Attempt reload regardless; this often yields a clearer error if the identifier is wrong. +// SFContentBlockerManager.reloadContentBlocker(withIdentifier: extensionIdentifier) { error in +// if let error = error as NSError? { +// print("❌ Reload error for \(extensionIdentifier): \(error.domain) code \(error.code) – \(error.localizedDescription)") +// print("ℹ️ If this persists: verify the extension target’s bundle ID, that the extension is enabled, and that it has access to the shared container.") +// } else { +// print("✅ Safari Content Blocker reloaded successfully.") +// } +// } +// } +// if SafariExtensionManager.shared.isExtensionReady { //If Safari is ready mark it in the Network extension + SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: "com.application.SelfControl.corebits.SelfControl-Safari-Extension") { state, error in + print("Safari Extension State Error :\(error, default: "nil")") + print("Safari Extension State :\(state?.isEnabled ?? false)") + Task { @MainActor in + if NetworkExtensionState.shared.isEnabled == true { + _ = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: state?.isEnabled ?? false) + NetworkExtensionState.shared.isSafariExtensionEnabled = state?.isEnabled ?? false + NetworkExtensionState.shared.printAll() + } } } - - // Attempt reload regardless; this often yields a clearer error if the identifier is wrong. - SFContentBlockerManager.reloadContentBlocker(withIdentifier: extensionIdentifier) { error in - if let error = error as NSError? { - print("❌ Reload error for \(extensionIdentifier): \(error.domain) code \(error.code) – \(error.localizedDescription)") - print("ℹ️ If this persists: verify the extension target’s bundle ID, that the extension is enabled, and that it has access to the shared container.") - } else { - print("✅ Safari Content Blocker reloaded successfully.") +// } + SFSafariApplication.dispatchMessage( + withName: "reloadList", + toExtensionWithIdentifier: "com.application.SelfControl.corebits.SelfControl-Safari-Extension", + userInfo: ["changed": true]) { error in + print("Safari Message toExtensionWithIdentifier: reloadList:", error) + if error != nil { + Task { @MainActor in + if NetworkExtensionState.shared.isEnabled == true && NetworkExtensionState.shared.isSafariExtensionEnabled == false { //possibly extension is ready now + _ = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: true) + NetworkExtensionState.shared.isSafariExtensionEnabled = true + NetworkExtensionState.shared.printAll() + } + } } } - } - } catch { - print("❌ Error writing blocker file:", error) + print("❌ Safari Error writing blocker file:", error) } } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionManager.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionManager.swift new file mode 100644 index 00000000..17b6e556 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionManager.swift @@ -0,0 +1,37 @@ +// +// SafariExtensionManager.swift +// SelfControl +// +// Created by Satendra Singh on 24/11/25. +// + +import Foundation + +final class SafariExtensionManager: ObservableObject { + static let shared = SafariExtensionManager() + private let appGroup = "group.com.application.SelfControl.corebits" + var onChange: (() -> Void)? + private var isReady: Bool = false + + private init() { + NotificationCenter.default.addObserver( + forName: UserDefaults.didChangeNotification, + object: UserDefaults(suiteName: appGroup), + queue: .main + ) { notification in + let shared = UserDefaults(suiteName: self.appGroup) + print("Received from extension:", shared?.string(forKey: "ready") ?? "") + if let value = shared?.bool(forKey: "ready"){ + print("Ready now:") + if self.isReady == false { + self.onChange?() + self.isReady = true + } + } + } + } + + var isExtensionReady: Bool { + return UserDefaults(suiteName: appGroup)?.bool(forKey: "ready") == true + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/PlistListner.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/ChromeExtensionRequestListner.swift similarity index 62% rename from Self-Control-Extension/SelfControl/SelfControlExtension/PlistListner.swift rename to Self-Control-Extension/SelfControl/SelfControlExtension/ChromeExtensionRequestListner.swift index d31f9467..1572003a 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/PlistListner.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/ChromeExtensionRequestListner.swift @@ -9,13 +9,13 @@ import Foundation import Network import os.log -final class PlistListner: NSObject, ObservableObject { - +final class ChromeExtensionRequestListner: NSObject, ObservableObject { + private var isChromeStatusSetInExtension: Bool = false var listener: NWListener? = try! NWListener(using: .tcp, on: 8080) var blockeddomainFetcher: (() -> [String])? func startListening() { - os_log("[SC] 🔍] NW startListening") + os_log("[SC] 🔍] PlistListner startListening") // let objects: [[String: Any]] = [ // ["id": 1, "name": "Alice", "status": "running"], // ["id": 2, "name": "Bob", "status": "stopped"], @@ -23,15 +23,23 @@ final class PlistListner: NSObject, ObservableObject { // ] listener = try! NWListener(using: .tcp, on: 8080) listener?.newConnectionHandler = { conn in + + Task { @MainActor in + print("newConnectionHandler isEnabled: \(NetworkExtensionState.shared.isEnabled)") + if NetworkExtensionState.shared.isEnabled == true && NetworkExtensionState.shared.isChromeExtensionEnabled == false { + NetworkExtensionState.shared.isChromeExtensionEnabled = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.chrome.rawValue, state: true) + NetworkExtensionState.shared.printAll() + } + } let blockedDomainList: [String] = self.blockeddomainFetcher?() ?? [] let blockedUrls = ["blocked": blockedDomainList] let jsonData = try! JSONSerialization.data(withJSONObject: blockedUrls, options: []) let jsonString = String(data: jsonData, encoding: .utf8)! - os_log("[SC] 🔍] NW newConnectionHandler") + os_log("[SC] 🔍] PlistListner newConnectionHandler") conn.start(queue: DispatchQueue.global(qos: .userInitiated)) // conn.receiveMessage { data, _, _, _ in - os_log("[SC] 🔍] NW conn.receiveMessage") + os_log("[SC] 🔍] PlistListner conn.receiveMessage") let response = """ HTTP/1.1 200 OK\r Content-Type: application/json\r @@ -40,7 +48,7 @@ final class PlistListner: NSObject, ObservableObject { \(jsonString) """ conn.send(content: response.data(using: .utf8), contentContext: .finalMessage , completion: .contentProcessed { error in - os_log("[SC] 🔍] NW Sent response:\(error)") + os_log("[SC] 🔍] PlistListner Sent response:\(error)") // conn.cancel() }) @@ -48,7 +56,8 @@ final class PlistListner: NSObject, ObservableObject { conn.stateUpdateHandler = { state in if state == .ready { - os_log("[SC] 🔍] NW stateUpdateHandler ready") + + os_log("[SC] 🔍] PlistListner stateUpdateHandler ready") } } } diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift index 0db08d63..115dff7d 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift @@ -4,6 +4,7 @@ // // Created by Satendra Singh on 11/10/25. // +import os.log enum Constants { /// File name for the JSON file with Safari rules. @@ -32,4 +33,27 @@ enum AllowedProcess: String { let ancestors: [String] = process.ancestors?.value(forKey: "name") as? [String] ?? [] return allCases.contains(where: { $0.rawValue == process.bundleID }) || ancestors.contains(where: { $0.lowercased().contains("chrome") || $0.lowercased().contains("safari") }) } + + static func isAllowedProcess(_ process: Process, isSafariExtensionActive: Bool = false, isChromeExtensionActive: Bool = false) -> Bool { + // Determine if the process is Safari or Chrome by bundle ID or ancestor name. + let ancestors: [String] = process.ancestors?.value(forKey: "name") as? [String] ?? [] + let isSafariProcess = + process.bundleID == AllowedProcess.safari.rawValue || + ancestors.contains(where: { $0.lowercased().contains("safari") }) + let isChromeProcess = + process.bundleID == AllowedProcess.chrome.rawValue || + ancestors.contains(where: { $0.lowercased().contains("chrome") }) + + // Allow only if the corresponding extension is active. +// os_log("[SC] 🔍] isSafariProcess: \(isSafariProcess), isChromeProcess: \(isChromeProcess)") + + if isSafariProcess { + return isSafariExtensionActive + } + if isChromeProcess { + return isChromeExtensionActive + } + return false + } + } diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift index 15a0ab3a..8f26eb97 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift @@ -77,7 +77,7 @@ class DNSProxyProvider: NEDNSProxyProvider { guard offset + length <= data.count else { NSLog("Incomplete DNS message") - os_log("SC] 🔍 Incomplete DNS message") + os_log("[SC] 🔍 Incomplete DNS message") break } diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift index 8f00f1d1..8bef5778 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift @@ -66,7 +66,7 @@ class DNSParser { } index += length + 1 } - return (labels.joined(separator: "."), index) + return c(labels.joined(separator: "."), index) } } diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift index 3cd3f4c3..c9032761 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift @@ -185,7 +185,6 @@ class FilterDataProvider: NEFilterDataProvider { extOffset += extLen } - return nil } @@ -193,88 +192,114 @@ class FilterDataProvider: NEFilterDataProvider { from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes data: Data - ) -> NEFilterDataVerdict { - - - let process = processFromflow(flow: flow) - // os_log(" FilterDataProvider: appIdAndName %{public}@, %{public}@", log: OSLog.default, type: .debug, process?.name ?? "", process?.path ?? "") - os_log(" FilterDataProvider: app %{public}@", log: OSLog.default, type: .debug, process?.description ?? "") - - if let process = process, AllowedProcess.isAllowedProcess(process) { - os_log("[SC] 🔍] FilterDataProvider: allowing as it is in allowed list.") - return .allow() - } + ) -> NEFilterDataVerdict { + + guard IPCConnection.shared.blockedUrls.count > 0 else { //No signinficant urls to block + return .allow() + } + + if RequestSourceAppValidator.isAllowedHost(flow: flow) { + return .allow() + } + guard let socketFlow = flow as? NEFilterSocketFlow else { - os_log("[SC] 🔍] Not a socket flow. Allowing.", log: OSLog.default, type: .info) - return .allow() + os_log("[SC] 🔍] Not a socket flow. Allowing.", log: OSLog.default, type: .info) + return .allow() } - if socketFlow.direction != .outbound { - os_log("[SC] 🔍] Not a inbound socket flow. Allowing.", log: OSLog.default, type: .info) - return .allow() - } - -// guard let socketFlow = flow as? NEFilterSocketFlow else { -// return .allow() -// } + if socketFlow.direction != .outbound { + os_log("[SC] 🔍] Not a inbound socket flow. Allowing.", log: OSLog.default, type: .info) + return .allow() + } + + // guard let socketFlow = flow as? NEFilterSocketFlow else { + // return .allow() + // } let requestString = String(data: data, encoding: .utf8) - if let requestString { - os_log("[SC] 🔍] data HTTP Request: %{public}@", requestString) - } - - // Try HTTP detection - if let requestString = requestString, - requestString.hasPrefix("GET ") || requestString.hasPrefix("POST ") { - - if let host = extractHost(from: requestString), - let path = extractPath(from: requestString) { - let urlString = "http://\(host)\(path)" - os_log("[SC] 🔍] data HTTP Request: %{public}@", urlString) - } - - } else if let sni = extractSNI(fromTLSData: data) { - os_log("[SC] 🔍] data TLS SNI Host: %{public}@", sni) - guard let hostDomain = TLDURLToDomain.getURLDomain(from: sni) else { - return .allow() - } - os_log("[SC] 🔍] data hostDomain: %{public}@", hostDomain) - - for url in IPCConnection.shared.blockedUrls { - if url.contains(hostDomain) { - os_log("[SC] 🔍] data Blocking flow Host match SNI:%{public}@, host:%{public}@", sni, hostDomain) - return .drop() - } - } - } - - return .allow() - } + if let requestString { + os_log("[SC] 🔍] data HTTP Request: %{public}@", requestString) + } + + // Try HTTP detection + if let requestString = requestString, + requestString.hasPrefix("GET ") || requestString.hasPrefix("POST ") { + + if let host = extractHost(from: requestString), + let path = extractPath(from: requestString) { + let urlString = "http://\(host)\(path)" + os_log("[SC] 🔍] data HTTP Request: %{public}@", urlString) + } + + } else if let sni = extractSNI(fromTLSData: data) { + os_log("[SC] 🔍] data TLS SNI Host: %{public}@", sni) + guard let hostDomain = TLDURLToDomain.getURLDomain(from: sni) else { + return .allow() + } + os_log("[SC] 🔍] data hostDomain: %{public}@", hostDomain) + + for url in IPCConnection.shared.blockedUrls { + if url.contains(hostDomain) { + os_log("[SC] 🔍] data Blocking flow Host match SNI:%{public}@, host:%{public}@", sni, hostDomain) + return .drop() + } + } + } + + return .allow() + } // Called for each new flow. override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { - // Provide peek sizes required by this overload - return .filterDataVerdict(withFilterInbound: false, - peekInboundBytes: 0, - filterOutbound: true, - peekOutboundBytes: Int.max) + // Provide peek sizes required by this overload os_log("[SC] 🔍] FilterDataProvider: handleNewFlow invoked", log: OSLog.default, type: .debug) -// if let appID = flow.sourceAppIdentifier { -// print("App making the request: \(appID)") -// } -// let appIdAndName = SourceAppAuditTokenBuilder.getAppInfo(from: flow) -// os_log("[SC] 🔍] FilterDataProvider: appIdAndName %@, %@", log: OSLog.default, type: .debug, appIdAndName.appName ?? "", appIdAndName.bundleID ?? "") guard IPCConnection.shared.blockedUrls.count > 0 else { //No signinficant urls to block return .allow() } - - let process = processFromflow(flow: flow) -// os_log(" FilterDataProvider: appIdAndName %{public}@, %{public}@", log: OSLog.default, type: .debug, process?.name ?? "", process?.path ?? "") - os_log(" FilterDataProvider: app %{public}@", log: OSLog.default, type: .debug, process?.description ?? "") - - if let process = process, AllowedProcess.isAllowedProcess(process) { - os_log("[SC] 🔍] FilterDataProvider: allowing as it is in allowed list.") + + if RequestSourceAppValidator.isAllowedHost(flow: flow) { return .allow() } + + guard let socketFlow = flow as? NEFilterSocketFlow else { + return .filterDataVerdict(withFilterInbound: false, + peekInboundBytes: 0, + filterOutbound: true, + peekOutboundBytes: Int.max) + +// return .allow() + } + + // Try to extract SNI hostname (HTTPS) + guard let hostname = socketFlow.remoteHostname else { + return .filterDataVerdict(withFilterInbound: false, + peekInboundBytes: 0, + filterOutbound: true, + peekOutboundBytes: Int.max) + } + + // Fallback to IP address if SNI is not present +// let remoteIP = (socketFlow.remoteEndpoint as? NWHostEndpoint)?.hostname ?? hostname + +// let host = hostname.isEmpty ? remoteIP : hostname + let lower = hostname.lowercased() + os_log("[SC] 🔍] Host SNI host:%{public}@", lower) + guard let hostDomain = TLDURLToDomain.getURLDomain(from: lower) else { + return .filterDataVerdict(withFilterInbound: false, + peekInboundBytes: 0, + filterOutbound: true, + peekOutboundBytes: Int.max) + } + + for url in IPCConnection.shared.blockedUrls { + if url.contains(hostDomain) { + os_log("[SC] 🔍] data Blocking flow Host:%{public}@", hostDomain) + return .drop() + } + } + + os_log("[SC] 🔍] Not a blocked domain. Allowing.", log: OSLog.default, type: .info) + return .allow() + guard let socketFlow = flow as? NEFilterSocketFlow else { os_log("[SC] 🔍] Not a socket flow. Allowing.", log: OSLog.default, type: .info) return .allow() @@ -308,12 +333,6 @@ class FilterDataProvider: NEFilterDataProvider { os_log("[SC] 🔍] Flow from remote endpoint: %{public}@, URL: %{public}@", log: OSLog.default, type: .debug, socketFlow.remoteEndpoint.debugDescription, flow.url?.description ?? "nil") - // Extract remote endpoint (if available). -// guard let remoteEndpoint = socketFlow.remoteEndpoint as? NWHostEndpoint else { -// os_log("[SC] 🔍] No valid remote endpoint. Allowing flow.", log: OSLog.default, type: .error) -// return .allow() -// } - if remoteHost.isEmpty { //Unable to get host return .allow() } @@ -340,34 +359,6 @@ class FilterDataProvider: NEFilterDataProvider { return .drop() } } -// if var urlString = flow.url?.absoluteString { -// os_log("[SC] 🔍] Checking URL path: %{public}@", urlString) -//// let blockedHosts = ["google.com/mail", "google.com/news", "facebook.com"] -// let blockedHosts = IPCConnection.shared.blockedUrls -// os_log("[SC] 🔍] BlockedList: %{public}@ checking:%{public}@",blockedHosts, urlString) -// if urlString.hasPrefix("www.") { -// let noWWW = String(urlString.dropFirst(4)) -// urlString = noWWW -// } -// -// for host in blockedHosts { -// if urlString.contains(host) { -// os_log("[SC] 🔍] Blocking flow to handleNewFlow %{public}@", urlString) -// return .drop() -//// return .allow() -// } -//// flow.url?.host() == url -// } -// } - // if blockedHosts.contains(flow.url?.path() ?? "") { - // os_log("Blocking flow to %@", remoteEndpoint.hostname) - // return .drop() - // } - // Only process outbound traffic. -// if socketFlow.direction != .outbound { -// os_log("[SC] 🔍] Non-outbound traffic. Allowing.", log: OSLog.default, type: .info) -// return .allow() -// } return .allow() @@ -515,17 +506,17 @@ class FilterDataProvider: NEFilterDataProvider { os_log("[SC] 🔍] Alert: User decision needed for flow %@", log: OSLog.default, type: .info, flow.debugDescription) } - func processFromflow(flow: NEFilterFlow) -> Process? { - guard let auditTokenData = flow.sourceAppAuditToken else { - return nil - } - - // Convert NSData → audit_token_t - var auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in - return ptr.load(as: audit_token_t.self) - } - return Process.init(&auditToken) - } +// func processFromflow(flow: NEFilterFlow) -> Process? { +// guard let auditTokenData = flow.sourceAppAuditToken else { +// return nil +// } +// +// // Convert NSData → audit_token_t +// var auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in +// return ptr.load(as: audit_token_t.self) +// } +// return Process.init(&auditToken) +// } } //https://developer.chrome.com/docs/extensions/how-to/distribute/install-extensions diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift index 286fbf67..eb1f541a 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift @@ -9,16 +9,22 @@ import Foundation import os.log import Network -/// App --> Provider IPC -@objc protocol ProviderCommunication { +enum ActiveBrowserExtensios: String { + case safari + case chrome +} + +/// App --> Provider IPC, to be implememted in Extension +@objc protocol AppToExtensionExtension { func register(_ completionHandler: @escaping (Bool) -> Void) func setBlockedURLs(_ urls: [String]) func setBlockedIPAddresses(_ ips: [String]) + // Use Objective-C bridgeable type for XPC surface + func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) } -/// Provider --> App IPC -@objc protocol AppCommunication { - +/// Provider --> App IPC to Be implememted in the app +@objc protocol ExtensionToApp { func promptUser(aboutFlow flowInfo: [String: String], responseHandler: @escaping (Bool) -> Void) func didSetUrls() } @@ -35,13 +41,16 @@ class IPCConnection: NSObject { var listener: NSXPCListener? var currentConnection: NSXPCConnection? - weak var delegate: AppCommunication? + weak var delegate: ExtensionToApp? static let shared = IPCConnection() // var blockedUrls: [String] = ProxyPreferences.getBlockedDomains() var blockedUrls: [String] = [String]() var blockedList = BlockOrAllowList(items: []) var blockedIPAddresses: Set = [] - + private(set) var isSafariExtensionEnable: Bool = false + private(set) var isGoogleChromeEnabled: Bool = false + // Published extension state for UI/observers + // MARK: Methods /** @@ -53,7 +62,9 @@ class IPCConnection: NSObject { guard let networkExtensionKeys = bundle.object(forInfoDictionaryKey: "NetworkExtension") as? [String: Any], let machServiceName = networkExtensionKeys["NEMachServiceName"] as? String else { - fatalError("Mach service name is missing from the Info.plist") +// fatalError("Mach service name is missing from the Info.plist") + os_log("[SC] 🔍] Mach service name is missing from the Info.plist") + return "" } return machServiceName @@ -71,7 +82,7 @@ class IPCConnection: NSObject { } /// This method is called by the app to register with the provider running in the system extension. - func register(withExtension bundle: Bundle, delegate: AppCommunication, completionHandler: @escaping (Bool) -> Void) { + func register(withExtension bundle: Bundle, delegate: ExtensionToApp, completionHandler: @escaping (Bool) -> Void) { self.delegate = delegate @@ -85,11 +96,11 @@ class IPCConnection: NSObject { let newConnection = NSXPCConnection(machServiceName: machServiceName, options: []) // The exported object is the delegate. - newConnection.exportedInterface = NSXPCInterface(with: AppCommunication.self) + newConnection.exportedInterface = NSXPCInterface(with: ExtensionToApp.self) newConnection.exportedObject = delegate // The remote object is the provider's IPCConnection instance. - newConnection.remoteObjectInterface = NSXPCInterface(with: ProviderCommunication.self) + newConnection.remoteObjectInterface = NSXPCInterface(with: AppToExtensionExtension.self) currentConnection = newConnection newConnection.resume() @@ -99,8 +110,9 @@ class IPCConnection: NSObject { self.currentConnection?.invalidate() self.currentConnection = nil completionHandler(false) - }) as? ProviderCommunication else { - fatalError("Failed to create a remote object proxy for the provider") + }) as? AppToExtensionExtension else { + os_log("Failed to create a remote object proxy for the provider") + return } providerProxy.register(completionHandler) // providerProxy.setBlockedURLs(blockedUrls) @@ -118,11 +130,12 @@ class IPCConnection: NSObject { } guard let appProxy = connection.remoteObjectProxyWithErrorHandler({ promptError in - os_log("[SC] 🔍] Failed to prompt the user: %@", promptError.localizedDescription) + os_log("[SC] 🔍] Failed to prompt the user: %{public}@", promptError.localizedDescription) self.currentConnection = nil responseHandler(true) - }) as? AppCommunication else { - fatalError("Failed to create a remote object proxy for the app") + }) as? ExtensionToApp else { + os_log("Failed to create a remote object proxy for the app") + return false } appProxy.promptUser(aboutFlow: flowInfo, responseHandler: responseHandler) @@ -138,11 +151,11 @@ extension IPCConnection: NSXPCListenerDelegate { func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { // The exported object is this IPCConnection instance. - newConnection.exportedInterface = NSXPCInterface(with: ProviderCommunication.self) + newConnection.exportedInterface = NSXPCInterface(with: AppToExtensionExtension.self) newConnection.exportedObject = self // The remote object is the delegate of the app's IPCConnection instance. - newConnection.remoteObjectInterface = NSXPCInterface(with: AppCommunication.self) + newConnection.remoteObjectInterface = NSXPCInterface(with: ExtensionToApp.self) newConnection.invalidationHandler = { self.currentConnection = nil @@ -162,8 +175,9 @@ extension IPCConnection: NSXPCListenerDelegate { os_log("[SC] 🔍] Enabling URL blocking") guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in os_log("[SC] 🔍] Failed to register with the provider: %@", registerError.localizedDescription) - }) as? ProviderCommunication else { - fatalError("Failed to create a remote object proxy for the provider") + }) as? AppToExtensionExtension else { + os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") + return } providerProxy.setBlockedURLs(urls) } @@ -172,15 +186,28 @@ extension IPCConnection: NSXPCListenerDelegate { os_log("[SC] 🔍] Enabling URL blocking") guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in os_log("[SC] 🔍] Failed to register with the provider: %@", registerError.localizedDescription) - }) as? ProviderCommunication else { - fatalError("Failed to create a remote object proxy for the provider") + }) as? AppToExtensionExtension else { + os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") + return } providerProxy.setBlockedIPAddresses(urls) } + + func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) -> Bool { + os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension:\(extensionTypeRawValue), state:\(state)") + guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in + os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension: %@", registerError.localizedDescription) + }) as? AppToExtensionExtension else { + os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") + return false + } + providerProxy.setActiveBrowserExtension(extensionTypeRawValue, state: state) + return true + } } -extension IPCConnection: ProviderCommunication { +extension IPCConnection: AppToExtensionExtension { func setBlockedIPAddresses(_ ips: [String]) { blockedIPAddresses = Set(ips) @@ -188,26 +215,47 @@ extension IPCConnection: ProviderCommunication { } func setBlockedURLs(_ urls: [String]) { - os_log("[SC] 🔍] Blocking: %{public}@",urls) + os_log("[SC] 🔍] Extension Received Blocking: %{public}@",urls) blockedUrls = urls // delegate?.didSetUrls() blockedList = BlockOrAllowList(items: blockedUrls) guard let connection = currentConnection else { print("[SC] 🔍] Cannot update blocked urls, app isn't registered") + os_log("[SC] 🔍] Cannot update blocked urls, app isn't registered") + return } guard let appProxy = connection.remoteObjectProxyWithErrorHandler({ promptError in - os_log("[SC] 🔍] Failed to prompt the user: %@", promptError.localizedDescription) + os_log("[SC] 🔍] Failed to prompt the user: %{public}@", promptError.localizedDescription) + os_log("Failed to create a remote object proxy for the app") // self.currentConnection = nil // responseHandler(true) - }) as? AppCommunication else { - fatalError("Failed to create a remote object proxy for the app") + }) as? ExtensionToApp else { + os_log("[SC] 🔍] Cannot update blocked urls, app isn't registered") + os_log("Failed to create a remote object proxy for the app") + return } appProxy.didSetUrls() } + func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { + // Map the raw value to the Swift enum if possible + os_log("[SC] 🔍] Received extension state to %{public}@", extensionTypeRawValue) + if let mapped = ActiveBrowserExtensios(rawValue: extensionTypeRawValue) { + switch mapped { + case .safari: + self.isSafariExtensionEnable = state + case .chrome: + self.isGoogleChromeEnabled = state + } + os_log("[SC] 🔍] Set browser extension state to %{public}@", extensionTypeRawValue) + os_log("[SC] 🔍] Set browser state to %{public}d", state) + } else { + os_log("[SC] 🔍] Unknown browser extension type %{public}@", extensionTypeRawValue) + } + } // MARK: ProviderCommunication diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift new file mode 100644 index 00000000..89611a24 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift @@ -0,0 +1,38 @@ +// +// HostAppValidator.swift +// SelfControl +// +// Created by Satendra Singh on 29/11/25. +// + +import NetworkExtension +import OSLog + +struct RequestSourceAppValidator { + + static func isAllowedHost(flow: NEFilterFlow) -> Bool { + let process = processFromflow(flow: flow) +// os_log(" FilterDataProvider: appIdAndName %{public}@, %{public}@", log: OSLog.default, type: .debug, process?.name ?? "", process?.path ?? "") + os_log(" FilterDataProvider: app %{public}@", log: OSLog.default, type: .debug, process?.description ?? "") + let isSafariExtensionEnabled = IPCConnection.shared.isSafariExtensionEnable + let isChromeExtensionEnabled = IPCConnection.shared.isGoogleChromeEnabled + + if let process = process, AllowedProcess.isAllowedProcess(process, isSafariExtensionActive: isSafariExtensionEnabled, isChromeExtensionActive: isChromeExtensionEnabled) { + os_log("[SC] 🔍] handleNewFlow: allowing as it is in allowed list.") + return true + } + return false + } + + static func processFromflow(flow: NEFilterFlow) -> Process? { + guard let auditTokenData = flow.sourceAppAuditToken else { + return nil + } + + // Convert NSData → audit_token_t + var auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in + return ptr.load(as: audit_token_t.self) + } + return Process.init(&auditToken) + } +} From e4198c712670835d90a46ea3e6630ec3a1868a7f Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 29 Dec 2025 22:45:01 +0530 Subject: [PATCH 03/64] implemented web extension, fixed existing project --- AppController.m | 1 - Common/SCSentry.m | 67 +-- Common/SCSettings.m | 8 +- Daemon/SCDaemon.m | 9 +- Daemon/SCDaemonXPC.m | 2 +- Daemon/selfcontrold-Info.plist | 2 +- Info.plist | 6 +- Podfile | 9 +- SCDurationSlider.m | 35 +- ...ontentBlockerExtensionRequestHandler.swift | 11 +- .../SafariExtensionConstants.swift | 32 ++ .../SafariExtensionHandler.swift | 84 +++- .../ServerResponse.swift | 56 +++ .../Constants.swift | 13 - .../ContentBlockerRequestHandler.swift | 78 ---- .../SelfControl Safari Extension/Info.plist | 15 - .../SelfControl Safari Extension.entitlements | 10 - .../SelfControl Safari Extension/block.html | 33 -- .../blockerList.json | 60 --- .../SelfControl.xcodeproj/project.pbxproj | 408 ++-------------- .../xcschemes/SelfControlExtension.xcscheme | 4 +- .../ChromeExtensionRequestListner.swift | 146 ++++++ .../SelfControl/Main/AppMover.swift | 126 +++++ .../SelfControl/Main/ContentView.swift | 88 ++-- .../SelfControl/Main/FilterViewModel.swift | 71 ++- .../Main/NetworkExtensionState.swift | 1 + .../Main/SafariExtensionWebView.swift | 2 +- .../Preferences/PreferencesView.swift | 1 - .../SafariExtension/BlockListViewModel.swift | 37 -- .../SafariExtensionManager.swift | 37 -- .../SafariExtension/SafariExtensionView.swift | 57 --- .../BlockListManager.swift | 82 ++-- .../SafariExtensionManager.swift | 80 ++++ .../SimpleRegexConverter.swift | 0 .../ChromeExtensionRequestListner.swift | 67 --- .../DNSProxyProvider.swift | 108 ----- .../SelfControlExtension/DNSQuestion.swift | 72 --- .../ExtensionsPreferencesManager.swift | 24 - .../FilterDataProvider.swift | 16 +- .../SelfControlExtension/IPCConnection.swift | 25 +- .../RequestSourceAppValidator.swift | 2 +- .../dynamic-url-blocker/background.js | 12 +- SelfControl.xcodeproj/project.pbxproj | 437 +++++++++++------- .../xcschemes/SCKillerHelper.xcscheme | 79 ++++ .../xcschemes/SelfControl.xcscheme | 90 ++++ SelfControl_Prefix.pch | 3 +- cli-main.m | 4 +- 47 files changed, 1258 insertions(+), 1352 deletions(-) rename Self-Control-Extension/SelfControl/{SelfControl Safari Extension => SelfControl Rules Extension}/ContentBlockerExtensionRequestHandler.swift (91%) create mode 100644 Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionConstants.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/Constants.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerRequestHandler.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/Info.plist delete mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/SelfControl Safari Extension.entitlements delete mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/block.html delete mode 100644 Self-Control-Extension/SelfControl/SelfControl Safari Extension/blockerList.json create mode 100644 Self-Control-Extension/SelfControl/SelfControl/ChromeExtensionSupport/ChromeExtensionRequestListner.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/AppMover.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListViewModel.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionManager.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionView.swift rename Self-Control-Extension/SelfControl/SelfControl/{SafariExtension => SafariExtensionSupport}/BlockListManager.swift (51%) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/SafariExtensionManager.swift rename Self-Control-Extension/SelfControl/SelfControl/{SafariExtension => SafariExtensionSupport}/SimpleRegexConverter.swift (100%) delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/ChromeExtensionRequestListner.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/ExtensionsPreferencesManager.swift create mode 100644 SelfControl.xcodeproj/xcshareddata/xcschemes/SCKillerHelper.xcscheme create mode 100644 SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme diff --git a/AppController.m b/AppController.m index 242bdabf..700200cd 100755 --- a/AppController.m +++ b/AppController.m @@ -31,7 +31,6 @@ #import "SCXPCClient.h" #import "SCBlockFileReaderWriter.h" #import "SCUIUtilities.h" -#import @interface AppController () {} diff --git a/Common/SCSentry.m b/Common/SCSentry.m index 5b97b797..33b35337 100644 --- a/Common/SCSentry.m +++ b/Common/SCSentry.m @@ -9,7 +9,8 @@ #import "SCSettings.h" #ifndef TESTING -#import +//#import +//#import #endif @implementation SCSentry @@ -17,24 +18,24 @@ @implementation SCSentry //org.eyebeam.SelfControl + (void)startSentry:(NSString*)componentId { #ifndef TESTING - [SentrySDK startWithConfigureOptions:^(SentryOptions *options) { - options.dsn = @"https://58fbe7145368418998067f88896007b2@o504820.ingest.sentry.io/5592195"; - options.releaseName = [NSString stringWithFormat: @"%@%@", componentId, SELFCONTROL_VERSION_STRING]; - options.enableAutoSessionTracking = NO; - options.environment = @"dev"; - - // make sure no data leaves the device if error reporting isn't enabled - options.beforeSend = ^SentryEvent * _Nullable(SentryEvent * _Nonnull event) { - if ([SCSentry errorReportingEnabled]) { - return event; - } else { - return NULL; - } - }; - }]; - [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { - [scope setTagValue: [[NSLocale currentLocale] localeIdentifier] forKey: @"localeId"]; - }]; +// [SentrySDK startWithConfigureOptions:^(SentryOptions *options) { +// options.dsn = @"https://58fbe7145368418998067f88896007b2@o504820.ingest.sentry.io/5592195"; +// options.releaseName = [NSString stringWithFormat: @"%@%@", componentId, SELFCONTROL_VERSION_STRING]; +// options.enableAutoSessionTracking = NO; +// options.environment = @"dev"; +// +// // make sure no data leaves the device if error reporting isn't enabled +// options.beforeSend = ^SentryEvent * _Nullable(SentryEvent * _Nonnull event) { +// if ([SCSentry errorReportingEnabled]) { +// return event; +// } else { +// return NULL; +// } +// }; +// }]; +// [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { +// [scope setTagValue: [[NSLocale currentLocale] localeIdentifier] forKey: @"localeId"]; +// }]; #endif } @@ -113,19 +114,19 @@ + (void)updateDefaultsContext { [defaultsDict removeObjectForKey: @"SULastProfileSubmissionDate"]; #ifndef TESTING - [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { - [scope setContextValue: defaultsDict forKey: @"NSUserDefaults"]; - }]; +// [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { +// [scope setContextValue: defaultsDict forKey: @"NSUserDefaults"]; +// }]; #endif } + (void)addBreadcrumb:(NSString*)message category:(NSString*)category { #ifndef TESTING - SentryBreadcrumb* crumb = [[SentryBreadcrumb alloc] init]; - crumb.level = kSentryLevelInfo; - crumb.category = category; - crumb.message = message; - [SentrySDK addBreadcrumb: crumb]; +// SentryBreadcrumb* crumb = [[SentryBreadcrumb alloc] init]; +// crumb.level = kSentryLevelInfo; +// crumb.category = category; +// crumb.message = message; +// [SentrySDK addBreadcrumb: crumb]; #endif } @@ -147,7 +148,7 @@ + (void)captureError:(NSError*)error { [[SCSettings sharedSettings] updateSentryContext]; [SCSentry updateDefaultsContext]; #ifndef TESTING - [SentrySDK captureError: error]; +// [SentrySDK captureError: error]; #endif } @@ -170,11 +171,11 @@ + (void)captureMessage:(NSString*)message withScopeBlock:(nullable void (^)(Sent [SCSentry updateDefaultsContext]; #ifndef TESTING - if (block != nil) { - [SentrySDK captureMessage: message withScopeBlock: block]; - } else { - [SentrySDK captureMessage: message]; - } +// if (block != nil) { +// [SentrySDK captureMessage: message withScopeBlock: block]; +// } else { +// [SentrySDK captureMessage: message]; +// } #endif } diff --git a/Common/SCSettings.m b/Common/SCSettings.m index e0b35082..d8239a34 100644 --- a/Common/SCSettings.m +++ b/Common/SCSettings.m @@ -9,7 +9,7 @@ #import #ifndef TESTING -#import +//#import #endif float const SYNC_INTERVAL_SECS = 30; @@ -485,9 +485,9 @@ - (void)updateSentryContext { } #ifndef TESTING - [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { - [scope setContextValue: dictCopy forKey: @"SCSettings"]; - }]; +// [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { +// [scope setContextValue: dictCopy forKey: @"SCSettings"]; +// }]; #endif } diff --git a/Daemon/SCDaemon.m b/Daemon/SCDaemon.m index 3fd746a2..bd9760f2 100644 --- a/Daemon/SCDaemon.m +++ b/Daemon/SCDaemon.m @@ -124,6 +124,7 @@ - (void)startInactivityTimer { } }]; } + - (void)resetInactivityTimer { self.lastActivityDate = [NSDate date]; } @@ -146,6 +147,11 @@ - (void)dealloc { #pragma mark - NSXPCListenerDelegate - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { +//#if DEBUG +// // In Debug builds, skip code signature validation to ease development. +// NSLog(@"[DEBUG] Accepting XPC connection without signature validation."); +// [SCSentry addBreadcrumb:@"DEBUG: accepted XPC connection without validation" category:@"daemon"]; +//#else // There is a potential security issue / race condition with matching based on PID, so we use the (technically private) auditToken instead audit_token_t auditToken = newConnection.auditToken; NSDictionary* guestAttributes = @{ @@ -159,7 +165,7 @@ - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConne SecRequirementRef isSelfControlApp; // versions before 4.0 didn't have hardened code signing, so aren't trustworthy to talk to the daemon // (plus the daemon didn't exist before 4.0 so there's really no reason they should want to run it!) - SecRequirementCreateWithString(CFSTR("anchor apple generic and (identifier \"org.eyebeam.SelfControl\" or identifier \"org.eyebeam.selfcontrol-cli\") and info [CFBundleVersion] >= \"407\" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = EG6ZYP3AQH)"), kSecCSDefaultFlags, &isSelfControlApp); + SecRequirementCreateWithString(CFSTR("anchor apple generic and (identifier \"org.eyebeam.SelfControl\" or identifier \"org.eyebeam.selfcontrol-cli\") and info [CFBundleVersion] >= \"407\""), kSecCSDefaultFlags, &isSelfControlApp); OSStatus clientValidityStatus = SecCodeCheckValidity(guest, kSecCSDefaultFlags, isSelfControlApp); CFRelease(guest); @@ -171,6 +177,7 @@ - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConne [SCSentry captureError: error]; return NO; } +//#endif SCDaemonXPC* scdXPC = [[SCDaemonXPC alloc] init]; newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol: @protocol(SCDaemonProtocol)]; diff --git a/Daemon/SCDaemonXPC.m b/Daemon/SCDaemonXPC.m index ad9f09c1..2676c21a 100644 --- a/Daemon/SCDaemonXPC.m +++ b/Daemon/SCDaemonXPC.m @@ -71,7 +71,7 @@ - (void)getVersionWithReply:(void(^)(NSString * version))reply { NSLog(@"XPC method called: getVersionWithReply"); // We specifically don't check for authorization here. Everyone is always allowed to get // the version of the helper tool. - reply(SELFCONTROL_VERSION_STRING); + reply(@"4.0.2"); } @end diff --git a/Daemon/selfcontrold-Info.plist b/Daemon/selfcontrold-Info.plist index fc0522a2..9c146397 100755 --- a/Daemon/selfcontrold-Info.plist +++ b/Daemon/selfcontrold-Info.plist @@ -52,7 +52,7 @@ Free and open-source under the GPL. SMAuthorizedClients - anchor apple generic and (identifier "org.eyebeam.SelfControl" or identifier "org.eyebeam.selfcontrol-cli") and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = EG6ZYP3AQH) + anchor apple generic and (identifier "org.eyebeam.SelfControl" or identifier "org.eyebeam.selfcontrol-cli") diff --git a/Info.plist b/Info.plist index 85a72114..7d80bf32 100755 --- a/Info.plist +++ b/Info.plist @@ -58,18 +58,18 @@ SMPrivilegedExecutables org.eyebeam.selfcontrold - anchor apple generic and identifier "org.eyebeam.selfcontrold" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = EG6ZYP3AQH) + anchor apple generic and identifier "org.eyebeam.selfcontrold" SUEnableAutomaticChecks SUEnableJavaScript + SUEnableSystemProfiling + SUFeedURL https://selfcontrolapp.com/appcast.xml SUPublicDSAKeyFile dsa_pub.pem - SUEnableSystemProfiling - SUSendProfileInfo diff --git a/Podfile b/Podfile index 1d715879..51468d78 100644 --- a/Podfile +++ b/Podfile @@ -1,6 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' -minVersion = '10.10' +minVersion = '11.5' platform :osx, minVersion @@ -11,10 +11,9 @@ plugin 'cocoapods-prune-localizations', { :localizations => supported_locales } target "SelfControl" do use_frameworks! :linkage => :static pod 'MASPreferences', '~> 1.1.4' - pod 'TransformerKit', '~> 1.1.1' pod 'FormatterKit/TimeIntervalFormatter', '~> 1.8.0' pod 'LetsMove', '~> 1.24' - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '7.3.0' + pod 'Sentry' # Add test target target 'SelfControlTests' do @@ -24,19 +23,15 @@ end target "SelfControl Killer" do use_frameworks! :linkage => :static - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '7.3.0' end # we can't use_frameworks on these because they're command-line tools # Sentry says we need use_frameworks, but they seem to work OK anyway? target "SCKillerHelper" do - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '7.3.0' end target "selfcontrol-cli" do - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '7.3.0' end target "org.eyebeam.selfcontrold" do - pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '7.3.0' end post_install do |pi| diff --git a/SCDurationSlider.m b/SCDurationSlider.m index 25e75376..14de541b 100644 --- a/SCDurationSlider.m +++ b/SCDurationSlider.m @@ -7,7 +7,6 @@ #import "SCDurationSlider.h" #import "SCTimeIntervalFormatter.h" -#import #define kValueTransformerName @"BlockDurationSliderTransformer" @@ -47,15 +46,17 @@ - (void)setMaxDuration:(NSInteger)maxDuration { } - (void)registerMinutesValueTransformer { - [NSValueTransformer registerValueTransformerWithName: kValueTransformerName - transformedValueClass: [NSNumber class] - returningTransformedValueWithBlock:^id _Nonnull(id _Nonnull value) { - // if it's not a number or convertable to one, IDK man - if (![value respondsToSelector: @selector(floatValue)]) return @0; - - long minutesValue = lroundf([value floatValue]); - return @(minutesValue); - }]; + +//SS: TODO: +// [NSValueTransformer registerValueTransformerWithName: kValueTransformerName +// transformedValueClass: [NSNumber class] +// returningTransformedValueWithBlock:^id _Nonnull(id _Nonnull value) { +// // if it's not a number or convertable to one, IDK man +// if (![value respondsToSelector: @selector(floatValue)]) return @0; +// +// long minutesValue = lroundf([value floatValue]); +// return @(minutesValue); +// }]; } - (NSInteger)durationValueMinutes { @@ -63,13 +64,13 @@ - (NSInteger)durationValueMinutes { } - (void)bindDurationToObject:(id)obj keyPath:(NSString*)keyPath { - [self bind: @"value" - toObject: obj - withKeyPath: keyPath - options: @{ - NSContinuouslyUpdatesValueBindingOption: @YES, - NSValueTransformerNameBindingOption: kValueTransformerName - }]; +// [self bind: @"value" +// toObject: obj +// withKeyPath: keyPath +// options: @{ +// NSContinuouslyUpdatesValueBindingOption: @YES, +// NSValueTransformerNameBindingOption: kValueTransformerName +// }]; } - (NSString*)durationDescription { diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerExtensionRequestHandler.swift b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ContentBlockerExtensionRequestHandler.swift similarity index 91% rename from Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerExtensionRequestHandler.swift rename to Self-Control-Extension/SelfControl/SelfControl Rules Extension/ContentBlockerExtensionRequestHandler.swift index 59e7c1c3..132253a8 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerExtensionRequestHandler.swift +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ContentBlockerExtensionRequestHandler.swift @@ -9,6 +9,8 @@ import os.log import Foundation public enum ContentBlockerExtensionRequestHandler { + typealias BlockerFile = SafariExtensionConstants.SafariBlockerFile + /// Handles content blocking extension request for rules. /// /// This method loads the content blocker rules JSON file from the shared container @@ -33,7 +35,7 @@ public enum ContentBlockerExtensionRequestHandler { } // Construct the path to the shared blocker list file - let sharedFileURL = appGroupURL.appendingPathComponent(Constants.SAFARI_BLOCKER_FILE_NAME) + let sharedFileURL = appGroupURL.appendingPathComponent(BlockerFile.fullName) // Determine which blocker list file to use var blockerListFileURL = sharedFileURL @@ -42,7 +44,7 @@ public enum ContentBlockerExtensionRequestHandler { // Fall back to the default blocker list included in the bundle guard - let defaultURL = Bundle.main.url(forResource: "blockerList", withExtension: "json") + let defaultURL = Bundle.main.url(forResource: BlockerFile.name, withExtension: BlockerFile.ext) else { context.cancelRequest( withError: createError( @@ -101,9 +103,8 @@ public enum ContentBlockerExtensionRequestHandler { else { return nil } - // Construct the path to the shared blocker list file - let sharedFileURL = appGroupURL.appendingPathComponent(Constants.SAFARI_BLOCKER_FILE_NAME) + let sharedFileURL = appGroupURL.appendingPathComponent(BlockerFile.fullName) // Determine which blocker list file to use var blockerListFileURL = sharedFileURL @@ -112,7 +113,7 @@ public enum ContentBlockerExtensionRequestHandler { // Fall back to the default blocker list included in the bundle guard - let defaultURL = Bundle.main.url(forResource: "blockerList", withExtension: "json") + let defaultURL = Bundle.main.url(forResource: BlockerFile.name, withExtension: BlockerFile.ext) else { return nil } diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionConstants.swift b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionConstants.swift new file mode 100644 index 00000000..d37c7844 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionConstants.swift @@ -0,0 +1,32 @@ +// +// SafariExtensionConstants.swift +// SelfControl Rules Extension +// +// Created by Satendra Singh on 09/12/25. +// + +import Foundation + +struct SafariExtensionConstants { + static let identifier = "com.application.SelfControl.corebits.SelfControl-Safari-Extension" + static let appGroup = "group.com.application.SelfControl.corebits" + static let serviceURL = "http://127.0.0.1:\(servicePort)/safari" + static let servicePort: UInt16 = 8532 + + struct UserDefaultsKeys { + static let isExtensionEnabled = "enabled" + static let isExtensionReady = "ready" + } + + enum MessagesName: String { + case reloadList = "reloadList" + case pageVisit = "PAGE_VISIT" + case redirect = "REDIRECT_BLOCKED_URL" + } + + struct SafariBlockerFile { + static let name: String = "blockerList" + static let ext: String = "json" + static var fullName: String { name + "." + ext } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift index 6706ffe6..be30f489 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift @@ -8,17 +8,35 @@ import SafariServices import os.log +typealias Const = SafariExtensionConstants + class SafariExtensionHandler: SFSafariExtensionHandler { + private let ping = ServerPing() + private let defaults = UserDefaults(suiteName: Const.appGroup) var blockedPatterns: [String] = [ "facebook.com/friends", "facebook.com/marketplace", "instagram.com/explore", "youtube.com/feed/subscriptions" ] - + + override init() { + super.init() + os_log(.default, "[SC] 🔍] The extension Initialized") + pingHostApp() + ping.start() + } + + deinit { + let defaults = UserDefaults(suiteName: Const.appGroup) +// defaults?.set(false, forKey: "ready") + defaults?.synchronize() + } + let redirectURL = "https://selfcontrol-blocked.local/blocked" // 🟢 Your custom page - private let appGroup = "group.com.application.SelfControl.corebits" - +// private let appGroup = "group.com.application.SelfControl.corebits" + private var isEanbled: Bool = false + override func beginRequest(with context: NSExtensionContext) { let request = context.inputItems.first as? NSExtensionItem refreshBlockedURLs() @@ -29,18 +47,29 @@ class SafariExtensionHandler: SFSafariExtensionHandler { profile = request?.userInfo?["profile"] as? UUID } os_log(.default, "[SC] 🔍] The extension received a request for profile: %{public}@", profile?.uuidString ?? "none") + pingHostApp() + updateExtensionState() } override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) { + typealias MSG = SafariExtensionConstants.MessagesName os_log("[SC] 🔍 The extension received a message: %{public}@", messageName) - if messageName == "reloadList" { + if messageName == MSG.reloadList.rawValue { refreshBlockedURLs() } - if messageName == "ready" { - pingHostApp() - } +// if messageName == "ready" { +// pingHostApp() +// } + +// if messageName == "enableService" { +// self.isEanbled = true +// } +// if messageName == "disableService" { +// self.isEanbled = false +// } - guard messageName == "PAGE_VISIT", + guard isEanbled else { return } + guard messageName == MSG.pageVisit.rawValue, let urlString = userInfo?["url"] as? String else { return } os_log("[SC] 🔍 Received URL: %{public}@", urlString) @@ -52,14 +81,22 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } } - private func pingHostApp() { - let defaults = UserDefaults(suiteName: appGroup) - defaults?.set(true, forKey: "ready") + private func pingHostApp() { + defaults?.set(true, forKey: Const.UserDefaultsKeys.isExtensionReady) defaults?.synchronize() +// updateExtensionState() + } + + private func updateExtensionState() { +// defaults?.synchronize() + isEanbled = defaults?.bool(forKey: Const.UserDefaultsKeys.isExtensionEnabled) ?? false + os_log(.default, "[SC] 🔍] extension State: %{public}d", isEanbled) } + + private func refreshBlockedURLs() { - let res = ContentBlockerExtensionRequestHandler.handleRequestList(groupIdentifier: appGroup) + let res = ContentBlockerExtensionRequestHandler.handleRequestList(groupIdentifier: Const.appGroup) os_log(.default, "[SC] 🔍] List of blocked URLs: %{public}@", res ?? "No data") let domains: [String] = res?.map({ $0.trigger.urlFilter }) ?? [] os_log(.default, "[SC] 🔍] List of blocked URLs: %{public}@", domains) @@ -133,11 +170,32 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } override func validateToolbarItem(in window: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) { + os_log(.default, "[SC] 🔍] validateToolbarItem") validationHandler(true, "") + updateExtensionState() + pingHostApp() } override func popoverViewController() -> SFSafariExtensionViewController { return SafariExtensionViewController.shared } - + + +// func startHeartbeat() { +// Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in +// self.writeHeartbeat() +// } +// } +// +// func writeHeartbeat() { +// guard let url = FileManager.default +// .containerURL(forSecurityApplicationGroupIdentifier: appGroup)? +// .appendingPathComponent("heartbeat.json") else { return } +// +// let data: [String: Any] = [ +// "timestamp": Date().timeIntervalSince1970 +// ] +// +// try? (data as NSDictionary).write(to: url) +// } } diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift new file mode 100644 index 00000000..7619a664 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift @@ -0,0 +1,56 @@ +// +// ServerResponse.swift +// SelfControl +// +// Created by Satendra Singh on 07/12/25. +// + + +import SafariServices +import os.log + +final class ServerPing { + var timer: Timer? + + init() { } + + func start() { + ping() + timer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(ping), userInfo: nil, repeats: true) + } + + @objc private func ping() { + fetchDataForExtension() + } + + + private func fetchDataForExtension() { + let url = URL(string: Const.serviceURL)! + + URLSession.shared.dataTask(with: url) { data, _, error in + if let error = error { + os_log(("[SC] Fetch failed: \(error.localizedDescription)")) + return + } + + guard let data = data else { + os_log(("[SC] Fetch data Nil")) + // completion([]) + return + } + os_log(("[SC] Fetch data successful: \(String(decoding: data, as: UTF8.self))")) + // do { + // let decoded = try JSONDecoder().decode(ServerResponse.self, from: data) + // completion(decoded.blocked) + // } catch { + // os_log(("[SC] Fetch failed: \(error.localizedDescription)")) + // completion([]) + // } + + }.resume() + } +} + +struct ServerResponse: Codable { + let blocked: [String] +} diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Constants.swift b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Constants.swift deleted file mode 100644 index 58dd76f3..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Constants.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Constants.swift -// SelfControl -// -// Created by Satendra Singh on 09/10/25. -// - - - -enum Constants { - /// File name for the JSON file with Safari rules. - static let SAFARI_BLOCKER_FILE_NAME = "blockerList.json" -} diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerRequestHandler.swift b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerRequestHandler.swift deleted file mode 100644 index e2fb7c43..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/ContentBlockerRequestHandler.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// ContentBlockerRequestHandler.swift -// SelfControl Safari Extension -// -// Created by Satendra Singh on 07/10/25. -// - -import Foundation -import UniformTypeIdentifiers -import os.log - -class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling { - // Must match the App Group used by the host app - private let appGroup = "group.com.application.SelfControl.corebits" - private let sharedFileName = "blockerList.json" - - func beginRequest(with context: NSExtensionContext) { - ContentBlockerExtensionRequestHandler.handleRequest(with: context, groupIdentifier: appGroup) - return - let item = NSExtensionItem() - - // Try to load rules from the shared App Group container first - if let sharedFileURL = sharedRulesFileURL(), FileManager.default.fileExists(atPath: sharedFileURL.path) { - let provider = NSItemProvider(contentsOf: sharedFileURL)! - item.attachments = [provider] - context.completeRequest(returningItems: [item], completionHandler: nil) - return - } - - // Fallback: load bundled blockerList.json from the extension resources - if let bundledURL = Bundle.main.url(forResource: "blockerList", withExtension: "json") { - let provider = NSItemProvider(contentsOf: bundledURL)! - item.attachments = [provider] - context.completeRequest(returningItems: [item], completionHandler: nil) - return - } - - // If neither exists, return an empty rules array to avoid errors -// let emptyRulesData = Data("[]".utf8) -// let tmpURL = writeTempData(emptyRulesData, suggestedName: sharedFileName) -// let provider = NSItemProvider(contentsOf: tmpURL)! -// item.attachments = [provider] -// context.completeRequest(returningItems: [item], completionHandler: nil) - } - - // MARK: - Helpers - - private func sharedRulesFileURL() -> URL? { - let fileManager = FileManager.default - guard let containerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { - NSLog("❌ App Group container not found for %@", appGroup) - os_log("[SC] 🔍] Safari ❌ App Group container not found for: %{public}@", appGroup) - return nil - } - return containerURL.appendingPathComponent(sharedFileName) - } - - private func writeTempData(_ data: Data, suggestedName: String) -> URL { - let tmpDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) - let url = tmpDir.appendingPathComponent(suggestedName) - do { - try data.write(to: url, options: .atomic) - } catch { - NSLog("❌ Failed to write temporary rules file: \(error.localizedDescription)") - os_log("[SC] 🔍] Safari ❌ Failed to write temporary rules file: %{public}@", error.localizedDescription) - - } - return url - } - - - func cancelRequest(withError error: any Error) { - - os_log("[SC] 🔍] Safari ❌ cancelRequeste: %{public}@", error.localizedDescription) - - } - -} diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Info.plist b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Info.plist deleted file mode 100644 index 0d1cc768..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/Info.plist +++ /dev/null @@ -1,15 +0,0 @@ - - - - - NSExtension - - NSExtensionPointIdentifier - com.apple.Safari.content-blocker - NSExtensionPrincipalClass - $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler - - NSHumanReadableDescription - Add a description of what your extension does here. - - diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/SelfControl Safari Extension.entitlements b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/SelfControl Safari Extension.entitlements deleted file mode 100644 index ab2dad3b..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/SelfControl Safari Extension.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.application-groups - - group.com.application.SelfControl.corebits - - - diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/block.html b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/block.html deleted file mode 100644 index 77a15722..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/block.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - -Site Blocked - - - - -

    🚫 Site Blocked

    -

    This website has been blocked by your Safari content blocker.

    -

    You can change this in Safari ▸ Extensions if you want to allow it.

    -
    Open Safari Extensions - - diff --git a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/blockerList.json b/Self-Control-Extension/SelfControl/SelfControl Safari Extension/blockerList.json deleted file mode 100644 index ea0ed75a..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl Safari Extension/blockerList.json +++ /dev/null @@ -1,60 +0,0 @@ -[ - { - "action": { - "type": "block" - }, - "trigger": { - "url-filter": "facebook.com/friends" - } - }, - { - "action": { - "type": "block" - }, - "trigger": { - "url-filter": "facebook.com/marketplace" - } - }, - { - "action": { - "type": "block" - }, - "trigger": { - "url-filter": "facebook.com/watch" - } - } -] -//[ -// { -// "trigger" : { -// "url_filter" : "^https?:\/\/(www\\.)?facebook\\.com\/friends.*" -// }, -// "action" : { -// "type" : "block" -// } -// }, -// { -// "trigger" : { -// "url_filter" : "^https?:\/\/(www\\.)?linkedin\\.com\/mynetwork.*" -// }, -// "action" : { -// "type" : "block" -// } -// }, -// { -// "trigger" : { -// "url_filter" : "^https?:\/\/(www\\.)?linkedin\\.com\/jobs.*" -// }, -// "action" : { -// "type" : "block" -// } -// }, -// { -// "trigger" : { -// "url_filter" : "^https?:\/\/(www\\.)?facebook\\.com\/marketplace.*" -// }, -// "action" : { -// "type" : "block" -// } -// } -//] diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index 26dedbc3..16032d30 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -9,9 +9,7 @@ /* Begin PBXBuildFile section */ 77BA53092D9D5E2A00863476 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77BA53082D9D5E2A00863476 /* NetworkExtension.framework */; }; 9A441CE32E36024E00A521CC /* com.application.SelfControl.corebits.network.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 9A7FFB602E950837007D4A0D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A7FFA0B2E91950D007D4A0D /* Cocoa.framework */; }; - 9AD524B22EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9AD524B12EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 9AD524CE2EC9F15500AAD5BF /* SelfControl Rules Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9AD524B12EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9AD524CE2EC9F15500AAD5BF /* SelfControlWebExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9AD524B12EC9B0EA00AAD5BF /* SelfControlWebExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -22,13 +20,6 @@ remoteGlobalIDString = 77BA53052D9D5E2A00863476; remoteInfo = SelfControlExtension; }; - 9AD524B32EC9B0EA00AAD5BF /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 77BA52CE2D9D5D7700863476 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 9AD524B02EC9B0EA00AAD5BF; - remoteInfo = "SelfControl Rules Extension"; - }; 9AD524CF2EC9F15500AAD5BF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 77BA52CE2D9D5D7700863476 /* Project object */; @@ -39,24 +30,13 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 9AD524C72EC9B0EA00AAD5BF /* Embed Foundation Extensions */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 13; - files = ( - 9AD524B22EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex in Embed Foundation Extensions */, - ); - name = "Embed Foundation Extensions"; - runOnlyForDeploymentPostprocessing = 0; - }; 9AD524D12EC9F15500AAD5BF /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( - 9AD524CE2EC9F15500AAD5BF /* SelfControl Rules Extension.appex in Embed Foundation Extensions */, + 9AD524CE2EC9F15500AAD5BF /* SelfControlWebExtension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -79,60 +59,46 @@ 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */ = {isa = PBXFileReference; explicitFileType = "wrapper.system-extension"; includeInIndex = 0; path = com.application.SelfControl.corebits.network.systemextension; sourceTree = BUILT_PRODUCTS_DIR; }; 77BA53082D9D5E2A00863476 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; 9A7FFA0B2E91950D007D4A0D /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; - 9A7FFB5F2E950837007D4A0D /* SelfControl Safari Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SelfControl Safari Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; - 9AD524982EC9B0E900AAD5BF /* SelfControl Rules.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SelfControl Rules.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 9AD524B12EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SelfControl Rules Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9AD524B12EC9B0EA00AAD5BF /* SelfControlWebExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SelfControlWebExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 77BA53142D9D5E2A00863476 /* Exceptions for "SelfControlExtension" folder in "SelfControlExtension" target */ = { + 77BA53142D9D5E2A00863476 /* Exceptions for "SelfControlExtension" folder in "SelfControlNetworkExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - ChromeExtensionRequestListner.swift, - DNSProxyProvider.swift, - DNSQuestion.swift, Info.plist, ); - target = 77BA53052D9D5E2A00863476 /* SelfControlExtension */; + target = 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */; }; - 9A6873742E280E320054AD3F /* Exceptions for "SelfControl" folder in "SelfControlExtension" target */ = { + 9A6873742E280E320054AD3F /* Exceptions for "SelfControl" folder in "SelfControlNetworkExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Preferences/ProxyPreferences.swift, ); - target = 77BA53052D9D5E2A00863476 /* SelfControlExtension */; + target = 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */; }; 9A6873762E2814B30054AD3F /* Exceptions for "SelfControlExtension" folder in "SelfControl" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( BlockOrAllowList.swift, - ChromeExtensionRequestListner.swift, IPCConnection.swift, "String+URL.swift", ); target = 77BA52D52D9D5D7700863476 /* SelfControl */; }; - 9A7FFB6D2E950837007D4A0D /* Exceptions for "SelfControl Safari Extension" folder in "SelfControl Safari Extension" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - Info.plist, - ); - target = 9A7FFB5E2E950837007D4A0D /* SelfControl Safari Extension */; - }; - 9ACEF3412ECCEAEA00E2CAC8 /* Exceptions for "SelfControl Safari Extension" folder in "SelfControl Rules Extension" target */ = { + 9A9AA3AC2EE87BA000AF720F /* Exceptions for "SelfControl Rules Extension" folder in "SelfControl" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - Constants.swift, - ContentBlockerExtensionRequestHandler.swift, + SafariExtensionConstants.swift, ); - target = 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */; + target = 77BA52D52D9D5D7700863476 /* SelfControl */; }; - 9AD524C32EC9B0EA00AAD5BF /* Exceptions for "SelfControl Rules Extension" folder in "SelfControl Rules Extension" target */ = { + 9AD524C32EC9B0EA00AAD5BF /* Exceptions for "SelfControl Rules Extension" folder in "SelfControlWebExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Info.plist, ); - target = 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */; + target = 9AD524B02EC9B0EA00AAD5BF /* SelfControlWebExtension */; }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -140,7 +106,7 @@ 77BA52D82D9D5D7700863476 /* SelfControl */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( - 9A6873742E280E320054AD3F /* Exceptions for "SelfControl" folder in "SelfControlExtension" target */, + 9A6873742E280E320054AD3F /* Exceptions for "SelfControl" folder in "SelfControlNetworkExtension" target */, ); path = SelfControl; sourceTree = ""; @@ -149,24 +115,16 @@ isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( 9A6873762E2814B30054AD3F /* Exceptions for "SelfControlExtension" folder in "SelfControl" target */, - 77BA53142D9D5E2A00863476 /* Exceptions for "SelfControlExtension" folder in "SelfControlExtension" target */, + 77BA53142D9D5E2A00863476 /* Exceptions for "SelfControlExtension" folder in "SelfControlNetworkExtension" target */, ); path = SelfControlExtension; sourceTree = ""; }; - 9A7FFB612E950837007D4A0D /* SelfControl Safari Extension */ = { - isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - 9A7FFB6D2E950837007D4A0D /* Exceptions for "SelfControl Safari Extension" folder in "SelfControl Safari Extension" target */, - 9ACEF3412ECCEAEA00E2CAC8 /* Exceptions for "SelfControl Safari Extension" folder in "SelfControl Rules Extension" target */, - ); - path = "SelfControl Safari Extension"; - sourceTree = ""; - }; 9AD524B52EC9B0EA00AAD5BF /* SelfControl Rules Extension */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( - 9AD524C32EC9B0EA00AAD5BF /* Exceptions for "SelfControl Rules Extension" folder in "SelfControl Rules Extension" target */, + 9A9AA3AC2EE87BA000AF720F /* Exceptions for "SelfControl Rules Extension" folder in "SelfControl" target */, + 9AD524C32EC9B0EA00AAD5BF /* Exceptions for "SelfControl Rules Extension" folder in "SelfControlWebExtension" target */, ); path = "SelfControl Rules Extension"; sourceTree = ""; @@ -189,21 +147,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 9A7FFB5C2E950837007D4A0D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 9A7FFB602E950837007D4A0D /* Cocoa.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 9AD524952EC9B0E900AAD5BF /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 9AD524AE2EC9B0EA00AAD5BF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -219,7 +162,6 @@ children = ( 77BA52D82D9D5D7700863476 /* SelfControl */, 77BA530A2D9D5E2A00863476 /* SelfControlExtension */, - 9A7FFB612E950837007D4A0D /* SelfControl Safari Extension */, 9AD524B52EC9B0EA00AAD5BF /* SelfControl Rules Extension */, 77BA53072D9D5E2A00863476 /* Frameworks */, 77BA52D72D9D5D7700863476 /* Products */, @@ -231,9 +173,7 @@ children = ( 77BA52D62D9D5D7700863476 /* SelfControl.app */, 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */, - 9A7FFB5F2E950837007D4A0D /* SelfControl Safari Extension.appex */, - 9AD524982EC9B0E900AAD5BF /* SelfControl Rules.app */, - 9AD524B12EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex */, + 9AD524B12EC9B0EA00AAD5BF /* SelfControlWebExtension.appex */, ); name = Products; sourceTree = ""; @@ -276,9 +216,9 @@ productReference = 77BA52D62D9D5D7700863476 /* SelfControl.app */; productType = "com.apple.product-type.application"; }; - 77BA53052D9D5E2A00863476 /* SelfControlExtension */ = { + 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */ = { isa = PBXNativeTarget; - buildConfigurationList = 77BA53152D9D5E2A00863476 /* Build configuration list for PBXNativeTarget "SelfControlExtension" */; + buildConfigurationList = 77BA53152D9D5E2A00863476 /* Build configuration list for PBXNativeTarget "SelfControlNetworkExtension" */; buildPhases = ( 77BA53022D9D5E2A00863476 /* Sources */, 77BA53032D9D5E2A00863476 /* Frameworks */, @@ -291,59 +231,16 @@ fileSystemSynchronizedGroups = ( 77BA530A2D9D5E2A00863476 /* SelfControlExtension */, ); - name = SelfControlExtension; + name = SelfControlNetworkExtension; packageProductDependencies = ( ); productName = SelfControlExtension; productReference = 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */; productType = "com.apple.product-type.system-extension"; }; - 9A7FFB5E2E950837007D4A0D /* SelfControl Safari Extension */ = { - isa = PBXNativeTarget; - buildConfigurationList = 9A7FFB6A2E950837007D4A0D /* Build configuration list for PBXNativeTarget "SelfControl Safari Extension" */; - buildPhases = ( - 9A7FFB5B2E950837007D4A0D /* Sources */, - 9A7FFB5C2E950837007D4A0D /* Frameworks */, - 9A7FFB5D2E950837007D4A0D /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - 9A7FFB612E950837007D4A0D /* SelfControl Safari Extension */, - ); - name = "SelfControl Safari Extension"; - packageProductDependencies = ( - ); - productName = "SelfControl Safari Extension"; - productReference = 9A7FFB5F2E950837007D4A0D /* SelfControl Safari Extension.appex */; - productType = "com.apple.product-type.app-extension"; - }; - 9AD524972EC9B0E900AAD5BF /* SelfControl Rules */ = { - isa = PBXNativeTarget; - buildConfigurationList = 9AD524C82EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControl Rules" */; - buildPhases = ( - 9AD524942EC9B0E900AAD5BF /* Sources */, - 9AD524952EC9B0E900AAD5BF /* Frameworks */, - 9AD524962EC9B0E900AAD5BF /* Resources */, - 9AD524C72EC9B0EA00AAD5BF /* Embed Foundation Extensions */, - ); - buildRules = ( - ); - dependencies = ( - 9AD524B42EC9B0EA00AAD5BF /* PBXTargetDependency */, - ); - name = "SelfControl Rules"; - packageProductDependencies = ( - ); - productName = "SelfControl Rules"; - productReference = 9AD524982EC9B0E900AAD5BF /* SelfControl Rules.app */; - productType = "com.apple.product-type.application"; - }; - 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */ = { + 9AD524B02EC9B0EA00AAD5BF /* SelfControlWebExtension */ = { isa = PBXNativeTarget; - buildConfigurationList = 9AD524C42EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControl Rules Extension" */; + buildConfigurationList = 9AD524C42EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControlWebExtension" */; buildPhases = ( 9AD524AD2EC9B0EA00AAD5BF /* Sources */, 9AD524AE2EC9B0EA00AAD5BF /* Frameworks */, @@ -356,11 +253,11 @@ fileSystemSynchronizedGroups = ( 9AD524B52EC9B0EA00AAD5BF /* SelfControl Rules Extension */, ); - name = "SelfControl Rules Extension"; + name = SelfControlWebExtension; packageProductDependencies = ( ); productName = "SelfControl Rules Extension"; - productReference = 9AD524B12EC9B0EA00AAD5BF /* SelfControl Rules Extension.appex */; + productReference = 9AD524B12EC9B0EA00AAD5BF /* SelfControlWebExtension.appex */; productType = "com.apple.product-type.app-extension"; }; /* End PBXNativeTarget section */ @@ -379,12 +276,6 @@ 77BA53052D9D5E2A00863476 = { CreatedOnToolsVersion = 16.2; }; - 9A7FFB5E2E950837007D4A0D = { - CreatedOnToolsVersion = 26.0; - }; - 9AD524972EC9B0E900AAD5BF = { - CreatedOnToolsVersion = 26.0; - }; 9AD524B02EC9B0EA00AAD5BF = { CreatedOnToolsVersion = 26.0; }; @@ -405,10 +296,8 @@ projectRoot = ""; targets = ( 77BA52D52D9D5D7700863476 /* SelfControl */, - 77BA53052D9D5E2A00863476 /* SelfControlExtension */, - 9A7FFB5E2E950837007D4A0D /* SelfControl Safari Extension */, - 9AD524972EC9B0E900AAD5BF /* SelfControl Rules */, - 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */, + 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */, + 9AD524B02EC9B0EA00AAD5BF /* SelfControlWebExtension */, ); }; /* End PBXProject section */ @@ -428,20 +317,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 9A7FFB5D2E950837007D4A0D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 9AD524962EC9B0E900AAD5BF /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 9AD524AF2EC9B0EA00AAD5BF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -466,20 +341,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 9A7FFB5B2E950837007D4A0D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 9AD524942EC9B0E900AAD5BF /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 9AD524AD2EC9B0EA00AAD5BF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -492,17 +353,12 @@ /* Begin PBXTargetDependency section */ 9A441CE22E36024300A521CC /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 77BA53052D9D5E2A00863476 /* SelfControlExtension */; + target = 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */; targetProxy = 9A441CE12E36024300A521CC /* PBXContainerItemProxy */; }; - 9AD524B42EC9B0EA00AAD5BF /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */; - targetProxy = 9AD524B32EC9B0EA00AAD5BF /* PBXContainerItemProxy */; - }; 9AD524D02EC9F15500AAD5BF /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 9AD524B02EC9B0EA00AAD5BF /* SelfControl Rules Extension */; + target = 9AD524B02EC9B0EA00AAD5BF /* SelfControlWebExtension */; targetProxy = 9AD524CF2EC9F15500AAD5BF /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -707,7 +563,7 @@ ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SelfControlExtension/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = SelfControlExtension; + INFOPLIST_KEY_CFBundleDisplayName = SelfControlNetworkExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "DNS lookup"; INFOPLIST_KEY_NSSystemExtensionUsageDescription = "testing extension"; @@ -741,7 +597,7 @@ ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SelfControlExtension/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = SelfControlExtension; + INFOPLIST_KEY_CFBundleDisplayName = SelfControlNetworkExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "DNS lookup"; INFOPLIST_KEY_NSSystemExtensionUsageDescription = "testing extension"; @@ -762,100 +618,6 @@ }; name = Release; }; - 9A7FFB6B2E950837007D4A0D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = "SelfControl Safari Extension/SelfControl Safari Extension.entitlements"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = X6FQ433AWK; - "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; - ENABLE_APP_SANDBOX = YES; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; - ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; - ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; - ENABLE_RESOURCE_ACCESS_CALENDARS = NO; - ENABLE_RESOURCE_ACCESS_CAMERA = NO; - ENABLE_RESOURCE_ACCESS_CONTACTS = NO; - ENABLE_RESOURCE_ACCESS_LOCATION = NO; - ENABLE_RESOURCE_ACCESS_PRINTING = NO; - ENABLE_RESOURCE_ACCESS_USB = NO; - ENABLE_USER_SELECTED_FILES = readonly; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "SelfControl Safari Extension/Info.plist"; - INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Safari Extension"; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@executable_path/../../../../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.application.SelfControl.corebits.SelfControl-Safari-Extension"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "SelfControl Dev Net Ext"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "SelfControl Dev Net Ext"; - REGISTER_APP_GROUPS = YES; - SKIP_INSTALL = YES; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 9A7FFB6C2E950837007D4A0D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = "SelfControl Safari Extension/SelfControl Safari Extension.entitlements"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = X6FQ433AWK; - "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; - ENABLE_APP_SANDBOX = YES; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; - ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; - ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; - ENABLE_RESOURCE_ACCESS_CALENDARS = NO; - ENABLE_RESOURCE_ACCESS_CAMERA = NO; - ENABLE_RESOURCE_ACCESS_CONTACTS = NO; - ENABLE_RESOURCE_ACCESS_LOCATION = NO; - ENABLE_RESOURCE_ACCESS_PRINTING = NO; - ENABLE_RESOURCE_ACCESS_USB = NO; - ENABLE_USER_SELECTED_FILES = readonly; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "SelfControl Safari Extension/Info.plist"; - INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Safari Extension"; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@executable_path/../../../../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.application.SelfControl.corebits.SelfControl-Safari-Extension"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "SelfControl Dev Net Ext"; - REGISTER_APP_GROUPS = YES; - SKIP_INSTALL = YES; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; 9AD524C52EC9B0EA00AAD5BF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -879,7 +641,7 @@ ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SelfControl Rules Extension/Info.plist"; - INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Rules Extension"; + INFOPLIST_KEY_CFBundleDisplayName = SelfControlWebExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -929,7 +691,7 @@ ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "SelfControl Rules Extension/Info.plist"; - INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Rules Extension"; + INFOPLIST_KEY_CFBundleDisplayName = SelfControlWebExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -956,90 +718,6 @@ }; name = Release; }; - 9AD524C92EC9B0EA00AAD5BF /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = KWW448CAK7; - ENABLE_APP_SANDBOX = YES; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; - ENABLE_USER_SELECTED_FILES = readonly; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Rules"; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INFOPLIST_KEY_NSMainStoryboardFile = Main; - INFOPLIST_KEY_NSPrincipalClass = NSApplication; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 26.0; - MARKETING_VERSION = 1.0; - OTHER_LDFLAGS = ( - "-framework", - SafariServices, - "-framework", - WebKit, - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.sample.SelfControl-Rules"; - PRODUCT_NAME = "$(TARGET_NAME)"; - REGISTER_APP_GROUPS = YES; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 9AD524CA2EC9B0EA00AAD5BF /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = KWW448CAK7; - ENABLE_APP_SANDBOX = YES; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; - ENABLE_USER_SELECTED_FILES = readonly; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = "SelfControl Rules"; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INFOPLIST_KEY_NSMainStoryboardFile = Main; - INFOPLIST_KEY_NSPrincipalClass = NSApplication; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 26.0; - MARKETING_VERSION = 1.0; - OTHER_LDFLAGS = ( - "-framework", - SafariServices, - "-framework", - WebKit, - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.sample.SelfControl-Rules"; - PRODUCT_NAME = "$(TARGET_NAME)"; - REGISTER_APP_GROUPS = YES; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1061,7 +739,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 77BA53152D9D5E2A00863476 /* Build configuration list for PBXNativeTarget "SelfControlExtension" */ = { + 77BA53152D9D5E2A00863476 /* Build configuration list for PBXNativeTarget "SelfControlNetworkExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( 77BA53162D9D5E2A00863476 /* Debug */, @@ -1070,16 +748,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 9A7FFB6A2E950837007D4A0D /* Build configuration list for PBXNativeTarget "SelfControl Safari Extension" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 9A7FFB6B2E950837007D4A0D /* Debug */, - 9A7FFB6C2E950837007D4A0D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 9AD524C42EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControl Rules Extension" */ = { + 9AD524C42EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControlWebExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( 9AD524C52EC9B0EA00AAD5BF /* Debug */, @@ -1088,15 +757,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 9AD524C82EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControl Rules" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 9AD524C92EC9B0EA00AAD5BF /* Debug */, - 9AD524CA2EC9B0EA00AAD5BF /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 77BA52CE2D9D5D7700863476 /* Project object */; diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlExtension.xcscheme b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlExtension.xcscheme index 97fcc93b..60e9eb61 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlExtension.xcscheme +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlExtension.xcscheme @@ -17,7 +17,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "77BA53052D9D5E2A00863476" BuildableName = "com.application.SelfControl.corebits.network.systemextension" - BlueprintName = "SelfControlExtension" + BlueprintName = "SelfControlNetworkExtension" ReferencedContainer = "container:SelfControl.xcodeproj"> @@ -62,7 +62,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "77BA53052D9D5E2A00863476" BuildableName = "com.application.SelfControl.corebits.network.systemextension" - BlueprintName = "SelfControlExtension" + BlueprintName = "SelfControlNetworkExtension" ReferencedContainer = "container:SelfControl.xcodeproj"> diff --git a/Self-Control-Extension/SelfControl/SelfControl/ChromeExtensionSupport/ChromeExtensionRequestListner.swift b/Self-Control-Extension/SelfControl/SelfControl/ChromeExtensionSupport/ChromeExtensionRequestListner.swift new file mode 100644 index 00000000..c848d747 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/ChromeExtensionSupport/ChromeExtensionRequestListner.swift @@ -0,0 +1,146 @@ +// +// PlistListner.swift +// SelfControlExtension +// +// Created by Satendra Singh on 16/08/25. +// + +import Foundation +import Network +import os.log + +enum ServicePath: String { + case chrome = "/chrome" + case safari = "/safari" +} + +final class ChromeExtensionRequestListner: NSObject, ObservableObject { + private var isChromeStatusSetInExtension: Bool = false + var listener: NWListener? + var blockeddomainFetcher: (() -> [String])? + var isBlockingEnabled: Bool = false + + func startListening() { + os_log("[SC] 🔍] PlistListner startListening") + + // Safely convert Int port to NWEndpoint.Port + guard let port = NWEndpoint.Port(rawValue: SafariConst.servicePort) else { + os_log("[SC] 🔍] Invalid service port: %d", SafariConst.servicePort) + return + } + do { + listener = try NWListener(using: .tcp, on: port) + } catch { + os_log("[SC] 🔍] Failed to create NWListener: %{public}@", error.localizedDescription) + return + } + + listener?.newConnectionHandler = { conn in + print("Path: \(conn.endpoint.debugDescription)") + conn.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, _, _, _ in + if let data = data, + let req = String(data: data, encoding: .utf8) { + print("Raw Request:", req) + if let service = req.httpPathFromConnection() { + switch service { + case .chrome: + print("Chrome request received") + os_log("[SC] 🔍] Chrome request received") + + self.sendChromeBlockedUrls(connection: conn) + self.updateChromeStatus() + case .safari: + // Handle Safari service path if needed + conn.cancel() + print("Safari request received") + os_log("[SC] 🔍] Safari request received") + self.updateSafariStatus() + break + } + } + } + } + + conn.start(queue: DispatchQueue.global(qos: .userInitiated)) + + conn.stateUpdateHandler = { state in + if state == .ready { + os_log("[SC] 🔍] PlistListner stateUpdateHandler ready") + } + if state == .cancelled { + os_log("[SC] 🔍] PlistListner stateUpdateHandler cancelled") + } + } + } + + listener?.start(queue: DispatchQueue.global(qos: .userInitiated)) + } + + func activateSafariBlocking() { + isBlockingEnabled = true + } + + func deactivateSafariBlocking() { + isBlockingEnabled = false + } + + private func sendChromeBlockedUrls(connection: NWConnection) { + print("sendChromeBlockedUrls") + + var blockedDomainList: [String] = self.blockeddomainFetcher?() ?? [] + if self.isBlockingEnabled == false { + blockedDomainList = [] + } + let blockedUrls = ["blocked": blockedDomainList] + let jsonData = try! JSONSerialization.data(withJSONObject: blockedUrls, options: []) + let jsonString = String(data: jsonData, encoding: .utf8)! + + os_log("[SC] 🔍] PlistListner newConnectionHandler") + os_log("[SC] 🔍] PlistListner conn.receiveMessage") + let response = """ + HTTP/1.1 200 OK\r + Content-Type: application/json\r + Access-Control-Allow-Origin: *\r + \r + \(jsonString) + """ + connection.send(content: response.data(using: .utf8), contentContext: .finalMessage , completion: .contentProcessed { error in + if let error = error { + os_log("[SC] 🔍] PlistListner Sent response error: %{public}@", "\(error)") + } else { + os_log("[SC] 🔍] PlistListner Sent response successfully") + } + }) + } + + private func updateSafariStatus() { + SafariExtensionManager.shared.lastUpdateReceivedTime = Date() + } + + private func updateChromeStatus() { + Task { @MainActor in + print("newConnectionHandler isEnabled: \(NetworkExtensionState.shared.isEnabled)") + if NetworkExtensionState.shared.isEnabled == true && NetworkExtensionState.shared.isChromeExtensionEnabled == false { + NetworkExtensionState.shared.isChromeExtensionEnabled = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.chrome.rawValue, state: true) + NetworkExtensionState.shared.printAll() + } + } + } + +} + +extension String { + func httpPathFromConnection() -> ServicePath? { + if let firstLine = components(separatedBy: "\r\n").first { + print("Request Line:", firstLine) + + let parts = firstLine.split(separator: " ") + if parts.count >= 2 { + let path = parts[1] + print("HTTP Path:", path) + return ServicePath(rawValue: String(path)) + } + } + return nil + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/AppMover.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/AppMover.swift new file mode 100644 index 00000000..f8cbae6a --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/AppMover.swift @@ -0,0 +1,126 @@ +// +// AppMover.swift +// SelfControl +// +// Created by Satendra Singh on 05/12/25. +// + +import Cocoa +import os.log + +final class AppMover { + + static func moveIfNeeded() { + // 1. Check current app path + let bundleURL = Bundle.main.bundleURL + let fm = FileManager.default + + // Prefer /Applications, fallback to ~/Applications + let appName = bundleURL.lastPathComponent + let systemApplicationsDir = URL(fileURLWithPath: "/Applications", isDirectory: true) + let userApplicationsDir = fm.homeDirectoryForCurrentUser.appendingPathComponent("Applications", isDirectory: true) + + let destinations = [ systemApplicationsDir, userApplicationsDir ] + + // 3. If already inside one of those destinations (and not in DMG, etc), skip + if destinations.contains(where: { bundleURL.deletingLastPathComponent() == $0 }) { + return // already correct place + } + + // 4. Ask user via alert + DispatchQueue.main.async { + let alert = NSAlert() + alert.messageText = "Move to Applications Folder?" + alert.informativeText = "Would you like to move this app to your Applications folder? It must quit and relaunch." + alert.addButton(withTitle: "Move to Applications Folder") + alert.addButton(withTitle: "Do Not Move") + let response = alert.runModal() + if response == .alertFirstButtonReturn { + performMove(to: destinations.first!, bundleURL: bundleURL) + } + } + } + + private static func performMove(to appDir: URL, bundleURL: URL) { + let fm = FileManager.default + + // Ensure destination directory exists + do { + try fm.createDirectory(at: appDir, withIntermediateDirectories: true, attributes: nil) + } catch { + os_log("[SC] 🔍] Could not create Applications directory: %@", error.localizedDescription) + print("Could not create Applications directory: \(error)") + } + + // Destination path for the app + let destURL = appDir.appendingPathComponent(bundleURL.lastPathComponent) + + // If exists, maybe prompt to overwrite + if fm.fileExists(atPath: destURL.path) { + // For simplicity: remove existing + do { + try fm.removeItem(at: destURL) + } catch { + os_log("[SC] 🔍] Could not remove existing app at destination: %@", error.localizedDescription) + print("Could not remove existing app at destination: \(error)") + // Handle error (maybe ask user, or abort) + } + } + + do { + // Copy the bundle + try fm.moveItem(at: bundleURL, to: destURL) + + // Clear quarantine attribute (so first launch doesn’t ask gatekeeper) + removeQuarantineRecursively(at: destURL) + // Relaunch the copied app + let config = NSWorkspace.OpenConfiguration() + NSWorkspace.shared.openApplication(at: destURL, configuration: config) { (app, err) in + if let err = err { + os_log("[SC] 🔍] Failed to relaunch at dest: %@", err.localizedDescription) + print("Failed to relaunch at dest: \(err)") + } + // After launching new instance, quit old one + exit(0) + } + + } catch { + os_log("[SC] 🔍] Error copying app to Applications: %@", error.localizedDescription) + print("Error copying app to Applications: \(error)") + // Optionally ask for authorization and retry + } + } + + private static func removeQuarantineAttribute(from url: URL) { + let path = url.path + + // Check if attribute exists + let attrName = "com.apple.quarantine" + + let result = getxattr(path, attrName, nil, 0, 0, 0) + if result >= 0 { + // The attribute exists → remove it + let removeResult = removexattr(path, attrName, 0) + if removeResult == 0 { + os_log("[SC] 🔍] Successfully removed quarantine attribute") + print("Successfully removed quarantine attribute") + } else { + os_log("[SC] 🔍] Failed to remove quarantine attribute") + print("Failed to remove quarantine attribute") + } + } else { + os_log("[SC] 🔍] No quarantine attribute found") + print("No quarantine attribute found") + } + } + + private static func removeQuarantineRecursively(at url: URL) { + let fm = FileManager.default + if let enumerator = fm.enumerator(at: url, includingPropertiesForKeys: nil) { + for case let fileURL as URL in enumerator { + removeQuarantineAttribute(from: fileURL) + } + } + removeQuarantineAttribute(from: url) // root bundle + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift index ced47c9e..7c32ba2d 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift @@ -19,53 +19,75 @@ struct ContentView: View { var body: some View { VStack(spacing: 20) { // Status indicator with image and text. - Button { - viewModel.activateExtension() - } label: { - Text("Install and Start Block") + HStack { + statusView + Button { + viewModel.activateExtension() + } label: { + Text("Install and Start Block") + } + // Start/Stop buttons. + HStack { + if viewModel.status == .stopped { + Button("Start") { + viewModel.startFilter() + } + } + if viewModel.status == .running { + Button("Stop") { + viewModel.stopFilter() + } + } + } } HStack { Spacer() - Slider(value: $viewModel.delay, in: 1...60) { + Slider(value: $viewModel.delay, in: 1...30) { Text("Time: \(viewModel.delay, specifier: "%.1f") Minutes") } Spacer() } - statusView + + Button { + if viewModel.isActiveBlocking == true { + viewModel.deactivateNetworkBlocking() + viewModel.cancelTimer() + } else { + if viewModel.startTimerWithSelectedDelay() == false { + return + } + viewModel.activateNetworkBlocking() + } + } label: { + Text(viewModel.isActiveBlocking ? "Deactivate Block" : "Activate Block") + } + // Show a progress indicator when in the indeterminate state. if viewModel.status == .indeterminate { ProgressView() .progressViewStyle(CircularProgressViewStyle()) } - - // Start/Stop buttons. - HStack { - if viewModel.status == .stopped { - Button("Start") { - viewModel.startFilter() - - } - } - if viewModel.status == .running { - Button("Stop") { - viewModel.stopFilter() - } - } - } - HStack { - TextField("Enter Url to test block", text: $newDomain) - Button("Test Url Blocking") { - viewModel.checkUrlRequest(url: newDomain) - } - } // Button { // viewModel.setBlockedUrls(urls: ProxyPreferences.getBlockedDomains()) // } label: { // Text("Enable Url Blocking") // } - SafariExtensionView() + HStack { + Button("Edit Blocklist") { + openWindow(id: "preferences") + } + Spacer() + } SafariExtensionWebView() .frame(minHeight: 100) + + HStack { + TextField("Enter Url to test block", text: $newDomain) + Button("Test Url Blocking") { + viewModel.checkUrlRequest(url: newDomain) + } + } + Spacer() } .padding() .frame(minWidth: 150, minHeight: 150) @@ -73,6 +95,11 @@ struct ContentView: View { // let urls = ProxyPreferences.getBlockedDomains() } + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + AppMover.moveIfNeeded() + } + } } } @@ -83,10 +110,6 @@ private extension ContentView { .clipShape(Circle()) .frame(width: 20, height: 20) Text("Status: \(viewModel.status.text)") - Button("Edit Blocklist") { - openWindow(id: "preferences") - - } } } } @@ -94,3 +117,4 @@ private extension ContentView { #Preview { ContentView() } + diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index f38f4f3d..f43a3c40 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -16,14 +16,19 @@ import SafariServices final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionRequestDelegate, ExtensionToApp { @Published var status: Status = .stopped @State private var domains = ProxyPreferences.getBlockedDomains() - private let listner = ChromeExtensionRequestListner() - @Published var delay: Double = 0.0 + private let chromeService = ChromeExtensionRequestListner() + @Published var delay: Double = 5.0 var blockedIPAddressed: [String] = [] private var cancellables = Set() - + @Published var isActiveBlocking: Bool = false + // Safari extension identifier used to query state private let safariExtensionIdentifier = "com.application.SelfControl.corebits.SelfControl-Safari-Extension" + // Timer to manage delayed actions based on `delay` (in minutes) + private var blockTimer: Timer? + private var timerFireDate: Date? + // Date formatter used to log entries lazy var dateFormatter: DateFormatter = { let formatter = DateFormatter() @@ -58,11 +63,15 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques override init() { super.init() onInit() + SafariExtensionManager.shared.onChange = { + print("SafariExtensionManager.shared.onChange++") + } + SafariExtensionManager.shared.resetExtensionState() self.extensionIdentifier = extensionBundle.bundleIdentifier - self.listner.blockeddomainFetcher = { + self.chromeService.blockeddomainFetcher = { return ProxyPreferences.getBlockedDomains() } - self.listner.startListening() + self.chromeService.startListening() // Print status whenever it changes $status @@ -80,6 +89,8 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques if let observer = observer { NotificationCenter.default.removeObserver(observer, name: .NEFilterConfigurationDidChange, object: NEFilterManager.shared()) } + blockTimer?.invalidate() + blockTimer = nil } func onInit() { @@ -108,7 +119,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques let isNEEnabled = NEFilterManager.shared().isEnabled Task { @MainActor in NetworkExtensionState.shared.isEnabled = isNEEnabled - if isNEEnabled == true { //Reset + if isNEEnabled == true { //Reset NetworkExtensionState.shared.isSafariExtensionEnabled = false NetworkExtensionState.shared.isChromeExtensionEnabled = false } @@ -132,7 +143,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } private func refreshBlockedIPs() { - self.listner.blockeddomainFetcher = { + self.chromeService.blockeddomainFetcher = { return ProxyPreferences.getBlockedDomains() } } @@ -380,5 +391,51 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques print("didSetUrls+++++") // URLs updated; refresh extension state in case Safari side changed. } + + func activateNetworkBlocking() { + print("activateNetworkBlocking+++++") + _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(_enable: true) + BlockListManager.activateSafariBlocking() + chromeService.activateSafariBlocking() + } + + func deactivateNetworkBlocking() { + print("deactivateNetworkBlocking+++++") + _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(_enable: false) + BlockListManager.deactivateSafariBlocking() + chromeService.deactivateSafariBlocking() + } + + // MARK: - Timer management + + func startTimerWithSelectedDelay() -> Bool { + cancelTimer() + let seconds = delay * 60.0 + guard seconds > 30 else { + os_log("[SC] 🔍] startTimerWithSelectedDelay called with non-positive delay: %f", seconds) + return false + } + timerFireDate = Date().addingTimeInterval(seconds) + os_log("[SC] 🔍] Scheduling timer to fire in %.0f seconds (%.2f minutes)", seconds, delay) + blockTimer = Timer.scheduledTimer(withTimeInterval: seconds, repeats: false) { [weak self] _ in + guard let self = self else { return } + os_log("[SC] 🔍] Timer fired. Deactivating network blocking.") + self.deactivateNetworkBlocking() + self.cancelTimer() + } + isActiveBlocking = true +// RunLoop.main.add(blockTimer!, forMode: .common) + return true + } + + func cancelTimer() { + if let fireDate = timerFireDate { + os_log("[SC] 🔍] Cancelling timer scheduled for %{public}@", fireDate as NSDate) + } + blockTimer?.invalidate() + blockTimer = nil + timerFireDate = nil + self.isActiveBlocking = false + } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/NetworkExtensionState.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/NetworkExtensionState.swift index 715d3c30..158cd1ef 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/NetworkExtensionState.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/NetworkExtensionState.swift @@ -14,6 +14,7 @@ final class NetworkExtensionState: ObservableObject { @Published var isEnabled: Bool @Published var isSafariExtensionEnabled: Bool @Published var isChromeExtensionEnabled: Bool +// @Published var isActive: Bool = false // Singleton instance, isolated to the MainActor static let shared: NetworkExtensionState = NetworkExtensionState(isEnabled: false) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift index 1920d47f..b3c0459c 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift @@ -62,7 +62,7 @@ final class WebPageViewModel: NSObject, WKNavigationDelegate, WKScriptMessageHan } } - private let extensionIdentifier = "com.application.SelfControl.corebits.SelfControl-Safari-Extension" + private let extensionIdentifier = SafariExtensionConstants.identifier func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift index b00abaa8..a57e5edf 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift @@ -41,7 +41,6 @@ struct PreferencesView: View { Button("Save Preferences") { ProxyPreferences.setBlockedDomains(domains) viewModel.setBlockedUrls(urls: domains) - BlockListViewModel().updateBlocker() } } .padding() diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListViewModel.swift deleted file mode 100644 index b8b29527..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListViewModel.swift +++ /dev/null @@ -1,37 +0,0 @@ -import Foundation -import Combine -import SafariServices - -/// ViewModel to manage blocked paths and write the blockerList.json into the App Group container. -class BlockListViewModel: ObservableObject { - @Published var blockedPaths: [String] = [ - "facebook.com/friends", - "youtube.com/shorts/", - "example.com/private/" - ] - private let appGroup = "group.com.application.SelfControl.corebits" - private let extensionIdentifier = "com.application.SelfControl.corebits.SelfControl-Safari-Extension" - - func addSample() { - blockedPaths.append("example.com/private/") - } - - func remove(at index: Int) { - guard index >= 0 && index < blockedPaths.count else { return } - blockedPaths.remove(at: index) - } - - func resetToDefaults() { - blockedPaths = [ - "facebook.com/friends", - "youtube.com/shorts/", - "example.com/private/" - ] - } - - func updateBlocker() { - let urls = ProxyPreferences.getBlockedDomains() - print("URLS: \(urls)") - BlockListManager.updateSafariBlockList(blockedPaths: urls, appGroup: appGroup, extensionIdentifier: extensionIdentifier) - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionManager.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionManager.swift deleted file mode 100644 index 17b6e556..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionManager.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// SafariExtensionManager.swift -// SelfControl -// -// Created by Satendra Singh on 24/11/25. -// - -import Foundation - -final class SafariExtensionManager: ObservableObject { - static let shared = SafariExtensionManager() - private let appGroup = "group.com.application.SelfControl.corebits" - var onChange: (() -> Void)? - private var isReady: Bool = false - - private init() { - NotificationCenter.default.addObserver( - forName: UserDefaults.didChangeNotification, - object: UserDefaults(suiteName: appGroup), - queue: .main - ) { notification in - let shared = UserDefaults(suiteName: self.appGroup) - print("Received from extension:", shared?.string(forKey: "ready") ?? "") - if let value = shared?.bool(forKey: "ready"){ - print("Ready now:") - if self.isReady == false { - self.onChange?() - self.isReady = true - } - } - } - } - - var isExtensionReady: Bool { - return UserDefaults(suiteName: appGroup)?.bool(forKey: "ready") == true - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionView.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionView.swift deleted file mode 100644 index 47080944..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SafariExtensionView.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// SafariExtensionView.swift -// SelfControl -// -// Created by Satendra Singh on 05/10/25. -// - -import SwiftUI - -struct SafariExtensionView: View { - @StateObject private var vm = BlockListViewModel() - - var body: some View { - VStack { -// HStack { -// Text("Blocked paths (domain/path)").font(.headline) -// Spacer() -// Button(action: vm.addSample) { -// Text("Add sample") -// } -// }.padding(.horizontal) -// -// List { -// ForEach(vm.blockedPaths.indices, id: \ .self) { idx in -// HStack { -// TextField("domain/path", text: $vm.blockedPaths[idx]) -// Button(action: { vm.remove(at: idx) }) { -// Image(systemName: "minus.circle") -// }.buttonStyle(BorderlessButtonStyle()) -// } -// } -// }.frame(minHeight: 200) - - HStack { - Button(action: vm.updateBlocker) { - Text("Update Blocker") - } - Spacer() - Button(action: vm.resetToDefaults) { - Text("Reset Defaults") - } - }.padding() - } - .onAppear { - vm.updateBlocker() - } - .padding() -// .frame(minWidth: 600, minHeight: 320) - } - //1550, 10300, soles, 250, - //50, sanitry, 1, 4, 2 ,6, 5 - //solems, 45, -} - -#Preview { - SafariExtensionView() -} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListManager.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift similarity index 51% rename from Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListManager.swift rename to Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift index 2f9a5b69..9eea843a 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/BlockListManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift @@ -1,5 +1,8 @@ import Foundation import SafariServices +import os.log + +typealias SafariConst = SafariExtensionConstants struct BlockRule: Codable { struct Trigger: Codable { @@ -61,58 +64,15 @@ enum BlockListManager { // Ensure folder exists before writing try fileManager.createDirectory(at: containerURL, withIntermediateDirectories: true, attributes: nil) - let fileURL = containerURL.appendingPathComponent("blockerList.json") + let fileURL = containerURL.appendingPathComponent(SafariConst.SAFARI_BLOCKER_FILE_NAME) try data.write(to: fileURL, options: .atomic) print("✅ Wrote blockerList.json to: \(fileURL.path)") -// SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionIdentifier) { (state, error) in -// guard let state = state, error == nil else { -// // Insert code to inform the user that something went wrong. -// return -// } -// if let error = error as NSError? { -// print("⚠️ State check error for \(extensionIdentifier): \(error.domain) code \(error.code) – \(error.localizedDescription)") -// print("ℹ️ Tips: Ensure the content blocker extension bundle identifier matches exactly, the extension is installed and enabled in Safari, and the app/extension share the same App Group: \(appGroup)") -// } else { -// print("🔍 Current state of \(extensionIdentifier):", state.description ?? "Unknown") -// if state.isEnabled == false { -// print("⚠️ Extension not enabled in Safari. Please enable it in Settings → Safari → Extensions.") -// } -// } -// -// DispatchQueue.main.async { -// if #available(macOS 13, *) { -//// webView.evaluateJavaScript("show(\(state.isEnabled), true)") -// } else { -//// webView.evaluateJavaScript("show(\(state.isEnabled), false)") -// } -// } -// } - // Query current state for diagnostics; proceed to reload regardless. -// SFContentBlockerManager.getStateOfContentBlocker(withIdentifier: extensionIdentifier) { state, error in -// if let error = error as NSError? { -// print("⚠️ State check error for \(extensionIdentifier): \(error.domain) code \(error.code) – \(error.localizedDescription)") -// print("ℹ️ Tips: Ensure the content blocker extension bundle identifier matches exactly, the extension is installed and enabled in Safari, and the app/extension share the same App Group: \(appGroup)") -// } else { -// print("🔍 Current state of \(extensionIdentifier):", state?.description ?? "Unknown") -// if state?.isEnabled == false { -// print("⚠️ Extension not enabled in Safari. Please enable it in Settings → Safari → Extensions.") -// } -// } -// -// // Attempt reload regardless; this often yields a clearer error if the identifier is wrong. -// SFContentBlockerManager.reloadContentBlocker(withIdentifier: extensionIdentifier) { error in -// if let error = error as NSError? { -// print("❌ Reload error for \(extensionIdentifier): \(error.domain) code \(error.code) – \(error.localizedDescription)") -// print("ℹ️ If this persists: verify the extension target’s bundle ID, that the extension is enabled, and that it has access to the shared container.") -// } else { -// print("✅ Safari Content Blocker reloaded successfully.") -// } -// } -// } // if SafariExtensionManager.shared.isExtensionReady { //If Safari is ready mark it in the Network extension - SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: "com.application.SelfControl.corebits.SelfControl-Safari-Extension") { state, error in + SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: SafariConst.identifier) { state, error in + os_log("[SC] 🔍 Safari Extension State Error: %{public}@", error?.localizedDescription ?? "") + os_log("[SC] 🔍 Safari Extension State: %{public}d", state?.isEnabled ?? false) print("Safari Extension State Error :\(error, default: "nil")") print("Safari Extension State :\(state?.isEnabled ?? false)") Task { @MainActor in @@ -125,11 +85,12 @@ enum BlockListManager { } // } SFSafariApplication.dispatchMessage( - withName: "reloadList", - toExtensionWithIdentifier: "com.application.SelfControl.corebits.SelfControl-Safari-Extension", + withName: SafariConst.MessagesName.reloadList.rawValue, + toExtensionWithIdentifier: SafariConst.identifier, userInfo: ["changed": true]) { error in + os_log("[SC] 🔍 Safari Message toExtensionWithIdentifier: reloadList: %{public}@", error?.localizedDescription ?? "") print("Safari Message toExtensionWithIdentifier: reloadList:", error) - if error != nil { + if error == nil { Task { @MainActor in if NetworkExtensionState.shared.isEnabled == true && NetworkExtensionState.shared.isSafariExtensionEnabled == false { //possibly extension is ready now _ = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: true) @@ -143,4 +104,25 @@ enum BlockListManager { print("❌ Safari Error writing blocker file:", error) } } + + static func updateExtensionState() { + Task { @MainActor in + if NetworkExtensionState.shared.isEnabled == true { + _ = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: SafariExtensionManager.shared.isExtensionReady) + } + } + } + + static func activateSafariBlocking() { + updateExtensionState() + SafariExtensionManager.shared.enableExtension() + os_log("Safari Message toExtensionWithIdentifier activateSafariBlocking called:") + } + + static func deactivateSafariBlocking() { + updateExtensionState() + SafariExtensionManager.shared.disableExtension() + _ = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: SafariExtensionManager.shared.isExtensionReady) + os_log("Safari Message toExtensionWithIdentifier deactivateSafariBlocking called:") + } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/SafariExtensionManager.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/SafariExtensionManager.swift new file mode 100644 index 00000000..a35061c3 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/SafariExtensionManager.swift @@ -0,0 +1,80 @@ +// +// SafariExtensionManager.swift +// SelfControl +// +// Created by Satendra Singh on 24/11/25. +// + +import Foundation +import os.log + +final class SafariExtensionManager: ObservableObject { + static let shared = SafariExtensionManager() + var onChange: (() -> Void)? + private var isReady: Bool = false + private var isEnabled = false + var lastUpdateReceivedTime: Date = .distantPast + + private init() { + NotificationCenter.default.addObserver( + forName: UserDefaults.didChangeNotification, + object: UserDefaults(suiteName: SafariConst.appGroup), + queue: .main + ) { notification in + let shared = UserDefaults(suiteName: SafariConst.appGroup) + print("Received from extension:", shared?.string(forKey: "ready") ?? "") + if let value = shared?.bool(forKey: "ready") { + print("Ready now:") + if self.isReady == false { + self.onChange?() + self.isReady = true + } + } + } + registerForUpdates() + } + + var isExtensionReady: Bool { + // Consider the extension "ready" if we received an update within the last 35 seconds + return Date().timeIntervalSince(lastUpdateReceivedTime) <= 35 +// return UserDefaults(suiteName: appGroup)?.bool(forKey: "ready") == true + } + + func enableExtension() { + let defaults = UserDefaults(suiteName: SafariConst.appGroup) + defaults?.set(true, forKey: "enabled") + defaults?.synchronize() + } + + func disableExtension() { + let defaults = UserDefaults(suiteName: SafariConst.appGroup) + defaults?.set(false, forKey: "enabled") + defaults?.synchronize() + } + + func resetExtensionState() { + let defaults = UserDefaults(suiteName: SafariConst.appGroup) + defaults?.set(false, forKey: "enabled") + defaults?.set(false, forKey: "ready") + defaults?.synchronize() + } + + func registerForUpdates() { +// let path = (NSHomeDirectory() + "/Library/Containers/com.apple.Safari/Data/Library/Preferences/com.apple.Safari.Extensions.plist") +// +// let fileDescriptor = open(path, O_EVTONLY) +// let source = DispatchSource.makeFileSystemObjectSource( +// fileDescriptor: fileDescriptor, +// eventMask: .write, +// queue: DispatchQueue.global() +// ) +// +// source.setEventHandler { +// print("Safari extension plist changed → check enable state") +// // call getStateOfSafariExtension again here +// } +// source.resume() + + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SimpleRegexConverter.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/SimpleRegexConverter.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SafariExtension/SimpleRegexConverter.swift rename to Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/SimpleRegexConverter.swift diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/ChromeExtensionRequestListner.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/ChromeExtensionRequestListner.swift deleted file mode 100644 index 1572003a..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/ChromeExtensionRequestListner.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// PlistListner.swift -// SelfControlExtension -// -// Created by Satendra Singh on 16/08/25. -// - -import Foundation -import Network -import os.log - -final class ChromeExtensionRequestListner: NSObject, ObservableObject { - private var isChromeStatusSetInExtension: Bool = false - var listener: NWListener? = try! NWListener(using: .tcp, on: 8080) - var blockeddomainFetcher: (() -> [String])? - - func startListening() { - os_log("[SC] 🔍] PlistListner startListening") -// let objects: [[String: Any]] = [ -// ["id": 1, "name": "Alice", "status": "running"], -// ["id": 2, "name": "Bob", "status": "stopped"], -// ["id": 3, "name": "Charlie", "status": "idle"] -// ] - listener = try! NWListener(using: .tcp, on: 8080) - listener?.newConnectionHandler = { conn in - - Task { @MainActor in - print("newConnectionHandler isEnabled: \(NetworkExtensionState.shared.isEnabled)") - if NetworkExtensionState.shared.isEnabled == true && NetworkExtensionState.shared.isChromeExtensionEnabled == false { - NetworkExtensionState.shared.isChromeExtensionEnabled = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.chrome.rawValue, state: true) - NetworkExtensionState.shared.printAll() - } - } - let blockedDomainList: [String] = self.blockeddomainFetcher?() ?? [] - let blockedUrls = ["blocked": blockedDomainList] - let jsonData = try! JSONSerialization.data(withJSONObject: blockedUrls, options: []) - let jsonString = String(data: jsonData, encoding: .utf8)! - - os_log("[SC] 🔍] PlistListner newConnectionHandler") - conn.start(queue: DispatchQueue.global(qos: .userInitiated)) -// conn.receiveMessage { data, _, _, _ in - os_log("[SC] 🔍] PlistListner conn.receiveMessage") - let response = """ - HTTP/1.1 200 OK\r - Content-Type: application/json\r - Access-Control-Allow-Origin: *\r - \r - \(jsonString) - """ - conn.send(content: response.data(using: .utf8), contentContext: .finalMessage , completion: .contentProcessed { error in - os_log("[SC] 🔍] PlistListner Sent response:\(error)") -// conn.cancel() - - }) -// } - - conn.stateUpdateHandler = { state in - if state == .ready { - - os_log("[SC] 🔍] PlistListner stateUpdateHandler ready") - } - } - } - listener?.start(queue: DispatchQueue.global(qos: .userInitiated)) -// RunLoop.main.run() - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift deleted file mode 100644 index 8f26eb97..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/DNSProxyProvider.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// DNSProxyProvider.swift -// SelfControl -// -// Created by Satendra Singh on 02/08/25. -// - - -import NetworkExtension -import os.log - -class DNSProxyProvider: NEDNSProxyProvider { - - // Cache for queried domains (thread-safe) - var observedDomains = Set() - let queue = DispatchQueue(label: "dns.sync.queue") - - override func startProxy(options: [String : Any]? = nil, completionHandler: @escaping (Error?) -> Void) { - NSLog("DNS Proxy started") - os_log("SC] 🔍 DNS Proxy started") - completionHandler(nil) - } - - override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool { - if let tcpFlow = flow as? NEAppProxyTCPFlow { - os_log("SC] 🔍 DNS Proxy NEAppProxyTCPFlow") - readFromTCP(flow: tcpFlow) - return true - } - - guard let udpFlow = flow as? NEAppProxyUDPFlow else { - return true - } - os_log("SC] 🔍 DNS Proxy NEAppProxyUDPFlow") - udpFlow.readDatagrams { datagrams, remoteEndpoints, error in - guard let datagrams = datagrams else { return } - - for data in datagrams { - if let message = DNSParser.parseMessage(data) { - for question in message.questions { - os_log("SC] 🔍 DNS Query for domain: %{public}@", question.name) - NSLog("🔍 DNS Query for domain: \(question.name)") - // Save domain name, blocklist, etc. - } - } - } - - // Respond or forward if you're proxying -// udpFlow.closeReadWithError(nil) -// udpFlow.closeWriteWithError(nil) - } - - return true - } - - override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - NSLog("DNS Proxy stopped") - os_log("SC] 🔍 DNS Proxy stopped") - completionHandler() - } - - func readFromTCP(flow: NEAppProxyTCPFlow) { - flow.readData { [weak self] data, error in - guard let data = data, !data.isEmpty else { - os_log("SC] 🔍 No data or error: : %{public}@", error?.localizedDescription ?? "Empty") - flow.closeReadWithError(nil) - flow.closeWriteWithError(nil) - return - } - - var offset = 0 58540 - - while offset + 2 <= data.count { - // DNS over TCP starts with 2-byte length prefix - let length = Int(data.uint16(at: offset)) - offset += 2 - - guard offset + length <= data.count else { - NSLog("Incomplete DNS message") - os_log("[SC] 🔍 Incomplete DNS message") - break - } - - let dnsData = data.subdata(in: offset..<(offset + length)) - if let message = DNSParser.parseMessage(dnsData) { - for q in message.questions { - NSLog("🌐 DNS (TCP) Query: \(q.name)") - os_log("SC] 🔍 🌐 (TCP) Query: %{public}@", q.name) - } - } - - offset += length - } - - // Optionally respond or forward, or keep reading - flow.closeReadWithError(nil) - flow.closeWriteWithError(nil) - } - } - -} - -extension Data { - public func uint16(at offset: Int) -> UInt16 { - guard offset + 1 < self.count else { return 0 } - return UInt16(self[offset]) << 8 | UInt16(self[offset + 1]) - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift deleted file mode 100644 index 8bef5778..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/DNSQuestion.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// DNSQuestion.swift -// SelfControl -// -// Created by Satendra Singh on 02/08/25. -// - -import Foundation - -struct DNSQuestion { - let name: String - let type: UInt16 - let qclass: UInt16 -} - -struct DNSMessage { - let id: UInt16 - let isQuery: Bool - let opcode: UInt8 - let questions: [DNSQuestion] -} - -class DNSParser { - static func parseMessage(_ data: Data) -> DNSMessage? { - guard data.count >= 12 else { return nil } // DNS header size - - let id = data.uint16(at: 0) - let flags = data.uint16(at: 2) - - let isQuery = (flags & 0x8000) == 0 - let opcode = UInt8((flags & 0x7800) >> 11) - let qdcount = data.uint16(at: 4) - - var offset = 12 - - var questions: [DNSQuestion] = [] - - for _ in 0..= offset + 4 else { return nil } - let type = data.uint16(at: offset) - let qclass = data.uint16(at: offset + 2) - offset += 4 - questions.append(DNSQuestion(name: name, type: type, qclass: qclass)) - } - - return DNSMessage(id: id, isQuery: isQuery, opcode: opcode, questions: questions) - } - - private static func parseDomainName(_ data: Data, offset: Int) -> (String, Int)? { - var labels: [String] = [] - var index = offset - while index < data.count { - let length = Int(data[index]) - if length == 0 { - index += 1 - break - } - guard index + length < data.count else { return nil } - let labelData = data.subdata(in: index+1..<(index+1+length)) - if let label = String(data: labelData, encoding: .utf8) { - labels.append(label) - } else { - return nil - } - index += length + 1 - } - return c(labels.joined(separator: "."), index) - } -} - diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/ExtensionsPreferencesManager.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/ExtensionsPreferencesManager.swift deleted file mode 100644 index 99580098..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/ExtensionsPreferencesManager.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ExtensionsPreferencesManager.swift -// SelfControl -// -// Created by Satendra Singh on 12/07/25. -// - -import Foundation -import os.log - -final class ExtensionsPreferencesManager { - let appGroup = "X6FQ433AWK.com.application.SelfControl.Extension" - var blockedDomains: [String] = [] - lazy var defaults = UserDefaults(suiteName: appGroup) - - init(blockedDomains: [String]) { - self.blockedDomains = blockedDomains - } - - init () { - blockedDomains = defaults?.stringArray(forKey: "BlockedDomains") ?? [] - os_log("SC] 🔍 DNS Blocked Domains Loaded: %{public}@", blockedDomains) - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift index c9032761..b49233fd 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift @@ -22,11 +22,11 @@ class FilterDataProvider: NEFilterDataProvider { override func startFilter(completionHandler: @escaping (Error?) -> Void) { os_log("[SC] 🔍] FilterDataProvider: Starting filter", log: OSLog.default, type: .info) - os_log("[SC] 🔍] captrued Domains: %{public}@", log: OSLog.default, type: .error, ExtensionsPreferencesManager().blockedDomains) -// os_log("[SC] 🔍] captrued blockedUrls: %{public}@", log: OSLog.default, type: .error, IPCConnection.shared.blockedUrls) + let blockedHosts = IPCConnection.shared.blockedUrls + + os_log("[SC] 🔍] Bloked blockedUrls: %{public}@", log: OSLog.default, type: .error, blockedHosts) // Filter incoming TCP connections on port 8888 - let blockedHosts = IPCConnection.shared.blockedUrls // let filterRules = blockedHosts.map { address -> NEFilterRule in // // let localNetwork = NWHostEndpoint(hostname: address as! String, port: FilterDataProvider.localPort) @@ -193,6 +193,10 @@ class FilterDataProvider: NEFilterDataProvider { readBytesStartOffset offset: Int, readBytes data: Data ) -> NEFilterDataVerdict { + + guard IPCConnection.shared.isServiceActive else { //Service is inactive + return .allow() + } guard IPCConnection.shared.blockedUrls.count > 0 else { //No signinficant urls to block return .allow() @@ -249,9 +253,11 @@ class FilterDataProvider: NEFilterDataProvider { // Called for each new flow. override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { - + guard IPCConnection.shared.isServiceActive else { + return .allow() + } // Provide peek sizes required by this overload - os_log("[SC] 🔍] FilterDataProvider: handleNewFlow invoked", log: OSLog.default, type: .debug) +// os_log("[SC] 🔍] FilterDataProvider: handleNewFlow invoked", log: OSLog.default, type: .debug) guard IPCConnection.shared.blockedUrls.count > 0 else { //No signinficant urls to block return .allow() } diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift index eb1f541a..9de01755 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift @@ -21,6 +21,7 @@ enum ActiveBrowserExtensios: String { func setBlockedIPAddresses(_ ips: [String]) // Use Objective-C bridgeable type for XPC surface func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) + func setEnableService(_ enable: Bool) } /// Provider --> App IPC to Be implememted in the app @@ -50,7 +51,8 @@ class IPCConnection: NSObject { private(set) var isSafariExtensionEnable: Bool = false private(set) var isGoogleChromeEnabled: Bool = false // Published extension state for UI/observers - + private(set) var isServiceActive: Bool = false + // MARK: Methods /** @@ -174,7 +176,7 @@ extension IPCConnection: NSXPCListenerDelegate { func enableURLBlocking(_ urls: [String]) { os_log("[SC] 🔍] Enabling URL blocking") guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] Failed to register with the provider: %@", registerError.localizedDescription) + os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) }) as? AppToExtensionExtension else { os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") return @@ -185,7 +187,7 @@ extension IPCConnection: NSXPCListenerDelegate { func enableIPAddressesBlocking(_ urls: [String]) { os_log("[SC] 🔍] Enabling URL blocking") guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] Failed to register with the provider: %@", registerError.localizedDescription) + os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) }) as? AppToExtensionExtension else { os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") return @@ -196,7 +198,7 @@ extension IPCConnection: NSXPCListenerDelegate { func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) -> Bool { os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension:\(extensionTypeRawValue), state:\(state)") guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension: %@", registerError.localizedDescription) + os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension: %{public}@", registerError.localizedDescription) }) as? AppToExtensionExtension else { os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") return false @@ -204,10 +206,25 @@ extension IPCConnection: NSXPCListenerDelegate { providerProxy.setActiveBrowserExtension(extensionTypeRawValue, state: state) return true } + + func sendMessageToEnableNetworkExtension(_enable: Bool) -> Bool { + os_log("[SC] 🔍] sendMessageToEnableNetworkExtension state:\(_enable)") + guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in + os_log("[SC] 🔍] sendMessageToEnableNetworkExtension: %{public}@", registerError.localizedDescription) + }) as? AppToExtensionExtension else { + os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") + return false + } + providerProxy.setEnableService(_enable) + return true + } } extension IPCConnection: AppToExtensionExtension { + func setEnableService(_ enable: Bool) { + self.isServiceActive = enable + } func setBlockedIPAddresses(_ ips: [String]) { blockedIPAddresses = Set(ips) diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift index 89611a24..3ef45685 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift @@ -18,7 +18,7 @@ struct RequestSourceAppValidator { let isChromeExtensionEnabled = IPCConnection.shared.isGoogleChromeEnabled if let process = process, AllowedProcess.isAllowedProcess(process, isSafariExtensionActive: isSafariExtensionEnabled, isChromeExtensionActive: isChromeExtensionEnabled) { - os_log("[SC] 🔍] handleNewFlow: allowing as it is in allowed list.") +// os_log("[SC] 🔍] handleNewFlow: allowing as it is in allowed list.") return true } return false diff --git a/Self-Control-Extension/dynamic-url-blocker/background.js b/Self-Control-Extension/dynamic-url-blocker/background.js index 3625f6a6..2aebc197 100644 --- a/Self-Control-Extension/dynamic-url-blocker/background.js +++ b/Self-Control-Extension/dynamic-url-blocker/background.js @@ -49,15 +49,9 @@ function ruleForDomain(domain) { } async function fetchData() { - // try { - // const response = await fetch("http://127.0.0.1:8080/"); - // const data = await response.json(); - // console.log("Swift app data:", data); - // } catch (err) { - // console.error("Error fetching from Swift app:", err); - // } + try { - const response = await fetch("http://127.0.0.1:8080/"); + const response = await fetch("http://127.0.0.1:8532/chrome"); const data = await response.json(); console.log("Array from Swift:", data); @@ -80,7 +74,7 @@ blockedDomains.forEach(domain => { }); return blockedDomains; } catch (err) { - console.error("Fetch failed:", err); + console.log("Fetch failed:", err); return []; } } diff --git a/SelfControl.xcodeproj/project.pbxproj b/SelfControl.xcodeproj/project.pbxproj index 2954aea5..9faf2ff6 100644 --- a/SelfControl.xcodeproj/project.pbxproj +++ b/SelfControl.xcodeproj/project.pbxproj @@ -7,11 +7,13 @@ objects = { /* Begin PBXBuildFile section */ - 5E6BEEBB5C6E29DADDB344CF /* libPods-selfcontrol-cli.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C85A094F15E20A0DB58665A8 /* libPods-selfcontrol-cli.a */; }; - 63BAC9E58A69B15D342B0E29 /* libPods-org.eyebeam.selfcontrold.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BF3973D41997900DF147B24 /* libPods-org.eyebeam.selfcontrold.a */; }; - 8CA8987104D2956493D6AF6B /* Pods_SelfControl_SelfControlTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F41DEF1E3926B4CF3AE2B76C /* Pods_SelfControl_SelfControlTests.framework */; }; + 227499ED6DAAD0EA7A2B7EA7 /* Pods_SelfControl_SelfControlTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F8FA3A9265EFA8D41713681 /* Pods_SelfControl_SelfControlTests.framework */; }; + 5688020A39CB3AB3A1B0C47F /* libPods-org.eyebeam.selfcontrold.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0105F335A671C845ECC31F6E /* libPods-org.eyebeam.selfcontrold.a */; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; + AAB27795D2331ECB7849EA24 /* libPods-selfcontrol-cli.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB0F38FB83A87078C5AAAC1C /* libPods-selfcontrol-cli.a */; }; + C459756AE31AF2606147BF55 /* libPods-SCKillerHelper.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 69A8CCDD56C5466D78CFADA8 /* libPods-SCKillerHelper.a */; }; + C6CCCB5B721CF78E05886AF0 /* Pods_SelfControl_Killer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 129E43BCECDC59A62307DAAB /* Pods_SelfControl_Killer.framework */; }; CB0385E119D77051004614B6 /* PreferencesAdvancedViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CB0385DD19D77051004614B6 /* PreferencesAdvancedViewController.xib */; }; CB0385E219D77051004614B6 /* PreferencesGeneralViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CB0385DF19D77051004614B6 /* PreferencesGeneralViewController.xib */; }; CB066F6C2652037E0076964D /* HostFileBlocker.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB0AE290FA74566006229B3 /* HostFileBlocker.m */; }; @@ -38,7 +40,6 @@ CB1CA65125ABA5BB0084A551 /* SCXPCClient.m in Sources */ = {isa = PBXBuildFile; fileRef = CB62FC3A24B124B900ADBC40 /* SCXPCClient.m */; }; CB1CA65E25ABA6150084A551 /* SCXPCClient.m in Sources */ = {isa = PBXBuildFile; fileRef = CB62FC3A24B124B900ADBC40 /* SCXPCClient.m */; }; CB1CA66525ABA6240084A551 /* SCXPCAuthorization.m in Sources */ = {isa = PBXBuildFile; fileRef = CB69C4ED25A3FD8A0030CFCD /* SCXPCAuthorization.m */; }; - CB20C5D8245699D700B9D749 /* version-header.h in Sources */ = {isa = PBXBuildFile; fileRef = CB20C5D7245699D700B9D749 /* version-header.h */; }; CB21D0A825BA7B4400236680 /* PacketFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = CBCA91111960D87300AFD20C /* PacketFilter.m */; }; CB21D0AE25BA7B4500236680 /* PacketFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = CBCA91111960D87300AFD20C /* PacketFilter.m */; }; CB249FED19D782230087BBB6 /* SelfControlIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = CB249FEC19D782230087BBB6 /* SelfControlIcon.icns */; }; @@ -206,9 +207,7 @@ CBED7D9925ABB911003080D6 /* selfcontrol-cli in Copy Executable Helper Tools */ = {isa = PBXBuildFile; fileRef = CBA2AFD20F39EC12005AFEBE /* selfcontrol-cli */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; CBEE50C10F48C21F00F5DF1C /* TimerWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CBEE50C00F48C21F00F5DF1C /* TimerWindowController.m */; }; CBF3B574217BADD7006D5F52 /* SCSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = CBF3B573217BADD7006D5F52 /* SCSettings.m */; }; - D4EDD26C770910569C31D36F /* libPods-SCKillerHelper.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AF899A50A17F0C6C8C6B84A2 /* libPods-SCKillerHelper.a */; }; - DC4DBA9148D8D67A11899C5E /* Pods_SelfControl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EBDE7B29D92764A409E4FDA /* Pods_SelfControl.framework */; }; - E263B809965135813A557CD5 /* Pods_SelfControl_Killer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86DAD6532C67CBE72E99084C /* Pods_SelfControl_Killer.framework */; }; + DDBB80A47678404455711991 /* Pods_SelfControl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A312616E5C8B89085B914A2 /* Pods_SelfControl.framework */; }; F5B8CBEE19EE21C30026F3A5 /* SCTimeIntervalFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B8CBED19EE21C30026F3A5 /* SCTimeIntervalFormatter.m */; }; /* End PBXBuildFile section */ @@ -523,31 +522,33 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0105F335A671C845ECC31F6E /* libPods-org.eyebeam.selfcontrold.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-org.eyebeam.selfcontrold.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 077F865138A2B4B27CACBBCA /* Pods-org.eyebeam.selfcontrold.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-org.eyebeam.selfcontrold.release.xcconfig"; path = "Pods/Target Support Files/Pods-org.eyebeam.selfcontrold/Pods-org.eyebeam.selfcontrold.release.xcconfig"; sourceTree = ""; }; 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; + 129E43BCECDC59A62307DAAB /* Pods_SelfControl_Killer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SelfControl_Killer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 190AA87F8521B9AEC0B60360 /* Pods-selfcontrol-cli.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-selfcontrol-cli.debug.xcconfig"; path = "Pods/Target Support Files/Pods-selfcontrol-cli/Pods-selfcontrol-cli.debug.xcconfig"; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; - 32B26CAAF2E2B648B3C0E892 /* Pods-SelfControl.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl/Pods-SelfControl.debug.xcconfig"; sourceTree = ""; }; + 2C77669C20BA40116545D8F7 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-org.eyebeam.selfcontrold.debug.xcconfig"; path = "Pods/Target Support Files/Pods-org.eyebeam.selfcontrold/Pods-org.eyebeam.selfcontrold.debug.xcconfig"; sourceTree = ""; }; 32CA4F630368D1EE00C91783 /* SelfControl_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelfControl_Prefix.pch; sourceTree = ""; }; 3EF418F71CC7F7FA002D99E8 /* nl */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; 3EF418F81CC7F7FA002D99E8 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 50CA92A5EB2C31792CA00641 /* Pods-SelfControl Killer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl Killer.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl Killer/Pods-SelfControl Killer.debug.xcconfig"; sourceTree = ""; }; - 5FA850E53EB64C54A1404AEF /* Pods-SelfControl.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl.release.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl/Pods-SelfControl.release.xcconfig"; sourceTree = ""; }; + 5F8FA3A9265EFA8D41713681 /* Pods_SelfControl_SelfControlTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SelfControl_SelfControlTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 640D40CB16D6E962003034B3 /* it */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 640D40CC16D6E962003034B3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 64682B3371BA0A5044028058 /* Pods-selfcontrol-cli.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-selfcontrol-cli.debug.xcconfig"; path = "Pods/Target Support Files/Pods-selfcontrol-cli/Pods-selfcontrol-cli.debug.xcconfig"; sourceTree = ""; }; - 69102D5CD4E9672D0EFBF25B /* Pods-selfcontrol-cli.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-selfcontrol-cli.release.xcconfig"; path = "Pods/Target Support Files/Pods-selfcontrol-cli/Pods-selfcontrol-cli.release.xcconfig"; sourceTree = ""; }; - 6EBDE7B29D92764A409E4FDA /* Pods_SelfControl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SelfControl.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 77CFA479BA7C3ECBC15F96D7 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-org.eyebeam.selfcontrold.debug.xcconfig"; path = "Pods/Target Support Files/Pods-org.eyebeam.selfcontrold/Pods-org.eyebeam.selfcontrold.debug.xcconfig"; sourceTree = ""; }; - 86DAD6532C67CBE72E99084C /* Pods_SelfControl_Killer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SelfControl_Killer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 8BF3973D41997900DF147B24 /* libPods-org.eyebeam.selfcontrold.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-org.eyebeam.selfcontrold.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 69A8CCDD56C5466D78CFADA8 /* libPods-SCKillerHelper.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SCKillerHelper.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 776343E85A532B35974B7F0E /* Pods-SelfControl-SelfControlTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl-SelfControlTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests.debug.xcconfig"; sourceTree = ""; }; + 861003B099EA6AC9C8943755 /* Pods-selfcontrol-cli.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-selfcontrol-cli.release.xcconfig"; path = "Pods/Target Support Files/Pods-selfcontrol-cli/Pods-selfcontrol-cli.release.xcconfig"; sourceTree = ""; }; 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8D1107320486CEB800E47090 /* SelfControl.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SelfControl.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F7722215B94F9BC9D67EBC9 /* Pods-SCKillerHelper.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SCKillerHelper.release.xcconfig"; path = "Pods/Target Support Files/Pods-SCKillerHelper/Pods-SCKillerHelper.release.xcconfig"; sourceTree = ""; }; 949F9F8815B32EC6007B8B42 /* zh-Hans */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 949F9F8D15B333A1007B8B42 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - AF899A50A17F0C6C8C6B84A2 /* libPods-SCKillerHelper.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SCKillerHelper.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - C72025F2499F9F07E281CB50 /* Pods-SelfControl-SelfControlTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl-SelfControlTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests.release.xcconfig"; sourceTree = ""; }; - C85A094F15E20A0DB58665A8 /* libPods-selfcontrol-cli.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-selfcontrol-cli.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A312616E5C8B89085B914A2 /* Pods_SelfControl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SelfControl.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A0FB5A12B3019E1F2A7AC7A1 /* Pods-SelfControl.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl/Pods-SelfControl.debug.xcconfig"; sourceTree = ""; }; + A910D97A726951AD77892BC9 /* Pods-SelfControl Killer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl Killer.release.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl Killer/Pods-SelfControl Killer.release.xcconfig"; sourceTree = ""; }; + C4CDA695CAF296145B1A6098 /* Pods-SCKillerHelper.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SCKillerHelper.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SCKillerHelper/Pods-SCKillerHelper.debug.xcconfig"; sourceTree = ""; }; CB0EEF5D20FD8CE00024D27B /* SelfControlTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SelfControlTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CB0EEF6120FD8CE00024D27B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CB0EEF7720FE49020024D27B /* SCUtilityTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCUtilityTests.m; sourceTree = ""; }; @@ -556,7 +557,6 @@ CB1465C325B0285300130D2E /* SCError.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = SCError.strings; sourceTree = ""; }; CB1B44BD23B7F1ED00EBA087 /* cheater-background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "cheater-background.png"; sourceTree = ""; }; CB1CA68425ABAA360084A551 /* selfcontrol-cli-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "selfcontrol-cli-Info.plist"; sourceTree = ""; }; - CB20C5D7245699D700B9D749 /* version-header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "version-header.h"; sourceTree = ""; }; CB249FEC19D782230087BBB6 /* SelfControlIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = SelfControlIcon.icns; sourceTree = ""; }; CB25806016C1FDBE0059C99A /* BlockManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlockManager.h; sourceTree = ""; }; CB25806116C1FDBE0059C99A /* BlockManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlockManager.m; sourceTree = ""; }; @@ -1431,7 +1431,6 @@ CBDB11792084236A0010397E /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/PreferencesGeneralViewController.strings; sourceTree = ""; }; CBDB117B2084236C0010397E /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/PreferencesGeneralViewController.strings"; sourceTree = ""; }; CBDB117C208423750010397E /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/PreferencesGeneralViewController.strings; sourceTree = ""; }; - CBDB117D208423770010397E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/PreferencesGeneralViewController.strings; sourceTree = ""; }; CBDB117E208423790010397E /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/PreferencesGeneralViewController.strings"; sourceTree = ""; }; CBDB117F2084237A0010397E /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/PreferencesGeneralViewController.strings; sourceTree = ""; }; CBDB11802084237C0010397E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/PreferencesGeneralViewController.strings; sourceTree = ""; }; @@ -1441,7 +1440,6 @@ CBDB1184208423850010397E /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/PreferencesAdvancedViewController.strings; sourceTree = ""; }; CBDB1185208423870010397E /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/PreferencesAdvancedViewController.strings; sourceTree = ""; }; CBDB1186208423880010397E /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/PreferencesAdvancedViewController.strings; sourceTree = ""; }; - CBDB1187208423890010397E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/PreferencesAdvancedViewController.strings; sourceTree = ""; }; CBDB11882084238B0010397E /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/PreferencesAdvancedViewController.strings"; sourceTree = ""; }; CBDB11892084238C0010397E /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/PreferencesAdvancedViewController.strings; sourceTree = ""; }; CBDB118A2084238E0010397E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/PreferencesAdvancedViewController.strings; sourceTree = ""; }; @@ -1463,14 +1461,12 @@ CBEE50C00F48C21F00F5DF1C /* TimerWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimerWindowController.m; sourceTree = ""; }; CBF3B572217BADD7006D5F52 /* SCSettings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCSettings.h; sourceTree = ""; }; CBF3B573217BADD7006D5F52 /* SCSettings.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCSettings.m; sourceTree = ""; }; - D0BABA9759C378EE7C619F91 /* Pods-SCKillerHelper.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SCKillerHelper.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SCKillerHelper/Pods-SCKillerHelper.debug.xcconfig"; sourceTree = ""; }; - E1139D5A5B92C88FFF62718F /* Pods-SCKillerHelper.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SCKillerHelper.release.xcconfig"; path = "Pods/Target Support Files/Pods-SCKillerHelper/Pods-SCKillerHelper.release.xcconfig"; sourceTree = ""; }; - E3346B8A670C55C686428776 /* Pods-SelfControl-SelfControlTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl-SelfControlTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests.debug.xcconfig"; sourceTree = ""; }; - EBDF1B23AA44B10313D79203 /* Pods-SelfControl Killer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl Killer.release.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl Killer/Pods-SelfControl Killer.release.xcconfig"; sourceTree = ""; }; - F0874F1ABF369B21F1CEADC1 /* Pods-org.eyebeam.selfcontrold.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-org.eyebeam.selfcontrold.release.xcconfig"; path = "Pods/Target Support Files/Pods-org.eyebeam.selfcontrold/Pods-org.eyebeam.selfcontrold.release.xcconfig"; sourceTree = ""; }; - F41DEF1E3926B4CF3AE2B76C /* Pods_SelfControl_SelfControlTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SelfControl_SelfControlTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D6B8103524C531EA735691E7 /* Pods-SelfControl-SelfControlTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl-SelfControlTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests.release.xcconfig"; sourceTree = ""; }; + E47C806863673411F32101A8 /* Pods-SelfControl.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl.release.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl/Pods-SelfControl.release.xcconfig"; sourceTree = ""; }; + E9BF454DB8270402C6914995 /* Pods-SelfControl Killer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelfControl Killer.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SelfControl Killer/Pods-SelfControl Killer.debug.xcconfig"; sourceTree = ""; }; F5B8CBEC19EE21C30026F3A5 /* SCTimeIntervalFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCTimeIntervalFormatter.h; sourceTree = ""; }; F5B8CBED19EE21C30026F3A5 /* SCTimeIntervalFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCTimeIntervalFormatter.m; sourceTree = ""; }; + FB0F38FB83A87078C5AAAC1C /* libPods-selfcontrol-cli.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-selfcontrol-cli.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1483,7 +1479,7 @@ 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, CB587E500F50FE8800C66A09 /* SystemConfiguration.framework in Frameworks */, CBB3FD7A0F53834B00244132 /* Security.framework in Frameworks */, - DC4DBA9148D8D67A11899C5E /* Pods_SelfControl.framework in Frameworks */, + DDBB80A47678404455711991 /* Pods_SelfControl.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1491,7 +1487,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8CA8987104D2956493D6AF6B /* Pods_SelfControl_SelfControlTests.framework in Frameworks */, + 227499ED6DAAD0EA7A2B7EA7 /* Pods_SelfControl_SelfControlTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1504,7 +1500,7 @@ CBD2677011ED92DE00042CD8 /* CoreFoundation.framework in Frameworks */, CBD2677311ED92EF00042CD8 /* Foundation.framework in Frameworks */, CBD2677511ED92F800042CD8 /* Cocoa.framework in Frameworks */, - 5E6BEEBB5C6E29DADDB344CF /* libPods-selfcontrol-cli.a in Frameworks */, + AAB27795D2331ECB7849EA24 /* libPods-selfcontrol-cli.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1516,7 +1512,7 @@ CB74D1162480E506002B2079 /* Security.framework in Frameworks */, CB74D1182480E506002B2079 /* Foundation.framework in Frameworks */, CB74D1192480E506002B2079 /* Cocoa.framework in Frameworks */, - 63BAC9E58A69B15D342B0E29 /* libPods-org.eyebeam.selfcontrold.a in Frameworks */, + 5688020A39CB3AB3A1B0C47F /* libPods-org.eyebeam.selfcontrold.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1527,7 +1523,7 @@ CB32D2B121902DB300B8CD68 /* IOKit.framework in Frameworks */, CB9C813019CFBBC000CDCAE1 /* Security.framework in Frameworks */, CB9C812F19CFBBB900CDCAE1 /* Cocoa.framework in Frameworks */, - E263B809965135813A557CD5 /* Pods_SelfControl_Killer.framework in Frameworks */, + C6CCCB5B721CF78E05886AF0 /* Pods_SelfControl_Killer.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1539,7 +1535,7 @@ CB9C812C19CFBB8400CDCAE1 /* Cocoa.framework in Frameworks */, CB9C812A19CFBB8000CDCAE1 /* Foundation.framework in Frameworks */, CB9C812819CFBB7B00CDCAE1 /* Security.framework in Frameworks */, - D4EDD26C770910569C31D36F /* libPods-SCKillerHelper.a in Frameworks */, + C459756AE31AF2606147BF55 /* libPods-SCKillerHelper.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1584,7 +1580,6 @@ isa = PBXGroup; children = ( CBDFFF4B24A07DB300622CEE /* SelfControl.entitlements */, - CB20C5D7245699D700B9D749 /* version-header.h */, CBD4848519D7611F0020F949 /* Podfile */, CBD4848619D7611F0020F949 /* TemplateIcon2x.png */, CBD266C611ED82DB00042CD8 /* CLI */, @@ -1643,12 +1638,12 @@ CB9C812919CFBB8000CDCAE1 /* Foundation.framework */, 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, - AF899A50A17F0C6C8C6B84A2 /* libPods-SCKillerHelper.a */, - 6EBDE7B29D92764A409E4FDA /* Pods_SelfControl.framework */, - 86DAD6532C67CBE72E99084C /* Pods_SelfControl_Killer.framework */, - F41DEF1E3926B4CF3AE2B76C /* Pods_SelfControl_SelfControlTests.framework */, - 8BF3973D41997900DF147B24 /* libPods-org.eyebeam.selfcontrold.a */, - C85A094F15E20A0DB58665A8 /* libPods-selfcontrol-cli.a */, + 69A8CCDD56C5466D78CFADA8 /* libPods-SCKillerHelper.a */, + 9A312616E5C8B89085B914A2 /* Pods_SelfControl.framework */, + 129E43BCECDC59A62307DAAB /* Pods_SelfControl_Killer.framework */, + 5F8FA3A9265EFA8D41713681 /* Pods_SelfControl_SelfControlTests.framework */, + 0105F335A671C845ECC31F6E /* libPods-org.eyebeam.selfcontrold.a */, + FB0F38FB83A87078C5AAAC1C /* libPods-selfcontrol-cli.a */, ); name = Frameworks; sourceTree = ""; @@ -2856,18 +2851,18 @@ isa = PBXGroup; children = ( CBB672FF25D61437006E4BC9 /* ArgumentParser */, - D0BABA9759C378EE7C619F91 /* Pods-SCKillerHelper.debug.xcconfig */, - E1139D5A5B92C88FFF62718F /* Pods-SCKillerHelper.release.xcconfig */, - 32B26CAAF2E2B648B3C0E892 /* Pods-SelfControl.debug.xcconfig */, - 5FA850E53EB64C54A1404AEF /* Pods-SelfControl.release.xcconfig */, - 50CA92A5EB2C31792CA00641 /* Pods-SelfControl Killer.debug.xcconfig */, - EBDF1B23AA44B10313D79203 /* Pods-SelfControl Killer.release.xcconfig */, - E3346B8A670C55C686428776 /* Pods-SelfControl-SelfControlTests.debug.xcconfig */, - C72025F2499F9F07E281CB50 /* Pods-SelfControl-SelfControlTests.release.xcconfig */, - 77CFA479BA7C3ECBC15F96D7 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */, - F0874F1ABF369B21F1CEADC1 /* Pods-org.eyebeam.selfcontrold.release.xcconfig */, - 64682B3371BA0A5044028058 /* Pods-selfcontrol-cli.debug.xcconfig */, - 69102D5CD4E9672D0EFBF25B /* Pods-selfcontrol-cli.release.xcconfig */, + C4CDA695CAF296145B1A6098 /* Pods-SCKillerHelper.debug.xcconfig */, + 8F7722215B94F9BC9D67EBC9 /* Pods-SCKillerHelper.release.xcconfig */, + A0FB5A12B3019E1F2A7AC7A1 /* Pods-SelfControl.debug.xcconfig */, + E47C806863673411F32101A8 /* Pods-SelfControl.release.xcconfig */, + E9BF454DB8270402C6914995 /* Pods-SelfControl Killer.debug.xcconfig */, + A910D97A726951AD77892BC9 /* Pods-SelfControl Killer.release.xcconfig */, + 776343E85A532B35974B7F0E /* Pods-SelfControl-SelfControlTests.debug.xcconfig */, + D6B8103524C531EA735691E7 /* Pods-SelfControl-SelfControlTests.release.xcconfig */, + 2C77669C20BA40116545D8F7 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */, + 077F865138A2B4B27CACBBCA /* Pods-org.eyebeam.selfcontrold.release.xcconfig */, + 190AA87F8521B9AEC0B60360 /* Pods-selfcontrol-cli.debug.xcconfig */, + 861003B099EA6AC9C8943755 /* Pods-selfcontrol-cli.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -2904,7 +2899,7 @@ isa = PBXNativeTarget; buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "SelfControl" */; buildPhases = ( - 54B25F59AEDB053782FAC10D /* [CP] Check Pods Manifest.lock */, + ED83FB631891EB03112D0E12 /* [CP] Check Pods Manifest.lock */, CB81A92E25B7B4B8006956F7 /* ShellScript */, 8D1107290486CEB800E47090 /* Resources */, 8D11072C0486CEB800E47090 /* Sources */, @@ -2913,7 +2908,7 @@ CBDFFF4624A044C900622CEE /* Copy Daemon Launch Service */, CB5E5FF81C3A5FD10038F331 /* ShellScript */, CB81AB2725B7EFA4006956F7 /* Embed Frameworks */, - BED0609C15A860C955A3552D /* [CP] Embed Pods Frameworks */, + 8D5294BD3A7C96234E1020B9 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -2929,12 +2924,12 @@ isa = PBXNativeTarget; buildConfigurationList = CB0EEF6420FD8CE00024D27B /* Build configuration list for PBXNativeTarget "SelfControlTests" */; buildPhases = ( - 2A27B423AFFABE56BC018570 /* [CP] Check Pods Manifest.lock */, + 45B13088DFEB2B358A409AEA /* [CP] Check Pods Manifest.lock */, CB841F05243A6D7F00274FDF /* Headers */, CB0EEF5920FD8CE00024D27B /* Sources */, CB0EEF5A20FD8CE00024D27B /* Frameworks */, CB0EEF5B20FD8CE00024D27B /* Resources */, - ED887BF54A55A00F42A6FD9C /* [CP] Embed Pods Frameworks */, + B8838D36E9932BFE90604211 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -2950,7 +2945,7 @@ isa = PBXNativeTarget; buildConfigurationList = CB74D11A2480E506002B2079 /* Build configuration list for PBXNativeTarget "org.eyebeam.selfcontrold" */; buildPhases = ( - D76E3B7390CC3DD10C32CD7F /* [CP] Check Pods Manifest.lock */, + 4B0AAF2464987231854A1AF3 /* [CP] Check Pods Manifest.lock */, CB74D1062480E506002B2079 /* ShellScript */, CB74D1072480E506002B2079 /* Sources */, CB74D1142480E506002B2079 /* Frameworks */, @@ -2968,12 +2963,11 @@ isa = PBXNativeTarget; buildConfigurationList = CB9C811119CFB79700CDCAE1 /* Build configuration list for PBXNativeTarget "SelfControl Killer" */; buildPhases = ( - C4455A3F0260679D1BD5962E /* [CP] Check Pods Manifest.lock */, + 840516A4AE7AFF18A0AAAF30 /* [CP] Check Pods Manifest.lock */, CB9C80F319CFB79700CDCAE1 /* Sources */, CB9C80F419CFB79700CDCAE1 /* Frameworks */, CB9C80F519CFB79700CDCAE1 /* Resources */, CB9C813119CFBBD300CDCAE1 /* Copy Helper Tools */, - AF328C5935C794FB31FCD1B5 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -2988,7 +2982,7 @@ isa = PBXNativeTarget; buildConfigurationList = CB9C811F19CFBA8500CDCAE1 /* Build configuration list for PBXNativeTarget "SCKillerHelper" */; buildPhases = ( - 69AC2704461942B09B953173 /* [CP] Check Pods Manifest.lock */, + A61E50D5FDB33A56935F8563 /* [CP] Check Pods Manifest.lock */, CB9C811719CFBA8500CDCAE1 /* Sources */, CB9C811819CFBA8500CDCAE1 /* Frameworks */, ); @@ -3005,7 +2999,7 @@ isa = PBXNativeTarget; buildConfigurationList = CBA2AFD60F39EC33005AFEBE /* Build configuration list for PBXNativeTarget "selfcontrol-cli" */; buildPhases = ( - 667CC580701BF2C2321C3B5D /* [CP] Check Pods Manifest.lock */, + 361D11319CEAD77BCA8D72EB /* [CP] Check Pods Manifest.lock */, CB20C5D6245696C500B9D749 /* ShellScript */, CBA2AFCF0F39EC12005AFEBE /* Sources */, CB54D4490F93E32B00AA22E9 /* Frameworks */, @@ -3028,9 +3022,9 @@ LastUpgradeCheck = 0930; TargetAttributes = { 8D1107260486CEB800E47090 = { - DevelopmentTeam = EG6ZYP3AQH; + DevelopmentTeam = KWW448CAK7; LastSwiftMigration = 1150; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.Mac = { enabled = 1; @@ -3039,26 +3033,24 @@ }; CB0EEF5C20FD8CE00024D27B = { CreatedOnToolsVersion = 9.4.1; - DevelopmentTeam = EG6ZYP3AQH; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; CB74D1052480E506002B2079 = { - DevelopmentTeam = EG6ZYP3AQH; - ProvisioningStyle = Manual; + DevelopmentTeam = KWW448CAK7; + ProvisioningStyle = Automatic; }; CB9C80F619CFB79700CDCAE1 = { CreatedOnToolsVersion = 6.0.1; - DevelopmentTeam = EG6ZYP3AQH; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; CB9C811A19CFBA8500CDCAE1 = { CreatedOnToolsVersion = 6.0.1; - DevelopmentTeam = EG6ZYP3AQH; - ProvisioningStyle = Manual; + DevelopmentTeam = KWW448CAK7; + ProvisioningStyle = Automatic; }; CBA2AFD10F39EC12005AFEBE = { - DevelopmentTeam = EG6ZYP3AQH; - ProvisioningStyle = Manual; + DevelopmentTeam = KWW448CAK7; + ProvisioningStyle = Automatic; }; }; }; @@ -3426,7 +3418,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 2A27B423AFFABE56BC018570 /* [CP] Check Pods Manifest.lock */ = { + 361D11319CEAD77BCA8D72EB /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3441,14 +3433,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelfControl-SelfControlTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-selfcontrol-cli-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 54B25F59AEDB053782FAC10D /* [CP] Check Pods Manifest.lock */ = { + 45B13088DFEB2B358A409AEA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3463,14 +3455,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelfControl-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SelfControl-SelfControlTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 667CC580701BF2C2321C3B5D /* [CP] Check Pods Manifest.lock */ = { + 4B0AAF2464987231854A1AF3 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3485,14 +3477,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-selfcontrol-cli-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-org.eyebeam.selfcontrold-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 69AC2704461942B09B953173 /* [CP] Check Pods Manifest.lock */ = { + 840516A4AE7AFF18A0AAAF30 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3507,77 +3499,185 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SCKillerHelper-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SelfControl Killer-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - AF328C5935C794FB31FCD1B5 /* [CP] Embed Pods Frameworks */ = { + 8D5294BD3A7C96234E1020B9 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-SelfControl Killer/Pods-SelfControl Killer-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Sentry-framework/Sentry.framework", - ); - name = "[CP] Embed Pods Frameworks"; + "${PODS_ROOT}/Target Support Files/Pods-SelfControl/Pods-SelfControl-resources.sh", + "${PODS_ROOT}/FormatterKit/FormatterKit/FormatterKit.bundle", + "${PODS_ROOT}/LetsMove/Base.lproj", + "${PODS_ROOT}/LetsMove/ca.lproj", + "${PODS_ROOT}/LetsMove/cs.lproj", + "${PODS_ROOT}/LetsMove/da.lproj", + "${PODS_ROOT}/LetsMove/de.lproj", + "${PODS_ROOT}/LetsMove/el.lproj", + "${PODS_ROOT}/LetsMove/en.lproj", + "${PODS_ROOT}/LetsMove/es.lproj", + "${PODS_ROOT}/LetsMove/fr.lproj", + "${PODS_ROOT}/LetsMove/hu.lproj", + "${PODS_ROOT}/LetsMove/it.lproj", + "${PODS_ROOT}/LetsMove/ja.lproj", + "${PODS_ROOT}/LetsMove/ko.lproj", + "${PODS_ROOT}/LetsMove/mk.lproj", + "${PODS_ROOT}/LetsMove/nb.lproj", + "${PODS_ROOT}/LetsMove/nl.lproj", + "${PODS_ROOT}/LetsMove/pl.lproj", + "${PODS_ROOT}/LetsMove/pt.lproj", + "${PODS_ROOT}/LetsMove/pt_BR.lproj", + "${PODS_ROOT}/LetsMove/ru.lproj", + "${PODS_ROOT}/LetsMove/sk.lproj", + "${PODS_ROOT}/LetsMove/sr.lproj", + "${PODS_ROOT}/LetsMove/sv.lproj", + "${PODS_ROOT}/LetsMove/tr.lproj", + "${PODS_ROOT}/LetsMove/vi-VN.lproj", + "${PODS_ROOT}/LetsMove/zh_CN.lproj", + "${PODS_ROOT}/LetsMove/zh_TW.lproj", + "${BUILT_PRODUCTS_DIR}/MASPreferences/MASPreferences.framework//Users/satendrasingh/Library/Developer/Xcode/DerivedData/SelfControl-cgvpnsqlwaorbygdskhcomgsthde/Build/Products/Debug/MASPreferences/MASPreferences.framework/Versions/A/Resources/en.lproj/MASPreferencesWindow.nib", + "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", + ); + name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FormatterKit.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Base.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ca.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/cs.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/da.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/de.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/el.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/en.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/es.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/fr.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/hu.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/it.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ja.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ko.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/mk.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nb.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nl.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/pl.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/pt.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/pt_BR.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ru.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sk.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sr.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sv.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/tr.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/vi-VN.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/zh_CN.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/zh_TW.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MASPreferencesWindow.nib", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelfControl Killer/Pods-SelfControl Killer-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelfControl/Pods-SelfControl-resources.sh\"\n"; showEnvVarsInLog = 0; }; - BED0609C15A860C955A3552D /* [CP] Embed Pods Frameworks */ = { + A61E50D5FDB33A56935F8563 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-SelfControl/Pods-SelfControl-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/FormatterKit/FormatterKit.framework", - "${BUILT_PRODUCTS_DIR}/LetsMove/LetsMove.framework", - "${BUILT_PRODUCTS_DIR}/MASPreferences/MASPreferences.framework", - "${BUILT_PRODUCTS_DIR}/Sentry-framework/Sentry.framework", - "${BUILT_PRODUCTS_DIR}/TransformerKit/TransformerKit.framework", - ); - name = "[CP] Embed Pods Frameworks"; + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FormatterKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LetsMove.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MASPreferences.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TransformerKit.framework", + "$(DERIVED_FILE_DIR)/Pods-SCKillerHelper-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelfControl/Pods-SelfControl-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - C4455A3F0260679D1BD5962E /* [CP] Check Pods Manifest.lock */ = { + B8838D36E9932BFE90604211 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); + "${PODS_ROOT}/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests-resources.sh", + "${PODS_ROOT}/FormatterKit/FormatterKit/FormatterKit.bundle", + "${PODS_ROOT}/LetsMove/Base.lproj", + "${PODS_ROOT}/LetsMove/ca.lproj", + "${PODS_ROOT}/LetsMove/cs.lproj", + "${PODS_ROOT}/LetsMove/da.lproj", + "${PODS_ROOT}/LetsMove/de.lproj", + "${PODS_ROOT}/LetsMove/el.lproj", + "${PODS_ROOT}/LetsMove/en.lproj", + "${PODS_ROOT}/LetsMove/es.lproj", + "${PODS_ROOT}/LetsMove/fr.lproj", + "${PODS_ROOT}/LetsMove/hu.lproj", + "${PODS_ROOT}/LetsMove/it.lproj", + "${PODS_ROOT}/LetsMove/ja.lproj", + "${PODS_ROOT}/LetsMove/ko.lproj", + "${PODS_ROOT}/LetsMove/mk.lproj", + "${PODS_ROOT}/LetsMove/nb.lproj", + "${PODS_ROOT}/LetsMove/nl.lproj", + "${PODS_ROOT}/LetsMove/pl.lproj", + "${PODS_ROOT}/LetsMove/pt.lproj", + "${PODS_ROOT}/LetsMove/pt_BR.lproj", + "${PODS_ROOT}/LetsMove/ru.lproj", + "${PODS_ROOT}/LetsMove/sk.lproj", + "${PODS_ROOT}/LetsMove/sr.lproj", + "${PODS_ROOT}/LetsMove/sv.lproj", + "${PODS_ROOT}/LetsMove/tr.lproj", + "${PODS_ROOT}/LetsMove/vi-VN.lproj", + "${PODS_ROOT}/LetsMove/zh_CN.lproj", + "${PODS_ROOT}/LetsMove/zh_TW.lproj", + "${BUILT_PRODUCTS_DIR}/MASPreferences/MASPreferences.framework/en.lproj/MASPreferencesWindow.nib", + "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", + ); + name = "[CP] Copy Pods Resources"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelfControl Killer-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FormatterKit.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Base.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ca.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/cs.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/da.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/de.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/el.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/en.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/es.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/fr.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/hu.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/it.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ja.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ko.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/mk.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nb.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nl.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/pl.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/pt.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/pt_BR.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ru.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sk.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sr.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sv.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/tr.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/vi-VN.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/zh_CN.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/zh_TW.lproj", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MASPreferencesWindow.nib", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; CB20C5D6245696C500B9D749 /* ShellScript */ = { @@ -3644,7 +3744,7 @@ shellPath = /bin/sh; shellScript = "# Type a script or drag a script file from your workspace to insert its path.\necho \"#define SELFCONTROL_VERSION_STRING @\\\"${MARKETING_VERSION}\\\"\" > \"${PROJECT_DIR}/version-header.h\"\n"; }; - D76E3B7390CC3DD10C32CD7F /* [CP] Check Pods Manifest.lock */ = { + ED83FB631891EB03112D0E12 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3659,39 +3759,13 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-org.eyebeam.selfcontrold-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SelfControl-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - ED887BF54A55A00F42A6FD9C /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/FormatterKit/FormatterKit.framework", - "${BUILT_PRODUCTS_DIR}/LetsMove/LetsMove.framework", - "${BUILT_PRODUCTS_DIR}/MASPreferences/MASPreferences.framework", - "${BUILT_PRODUCTS_DIR}/Sentry-framework/Sentry.framework", - "${BUILT_PRODUCTS_DIR}/TransformerKit/TransformerKit.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FormatterKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LetsMove.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MASPreferences.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TransformerKit.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelfControl-SelfControlTests/Pods-SelfControl-SelfControlTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -3855,7 +3929,6 @@ CBB67D5D25D6165B006E4BC9 /* NSDictionary+RubyDescription.m in Sources */, CBB67D5425D6165B006E4BC9 /* XPMArgsKonstants.m in Sources */, CBB67D5325D6165B006E4BC9 /* XPMMutableAttributedArray.m in Sources */, - CB20C5D8245699D700B9D749 /* version-header.h in Sources */, CBB67D5725D6165B006E4BC9 /* XPMCountedArgument.m in Sources */, CBA2AFD90F39EC46005AFEBE /* cli-main.m in Sources */, CBB67D5925D6165B006E4BC9 /* XPMArgumentPackage.m in Sources */, @@ -3900,7 +3973,6 @@ CBDB1184208423850010397E /* sv */, CBDB1185208423870010397E /* ja */, CBDB1186208423880010397E /* it */, - CBDB1187208423890010397E /* tr */, CBDB11882084238B0010397E /* pt-BR */, CBDB11892084238C0010397E /* ko */, CBDB118A2084238E0010397E /* nl */, @@ -3920,7 +3992,6 @@ CBDB11792084236A0010397E /* ja */, CBDB117B2084236C0010397E /* zh-Hans */, CBDB117C208423750010397E /* it */, - CBDB117D208423770010397E /* tr */, CBDB117E208423790010397E /* pt-BR */, CBDB117F2084237A0010397E /* ko */, CBDB11802084237C0010397E /* nl */, @@ -4052,15 +4123,17 @@ /* Begin XCBuildConfiguration section */ C01FCF4B08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 32B26CAAF2E2B648B3C0E892 /* Pods-SelfControl.debug.xcconfig */; + baseConfigurationReference = A0FB5A12B3019E1F2A7AC7A1 /* Pods-SelfControl.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = KWW448CAK7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", @@ -4073,6 +4146,7 @@ INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11.5; PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.${PRODUCT_NAME:identifier}"; PRODUCT_NAME = SelfControl; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4085,7 +4159,7 @@ }; C01FCF4C08A954540054247B /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5FA850E53EB64C54A1404AEF /* Pods-SelfControl.release.xcconfig */; + baseConfigurationReference = E47C806863673411F32101A8 /* Pods-SelfControl.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -4104,6 +4178,7 @@ INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11.5; PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.${PRODUCT_NAME:identifier}"; PRODUCT_NAME = SelfControl; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4213,7 +4288,7 @@ }; CB0EEF6520FD8CE00024D27B /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E3346B8A670C55C686428776 /* Pods-SelfControl-SelfControlTests.debug.xcconfig */; + baseConfigurationReference = 776343E85A532B35974B7F0E /* Pods-SelfControl-SelfControlTests.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -4226,8 +4301,12 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -4244,12 +4323,14 @@ MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = org.selfcontrolapp.SelfControlTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; }; name = Debug; }; CB0EEF6620FD8CE00024D27B /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C72025F2499F9F07E281CB50 /* Pods-SelfControl-SelfControlTests.release.xcconfig */; + baseConfigurationReference = D6B8103524C531EA735691E7 /* Pods-SelfControl-SelfControlTests.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -4278,15 +4359,17 @@ }; CB74D11B2480E506002B2079 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 77CFA479BA7C3ECBC15F96D7 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */; + baseConfigurationReference = 2C77669C20BA40116545D8F7 /* Pods-org.eyebeam.selfcontrold.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = KWW448CAK7; GCC_DYNAMIC_NO_PIC = NO; GCC_MODEL_TUNING = G5; GCC_NO_COMMON_BLOCKS = NO; @@ -4296,6 +4379,7 @@ GCC_VERSION = ""; INFOPLIST_FILE = "Daemon/selfcontrold-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11.5; OTHER_LDFLAGS = ( "-sectcreate", __TEXT, @@ -4320,7 +4404,7 @@ }; CB74D11C2480E506002B2079 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F0874F1ABF369B21F1CEADC1 /* Pods-org.eyebeam.selfcontrold.release.xcconfig */; + baseConfigurationReference = 077F865138A2B4B27CACBBCA /* Pods-org.eyebeam.selfcontrold.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -4336,6 +4420,7 @@ GCC_VERSION = ""; INFOPLIST_FILE = "Daemon/selfcontrold-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11.5; OTHER_LDFLAGS = ( "-sectcreate", __TEXT, @@ -4360,7 +4445,7 @@ }; CB9C811219CFB79700CDCAE1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 50CA92A5EB2C31792CA00641 /* Pods-SelfControl Killer.debug.xcconfig */; + baseConfigurationReference = E9BF454DB8270402C6914995 /* Pods-SelfControl Killer.debug.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -4375,9 +4460,12 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 410; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -4395,17 +4483,20 @@ GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "SelfControl Killer/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.selfcontrolapp.SelfControl-Killer"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SWIFT_VERSION = 4.0; }; name = Debug; }; CB9C811319CFB79700CDCAE1 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EBDF1B23AA44B10313D79203 /* Pods-SelfControl Killer.release.xcconfig */; + baseConfigurationReference = A910D97A726951AD77892BC9 /* Pods-SelfControl Killer.release.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -4434,16 +4525,18 @@ GCC_WARN_UNUSED_FUNCTION = YES; INFOPLIST_FILE = "SelfControl Killer/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.selfcontrolapp.SelfControl-Killer"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 4.0; }; name = Release; }; CB9C812019CFBA8500CDCAE1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D0BABA9759C378EE7C619F91 /* Pods-SCKillerHelper.debug.xcconfig */; + baseConfigurationReference = C4CDA695CAF296145B1A6098 /* Pods-SCKillerHelper.debug.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -4458,7 +4551,9 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = KWW448CAK7; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -4475,18 +4570,20 @@ GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; }; name = Debug; }; CB9C812119CFBA8500CDCAE1 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E1139D5A5B92C88FFF62718F /* Pods-SCKillerHelper.release.xcconfig */; + baseConfigurationReference = 8F7722215B94F9BC9D67EBC9 /* Pods-SCKillerHelper.release.xcconfig */; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -4513,27 +4610,32 @@ GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; }; name = Release; }; CBA2AFD40F39EC14005AFEBE /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 64682B3371BA0A5044028058 /* Pods-selfcontrol-cli.debug.xcconfig */; + baseConfigurationReference = 190AA87F8521B9AEC0B60360 /* Pods-selfcontrol-cli.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - CODE_SIGN_IDENTITY = "Developer ID Application: Charles Stigler (EG6ZYP3AQH)"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CREATE_INFOPLIST_SECTION_IN_BINARY = YES; CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = KWW448CAK7; FRAMEWORK_SEARCH_PATHS = ( - "$(PLATFORM_DIR)/Developer/Library/Frameworks\n\n$(PLATFORM_DIR)/Developer/Library/Frameworks\n\n", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", ); GCC_DYNAMIC_NO_PIC = NO; GCC_MODEL_TUNING = G5; @@ -4545,6 +4647,7 @@ INFOPLIST_FILE = "selfcontrol-cli-Info.plist"; INFOPLIST_PREPROCESS = YES; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11.5; PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.selfcontrol-cli"; PRODUCT_NAME = "selfcontrol-cli"; PROVISIONING_PROFILE = ""; @@ -4558,7 +4661,7 @@ }; CBA2AFD50F39EC14005AFEBE /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 69102D5CD4E9672D0EFBF25B /* Pods-selfcontrol-cli.release.xcconfig */; + baseConfigurationReference = 861003B099EA6AC9C8943755 /* Pods-selfcontrol-cli.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -4568,7 +4671,8 @@ CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; FRAMEWORK_SEARCH_PATHS = ( - "$(PLATFORM_DIR)/Developer/Library/Frameworks\n\n$(PLATFORM_DIR)/Developer/Library/Frameworks\n\n", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", ); GCC_MODEL_TUNING = G5; GCC_NO_COMMON_BLOCKS = NO; @@ -4578,6 +4682,7 @@ INFOPLIST_FILE = "selfcontrol-cli-Info.plist"; INFOPLIST_PREPROCESS = YES; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11.5; PRODUCT_BUNDLE_IDENTIFIER = "org.eyebeam.selfcontrol-cli"; PRODUCT_NAME = "selfcontrol-cli"; PROVISIONING_PROFILE = ""; diff --git a/SelfControl.xcodeproj/xcshareddata/xcschemes/SCKillerHelper.xcscheme b/SelfControl.xcodeproj/xcshareddata/xcschemes/SCKillerHelper.xcscheme new file mode 100644 index 00000000..d960f986 --- /dev/null +++ b/SelfControl.xcodeproj/xcshareddata/xcschemes/SCKillerHelper.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme b/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme new file mode 100644 index 00000000..16cc8d56 --- /dev/null +++ b/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SelfControl_Prefix.pch b/SelfControl_Prefix.pch index 0af9d99b..20c7d07a 100755 --- a/SelfControl_Prefix.pch +++ b/SelfControl_Prefix.pch @@ -4,10 +4,11 @@ #ifdef __OBJC__ +#define SELFCONTROL_VERSION_STRING @"4.0.2" + #import #import -#import "version-header.h" #import "DeprecationSilencers.h" #import "SCUtility.h" #import "SCErr.h" diff --git a/cli-main.m b/cli-main.m index 46958cca..2f59e902 100755 --- a/cli-main.m +++ b/cli-main.m @@ -232,10 +232,10 @@ int main(int argc, char* argv[]) { NSLog(@"%@", blockIsRunning ? @"YES" : @"NO"); } else if ([arguments booleanValueForSignature: versionSig]) { [SCSentry addBreadcrumb: @"CLI method --version called" category: @"cli"]; - NSLog(SELFCONTROL_VERSION_STRING); +// NSLog(SELFCONTROL_VERSION_STRING); } else { // help / usage message - printf("SelfControl CLI Tool v%s\n", [SELFCONTROL_VERSION_STRING UTF8String]); +// printf("SelfControl CLI Tool v%s\n", [SELFCONTROL_VERSION_STRING UTF8String]); printf("Usage: selfcontrol-cli [--uid ] []\n\n"); printf("Valid commands:\n"); printf("\n start --> starts a SelfControl block\n"); From bb56c78742375449544ec1adbeda4c71cea37b68 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 14 Jan 2026 21:04:12 +0530 Subject: [PATCH 04/64] added legacy tool files --- .../Block Management/AllowlistScraper.h | 17 + .../Block Management/AllowlistScraper.m | 73 +++ .../Block Management/BlockManager.h | 62 ++ .../Block Management/BlockManager.m | 531 ++++++++++++++++ .../Common/NSString+IPAddress.h | 33 + .../Common/NSString+IPAddress.m | 49 ++ .../Block Management/HostFileBlocker.h | 64 ++ .../Block Management/HostFileBlocker.m | 234 ++++++++ .../Block Management/HostFileBlockerSet.h | 20 + .../Block Management/HostFileBlockerSet.m | 121 ++++ .../Block Management/HostImporter.h | 54 ++ .../Block Management/HostImporter.m | 229 +++++++ .../Block Management/PacketFilter.h | 33 + .../Block Management/PacketFilter.m | 267 ++++++++ .../Block Management/SCBlockEntry.h | 26 + .../Block Management/SCBlockEntry.m | 124 ++++ .../ThunderbirdPreferenceParser.h | 62 ++ .../ThunderbirdPreferenceParser.m | 207 +++++++ .../Common/DeprecationSilencers.h | 23 + .../org.eyebeam.selfcontrold/Common/SCErr.h | 25 + .../org.eyebeam.selfcontrold/Common/SCErr.m | 42 ++ .../Common/SCFileWatcher.h | 27 + .../Common/SCFileWatcher.m | 104 ++++ .../Common/SCSettings.h | 42 ++ .../Common/SCSettings.m | 568 ++++++++++++++++++ .../Common/SCXPCAuthorization.h | 24 + .../Common/SCXPCAuthorization.m | 186 ++++++ .../Common/SCXPCClient.h | 28 + .../Common/SCXPCClient.m | 352 +++++++++++ .../Common/Utilities/LaunchctlHelper.h | 35 ++ .../Common/Utilities/LaunchctlHelper.m | 38 ++ .../Common/Utilities/SCBlockUtilities.h | 27 + .../Common/Utilities/SCBlockUtilities.m | 76 +++ .../Common/Utilities/SCConstants.h | 21 + .../Common/Utilities/SCConstants.m | 75 +++ .../Common/Utilities/SCHelperToolUtilities.h | 45 ++ .../Common/Utilities/SCHelperToolUtilities.m | 183 ++++++ .../Common/Utilities/SCMiscUtilities.h | 33 + .../Common/Utilities/SCMiscUtilities.m | 250 ++++++++ .../org.eyebeam.selfcontrold/ConsoleLog.h | 18 + .../org.eyebeam.selfcontrold/ConsoleLog.m | 17 + .../org.eyebeam.selfcontrold/DaemonMain.m | 25 + .../org.eyebeam.selfcontrold/SCDaemon.h | 40 ++ .../org.eyebeam.selfcontrold/SCDaemon.m | 195 ++++++ .../SCDaemonBlockMethods.h | 36 ++ .../SCDaemonBlockMethods.m | 391 ++++++++++++ .../SCDaemonProtocol.h | 28 + .../org.eyebeam.selfcontrold/SCDaemonXPC.h | 19 + .../org.eyebeam.selfcontrold/SCDaemonXPC.m | 90 +++ .../SCDaemon_Prefix.pch | 21 + .../org.eyebeam.selfcontrold.plist | 19 + .../selfcontrold-Info.plist | 58 ++ 52 files changed, 5367 insertions(+) create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/AllowlistScraper.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/AllowlistScraper.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/BlockManager.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/BlockManager.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/Common/NSString+IPAddress.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/Common/NSString+IPAddress.m create mode 100755 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlocker.h create mode 100755 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlocker.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlockerSet.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlockerSet.m create mode 100755 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostImporter.h create mode 100755 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostImporter.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/PacketFilter.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/PacketFilter.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/SCBlockEntry.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/SCBlockEntry.m create mode 100755 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/ThunderbirdPreferenceParser.h create mode 100755 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/ThunderbirdPreferenceParser.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/DeprecationSilencers.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCErr.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCErr.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCFileWatcher.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCFileWatcher.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCSettings.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCSettings.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCAuthorization.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCAuthorization.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCClient.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCClient.m create mode 100755 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/LaunchctlHelper.h create mode 100755 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/LaunchctlHelper.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCBlockUtilities.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCBlockUtilities.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCConstants.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCConstants.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCHelperToolUtilities.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCHelperToolUtilities.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCMiscUtilities.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCMiscUtilities.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/DaemonMain.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonBlockMethods.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonBlockMethods.m create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonProtocol.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.h create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.m create mode 100755 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon_Prefix.pch create mode 100644 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/org.eyebeam.selfcontrold.plist create mode 100755 Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/selfcontrold-Info.plist diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/AllowlistScraper.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/AllowlistScraper.h new file mode 100644 index 00000000..179dda9e --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/AllowlistScraper.h @@ -0,0 +1,17 @@ +// +// AllowlistScraper.h +// SelfControl +// +// Created by Charles Stigler on 10/7/14. +// +// + +#import + +@class SCBlockEntry; + +@interface AllowlistScraper : NSObject + ++ (NSSet*)relatedBlockEntries:(NSString*)domain; + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/AllowlistScraper.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/AllowlistScraper.m new file mode 100644 index 00000000..de35de52 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/AllowlistScraper.m @@ -0,0 +1,73 @@ +// +// AllowlistScraper.m +// SelfControl +// +// Created by Charles Stigler on 10/7/14. +// +// + +#import "AllowlistScraper.h" +#import "SCBlockEntry.h" + +@implementation AllowlistScraper + ++ (NSSet*)relatedBlockEntries:(NSString*)domain; { + NSURL* rootURL = [NSURL URLWithString: [NSString stringWithFormat: @"http://%@", domain]]; + if (!rootURL) { + return nil; + } + + // stale data is OK + NSURLRequest* request = [NSURLRequest requestWithURL: rootURL cachePolicy: NSURLRequestReturnCacheDataElseLoad timeoutInterval: 5]; + NSURLResponse* response = nil; + NSError* error = nil; + NSData* data = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error]; + if (!response) { + return nil; + } + NSString* html = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; + if (!html) { + return nil; + } + + // these sites are often featured in "share links" on a wide variety of websites + // we really don't want to add them to the allowlist, since they're also + // super distracting. So explicitly flag them to skip in this process + NSArray* neverAddSites = @[ + @"instagram.com", + @"www.instagram.com", + @"twitter.com", + @"www.twitter.com", + @"facebook.com", + @"www.facebook.com", + @"reddit.com", + @"www.reddit.com", + @"youtube.com", + @"www.youtube.com", + @"pinterest.com", + @"plus.google.com", + @"www.pinterest.com", + @"linkedin.com", + @"www.linkedin.com", + @"tumblr.com", + @"www.tumblr.com" + ]; + + NSDataDetector* dataDetector = [[NSDataDetector alloc] initWithTypes: NSTextCheckingTypeLink error: nil]; + NSCountedSet* relatedEntries = [NSCountedSet set]; + [dataDetector enumerateMatchesInString: html + options: kNilOptions + range: NSMakeRange(0, [html length]) + usingBlock:^(NSTextCheckingResult* result, NSMatchingFlags flags, BOOL* stop) { + if (([result.URL.scheme isEqualToString: @"http"] || [result.URL.scheme isEqualToString: @"https"]) + && [result.URL.host length] + && ![result.URL.host isEqualToString: rootURL.host] + && ![neverAddSites containsObject: result.URL.host]) { + [relatedEntries addObject: [SCBlockEntry entryWithHostname: result.URL.host]]; + } + }]; + + return relatedEntries; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/BlockManager.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/BlockManager.h new file mode 100644 index 00000000..3aa31d2d --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/BlockManager.h @@ -0,0 +1,62 @@ +// +// BlockManager.h +// SelfControl +// +// Created by Charlie Stigler on 2/5/13. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import +#import "PacketFilter.h" +#import "NSString+IPAddress.h" + +@class SCBlockEntry; +@class HostFileBlockerSet; + +@interface BlockManager : NSObject { + NSOperationQueue* opQueue; + PacketFilter* pf; + HostFileBlockerSet* hostBlockerSet; + BOOL hostsBlockingEnabled; + BOOL isAllowlist; + BOOL allowLocal; + BOOL includeCommonSubdomains; + BOOL includeLinkedDomains; + NSMutableSet* addedBlockEntries; +} + +- (BlockManager*)initAsAllowlist:(BOOL)allowlist; +- (BlockManager*)initAsAllowlist:(BOOL)allowlist allowLocal:(BOOL)local; +- (BlockManager*)initAsAllowlist:(BOOL)allowlist allowLocal:(BOOL)local includeCommonSubdomains:(BOOL)blockCommon; +- (BlockManager*)initAsAllowlist:(BOOL)allowlist allowLocal:(BOOL)local includeCommonSubdomains:(BOOL)blockCommon includeLinkedDomains:(BOOL)includeLinked; + +- (void)enterAppendMode; +- (void)finishAppending; +- (void)prepareToAddBlock; +- (void)finalizeBlock; +- (void)addBlockEntryFromString:(NSString*)entry; +- (void)addBlockEntry:(SCBlockEntry*)entry; +- (void)addBlockEntriesFromStrings:(NSArray*)blockList; +- (BOOL)clearBlock; +- (BOOL)forceClearBlock; +- (BOOL)blockIsActive; + +- (NSArray*)commonSubdomainsForHostName:(NSString*)hostName; ++ (NSArray*)ipAddressesForDomainName:(NSString*)domainName; +- (BOOL)domainIsGoogle:(NSString*)domainName; + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/BlockManager.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/BlockManager.m new file mode 100644 index 00000000..d7989f51 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/BlockManager.m @@ -0,0 +1,531 @@ +// +// BlockManager.m +// SelfControl +// +// Created by Charles Stigler on 2/5/13. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import "BlockManager.h" +#import "AllowlistScraper.h" +#import "SCBlockEntry.h" +#include +#include +#import "HostFileBlockerSet.h" + +@implementation BlockManager + +BOOL appendMode = NO; + +- (BlockManager*)init { + return [self initAsAllowlist: NO allowLocal: YES includeCommonSubdomains: YES]; +} + +- (BlockManager*)initAsAllowlist:(BOOL)allowlist { + return [self initAsAllowlist: allowlist allowLocal: YES includeCommonSubdomains: YES]; +} + +- (BlockManager*)initAsAllowlist:(BOOL)allowlist allowLocal:(BOOL)local { + return [self initAsAllowlist: allowlist allowLocal: local includeCommonSubdomains: YES]; +} +- (BlockManager*)initAsAllowlist:(BOOL)allowlist allowLocal:(BOOL)local includeCommonSubdomains:(BOOL)blockCommon { + return [self initAsAllowlist: allowlist allowLocal: local includeCommonSubdomains: blockCommon includeLinkedDomains: YES]; +} + +- (BlockManager*)initAsAllowlist:(BOOL)allowlist allowLocal:(BOOL)local includeCommonSubdomains:(BOOL)blockCommon includeLinkedDomains:(BOOL)includeLinked { + if(self = [super init]) { + opQueue = [[NSOperationQueue alloc] init]; + [opQueue setMaxConcurrentOperationCount: 35]; + + pf = [[PacketFilter alloc] initAsAllowlist: allowlist]; + hostBlockerSet = [[HostFileBlockerSet alloc] init]; + hostsBlockingEnabled = NO; + + isAllowlist = allowlist; + allowLocal = local; + includeCommonSubdomains = blockCommon; + includeLinkedDomains = includeLinked; + addedBlockEntries = [NSMutableSet set]; + } + + return self; +} + +- (void)prepareToAddBlock { + for (HostFileBlocker* blocker in hostBlockerSet.blockers) { + if([blocker containsSelfControlBlock]) { + [blocker removeSelfControlBlock]; + [blocker writeNewFileContents]; + } + } + + if(!isAllowlist && ![hostBlockerSet.defaultBlocker containsSelfControlBlock]) { + [hostBlockerSet createBackupHostsFile]; + [hostBlockerSet addSelfControlBlockHeader]; + hostsBlockingEnabled = YES; + } else { + hostsBlockingEnabled = NO; + } +} + +- (void)enterAppendMode { + if (isAllowlist) { + NSLog(@"ERROR: can't append to allowlist block"); + return; + } + if(![hostBlockerSet.defaultBlocker containsSelfControlBlock]) { + NSLog(@"ERROR: can't append to hosts block that doesn't yet exist"); + return; + } + + hostsBlockingEnabled = YES; + appendMode = YES; + [pf enterAppendMode]; +} +- (void)finishAppending { + NSLog(@"BlockManager: About to run operation queue for appending..."); + NSDate* startedRunning = [NSDate date]; + [opQueue waitUntilAllOperationsAreFinished]; + NSDate* finishedRunning = [NSDate date]; + NSTimeInterval runTime = [finishedRunning timeIntervalSinceDate: startedRunning]; + NSLog(@"BlockManager: Operation queue ran in %f seconds!", runTime); + + [hostBlockerSet writeNewFileContents]; + [pf finishAppending]; + [pf refreshPFRules]; + appendMode = NO; +} + +- (void)finalizeBlock { + NSLog(@"BlockManager: About to run operation queue..."); + NSDate* startedRunning = [NSDate date]; + [opQueue waitUntilAllOperationsAreFinished]; + NSDate* finishedRunning = [NSDate date]; + NSTimeInterval runTime = [finishedRunning timeIntervalSinceDate: startedRunning]; + NSLog(@"BlockManager: Operation queue ran in %f seconds!", runTime); + + if(hostsBlockingEnabled) { + [hostBlockerSet addSelfControlBlockFooter]; + [hostBlockerSet writeNewFileContents]; + } + + [pf startBlock]; +} + +- (void)enqueueBlockEntry:(SCBlockEntry*)entry { + NSBlockOperation* op = [NSBlockOperation blockOperationWithBlock:^{ + [self addBlockEntry: entry]; + }]; + [opQueue addOperation: op]; +} + +- (void)addBlockEntry:(SCBlockEntry*)entry { + // nil entries = something didn't parse right + if (entry == nil) return; + + // NSMutableSet is NOT thread-safe + @synchronized (addedBlockEntries) { + // don't try to block the same thing twice + if ([addedBlockEntries containsObject: entry]) { + return; + } + [addedBlockEntries addObject: entry]; + } + + BOOL isIP = [entry.hostname isValidIPAddress]; + BOOL isIPv4 = [entry.hostname isValidIPv4Address]; + + if([entry.hostname isEqualToString: @"*"]) { + [pf addRuleWithIP: nil port: entry.port maskLen: 0]; + } else if(isIPv4) { // current we do NOT do ipfw blocking for IPv6 + [pf addRuleWithIP: entry.hostname port: entry.port maskLen: entry.maskLen]; + } else if(!isIP) { // domain name + // Google requires special handling + if ([self domainIsGoogle: entry.hostname]) { + if (isAllowlist) { + // just add the whole Google IP range, it's way too error-prone to do an allowlist block of Google any other way + // last updated: 9/23/21 from https://www.gstatic.com/ipranges/goog.json + [self addGoogleIPsToPF]; + } + // for blocklist blocks, just skip blocking Google by IP + // because we'd end up blocking more than the user wants (i.e. Search/Mail) + // rely on the domain-level blocking instead + } else { + // non-Google domains just get looked up and blocked by IP + NSArray* addresses = [BlockManager ipAddressesForDomainName: entry.hostname]; + + for(NSUInteger i = 0; i < [addresses count]; i++) { + NSString* ip = addresses[i]; + + [pf addRuleWithIP: ip port: entry.port maskLen: entry.maskLen]; + } + } + } + + if(hostsBlockingEnabled && ![entry.hostname isEqualToString: @"*"] && !entry.port && !isIP) { + if (appendMode) { + [hostBlockerSet appendExistingBlockWithRuleForDomain: entry.hostname]; + } else { + [hostBlockerSet addRuleBlockingDomain: entry.hostname]; + } + } +} + +- (void)addBlockEntryFromString:(NSString*)entryString { + SCBlockEntry* entry = [SCBlockEntry entryFromString: entryString]; + + // nil means that we don't have anything valid to block in this entry + if (entry == nil) return; + + // enqueue new entries _before_ running this one, so they can happen in parallel + NSArray* relatedEntries = [self relatedBlockEntriesForEntry: entry]; + for (SCBlockEntry* relatedEntry in relatedEntries) { + [self enqueueBlockEntry: relatedEntry]; + } + + [self addBlockEntry: entry]; +} + +- (void)addBlockEntriesFromStrings:(NSArray*)blockList { + for(NSUInteger i = 0; i < [blockList count]; i++) { + NSBlockOperation* op = [NSBlockOperation blockOperationWithBlock:^{ + [self addBlockEntryFromString: blockList[i]]; + }]; + [opQueue addOperation: op]; + } +} + +- (BOOL)clearBlock { + [pf stopBlock: false]; + BOOL pfSuccess = ![pf containsSelfControlBlock]; + + [hostBlockerSet removeSelfControlBlock]; + BOOL hostSuccess = [hostBlockerSet writeNewFileContents]; + // Revert the host file blocker's file contents to disk so we can check + // whether or not it still contains the block (aka we messed up). + [hostBlockerSet revertFileContentsToDisk]; + hostSuccess = hostSuccess && ![hostBlockerSet containsSelfControlBlock]; + + BOOL clearedSuccessfully = hostSuccess && pfSuccess; + + if(clearedSuccessfully) + NSLog(@"INFO: Block successfully cleared."); + else { + if (!pfSuccess) { + NSLog(@"WARNING: Error clearing pf block. Tring to clear using force."); + [pf stopBlock: true]; + } + if (!hostSuccess) { + NSLog(@"WARNING: Error removing hostfile block. Attempting to restore host file backup."); + [hostBlockerSet restoreBackupHostsFile]; + } + + clearedSuccessfully = ![self blockIsActive]; + + if ([hostBlockerSet.defaultBlocker containsSelfControlBlock]) { + NSLog(@"ERROR: Host file backup could not be restored. This may result in a permanent block."); + } + if ([pf containsSelfControlBlock]) { + NSLog(@"ERROR: Firewall rules could not be cleared. This may result in a permanent block."); + } + if (clearedSuccessfully) { + NSLog(@"INFO: Firewall rules successfully cleared."); + } + } + + [hostBlockerSet deleteBackupHostsFile]; + + return clearedSuccessfully; +} + +- (BOOL)forceClearBlock { + [pf stopBlock: YES]; + BOOL pfSuccess = ![pf containsSelfControlBlock]; + + [hostBlockerSet removeSelfControlBlock]; + BOOL hostSuccess = [hostBlockerSet writeNewFileContents]; + // Revert the host file blocker's file contents to disk so we can check + // whether or not it still contains the block (aka we messed up). + [hostBlockerSet revertFileContentsToDisk]; + hostSuccess = hostSuccess && ![hostBlockerSet containsSelfControlBlock]; + + BOOL clearedSuccessfully = hostSuccess && pfSuccess; + + if(clearedSuccessfully) + NSLog(@"INFO: Block successfully cleared."); + else { + if (!pfSuccess) { + NSLog(@"ERROR: Error clearing pf block. This may result in a permanent block."); + } + if (!hostSuccess) { + NSLog(@"WARNING: Error removing hostfile block. Attempting to restore host file backup."); + [hostBlockerSet restoreBackupHostsFile]; + } + + clearedSuccessfully = ![self blockIsActive]; + + if ([hostBlockerSet.defaultBlocker containsSelfControlBlock]) { + NSLog(@"ERROR: Host file backup could not be restored. This may result in a permanent block."); + } + if (clearedSuccessfully) { + NSLog(@"INFO: Firewall rules successfully cleared."); + } + } + + return clearedSuccessfully; +} + +- (BOOL)blockIsActive { + return [hostBlockerSet.defaultBlocker containsSelfControlBlock] || [pf containsSelfControlBlock]; +} + +- (NSArray*)commonSubdomainsForHostName:(NSString*)hostName { + NSMutableSet* newHosts = [NSMutableSet set]; + + // If the domain ends in facebook.com... Special case for Facebook because + // users will often forget to block some of its many mirror subdomains that resolve + // to different IPs, i.e. hs.facebook.com. Thanks to Danielle for raising this issue. + if([hostName hasSuffix: @"facebook.com"]) { + // pulled list of facebook IP ranges from https://developers.facebook.com/docs/sharing/webmasters/crawler + // TODO: pull these automatically by running: + // whois -h whois.radb.net -- '-i origin AS32934' | grep ^route + // (looks like they now use 2 different AS numbers: https://www.facebook.com/peering/) + NSArray* facebookIPs = @[@"31.13.24.0/21", + @"31.13.64.0/18", + @"45.64.40.0/22", + @"66.220.144.0/20", + @"69.63.176.0/20", + @"69.171.224.0/19", + @"74.119.76.0/22", + @"102.132.96.0/20", + @"103.4.96.0/22", + @"129.134.0.0/16", + @"147.75.208.0/20", + @"157.240.0.0/16", + @"173.252.64.0/18", + @"179.60.192.0/22", + @"185.60.216.0/22", + @"185.89.216.0/22", + @"199.201.64.0/22", + @"204.15.20.0/22"]; + + [newHosts addObjectsFromArray: facebookIPs]; + } + if ([hostName hasSuffix: @"twitter.com"]) { + [newHosts addObject: @"api.twitter.com"]; + } + + if ([hostName hasSuffix: @"netflix.com"]) { + [newHosts addObject: @"assets.nflxext.com"]; + [newHosts addObject: @"codex.nflxext.com"]; + [newHosts addObject: @"nflxext.com"]; + } + + // Block the domain with no subdomains, if www.domain is blocked + if([hostName rangeOfString: @"www."].location == 0) { + [newHosts addObject: [hostName substringFromIndex: 4]]; + } else { // Or block www.domain otherwise + [newHosts addObject: [@"www." stringByAppendingString: hostName]]; + } + + return [newHosts allObjects]; +} + +// by Jakob Egger, taken from: https://eggerapps.at/blog/2014/hostname-lookups.html ++ (NSString*)stringForAddress:(NSData*)addressData error:(NSError**)outError { + char hbuf[NI_MAXHOST]; + int gai_error = getnameinfo(addressData.bytes, (socklen_t)addressData.length, hbuf, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + if (gai_error) { + if (outError) *outError = [NSError errorWithDomain:@"MyDomain" code:gai_error userInfo:@{NSLocalizedDescriptionKey:@(gai_strerror(gai_error))}]; + return nil; + } + return [NSString stringWithUTF8String:hbuf]; +} ++ (NSArray*)ipAddressesForDomainName:(NSString*)domainName { + if(domainName == nil) return @[]; + + NSDate* startedResolving = [NSDate date]; + CFHostRef cfHost = CFHostCreateWithName(kCFAllocatorDefault, (__bridge CFStringRef)domainName); + CFStreamError streamErr; + // TODO: call CFHostsScheduleWithRunLoop to put this on a background thread, so we can cancel/timeout early + CFHostStartInfoResolution(cfHost, kCFHostAddresses, &streamErr); + if (streamErr.error) { + NSLog(@"BlockManager: Warning: failed to resolve addresses for %@ with stream error", domainName); + CFRelease(cfHost); + return @[]; + } + + NSArray* addresses = (__bridge NSArray*)CFHostGetAddressing(cfHost, NULL); + + NSMutableArray* stringAddresses = [NSMutableArray array]; + if (addresses != NULL) { + for (NSData* addrData in addresses) { + NSError* parseErr; + NSString* ipStr = [BlockManager stringForAddress: addrData error: &parseErr]; + if (ipStr) { + [stringAddresses addObject: ipStr]; + } else { + NSLog(@"BlockManager: Warning: Failed to parse IP struct for domain %@ with error %@", domainName, parseErr); + } + } + } else { + NSLog(@"BlockManager: Warning: failed to resolve addresses for %@", domainName); + } + + // log slow resolutions + NSDate* finishedResolving = [NSDate date]; + NSTimeInterval resolutionTime = [finishedResolving timeIntervalSinceDate: startedResolving]; + if (resolutionTime > 2.5) { + NSLog(@"BlockManager: Warning: took %f seconds to resolve %@", resolutionTime, domainName); + } + + CFRelease(cfHost); + + return stringAddresses; +} + ++ (NSPredicate*)googleTesterPredicate { + static NSPredicate* pred = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString* googleRegex = @"^([a-z0-9]+\\.)*(google|youtube|picasa|sketchup|blogger|blogspot)\\.([a-z]{1,3})(\\.[a-z]{1,3})?$"; + pred = [NSPredicate + predicateWithFormat: @"SELF MATCHES %@", + googleRegex + ]; + }); + + return pred; +} +- (BOOL)domainIsGoogle:(NSString*)domainName { + return [[BlockManager googleTesterPredicate] evaluateWithObject: domainName]; +} + +- (NSArray*)relatedBlockEntriesForEntry:(SCBlockEntry*)entry { + // nil means that we don't have anything valid to block in this entry, therefore no related entries either + if (entry == nil) return @[]; + + NSMutableArray* relatedEntries = [NSMutableArray array]; + + if (isAllowlist && includeLinkedDomains && ![entry.hostname isValidIPAddress]) { + NSDate* startedScraping = [NSDate date]; + NSArray* scrapedEntries = [[AllowlistScraper relatedBlockEntries: entry.hostname] allObjects]; + NSDate* finishedScraping = [NSDate date]; + NSTimeInterval resolutionTime = [finishedScraping timeIntervalSinceDate: startedScraping]; + if (resolutionTime > 5.0) { + NSLog(@"BlockManager: Warning: allowlist scraper took %f seconds on %@", resolutionTime, entry.hostname); + } + [relatedEntries addObjectsFromArray: scrapedEntries]; + } + + if(![entry.hostname isValidIPAddress] && includeCommonSubdomains) { + NSArray* commonSubdomains = [self commonSubdomainsForHostName: entry.hostname]; + + for (NSString* subdomain in commonSubdomains) { + // we do not pull port, we leave the port number the same as we got it + SCBlockEntry* subdomainEntry = [SCBlockEntry entryFromString: subdomain]; + + if (subdomainEntry == nil) continue; + + [relatedEntries addObject: subdomainEntry]; + } + } + + return relatedEntries; +} + +- (void)addGoogleIPsToPF { + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"8.34.208.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"8.35.192.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"23.236.48.0" port: 0 maskLen: 240]; + [pf addRuleWithIP: @"23.251.128.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"34.64.0.0" port: 0 maskLen: 10]; + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"8.8.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"34.128.0.0" port: 0 maskLen: 10]; + [pf addRuleWithIP: @"35.184.0.0" port: 0 maskLen: 13]; + [pf addRuleWithIP: @"35.192.0.0" port: 0 maskLen: 14]; + [pf addRuleWithIP: @"35.196.0.0" port: 0 maskLen: 15]; + [pf addRuleWithIP: @"35.198.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"35.199.0.0" port: 0 maskLen: 17]; + [pf addRuleWithIP: @"35.199.128.0" port: 0 maskLen: 18]; + [pf addRuleWithIP: @"35.200.0.0" port: 0 maskLen: 13]; + [pf addRuleWithIP: @"35.208.0.0" port: 0 maskLen: 12]; + [pf addRuleWithIP: @"35.224.0.0" port: 0 maskLen: 12]; + [pf addRuleWithIP: @"35.240.0.0" port: 0 maskLen: 13]; + [pf addRuleWithIP: @"64.15.112.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"64.233.160.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"66.102.0.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"66.249.64.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"70.32.128.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"72.14.192.0" port: 0 maskLen: 18]; + [pf addRuleWithIP: @"74.114.24.0" port: 0 maskLen: 21]; + [pf addRuleWithIP: @"74.125.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"104.154.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"104.196.0.0" port: 0 maskLen: 14]; + [pf addRuleWithIP: @"104.237.160.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"107.167.160.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"107.178.192.0" port: 0 maskLen: 18]; + [pf addRuleWithIP: @"108.59.80.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"108.170.192.0" port: 0 maskLen: 18]; + [pf addRuleWithIP: @"108.177.0.0" port: 0 maskLen: 17]; + [pf addRuleWithIP: @"130.211.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"136.112.0.0" port: 0 maskLen: 12]; + [pf addRuleWithIP: @"142.250.0.0" port: 0 maskLen: 15]; + [pf addRuleWithIP: @"146.148.0.0" port: 0 maskLen: 17]; + [pf addRuleWithIP: @"162.216.148.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"162.222.176.0" port: 0 maskLen: 21]; + [pf addRuleWithIP: @"172.110.32.0" port: 0 maskLen: 21]; + [pf addRuleWithIP: @"172.217.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"172.253.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"173.194.0.0" port: 0 maskLen: 16]; + [pf addRuleWithIP: @"173.255.112.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"192.158.28.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"192.178.0.0" port: 0 maskLen: 15]; + [pf addRuleWithIP: @"193.186.4.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"199.36.154.0" port: 0 maskLen: 23]; + [pf addRuleWithIP: @"199.36.156.0" port: 0 maskLen: 24]; + [pf addRuleWithIP: @"199.192.112.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"199.223.232.0" port: 0 maskLen: 21]; + [pf addRuleWithIP: @"207.223.160.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"208.65.152.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"208.68.108.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"208.81.188.0" port: 0 maskLen: 22]; + [pf addRuleWithIP: @"208.117.224.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"209.85.128.0" port: 0 maskLen: 17]; + [pf addRuleWithIP: @"216.58.192.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"216.73.80.0" port: 0 maskLen: 20]; + [pf addRuleWithIP: @"216.239.32.0" port: 0 maskLen: 19]; + [pf addRuleWithIP: @"2001:4860::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2404:6800::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2404:f340::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2600:1900::" port: 0 maskLen: 28]; + [pf addRuleWithIP: @"2606:73c0::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2607:f8b0::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2620:11a:a000::" port: 0 maskLen: 40]; + [pf addRuleWithIP: @"2620:120:e000::" port: 0 maskLen: 40]; + [pf addRuleWithIP: @"2800:3f0::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2a00:1450::" port: 0 maskLen: 32]; + [pf addRuleWithIP: @"2c0f:fb50::" port: 0 maskLen: 32]; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/Common/NSString+IPAddress.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/Common/NSString+IPAddress.h new file mode 100644 index 00000000..36c52360 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/Common/NSString+IPAddress.h @@ -0,0 +1,33 @@ +// +// NSString+IPAddress.h +// SelfControl +// +// Created by Charlie Stigler on 2/5/13. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import +#include + +// methods to check whether the string is a valid IP address +@interface NSString (IPAddress) + +@property (nonatomic, getter=isValidIPv4Address, readonly) BOOL validIPv4Address; +@property (nonatomic, getter=isValidIPv6Address, readonly) BOOL validIPv6Address; +@property (nonatomic, getter=isValidIPAddress, readonly) BOOL validIPAddress; + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/Common/NSString+IPAddress.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/Common/NSString+IPAddress.m new file mode 100644 index 00000000..5b4b6aec --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/Common/NSString+IPAddress.m @@ -0,0 +1,49 @@ +// +// NSString+IPAddress.m +// SelfControl +// +// Created by Charlie Stigler on 2/5/13. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import "NSString+IPAddress.h" + +@implementation NSString (IPAddress) + +// These methods adapted from code posted by Evan Schoenberg to Stack Overflow +// and licensed under the Attribution-ShareAlike 3.0 Unported license +// http://stackoverflow.com/questions/1679152/how-to-validate-an-ip-address-with-regular-expression-in-objective-c + +- (BOOL)isValidIPv4Address { + struct in_addr throwaway; + int success = inet_pton(AF_INET, [self UTF8String], &throwaway); + + return (success == 1); +} + +- (BOOL)isValidIPv6Address { + struct in6_addr throwaway; + int success = inet_pton(AF_INET6, [self UTF8String], &throwaway); + + return (success == 1); +} + +- (BOOL)isValidIPAddress { + return ([self isValidIPv4Address] || [self isValidIPv6Address]); +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlocker.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlocker.h new file mode 100755 index 00000000..eb47b3eb --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlocker.h @@ -0,0 +1,64 @@ +// +// HostFileBlocker.h +// SelfControl +// +// Created by Charlie Stigler on 4/28/09. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import + +@protocol HostFileBlocker + +- (BOOL)deleteBackupHostsFile; + +- (void)revertFileContentsToDisk; + +- (BOOL)writeNewFileContents; + +- (void)addSelfControlBlockHeader; + +- (void)addSelfControlBlockFooter; + +- (BOOL)createBackupHostsFile; + +- (BOOL)restoreBackupHostsFile; + +- (void)addRuleBlockingDomain:(NSString*)domainName; +- (void)appendExistingBlockWithRuleForDomain:(NSString*)domainName; + +- (BOOL)containsSelfControlBlock; + +- (void)removeSelfControlBlock; + +@end + + +@interface HostFileBlocker : NSObject { + NSString* hostFilePath; + + NSLock* strLock; + NSMutableString* newFileContents; + NSStringEncoding stringEnc; + NSFileManager* fileMan; +} + +- (instancetype)initWithPath:(NSString*)path; + ++ (BOOL)blockFoundInHostsFile; + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlocker.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlocker.m new file mode 100755 index 00000000..d96f71a7 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlocker.m @@ -0,0 +1,234 @@ +// +// HostFileBlocker.m +// SelfControl +// +// Created by Charlie Stigler on 4/28/09. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import "HostFileBlocker.h" + +NSString* const kHostFileBlockerPath = @"/etc/hosts"; +NSString* const kHostFileBlockerSelfControlHeader = @"# BEGIN SELFCONTROL BLOCK"; +NSString* const kHostFileBlockerSelfControlFooter = @"# END SELFCONTROL BLOCK"; +NSString* const kDefaultHostsFileContents = @"##\n" +"# Host Database\n" +"#\n" +"# localhost is used to configure the loopback interface\n" +"# when the system is booting. Do not change this entry.\n" +"##\n" +"127.0.0.1 localhost\n" +"255.255.255.255 broadcasthost\n" +"::1 localhost\n" +"fe80::1%lo0 localhost\n\n"; + +@implementation HostFileBlocker + +- (instancetype)init { + return [self initWithPath: kHostFileBlockerPath]; +} +- (instancetype)initWithPath:(NSString*)path { + if(self = [super init]) { + hostFilePath = path; + fileMan = [[NSFileManager alloc] init]; + strLock = [[NSLock alloc] init]; + newFileContents = [NSMutableString stringWithContentsOfFile: hostFilePath usedEncoding: &stringEnc error: NULL]; + if(!newFileContents) { + // if we lost our hosts file, replace it with the OS X default + newFileContents = [NSMutableString stringWithString: kDefaultHostsFileContents]; + } + } + + return self; +} + ++ (BOOL)blockFoundInHostsFile { + // last try if we can't find a block anywhere: check the host file, and see if a block is in there + NSString* hostFileContents = [NSString stringWithContentsOfFile: kHostFileBlockerPath encoding: NSUTF8StringEncoding error: NULL]; + if(hostFileContents != nil && [hostFileContents rangeOfString: kHostFileBlockerSelfControlHeader].location != NSNotFound) { + return YES; + } + + return NO; +} + +- (void)revertFileContentsToDisk { + [strLock lock]; + + newFileContents = [NSMutableString stringWithContentsOfFile: hostFilePath usedEncoding: &stringEnc error: NULL]; + if(!newFileContents) { + newFileContents = [NSMutableString stringWithString: kDefaultHostsFileContents]; + } + + [strLock unlock]; +} + +- (BOOL)writeNewFileContents { + [strLock lock]; + + BOOL ret = [newFileContents writeToFile: hostFilePath atomically: YES encoding: stringEnc error: NULL]; + + [strLock unlock]; + return ret; +} + +- (NSString*)backupHostFilePath { + return [hostFilePath stringByAppendingPathExtension: @"bak"]; +} + +- (BOOL)createBackupHostsFile { + [self deleteBackupHostsFile]; + + if (![fileMan fileExistsAtPath: hostFilePath]) { + [kDefaultHostsFileContents writeToFile: hostFilePath atomically:true encoding: NSUTF8StringEncoding error: NULL]; + } + + if(![fileMan isReadableFileAtPath: hostFilePath] || [fileMan fileExistsAtPath: [self backupHostFilePath]]) { + return NO; + } + + return [fileMan copyItemAtPath: hostFilePath toPath: [self backupHostFilePath] error: nil]; +} + +- (BOOL)deleteBackupHostsFile { + if(![fileMan isDeletableFileAtPath: [self backupHostFilePath]]) + return NO; + + return [fileMan removeItemAtPath: [self backupHostFilePath] error: nil]; +} + +- (BOOL)restoreBackupHostsFile { + NSString* backupPath = [self backupHostFilePath]; + + if(![fileMan removeItemAtPath: hostFilePath error: nil]) + return NO; + if(![fileMan isReadableFileAtPath: backupPath] || ![fileMan moveItemAtPath: backupPath toPath: hostFilePath error: nil]) + return NO; + + return YES; +} + +- (void)addSelfControlBlockHeader { + [strLock lock]; + [newFileContents appendString: @"\n"]; + [newFileContents appendString: kHostFileBlockerSelfControlHeader]; + [newFileContents appendString: @"\n"]; + [strLock unlock]; +} + +- (void)addSelfControlBlockFooter { + [strLock lock]; + [newFileContents appendString: kHostFileBlockerSelfControlFooter]; + [newFileContents appendString: @"\n"]; + [strLock unlock]; +} + +- (NSArray*)ruleStringsToBlockDomain:(NSString*)domainName { + return @[ + [NSString stringWithFormat: @"0.0.0.0\t%@\n", domainName], + [NSString stringWithFormat: @"::\t%@\n", domainName] + ]; +} + +- (void)addRuleBlockingDomain:(NSString*)domainName { + [strLock lock]; + NSArray* ruleStrings = [self ruleStringsToBlockDomain: domainName]; + for (NSString* ruleString in ruleStrings) { + [newFileContents appendString: ruleString]; + } + [strLock unlock]; +} + +- (void)appendExistingBlockWithRuleForDomain:(NSString*)domainName { + [strLock lock]; + NSRange footerLocation = [newFileContents rangeOfString: kHostFileBlockerSelfControlFooter]; + if (footerLocation.location == NSNotFound) { + // we can't append if a block isn't in the file already! + NSLog(@"WARNING: can't append to host block because footer can't be found"); + } else { + // combine the rule strings and insert em all at once to make the math easier + NSArray* ruleStrings = [self ruleStringsToBlockDomain: domainName]; + + NSMutableString* combinedRuleString = [NSMutableString string]; + for (NSString* ruleString in ruleStrings) { + [combinedRuleString appendString: ruleString]; + } + + [newFileContents insertString: combinedRuleString atIndex: footerLocation.location]; + } + [strLock unlock]; +} + +- (BOOL)containsSelfControlBlock { + [strLock lock]; + + BOOL ret = ([newFileContents rangeOfString: kHostFileBlockerSelfControlHeader].location != NSNotFound); + + [strLock unlock]; + return ret; +} + +- (void)removeSelfControlBlock { + if(![self containsSelfControlBlock]) + return; + + [strLock lock]; + + NSRange startRange = [newFileContents rangeOfString: kHostFileBlockerSelfControlHeader]; + NSRange endRange = [newFileContents rangeOfString: kHostFileBlockerSelfControlFooter]; + + // generate a delete range that properly removes the block from the hosts file + NSUInteger deleteRangeStart = startRange.location; + NSUInteger deleteRangeLength; + + // there are usually newlines placed before/after the header/footer + // we should remove them if possible to keep the hosts file looking tidy + // only remove the previous character if we aren't at the start of the file (or we'll crash) + if (deleteRangeStart > 0) { + unichar prevChar = [newFileContents characterAtIndex: deleteRangeStart - 1]; + // if the previous character isn't a newline, don't delete it + if ([[NSCharacterSet newlineCharacterSet] characterIsMember: prevChar]) { + deleteRangeStart--; + } + } + + NSUInteger maxDeleteLength = [newFileContents length] - deleteRangeStart; + // if we lost the block footer somehow... well, crap, just delete everything below the header + // this isn't ideal and we might bork other stuff, but it's better than leaving the block on + if (endRange.location == NSNotFound) { + deleteRangeLength = maxDeleteLength; + } else { + deleteRangeLength = MIN(maxDeleteLength, (endRange.location + endRange.length) - deleteRangeStart); + + // as above, look at removing the excess newline if possible + if (deleteRangeLength < maxDeleteLength) { + unichar nextChar = [newFileContents characterAtIndex: deleteRangeStart + deleteRangeLength]; + // if the next character isn't a newline, don't delete it + if ([[NSCharacterSet newlineCharacterSet] characterIsMember: nextChar]) { + deleteRangeLength++; + } + } + } + + NSRange deleteRange = NSMakeRange(deleteRangeStart, deleteRangeLength); + + [newFileContents deleteCharactersInRange: deleteRange]; + + [strLock unlock]; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlockerSet.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlockerSet.h new file mode 100644 index 00000000..249022d6 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlockerSet.h @@ -0,0 +1,20 @@ +// +// HostFileBlockerSet.h +// SelfControl +// +// Created by Charlie Stigler on 3/7/21. +// + +#import +#import "HostFileBlocker.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface HostFileBlockerSet : NSObject + +@property (readonly) NSArray* blockers; +@property (readonly) HostFileBlocker* defaultBlocker; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlockerSet.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlockerSet.m new file mode 100644 index 00000000..0cc61b2e --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostFileBlockerSet.m @@ -0,0 +1,121 @@ +// +// HostFileBlockerSet.m +// SelfControl +// +// Created by Charlie Stigler on 3/7/21. +// + +#import "HostFileBlockerSet.h" + +@implementation HostFileBlockerSet + +- (instancetype)init { + return [self initWithCommonFiles]; +} +- (instancetype)initWithCommonFiles { + NSFileManager* fileMan = [NSFileManager defaultManager]; + NSArray* commonBackupHostFilePaths = @[ + // Juniper Pulse + @"/etc/pulse-hosts.bak", + @"/etc/jnpr-pulse-hosts.bak", + @"/etc/pulse.hosts.bak", + @"/etc/jnpr-nc-hosts.bak", + + // Cisco AnyConnect + @"/etc/hosts.ac" + ]; + + NSMutableArray* hostFileBlockers = [NSMutableArray arrayWithCapacity: commonBackupHostFilePaths.count + 1]; + + _defaultBlocker = [HostFileBlocker new]; + [hostFileBlockers addObject: _defaultBlocker]; + + for (NSString* path in commonBackupHostFilePaths) { + if ([fileMan isReadableFileAtPath: path]) { + NSLog(@"INFO: found backup VPN host file at %@", path); + HostFileBlocker* blocker = [[HostFileBlocker alloc] initWithPath: path]; + [hostFileBlockers addObject: blocker]; + } + } + + _blockers = hostFileBlockers; + + return self; +} + +- (BOOL)deleteBackupHostsFile { + BOOL ret = YES; + for (HostFileBlocker* blocker in self.blockers) { + ret = ret && [blocker deleteBackupHostsFile]; + } + return ret; +} + +- (void)revertFileContentsToDisk { + for (HostFileBlocker* blocker in self.blockers) { + [blocker revertFileContentsToDisk]; + } +} + +- (BOOL)writeNewFileContents { + BOOL ret = YES; + for (HostFileBlocker* blocker in self.blockers) { + ret = ret && [blocker writeNewFileContents]; + } + return ret; +} + +- (void)addSelfControlBlockHeader { + for (HostFileBlocker* blocker in self.blockers) { + [blocker addSelfControlBlockHeader]; + } +} + +- (void)addSelfControlBlockFooter { + for (HostFileBlocker* blocker in self.blockers) { + [blocker addSelfControlBlockFooter]; + } +} + +- (BOOL)createBackupHostsFile { + BOOL ret = YES; + for (HostFileBlocker* blocker in self.blockers) { + ret = ret && [blocker createBackupHostsFile]; + } + return ret; +} + +- (BOOL)restoreBackupHostsFile { + BOOL ret = YES; + for (HostFileBlocker* blocker in self.blockers) { + ret = ret && [blocker restoreBackupHostsFile]; + } + return ret; +} + +- (void)addRuleBlockingDomain:(NSString*)domainName { + for (HostFileBlocker* blocker in self.blockers) { + [blocker addRuleBlockingDomain: domainName]; + } +} +- (void)appendExistingBlockWithRuleForDomain:(NSString*)domainName { + for (HostFileBlocker* blocker in self.blockers) { + [blocker appendExistingBlockWithRuleForDomain: domainName]; + } +} + +- (BOOL)containsSelfControlBlock { + BOOL ret = NO; + for (HostFileBlocker* blocker in self.blockers) { + ret = ret || [blocker containsSelfControlBlock]; + } + return ret; +} + +- (void)removeSelfControlBlock { + for (HostFileBlocker* blocker in self.blockers) { + [blocker removeSelfControlBlock]; + } +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostImporter.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostImporter.h new file mode 100755 index 00000000..fb06c44f --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostImporter.h @@ -0,0 +1,54 @@ +// +// HostImporter.h +// SelfControl +// +// Created by Charlie Stigler on 2/16/09. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#import +#import "ThunderbirdPreferenceParser.h" + +// A small class to handle getting the mail hostnames from different mail +// programs (just Mail and Thunderbird currently). +@interface HostImporter : NSObject { +} + ++ (NSArray*)commonDistractingWebsites; ++ (NSArray*)newsAndPublications; + +// Returns an autoreleased instance of NSArray containing all incoming hostnames +// imported from the user's instance of Mail.app ++ (NSArray*)incomingMailHostnamesFromMail; + +// Returns an autoreleased instance of NSArray containing all outgoing hostnames +// imported from the user's instance of Mail.app ++ (NSArray*)outgoingMailHostnamesFromMail; + ++ (NSArray*)incomingMailHostnamesFromMailMate; ++ (NSArray*)outgoingMailHostnamesFromMailMate; + +// Returns an autoreleased instance of NSArray containing all incoming hostnames +// imported from the default profile of the user's instance of Thunderbird ++ (NSArray*)incomingMailHostnamesFromThunderbird; + +// Returns an autoreleased instance of NSArray containing all outgoing hostnames +// imported from the default profile of the user's instance of Thunderbird ++ (NSArray*)outgoingMailHostnamesFromThunderbird; + +@end \ No newline at end of file diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostImporter.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostImporter.m new file mode 100755 index 00000000..43b2475d --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/HostImporter.m @@ -0,0 +1,229 @@ +// +// HostImporter.m +// SelfControl +// +// Created by Charlie Stigler on 2/16/09. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#import "HostImporter.h" + +@implementation HostImporter + ++ (NSArray*)commonDistractingWebsites { + return @[ + @"facebook.com", + @"twitter.com", + @"reddit.com", + @"tumblr.com", + @"youtube.com", + @"9gag.com", + @"netflix.com", + @"hulu.com", + @"buzzfeed.com", + @"dailymotion.com", + @"collegehumor.com", + @"funnyordie.com", + @"vine.co", + @"pinterest.com", + @"stumbleupon.com", + @"instagram.com", + ]; +} ++ (NSArray*)newsAndPublications { + return @[ + @"cnn.com", + @"huffingtonpost.com", + @"foxnews.com", + @"nytimes.com", + @"bbc.com", + @"bbc.co.uk", + @"telegraph.co.uk", + @"news.google.com", + @"buzzfeed.com", + @"vice.com", + @"gawker.com", + @"tumblr.com", + @"forbes.com", + @"gothamist.com", + @"jezebel.com", + @"usatoday.com", + @"theonion.com", + @"news.yahoo.com", + @"washingtonpost.com", + @"wsj.com", + @"theguardian.com", + @"latimes.com", + @"nydailynews.com", + @"salon.com", + @"msnbc.com", + @"rt.com", + @"bloomberg.com", + @"aol.com", + @"drudgereport.com", + @"nationalgeographic.com", + @"vice.com", + @"nypost.com", + @"chicagotribune.com", + @"msn.com", + @"usnews.com", + ]; +} + ++ (NSArray*)incomingMailHostnamesFromMail { + NSMutableArray* hostnames = [NSMutableArray arrayWithCapacity: 10]; + NSString* sandboxedPreferences = [@"~/Library/Containers/com.apple.mail/Data/Library/Preferences/com.apple.mail" stringByExpandingTildeInPath]; + NSDictionary* defaults = [[NSUserDefaults standardUserDefaults] persistentDomainForName: sandboxedPreferences]; + NSArray* incomingAccounts = defaults[@"MailAccounts"]; + for(NSUInteger i = 0; i < [incomingAccounts count]; i++) { + NSMutableString* hostname = [incomingAccounts[i][@"Hostname"] mutableCopy]; + // The LocalAccountName account has no hostname, so trying to add it would cause an error + if(hostname != nil) { + // If it has a defined port number, add it to the host to block for added + // block specificity, so only incoming mail is blocked. + if([incomingAccounts[i][@"PortNumber"] length]) { + [hostname appendString: @":"]; + [hostname appendString: incomingAccounts[i][@"PortNumber"]]; + // If it doesn't have a defined port number, we'll go through and choose + // the default port for the type of account it is. + } else if([incomingAccounts[i][@"AccountType"] isEqual: @"POPAccount"]) { + if(incomingAccounts[i][@"SSLEnabled"]) { + [hostname appendString: @":995"]; + } else { + [hostname appendString: @":110"]; + } + } else if([incomingAccounts[i][@"AccountType"] isEqual: @"IMAPAccount"]) { + if(incomingAccounts[i][@"SSLEnabled"]) { + [hostname appendString: @":993"]; + } else { + [hostname appendString: @":143"]; + } + } + [hostnames addObject: hostname]; + } + } + + return hostnames; +} + ++ (NSArray*)outgoingMailHostnamesFromMail { + NSMutableArray* hostnames = [NSMutableArray arrayWithCapacity: 10]; + NSString* sandboxedPreferences = [@"~/Library/Containers/com.apple.mail/Data/Library/Preferences/com.apple.mail" stringByExpandingTildeInPath]; + NSDictionary* defaults = [[NSUserDefaults standardUserDefaults] persistentDomainForName: sandboxedPreferences]; + NSArray* outgoingAccounts = defaults[@"DeliveryAccounts"]; + for(NSUInteger i = 0; i < [outgoingAccounts count]; i++) { + NSMutableString* hostname = [outgoingAccounts[i][@"Hostname"] mutableCopy]; + if(hostname != nil) { + if([outgoingAccounts[i][@"PortNumber"] length]) { + [hostname appendString: @":"]; + [hostname appendString: outgoingAccounts[i][@"PortNumber"]]; + } else if (outgoingAccounts[i][@"SSLEnabled"]) { + // If it doesn't have a defined port number, we'll block all the default outoging ports + [hostnames addObject: [hostname stringByAppendingString: @":465"]]; + [hostname appendString: @":587"]; + } else { + [hostname appendString: @":25"]; + } + + [hostnames addObject: hostname]; + } + } + + return hostnames; +} + ++ (NSArray*)incomingMailHostnamesFromMailMate { + NSMutableArray* hostnames = [NSMutableArray arrayWithCapacity: 10]; + + NSString* sourcesPath = [@"~/Library/Application Support/MailMate/Sources.plist" stringByExpandingTildeInPath]; + NSData* plistData = [NSData dataWithContentsOfFile: sourcesPath]; + if (!plistData) return hostnames; + + NSDictionary* prefs = [NSPropertyListSerialization propertyListWithData: plistData options: NSPropertyListImmutable format: nil error: nil]; + if (!prefs) return hostnames; + + NSArray* accounts = prefs[@"sources"]; + for(NSUInteger i = 0; i < [accounts count]; i++) { + NSURL* serverURL = [NSURL URLWithString: accounts[i][@"serverURL"]]; + if (!serverURL) continue; + + NSMutableString* hostname = [serverURL.host mutableCopy]; + if (![hostname length]) continue; + + // If it has a defined port number, add it to the host to block for added + // block specificity, so only incoming mail is blocked. + if([accounts[i][@"port"] length]) { + [hostname appendString: @":"]; + [hostname appendString: accounts[i][@"port"]]; + } else if([serverURL.scheme isEqualToString: @"imap"]) { + [hostname appendString: @":993"]; + [hostnames addObject: [hostname stringByAppendingString: @":143"]]; + } else if([serverURL.scheme isEqualToString: @"pop"]) { + [hostname appendString: @":995"]; + [hostnames addObject: [hostname stringByAppendingString: @":110"]]; + } + + [hostnames addObject: hostname]; + } + + return hostnames; +} ++ (NSArray*)outgoingMailHostnamesFromMailMate { + NSMutableArray* hostnames = [NSMutableArray arrayWithCapacity: 10]; + + NSString* submissionPath = [@"~/Library/Application Support/MailMate/Submission.plist" stringByExpandingTildeInPath]; + NSData* plistData = [NSData dataWithContentsOfFile: submissionPath]; + if (!plistData) return hostnames; + + NSDictionary* prefs = [NSPropertyListSerialization propertyListWithData: plistData options: NSPropertyListImmutable format: nil error: nil]; + if (!prefs) return hostnames; + + NSArray* smtpServers = prefs[@"smtpServers"]; + for(NSUInteger i = 0; i < [smtpServers count]; i++) { + NSURL* serverURL = [NSURL URLWithString: smtpServers[i][@"serverURL"]]; + if (!serverURL) continue; + + NSMutableString* hostname = [serverURL.host mutableCopy]; + if (![hostname length]) continue; + + // If it has a defined port number, add it to the host to block for added + // block specificity, so only incoming mail is blocked. + if([smtpServers[i][@"port"] length]) { + [hostname appendString: @":"]; + [hostname appendString: smtpServers[i][@"port"]]; + } else { + [hostname appendString: @":25"]; + [hostnames addObject: [hostname stringByAppendingString: @":587"]]; + } + + [hostnames addObject: hostname]; + } + + return hostnames; +} + + ++ (NSArray*)incomingMailHostnamesFromThunderbird { + return [ThunderbirdPreferenceParser incomingHostnames]; +} + ++ (NSArray*)outgoingMailHostnamesFromThunderbird { + return [ThunderbirdPreferenceParser outgoingHostnames]; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/PacketFilter.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/PacketFilter.h new file mode 100644 index 00000000..3b07bd6d --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/PacketFilter.h @@ -0,0 +1,33 @@ +// +// PacketFilter.h +// SelfControl +// +// Created by Charles Stigler on 6/29/14. +// +// + +#import + +@class SCBlockEntry; + +@interface PacketFilter : NSObject { + NSMutableString* rules; + BOOL isAllowlist; +} + ++ (BOOL)blockFoundInPF; + +- (PacketFilter*)initAsAllowlist: (BOOL)allowlist; +- (void)addBlockHeader:(NSMutableString*)configText; +- (void)addAllowlistFooter:(NSMutableString*)configText; +- (void)addRuleWithIP:(NSString*)ip port:(NSInteger)port maskLen:(NSInteger)maskLen; +- (void)writeConfiguration; +- (int)startBlock; +- (int)stopBlock:(BOOL)force; +- (void)addSelfControlConfig; +- (BOOL)containsSelfControlBlock; +- (void)enterAppendMode; +- (void)finishAppending; +- (int)refreshPFRules; + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/PacketFilter.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/PacketFilter.m new file mode 100644 index 00000000..6d6caa79 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/PacketFilter.m @@ -0,0 +1,267 @@ +// +// PacketFilter.m +// SelfControl +// +// Created by Charles Stigler on 6/29/14. +// +// + +#import "PacketFilter.h" + +NSString* const kPfctlExecutablePath = @"/sbin/pfctl"; +NSString* const kPFConfPath = @"/etc/pf.conf"; +NSString* const kPFAnchorCommand = @"anchor \"org.eyebeam\""; + +@implementation PacketFilter + +NSFileHandle* appendFileHandle; + ++ (BOOL)blockFoundInPF { + // last try if we can't find a block anywhere: check the host file, and see if a block is in there + NSString* pfConfContents = [NSString stringWithContentsOfFile: kPFConfPath encoding: NSUTF8StringEncoding error: NULL]; + if(pfConfContents != nil && [pfConfContents rangeOfString: kPFAnchorCommand].location != NSNotFound) { + return YES; + } + + return NO; +} + +- (PacketFilter*)initAsAllowlist: (BOOL)allowlist { + if (self = [super init]) { + isAllowlist = allowlist; + rules = [NSMutableString stringWithCapacity: 1000]; + } + return self; +} + +- (void)addBlockHeader:(NSMutableString*)configText { + [configText appendString: @"# Options\n" + "set block-policy drop\n" + "set fingerprints \"/etc/pf.os\"\n" + "set ruleset-optimization basic\n" + "set skip on lo0\n" + "\n" + "#\n" + "# org.eyebeam ruleset for SelfControl blocks\n" + "#\n"]; + + if (isAllowlist) { + [configText appendString: @"block return out proto tcp from any to any\n" + "block return out proto udp from any to any\n" + "\n"]; + } +} +- (void)addAllowlistFooter:(NSMutableString*)configText { + [configText appendString: @"pass out proto tcp from any to any port 53\n"]; + [configText appendString: @"pass out proto udp from any to any port 53\n"]; + [configText appendString: @"pass out proto udp from any to any port 123\n"]; + [configText appendString: @"pass out proto udp from any to any port 67\n"]; + [configText appendString: @"pass out proto tcp from any to any port 67\n"]; + [configText appendString: @"pass out proto udp from any to any port 68\n"]; + [configText appendString: @"pass out proto tcp from any to any port 68\n"]; + [configText appendString: @"pass out proto udp from any to any port 5353\n"]; + [configText appendString: @"pass out proto tcp from any to any port 5353\n"]; +} + +- (NSArray*)ruleStringsForIP:(NSString*)ip port:(NSInteger)port maskLen:(NSInteger)maskLen { + NSMutableString* rule = [NSMutableString stringWithString: @"from any to "]; + + if (ip) { + [rule appendString: ip]; + } else { + [rule appendString: @"any"]; + } + + if (maskLen) { + [rule appendString: [NSString stringWithFormat: @"/%ld", (long)maskLen]]; + } + + if (port) { + [rule appendString: [NSString stringWithFormat: @" port %ld", (long)port]]; + } + + if (isAllowlist) { + return @[ + [NSString stringWithFormat: @"pass out proto tcp %@\n", rule], + [NSString stringWithFormat: @"pass out proto udp %@\n", rule] + ]; + } else { + return @[ + [NSString stringWithFormat: @"block return out proto tcp %@\n", rule], + [NSString stringWithFormat: @"block return out proto udp %@\n", rule] + ]; + } +} +- (void)addRuleWithIP:(NSString*)ip port:(NSInteger)port maskLen:(NSInteger)maskLen { + @synchronized(self) { + NSArray* ruleStrings = [self ruleStringsForIP: ip port: port maskLen: maskLen]; + for (NSString* ruleString in ruleStrings) { + if (appendFileHandle) { + [appendFileHandle writeData: [ruleString dataUsingEncoding:NSUTF8StringEncoding]]; + } else { + [rules appendString: ruleString]; + } + } + } +} + +- (void)writeConfiguration { + NSMutableString* filterConfiguration = [NSMutableString stringWithCapacity: 1000]; + + [self addBlockHeader: filterConfiguration]; + [filterConfiguration appendString: rules]; + + if (isAllowlist) { + [self addAllowlistFooter: filterConfiguration]; + } + + [filterConfiguration writeToFile: @"/etc/pf.anchors/org.eyebeam" atomically: true encoding: NSUTF8StringEncoding error: nil]; +} + +- (void)enterAppendMode { + if (isAllowlist) { + NSLog(@"WARNING: Can't append rules to allowlist blocks - ignoring"); + return; + } + + // open the file and prepare to write to the very bottom (no footer since it's not an allowlist) + appendFileHandle = [NSFileHandle fileHandleForWritingAtPath: @"/etc/pf.anchors/org.eyebeam"]; + if (!appendFileHandle) { + NSLog(@"ERROR: Failed to get handle for pf.anchors file while attempting to append rules"); + return; + } + + [appendFileHandle seekToEndOfFile]; +} +- (void)finishAppending { + [appendFileHandle closeFile]; + appendFileHandle = nil; +} + +- (void)appendRulesToCurrentBlockConfiguration:(NSArray*)newEntryDicts { + if (newEntryDicts.count < 1) return; + if (isAllowlist) { + NSLog(@"WARNING: Can't append rules to allowlist blocks - ignoring"); + return; + } + + // open the file and prepare to write to the very bottom (no footer since it's not an allowlist) + // NOTE FOR FUTURE: NSFileHandle can't append lines to the middle of the file anyway, + // would need to read in the whole thing + write out again + NSFileHandle* fileHandle = [NSFileHandle fileHandleForWritingAtPath: @"/etc/pf.anchors/org.eyebeam"]; + if (!fileHandle) { + NSLog(@"ERROR: Failed to get handle for pf.anchors file while attempting to append rules"); + return; + } + + [fileHandle seekToEndOfFile]; + for (NSDictionary* entryHostInfo in newEntryDicts) { + NSString* hostName = entryHostInfo[@"hostName"]; + int portNum = [entryHostInfo[@"port"] intValue]; + int maskLen = [entryHostInfo[@"maskLen"] intValue]; + + NSArray* ruleStrings = [self ruleStringsForIP: hostName port: portNum maskLen: maskLen]; + for (NSString* ruleString in ruleStrings) { + [fileHandle writeData: [ruleString dataUsingEncoding:NSUTF8StringEncoding]]; + } + } + [fileHandle closeFile]; +} + +- (int)startBlock { + [self addSelfControlConfig]; + [self writeConfiguration]; + + NSArray* args = [@"-E -f /etc/pf.conf -F states" componentsSeparatedByString: @" "]; + + NSTask* task = [[NSTask alloc] init]; + [task setLaunchPath: kPfctlExecutablePath]; + [task setArguments: args]; + + NSPipe* inPipe = [[NSPipe alloc] init]; + NSFileHandle* readHandle = [inPipe fileHandleForReading]; + [task setStandardOutput: inPipe]; + [task setStandardError: inPipe]; + + [task launch]; + NSString* pfctlOutput = [[NSString alloc] initWithData: [readHandle readDataToEndOfFile] encoding: NSUTF8StringEncoding]; + [readHandle closeFile]; + [task waitUntilExit]; + + NSArray* lines = [pfctlOutput componentsSeparatedByString: @"\n"]; + for (NSString* line in lines) { + if ([line hasPrefix: @"Token : "]) { + [self writePFToken: [line substringFromIndex: [@"Token : " length]] error: nil]; + break; + } + } + + return [task terminationStatus]; +} +- (int)refreshPFRules { + NSArray* args = [@"-f /etc/pf.conf -F states" componentsSeparatedByString: @" "]; + + NSTask* task = [[NSTask alloc] init]; + [task setLaunchPath: kPfctlExecutablePath]; + [task setArguments: args]; + [task launch]; + [task waitUntilExit]; + + return [task terminationStatus]; +} + +- (void)writePFToken:(NSString*)token error:(NSError**)error { + [token writeToFile: @"/etc/SelfControlPFToken" atomically: YES encoding: NSUTF8StringEncoding error: error]; +} +- (NSString*)readPFToken:(NSError**)error { + return [NSString stringWithContentsOfFile: @"/etc/SelfControlPFToken" encoding: NSUTF8StringEncoding error: error]; +} + +- (int)stopBlock:(BOOL)force { + NSError* err; + NSString* token = [self readPFToken: &err]; + + [@"" writeToFile: @"/etc/pf.anchors/org.eyebeam" atomically: true encoding: NSUTF8StringEncoding error: nil]; + NSString* mainConf = [NSString stringWithContentsOfFile: @"/etc/pf.conf" encoding: NSUTF8StringEncoding error: nil]; + NSArray* lines = [mainConf componentsSeparatedByString: @"\n"]; + NSMutableString* newConf = [NSMutableString stringWithCapacity: [mainConf length]]; + for (NSString* line in lines) { + if ([line rangeOfString: @"org.eyebeam"].location == NSNotFound) { + [newConf appendFormat: @"%@\n", line]; + } + } + newConf = [[newConf stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy]; + [newConf appendString: @"\n"]; + [newConf writeToFile: @"/etc/pf.conf" atomically: true encoding: NSUTF8StringEncoding error: nil]; + + NSString* commandString; + if ([token length] && !force) { + commandString = [NSString stringWithFormat: @"-X %@ -f /etc/pf.conf", token]; + } else { + commandString = @"-d -f /etc/pf.conf"; + } + NSArray* args = [commandString componentsSeparatedByString: @" "]; + + NSTask* task = [NSTask launchedTaskWithLaunchPath: kPfctlExecutablePath arguments: args]; + [task waitUntilExit]; + return [task terminationStatus]; +} + +- (void)addSelfControlConfig { + NSMutableString* pfConf = [NSMutableString stringWithContentsOfFile: @"/etc/pf.conf" encoding: NSUTF8StringEncoding error: nil]; + + if ([pfConf rangeOfString: @"/etc/pf.anchors/org.eyebeam"].location == NSNotFound) { + [pfConf appendString: @"\n" + "anchor \"org.eyebeam\"\n" + "load anchor \"org.eyebeam\" from \"/etc/pf.anchors/org.eyebeam\"\n"]; + } + + [pfConf writeToFile: @"/etc/pf.conf" atomically: true encoding: NSUTF8StringEncoding error: nil]; +} + +- (BOOL)containsSelfControlBlock { + NSString* mainConf = [NSString stringWithContentsOfFile: @"/etc/pf.conf" encoding: NSUTF8StringEncoding error: nil]; + return mainConf != nil && [mainConf rangeOfString: @"org.eyebeam"].location != NSNotFound; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/SCBlockEntry.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/SCBlockEntry.h new file mode 100644 index 00000000..3a6afd2a --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/SCBlockEntry.h @@ -0,0 +1,26 @@ +// +// SCBlockEntry.h +// SelfControl +// +// Created by Charlie Stigler on 1/20/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SCBlockEntry : NSObject + +@property (nonatomic) NSString* hostname; +@property (nonatomic) NSInteger port; +@property (nonatomic) NSInteger maskLen; + ++ (instancetype)entryWithHostname:(NSString*)hostname; ++ (instancetype)entryWithHostname:(NSString*)hostname port:(NSInteger)port maskLen:(NSInteger)maskLen; ++ (instancetype)entryFromString:(NSString*)domainString; + +- (BOOL)isEqualToEntry:(SCBlockEntry*)otherEntry; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/SCBlockEntry.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/SCBlockEntry.m new file mode 100644 index 00000000..a7a49a97 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/SCBlockEntry.m @@ -0,0 +1,124 @@ +// +// SCBlockEntry.m +// SelfControl +// +// Created by Charlie Stigler on 1/20/21. +// + +#import "SCBlockEntry.h" + +@implementation SCBlockEntry + +- (instancetype)init { + return [self initWithHostname: nil port: 0 maskLen: 0]; +} +- (instancetype)initWithHostname:(NSString*)hostname { + return [self initWithHostname: hostname port: 0 maskLen: 0]; +} +- (instancetype)initWithHostname:(NSString*)hostname port:(NSInteger)port maskLen:(NSInteger)maskLen { + if (self = [super init]) { + _hostname = hostname; + _port = port; + _maskLen = maskLen; + } + return self; +} + ++ (instancetype)entryWithHostname:(NSString*)hostname port:(NSInteger)port maskLen:(NSInteger)maskLen { + return [[SCBlockEntry alloc] initWithHostname: hostname port: port maskLen: maskLen]; +} + ++ (instancetype)entryWithHostname:(NSString*)hostname { + return [[SCBlockEntry alloc] initWithHostname: hostname]; +} + ++ (instancetype)entryFromString:(NSString*)hostString { + // don't do anything with blank hostnames, however they got on the list... + // otherwise they could end up screwing up the block + if (![[hostString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] length]) { + return nil; + } + + NSString* hostname; + // returning 0 for either of these values means "couldn't find it in the string" + int maskLen = 0; + int port = 0; + + NSArray* splitString = [hostString componentsSeparatedByString: @"/"]; + hostname = splitString[0]; + + NSString* stringToSearchForPort = splitString[0]; + + if([splitString count] >= 2) { + maskLen = [splitString[1] intValue]; + + // we expect the port number to come after the IP/masklen + stringToSearchForPort = splitString[1]; + } + + splitString = [stringToSearchForPort componentsSeparatedByString: @":"]; + + // only if hostName wasn't already split off by the maskLen + if([stringToSearchForPort isEqualToString: hostname]) { + hostname = splitString[0]; + } + + if([splitString count] >= 2) { + port = [splitString[1] intValue]; + } + + if([hostname isEqualToString: @""]) { + hostname = @"*"; + } + + // we won't block host * (everywhere) without a port number... it's just too likely to be mistaken. + // Use a allowlist if that's what you want! + if ([hostname isEqualToString: @"*"] && !port) { + return nil; + } + + return [SCBlockEntry entryWithHostname: hostname port: port maskLen: maskLen]; +} + +- (NSString*)description { + return [NSString stringWithFormat: @"[Entry: hostname = %@, port = %ld, maskLen = %ld]", self.hostname, (long)self.port, (long)self.maskLen]; +} + +// method implementations of isEqual, isEqualToEntry, and hash are based on this answer from StackOverflow: https://stackoverflow.com/q/254281 + +- (BOOL)isEqual:(id)other { + if (other == self) + return YES; + if (!other || ![other isKindOfClass: [self class]]) + return NO; + return [self isEqualToEntry: other]; +} + +- (BOOL)isEqualToEntry:(SCBlockEntry*)otherEntry { + if (otherEntry == nil) return NO; + if (self == otherEntry) return YES; + + if ([self.hostname isEqualToString: otherEntry.hostname] && self.port == otherEntry.port && self.maskLen == otherEntry.maskLen) { + return YES; + } else { + return NO; + } +} + +- (NSUInteger)hash { + NSUInteger prime = 31; + NSUInteger result = 1; + + if (self.hostname == nil) { + result = prime * result; + } else { + result = prime * result + [self.hostname hash]; + } + + result = prime * result + (NSUInteger)self.port; + result = prime * result + (NSUInteger)self.maskLen; + + return result; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/ThunderbirdPreferenceParser.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/ThunderbirdPreferenceParser.h new file mode 100755 index 00000000..2b2f2571 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/ThunderbirdPreferenceParser.h @@ -0,0 +1,62 @@ +// +// ThunderbirdPreferenceParser.h +// SelfControl +// +// Created by Charlie Stigler on 2/17/09. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#import + +// A class designed to provide all necessary methods for parsing the incoming +// and outgoing mail hostnames from Thunderbird configuration files, and made +// to be easily extensible in case further configuration information is required. +@interface ThunderbirdPreferenceParser : NSObject { +} + +// Returns an autoreleased instance of NSString containing the full, absolute +// path to the Thunderbird support folder (usually ~/Library/Thunderbird) ++ (NSString*)pathToSupportFolder; + +// Returns YES if Thunderbird is detected as being installed (opened at least +// once, and therefore preferences initialized) on the current system, or NO +// if Thunderbird's preferences were not found. ++ (BOOL)thunderbirdIsInstalled; + +// Returns an autoreleased instance of NSString containing the full, absolute +// path to the Thunderbird default profile folder, or the profile named "default" +// if no profile folder is marked as default, or the first profile if no folder +// is named "default". Returns nil if no profiles exist. ++ (NSString*)pathToDefaultProfile; + +// Returns an autoreleased instance of NSString containing the full, absolute +// path to the Thunderbird prefs.js for the default profile (see +// PathToDefaultProfile for details on how the "default profile" is determined) ++ (NSString*)pathToPrefsJsFile; + +// Returns an autoreleased instance of NSArray containing all incoming hostnames +// for the Thunderbird default profile (see PathToDefaultProfile for details on +// how the "default profile" is determined) ++ (NSArray*)incomingHostnames; + +// Returns an autoreleased instance of NSArray containing all outgoing hostnames +// for the Thunderbird default profile (see PathToDefaultProfile for details on +// how the "default profile" is determined) ++ (NSArray*)outgoingHostnames; + +@end \ No newline at end of file diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/ThunderbirdPreferenceParser.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/ThunderbirdPreferenceParser.m new file mode 100755 index 00000000..dc016430 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Block Management/ThunderbirdPreferenceParser.m @@ -0,0 +1,207 @@ +// +// ThunderbirdPreferenceParser.m +// SelfControl +// +// Created by Charlie Stigler on 2/17/09. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + + +#import "ThunderbirdPreferenceParser.h" + +NSString* const kThunderbirdSupportFolderPath = @"~/Library/Thunderbird"; + +@implementation ThunderbirdPreferenceParser + ++ (NSString*)pathToSupportFolder { + return [kThunderbirdSupportFolderPath stringByExpandingTildeInPath]; +} + ++ (BOOL)thunderbirdIsInstalled { + NSString* profilesIniPath = [[self pathToSupportFolder] stringByAppendingPathComponent: @"profiles.ini"]; + return [[NSFileManager defaultManager] isReadableFileAtPath: profilesIniPath]; +} + ++ (NSString*)pathToDefaultProfile { + NSString* profilesIniPath = [[self pathToSupportFolder] stringByAppendingPathComponent: @"profiles.ini"]; + if(![[NSFileManager defaultManager] isReadableFileAtPath: profilesIniPath]) + return nil; + + NSString* profilesIniContents = [NSString stringWithContentsOfFile: profilesIniPath encoding: NSUTF8StringEncoding error: NULL]; + NSScanner* profilesIniScanner = [NSScanner scannerWithString: profilesIniContents]; + NSMutableArray* profiles = [NSMutableArray arrayWithCapacity: 1]; + + [profilesIniScanner scanUpToString: @"[Profile" intoString: NULL]; + [profilesIniScanner scanUpToCharactersFromSet: [NSCharacterSet newlineCharacterSet] + intoString: NULL]; + + if([profilesIniScanner isAtEnd]) + return nil; + + do { // for each profile... + NSString* subString; + NSMutableDictionary* profile = [NSMutableDictionary dictionaryWithCapacity: 4]; + [profilesIniScanner scanUpToString: @"\n\n" + intoString: &subString]; + + NSScanner* subStringScanner = [NSScanner scannerWithString: subString]; + + while(![subStringScanner isAtEnd]) { // scan for more attributes + NSString* key; + + // Read in the key and value + [subStringScanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString: @"="] + + intoString: &key]; + + [subStringScanner scanCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString: @"="] + intoString: NULL]; + + // deal with each possible attribute + if([key isEqual: @"Name"]) { + NSString* profileName; + [subStringScanner scanUpToCharactersFromSet: [NSCharacterSet newlineCharacterSet] + intoString: &profileName]; + profile[@"Name"] = profileName; + } + else if([key isEqual: @"IsRelative"]) { + int isRelative = 1; + [subStringScanner scanInt: &isRelative]; + profile[@"IsRelative"] = @(isRelative); + } + else if([key isEqual: @"Default"]) { + int isDefault = 0; + [subStringScanner scanInt: &isDefault]; + profile[@"Default"] = @(isDefault); + } + else if([key isEqual: @"Path"]) { + NSString* path; + [subStringScanner scanUpToCharactersFromSet: [NSCharacterSet newlineCharacterSet] + intoString: &path]; + profile[@"Path"] = path; + } + } + + [profiles addObject: profile]; // add the profile into the array of profiles + } while(![profilesIniScanner isAtEnd]); + + NSDictionary* defaultProfile = nil; + + for(NSUInteger i = 0; i < [profiles count]; i++) { + NSDictionary* p = profiles[i]; + if([p[@"Default"] isEqual: @1]) { + defaultProfile = p; + break; + } + if([p[@"Name"] isEqual: @"default"]) + defaultProfile = p; + } + + if(defaultProfile == nil) { + defaultProfile = profiles[0]; + } + if(defaultProfile == nil) + return nil; + + NSString* pathToProfile = defaultProfile[@"Path"]; + if(pathToProfile == nil) + return nil; + + if([defaultProfile[@"IsRelative"] isEqual: @0]) + return [pathToProfile stringByStandardizingPath]; + else + return [[[self pathToSupportFolder] stringByAppendingPathComponent: pathToProfile] stringByStandardizingPath]; +} + ++ (NSString*)pathToPrefsJsFile { + NSString* pathToDefaultProfile = [self pathToDefaultProfile]; + if(pathToDefaultProfile == nil) + return nil; + return [pathToDefaultProfile stringByAppendingPathComponent: @"prefs.js"]; +} + ++ (NSArray*)incomingHostnames { + NSString* pathToPrefsJsFile = [self pathToPrefsJsFile]; + if(pathToPrefsJsFile == nil) + return @[]; + if(![[NSFileManager defaultManager] isReadableFileAtPath: pathToPrefsJsFile]) + return @[]; + + NSArray* prefsJsLines = [[NSString stringWithContentsOfFile: pathToPrefsJsFile encoding: NSUTF8StringEncoding error: NULL] componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]]; + NSMutableArray* hostnames = [NSMutableArray arrayWithCapacity: 10]; + + for(NSUInteger i = 0; i < [prefsJsLines count]; i++) { + NSString* line = prefsJsLines[i]; + // All of the asterisks are necessary for globbing so that any amount of + // whitespace will work. + if([line isLike: @"*user_pref(*\"mail.server.server*.hostname\"*,*\"*\"*)*;*"]) { + NSArray* parts = [line componentsSeparatedByString: @"\""]; + // If the hostname is "Local Folders", it's a special Thunderbird thing, + // and obviously not something we can block. + if([parts count] >= 4 && ![parts[3] isEqual: @"Local Folders"]) { + [hostnames addObject: [parts[3] stringByAppendingString: @":110"]]; + } + } + else if([line isLike: @"*user_pref(*\"mail.server.server*.port\"*,*)*;*"]) { + NSArray* parts = [line componentsSeparatedByString: @","]; + parts = [parts[1] componentsSeparatedByString: @")"]; + int portNumber = [[parts[0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] intValue]; + NSString* alteredHost = [hostnames[([hostnames count] - 1)] componentsSeparatedByString: @":"][0]; + alteredHost = [alteredHost stringByAppendingFormat: @":%d", portNumber]; + hostnames[([hostnames count] - 1)] = alteredHost; + } + } + + return hostnames; +} + ++ (NSArray*)outgoingHostnames { + NSString* pathToPrefsJsFile = [self pathToPrefsJsFile]; + if(pathToPrefsJsFile == nil) + return @[]; + if(![[NSFileManager defaultManager] isReadableFileAtPath: pathToPrefsJsFile]) + return @[]; + + NSArray* prefsJsLines = [[NSString stringWithContentsOfFile: pathToPrefsJsFile encoding: NSUTF8StringEncoding error: NULL] componentsSeparatedByString: @"\n"]; + NSMutableArray* hostnames = [NSMutableArray arrayWithCapacity: 10]; + + for(NSUInteger i = 0; i < [prefsJsLines count]; i++) { + NSString* line = prefsJsLines[i]; + if([line isLike: @"*user_pref(*\"mail.smtpserver.smtp*.hostname\"*,*\"*\"*)*;*"]) { + NSArray* parts = [line componentsSeparatedByString: @"\""]; + if([parts count] >= 4 && ![parts[3] isEqual: @"Local Folders"]) { + [hostnames addObject: [parts[3] stringByAppendingString: @":25"]]; + } + } + // If there's a port number, add it to the last added hostname. Yes, it could + // technically be associated with a different hostname, but only if the user + // was manually editing the preferences file and changed it stupidly. + else if([line isLike: @"*user_pref(*\"mail.smtpserver.smtp*.port\"*,*)*;*"]) { + NSArray* parts = [line componentsSeparatedByString: @","]; + parts = [parts[1] componentsSeparatedByString: @")"]; + int portNumber = [[parts[0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] intValue]; + NSString* alteredHost = [hostnames[([hostnames count] - 1)] componentsSeparatedByString: @":"][0]; + alteredHost = [alteredHost stringByAppendingFormat: @":%d", portNumber]; + hostnames[([hostnames count] - 1)] = alteredHost; + } + } + + return hostnames; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/DeprecationSilencers.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/DeprecationSilencers.h new file mode 100644 index 00000000..949b4df7 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/DeprecationSilencers.h @@ -0,0 +1,23 @@ +// +// DeprecationSilencers.h +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#ifndef DeprecationSilencers_h +#define DeprecationSilencers_h + +// great idea taken from this guy: https://stackoverflow.com/a/26564750 + +#define SILENCE_DEPRECATION(expr) \ +do { \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \ +expr; \ +_Pragma("clang diagnostic pop") \ +} while(0) + +#define SILENCE_OSX10_10_DEPRECATION(expr) SILENCE_DEPRECATION(expr) + +#endif /* DeprecationSilencers_h */ diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCErr.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCErr.h new file mode 100644 index 00000000..5bce0a8f --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCErr.h @@ -0,0 +1,25 @@ +// +// SCErr.h +// SelfControl +// +// Created by Charlie Stigler on 1/13/21. +// + +#import + +// copied from StackOverflow answer by Wolfgang Schreurs: https://stackoverflow.com/a/14086231 +#define SC_ERROR_KEY(code) [NSString stringWithFormat:@"%ld", (long)code] +#define SC_ERROR_LOCALIZED_DESCRIPTION(code) NSLocalizedStringFromTable(SC_ERROR_KEY(code), @"SCError", nil) + +FOUNDATION_EXPORT NSString * _Nonnull const kSelfControlErrorDomain; + +NS_ASSUME_NONNULL_BEGIN + +@interface SCErr : NSObject + ++ (NSError*)errorWithCode:(NSInteger)errorCode subDescription:(NSString* _Nullable)subDescription; ++ (NSError*)errorWithCode:(NSInteger)errorCode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCErr.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCErr.m new file mode 100644 index 00000000..069aa18e --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCErr.m @@ -0,0 +1,42 @@ +// +// SCErr.m +// SelfControl +// +// Created by Charlie Stigler on 1/13/21. +// + +#import "SCErr.h" + +NSString *const kSelfControlErrorDomain = @"SelfControlErrorDomain"; + +@implementation SCErr + ++ (NSError*)errorWithCode:(NSInteger)errorCode subDescription:(NSString * _Nullable )subDescription { + BOOL descriptionNotFound = NO; + NSString* description = SC_ERROR_LOCALIZED_DESCRIPTION(errorCode); + + // if we couldn't find a localized description key, that probably means + // we're in the daemon or somewhere else where .strings aren't available. + // flag that so the app can fill in the details later + if ([description isEqualToString: SC_ERROR_KEY(errorCode)]) { + description = [NSString stringWithFormat: @"SelfControl hit an unknown error with code %ld.", errorCode]; + descriptionNotFound = YES; + } + + if (subDescription != nil) { + description = [NSString stringWithFormat: description, subDescription]; + } + + return [NSError errorWithDomain: kSelfControlErrorDomain + code: errorCode + userInfo: @{ + NSLocalizedDescriptionKey: description, + @"SCDescriptionNotFound": @(descriptionNotFound) + }]; +} + ++ (NSError*)errorWithCode:(NSInteger)errorCode { + return [SCErr errorWithCode: errorCode subDescription: nil]; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCFileWatcher.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCFileWatcher.h new file mode 100644 index 00000000..4ef12241 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCFileWatcher.h @@ -0,0 +1,27 @@ +// +// SCFileWatcher.h +// SelfControl +// +// Created by Charlie Stigler on 3/20/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^SCFileWatcherCallback)(NSError* _Nullable error); + +@interface SCFileWatcher : NSObject + +@property (readonly) FSEventStreamRef eventStream; +@property (strong, readonly) SCFileWatcherCallback callbackBlock; +@property (strong, readonly) NSString* filePath; + ++ (instancetype)watcherWithFile:(NSString*)watchPath block:(void(^)(NSError* error))callbackBlock; +- (instancetype)initWithFile:(NSString*)watchPath block:(void(^)(NSError* error))callbackBlock; + +- (void)stopWatching; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCFileWatcher.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCFileWatcher.m new file mode 100644 index 00000000..4a26448e --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCFileWatcher.m @@ -0,0 +1,104 @@ +// +// SCFileWatcher.m +// SelfControl +// +// Created by Charlie Stigler on 3/20/21. +// + +#import "SCFileWatcher.h" +#include + +@implementation SCFileWatcher + +static void SCFileWatcherGlobalCallback( + ConstFSEventStreamRef streamRef, + void *callbackCtxInfo, + size_t numEvents, + void *eventPaths, // CFArrayRef + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + NSArray* paths = (__bridge NSArray*)eventPaths; + SCFileWatcher* watcher = (__bridge SCFileWatcher*)callbackCtxInfo; + [watcher directoryWatcherTriggered: paths flags: eventFlags]; +} + +- (void)directoryWatcherTriggered:(NSArray*)eventPaths flags:(const FSEventStreamEventFlags[])eventFlags { + BOOL triggerFileWatcher = NO; + + for (unsigned int i = 0; i < eventPaths.count; i++) { + NSString* eventPath = [eventPaths[i] stringByStandardizingPath]; + + if ([eventPath isEqualToString: self.filePath]) { + triggerFileWatcher = YES; + } + } + + if (triggerFileWatcher) { + self.callbackBlock(nil); + } +} + ++ (instancetype)watcherWithFile:(NSString*)watchPath block:(void(^)(NSError* error))callbackBlock { + return [[SCFileWatcher new] initWithFile: watchPath block: callbackBlock]; +} + +- (instancetype)initWithFile:(NSString*)watchPath block:(void(^)(NSError* error))callbackBlock { + self = [super init]; + + NSFileManager* fileMan = [NSFileManager defaultManager]; + _filePath = [watchPath stringByStandardizingPath]; + BOOL isDirectory; + [fileMan fileExistsAtPath: self.filePath isDirectory: &isDirectory]; + + NSString* directoryPath; + if (isDirectory) { + directoryPath = self.filePath; + } else { + directoryPath = [self.filePath stringByDeletingLastPathComponent]; + } + + FSEventStreamContext callbackCtx; + callbackCtx.version = 0; + callbackCtx.info = (__bridge void *)self; + callbackCtx.retain = NULL; + callbackCtx.release = NULL; + callbackCtx.copyDescription = NULL; + + FSEventStreamRef eventStream = FSEventStreamCreate( + kCFAllocatorDefault, + &SCFileWatcherGlobalCallback, + &callbackCtx, // context + (__bridge CFArrayRef)@[directoryPath], + kFSEventStreamEventIdSinceNow, + 1.5, // seconds to throttle callbacks + kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagMarkSelf | kFSEventStreamCreateFlagIgnoreSelf | kFSEventStreamCreateFlagFileEvents + ); + + FSEventStreamScheduleWithRunLoop(eventStream, + [[NSRunLoop currentRunLoop] getCFRunLoop], + kCFRunLoopDefaultMode); + if (!FSEventStreamStart(eventStream)) { + NSLog(@"WARNING: failed to start watching file %@", watchPath); + FSEventStreamUnscheduleFromRunLoop(eventStream, [[NSRunLoop currentRunLoop] getCFRunLoop], kCFRunLoopDefaultMode); + FSEventStreamInvalidate(eventStream); + FSEventStreamRelease(eventStream); + return nil; + } + + _eventStream = eventStream; + _callbackBlock = callbackBlock; + + return self; +} + +- (void)stopWatching { + FSEventStreamStop(self.eventStream); + FSEventStreamUnscheduleFromRunLoop(self.eventStream, [[NSRunLoop currentRunLoop] getCFRunLoop], kCFRunLoopDefaultMode); + FSEventStreamInvalidate(self.eventStream); + FSEventStreamRelease(self.eventStream); + + _eventStream = NULL; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCSettings.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCSettings.h new file mode 100644 index 00000000..d1f9da3f --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCSettings.h @@ -0,0 +1,42 @@ +// +// SCLockFileUtilities.h +// SelfControl +// +// Created by Charles Stigler on 20/10/2018. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SCSettings : NSObject + +@property (readonly) uid_t userId; +@property (readonly) NSDictionary* dictionaryRepresentation; +@property (nonatomic, getter=isReadOnly) BOOL readOnly; + +@property (class, nonatomic, readonly) NSString* settingsFileName; +@property (class, nonatomic, readonly) NSString* securedSettingsFilePath; + ++ (instancetype)sharedSettings; + +- (void)reloadSettings; +- (void)writeSettingsWithCompletion:(nullable void(^)(NSError* _Nullable))completionBlock; +- (void)writeSettings; +- (void)synchronizeSettingsWithCompletion:(nullable void(^)(NSError* _Nullable))completionBlock; +- (void)synchronizeSettings; +- (NSError*)syncSettingsAndWait:(NSInteger)timeoutSecs; + +- (void)setValue:(id)value forKey:(NSString*)key stopPropagation:(BOOL)stopPropagation; +- (void)setValue:(nullable id)value forKey:(NSString*)key; + +- (id)valueForKey:(NSString*)key; +- (BOOL)boolForKey:(NSString*)key; + +- (void)updateSentryContext; + +- (void)resetAllSettingsToDefaults; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCSettings.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCSettings.m new file mode 100644 index 00000000..8266028b --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCSettings.m @@ -0,0 +1,568 @@ +// +// SCLockFileUtilities.m +// SelfControl +// +// Created by Charles Stigler on 20/10/2018. +// + +#import "SCSettings.h" +#import + +#ifndef TESTING +//#import +#endif + +float const SYNC_INTERVAL_SECS = 30; +float const SYNC_LEEWAY_SECS = 30; +NSString* const SETTINGS_FILE_DIR = @"/usr/local/etc/"; + +@interface SCSettings () + +// Private vars +@property (readonly) NSMutableDictionary* settingsDict; +@property NSDate* lastSynchronizedWithDisk; +@property dispatch_source_t syncTimer; +@property dispatch_source_t debouncedChangeTimer; + +@end + +@implementation SCSettings + ++ (instancetype)sharedSettings { + static SCSettings* globalSettings = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + globalSettings = [SCSettings new]; + }); + return globalSettings; +} + +- (instancetype)init { + if (self = [super init]) { + // we will only write out settings if we have root permissions (i.e. the EUID is 0) + // otherwise, we won't/shouldn't have permissions to write to the settings file + // in practice, what this means is that the daemon writes settings, and the app/CLI only read + _readOnly = (geteuid() != 0); + + _settingsDict = nil; + + [[NSDistributedNotificationCenter defaultCenter] addObserver: self + selector: @selector(onSettingChanged:) + name: @"org.eyebeam.SelfControl.SCSettingsValueChanged" + object: nil + suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately]; + } + return self; +} + ++ (NSString*)settingsFileName { + static NSString* fileName = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fileName = [NSString stringWithFormat: @".%@.plist", [SCMiscUtilities sha1: [NSString stringWithFormat: @"SelfControlUserPreferences%@", [SCMiscUtilities getSerialNumber]]]]; + }); + + return fileName; +} ++ (NSString*)securedSettingsFilePath { + static NSString* filePath = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + filePath = [NSString stringWithFormat: @"%@%@", SETTINGS_FILE_DIR, SCSettings.settingsFileName]; + }); + + return filePath; +} + +// NOTE: there should be a default setting for each valid setting, even if it's nil/zero/etc +- (NSDictionary*)defaultSettingsDict { + return @{ + @"BlockEndDate": [NSDate distantPast], + @"ActiveBlocklist": @[], + @"ActiveBlockAsWhitelist": @NO, + + @"BlockIsRunning": @NO, // tells us whether a block is actually running on the system (to the best of our knowledge) + @"TamperingDetected": @NO, + + // block settings + // the user sets these in defaults, then when a block is started they're copied over to settings + @"EvaluateCommonSubdomains": @YES, + @"IncludeLinkedDomains": @YES, + @"BlockSoundShouldPlay": @NO, + @"BlockSound": @5, + @"ClearCaches": @YES, + @"AllowLocalNetworks": @YES, + + @"EnableErrorReporting": @([SCMiscUtilities systemThirdPartyCrashReportingEnabled]), + + @"SettingsVersionNumber": @0, + @"LastSettingsUpdate": [NSDate distantPast] // special value that keeps track of when we last updated our settings + }; +} + +- (void)initializeSettingsDict { + // make sure we only load the settings dictionary once, even if called simultaneously from multiple threads + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + @synchronized (self) { + self->_settingsDict = [NSMutableDictionary dictionaryWithContentsOfFile: SCSettings.securedSettingsFilePath]; + + BOOL isTest = [[NSUserDefaults standardUserDefaults] boolForKey: @"isTest"]; + if (isTest) NSLog(@"Ignoring settings on disk because we're unit-testing"); + + // if we don't have a settings dictionary on disk yet, + // set it up with the default values (and migrate legacy settings also) + // also if we're running tests, just use the default dict + if (self->_settingsDict == nil || isTest) { + self->_settingsDict = [[self defaultSettingsDict] mutableCopy]; + + // write out our brand-new settings to disk! + if (!self.readOnly) { + [self writeSettings]; + } + // [SCSentry addBreadcrumb: @"Initialized SCSettings to default settings" category: @"settings"]; + } + + // we're now current with disk! + self->lastSynchronizedWithDisk = [NSDate date]; + + [self startSyncTimer]; + } + }); +} + +- (NSDictionary*)settingsDict { + if (_settingsDict == nil) { + [self initializeSettingsDict]; + } + return _settingsDict; +} + +- (NSDictionary*)dictionaryRepresentation { + NSMutableDictionary* dictCopy = [self.settingsDict mutableCopy]; + + // fill in any gaps with default values (like we did if they called valueForKey:) + for (NSString* key in [[self defaultSettingsDict] allKeys]) { + if (dictCopy[key] == nil) { + dictCopy[key] = [self defaultSettingsDict][key]; + } + } + + return dictCopy; +} + +// both reloadSettings and writeSettings are synchronized with the same object, so +// at any given time we are running a maximum of one of these methods, on one thread. +// we don't want to be reading the file on one thread and writing out two different versions +// on two other threads + +- (void)reloadSettings { + // if the settings dictionary hasn't been loaded the first time, do that instead of reloading + if (_settingsDict == nil) { + [self initializeSettingsDict]; + return; + } + + @synchronized (self) { + NSDictionary* settingsFromDisk = [NSDictionary dictionaryWithContentsOfFile: SCSettings.securedSettingsFilePath]; + + int diskSettingsVersion = [settingsFromDisk[@"SettingsVersionNumber"] intValue]; + int memorySettingsVersion = [[self valueForKey: @"SettingsVersionNumber"] intValue]; + NSDate* diskSettingsLastUpdated = settingsFromDisk[@"LastSettingsUpdate"]; + NSDate* memorySettingsLastUpdated = [self valueForKey: @"LastSettingsUpdate"]; + + // occasionally we can end up with timestamps from the future + // (usually because the user moved their system clock forward, then back again) + // it's a weird edge case and we should just fix that when we see it + if ([diskSettingsLastUpdated timeIntervalSinceNow] > 0) { + // we'll pretend the disk was written 1 second ago in this case to avoid weird edge conditions + diskSettingsLastUpdated = [[NSDate date] dateByAddingTimeInterval: 1.0]; + } + if ([memorySettingsLastUpdated timeIntervalSinceNow] > 0) { + memorySettingsLastUpdated = [NSDate date]; + [self setValue: memorySettingsLastUpdated forKey: @"LastSettingsUpdate"]; + } + + if (diskSettingsLastUpdated == nil) diskSettingsLastUpdated = [NSDate distantPast]; + + // try to decide which is more recent by version number, tiebreak by date + BOOL diskMoreRecentThanMemory = NO; + if (diskSettingsVersion == memorySettingsVersion) { + diskMoreRecentThanMemory = ([diskSettingsLastUpdated timeIntervalSinceDate: memorySettingsLastUpdated] > 0); + } else { + diskMoreRecentThanMemory = (diskSettingsVersion > memorySettingsVersion); + } + + if (diskMoreRecentThanMemory) { + _settingsDict = [settingsFromDisk mutableCopy]; + self.lastSynchronizedWithDisk = [NSDate date]; + NSLog(@"Newer SCSettings found on disk (version %d vs %d with time interval %f), updating...", diskSettingsVersion, memorySettingsVersion, [diskSettingsLastUpdated timeIntervalSinceDate: memorySettingsLastUpdated]); + // [SCSentry addBreadcrumb: @"Updated SCSettings to newer settings found on disk" category: @"settings"]; + } + } +} +- (void)writeSettingsWithCompletion:(nullable void(^)(NSError* _Nullable))completionBlock { + @synchronized (self) { + if (self.readOnly) { + NSLog(@"WARNING: Read-only SCSettings instance can't write out settings"); + NSError* err = [SCErr errorWithCode: 600]; + // [SCSentry captureError: err]; + if (completionBlock != nil) { + completionBlock(err); + } + return; + } + +#if TESTING + // no writing to disk during unit tests + NSLog(@"Would write settings to disk now (but no writing during unit tests)"); + if (completionBlock != nil) completionBlock(nil); + return; +#endif + + // don't spend time on the main thread writing out files - it's OK for this to happen without blocking other things + dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSError* serializationErr; + NSData* plistData = [NSPropertyListSerialization dataWithPropertyList: self.settingsDict + format: NSPropertyListBinaryFormat_v1_0 + options: kNilOptions + error: &serializationErr]; + + if (plistData == nil) { + NSLog(@"NSPropertyListSerialization error: %@", serializationErr); + if (completionBlock != nil) completionBlock(serializationErr); + return; + } + + NSError* createDirectoryErr; + BOOL createDirectorySuccessful = [[NSFileManager defaultManager] createDirectoryAtURL: [NSURL fileURLWithPath: SETTINGS_FILE_DIR] + withIntermediateDirectories: YES + attributes: @{ + NSFileOwnerAccountID: [NSNumber numberWithUnsignedLong: 0], + NSFileGroupOwnerAccountID: [NSNumber numberWithUnsignedLong: 0], + NSFilePosixPermissions: [NSNumber numberWithShort: 0755] + } + error: &createDirectoryErr]; + if (!createDirectorySuccessful) { + NSLog(@"WARNING: Failed to create %@ folder to store SCSettings. Error was %@", SETTINGS_FILE_DIR, createDirectoryErr); + // [SCSentry addBreadcrumb: [NSString stringWithFormat: @"Failed to create directory for SCSettings with error %@", createDirectoryErr] category:@"settings"]; + } + + NSError* chmodDirectoryErr; + BOOL chmodDirectorySuccessful = [[NSFileManager defaultManager] + setAttributes: @{ + NSFilePosixPermissions: [NSNumber numberWithShort: 0755] + } + ofItemAtPath: SETTINGS_FILE_DIR + error: &chmodDirectoryErr]; + if (!chmodDirectorySuccessful) { + NSLog(@"WARNING: Failed to set permissions on %@ folder to store SCSettings. Error was %@", SETTINGS_FILE_DIR, chmodDirectoryErr); + // [SCSentry addBreadcrumb: [NSString stringWithFormat: @"Failed to set directory permissions for SCSettings with error %@", chmodDirectoryErr] category:@"settings"]; + } + + NSError* writeErr; + BOOL writeSuccessful = [plistData writeToFile: SCSettings.securedSettingsFilePath + options: NSDataWritingAtomic + error: &writeErr + ]; + + NSError* chmodErr; + BOOL chmodSuccessful = [[NSFileManager defaultManager] + setAttributes: @{ + NSFileOwnerAccountID: [NSNumber numberWithUnsignedLong: 0], + NSFileGroupOwnerAccountID: [NSNumber numberWithUnsignedLong: 0], + NSFilePosixPermissions: [NSNumber numberWithShort: 0755] + } + ofItemAtPath: SCSettings.securedSettingsFilePath + error: &chmodErr]; + + if (writeSuccessful) { + self.lastSynchronizedWithDisk = [NSDate date]; + } + + if (!writeSuccessful) { + NSLog(@"Failed to write secured settings to file %@", SCSettings.securedSettingsFilePath); + // [SCSentry captureError: writeErr]; + if (completionBlock != nil) completionBlock(writeErr); + } else if (!chmodSuccessful) { + NSLog(@"Failed to change secured settings file owner/permissions secured settings for file %@ with error %@", SCSettings.securedSettingsFilePath, chmodErr); + // [SCSentry captureError: chmodErr]; + if (completionBlock != nil) completionBlock(chmodErr); + } else { + // [SCSentry addBreadcrumb: @"Successfully wrote SCSettings out to file" category: @"settings"]; + if (completionBlock != nil) completionBlock(nil); + } + }); + } +} +- (void)writeSettings { + // by default, just log all errors + [self writeSettingsWithCompletion:^(NSError * _Nullable err) { + if (err != nil) { + NSLog(@"Error writing SCSettings: %@", err); + } + }]; +} +- (void)synchronizeSettingsWithCompletion:(nullable void (^)(NSError * _Nullable))completionBlock { + [self reloadSettings]; + + NSDate* lastSettingsUpdate = [self valueForKey: @"LastSettingsUpdate"]; + + // occasionally we can end up with timestamps from the future + // (usually because the user moved their system clock forward, then back again) + // it's a weird edge case and we should just fix that when we see it + if ([lastSettingsUpdate timeIntervalSinceNow] > 0) { + [self setValue: [NSDate date] forKey: @"LastSettingsUpdate"]; + } + + if ([lastSettingsUpdate timeIntervalSinceDate: self.lastSynchronizedWithDisk] > 0 && !self.readOnly) { + NSLog(@" --> Writing settings to disk (haven't been written since %@)", self.lastSynchronizedWithDisk); + [self writeSettingsWithCompletion: completionBlock]; + } else { + if(completionBlock != nil) { + // don't just run the callback asynchronously, since it makes this method harder to reason about + // (it'd sometimes call back synchronously and sometimes async) +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + completionBlock(nil); +// }); + } + } +} +- (void)synchronizeSettings { + [self synchronizeSettingsWithCompletion: nil]; +} + +- (NSError*)syncSettingsAndWait:(NSInteger)timeoutSecs { + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + __block NSError* retErr = nil; + + // do this on another thread so it doesn't deadlock our semaphore + // (also dispatch_async ensures correct behavior even if synchronizeSettingsWithCompletion itself returns synchronously) + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self synchronizeSettingsWithCompletion:^(NSError* err) { + retErr = err; + + dispatch_semaphore_signal(sema); + }]; + }); + + if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, (int64_t)timeoutSecs * (int64_t)NSEC_PER_SEC))) { + retErr = [SCErr errorWithCode: 601]; + } + + return retErr; +} + +- (void)setValue:(id)value forKey:(NSString*)key stopPropagation:(BOOL)stopPropagation { + // if we're a readonly instance, we generally shouldn't be allowing values to be set + // the only exception is receiving value updates (via notification) from other processes + // in which case stopPropagation will be true + if (self.readOnly && !stopPropagation) { + NSLog(@"WARNING: Read-only SCSettings instance can't update values (setting %@ to %@)", key, value); + return; + } + + // we can't store nils in a dictionary + // so we sneak around it + if (value == nil) { + value = [NSNull null]; + } + + // locking everything on self is kinda inefficient/unnecessary + // since it means we can only set one value at a time, and never when reading/writing from disk + // but it seems to be OK for now - we'll improve later + @synchronized (self) { + // if we're about to insert NSNull anyway, may as well just unset the value + if ([value isEqual: [NSNull null]]) { + [self.settingsDict removeObjectForKey: key]; + } else { + [self.settingsDict setValue: value forKey: key]; + } + + // record the update + int newVersionNumber = [[self valueForKey: @"SettingsVersionNumber"] intValue] + 1; + [self.settingsDict setValue: [NSNumber numberWithInt: newVersionNumber] forKey: @"SettingsVersionNumber"]; + [self.settingsDict setValue: [NSDate date] forKey: @"LastSettingsUpdate"]; + } + + // notify other instances (presumably in other processes) + // stopPropagation is a flag that stops one setting change from bouncing back and forth for ages + // between two processes. It indicates that the change started in another process + if (!stopPropagation) { + [[NSDistributedNotificationCenter defaultCenter] postNotificationName: @"org.eyebeam.SelfControl.SCSettingsValueChanged" + object: self.description + userInfo: @{ + @"key": key, + @"value": value, + @"versionNumber": self.settingsDict[@"SettingsVersionNumber"], + @"date": [NSDate date] + } + options: NSNotificationDeliverImmediately | NSNotificationPostToAllSessions + ]; + } +} + +- (void)setValue:(id)value forKey:(NSString*)key { + [self setValue: value forKey: key stopPropagation: NO]; +} + +- (id)valueForKey:(NSString*)key { + id value = [self.settingsDict valueForKey: key]; + + // when we get an NSNull we have to unwrap it and remember that means nil + if ([value isEqual: [NSNull null]]) { + value = nil; + } + + // if we don't have a value in our dictionary but we do have a default value, use that instead! + if (value == nil && [self defaultSettingsDict][key] != nil) { + value = [self defaultSettingsDict][key]; + } + + return value; +} +- (BOOL)boolForKey:(NSString*)key { + return [[self valueForKey: key] boolValue]; +} + +- (void)startSyncTimer { + if (self.syncTimer != nil) { + // we already have a timer, so no need to start another + return; + } + + // set up a timer so values get synchronized to disk on a regular basis + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + self.syncTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + if (self.syncTimer) { + dispatch_source_set_timer(self.syncTimer, dispatch_time(DISPATCH_TIME_NOW, SYNC_INTERVAL_SECS * NSEC_PER_SEC), SYNC_INTERVAL_SECS * NSEC_PER_SEC, SYNC_LEEWAY_SECS * NSEC_PER_SEC); + dispatch_source_set_event_handler(self.syncTimer, ^{ + [self synchronizeSettings]; + }); + dispatch_resume(self.syncTimer); + } +} +- (void)cancelSyncTimers { + if (self.syncTimer != nil) { + dispatch_source_cancel(self.syncTimer); + self.syncTimer = nil; + } + + if (self.debouncedChangeTimer != nil) { + dispatch_source_cancel(self.debouncedChangeTimer); + self.debouncedChangeTimer = nil; + } +} + +- (void)updateSentryContext { + // make sure Sentry has the latest context in the event of a crash + + NSMutableDictionary* dictCopy = [self.settingsDict mutableCopy]; + + // fill in any gaps with default values (like we did if they called valueForKey:) + for (NSString* key in [[self defaultSettingsDict] allKeys]) { + if (dictCopy[key] == nil) { + dictCopy[key] = [self defaultSettingsDict][key]; + } + } + + // eliminate privacy-sensitive data (i.e. blocklist) + // but store the blocklist length as a useful piece of debug info + id activeBlocklist = dictCopy[@"ActiveBlocklist"]; + NSUInteger blocklistLength = (activeBlocklist == nil) ? 0 : ((NSArray*)activeBlocklist).count; + [dictCopy setObject: @(blocklistLength) forKey: @"ActiveBlocklistLength"]; + [dictCopy removeObjectForKey: @"Blocklist"]; + [dictCopy removeObjectForKey: @"ActiveBlocklist"]; + + // and serialize dates to string, since Sentry has a hard time with that + NSArray* dateKeys = @[@"BlockEndDate", @"LastSettingsUpdate"]; + for (NSString* dateKey in dateKeys) { + dictCopy[dateKey] = [NSDateFormatter localizedStringFromDate: dictCopy[dateKey] + dateStyle: NSDateFormatterShortStyle + timeStyle: NSDateFormatterFullStyle]; + } + +#ifndef TESTING +// [SentrySDK configureScope:^(SentryScope * _Nonnull scope) { +// [scope setContextValue: dictCopy forKey: @"SCSettings"]; +// }]; +#endif +} + +- (void)onSettingChanged:(NSNotification*)note { + // note.object is a string, so we can't just do a simple == to see if the object is self + // but if we check our description against it, that will do the same thing because description + // includes the memory address. Don't override description or this logic will break!! + if ([note.object isEqualToString: [self description]]) { + // we don't need to listen to our own notifications + return; + } + + if (note.userInfo[@"key"] == nil) { + // something's wrong - we don't have a key to set + return; + } + + // if this change happened before our latest update, it's kinda unclear what the end state should be + // so ignore it and just queue up a sync instead + int noteVersionNumber = [note.userInfo[@"versionNumber"] intValue]; + NSDate* noteSettingUpdated = note.userInfo[@"date"]; + int ourSettingsVersionNumber = [[self valueForKey: @"SettingsVersionNumber"] intValue]; + NSDate* ourSettingsLastUpdated = [self valueForKey: @"LastSettingsUpdate"]; + + // check by version number, tiebreak by last updated date + BOOL noteMoreRecentThanSettings = NO; + if (noteVersionNumber == ourSettingsVersionNumber) { + noteMoreRecentThanSettings = ([noteSettingUpdated timeIntervalSinceDate: ourSettingsLastUpdated] > 0); + } else { + noteMoreRecentThanSettings = (noteVersionNumber > ourSettingsVersionNumber); + } + + if (!noteMoreRecentThanSettings) { + NSLog(@"Ignoring setting change notification as %@ is older than %@", noteSettingUpdated, ourSettingsLastUpdated); + } else { + NSLog(@"Accepting propagated change (%@ --> %@) since version %d is newer than %d and/or %@ is newer than %@", note.userInfo[@"key"], note.userInfo[@"value"], noteVersionNumber, ourSettingsVersionNumber, noteSettingUpdated, ourSettingsLastUpdated); + + // mirror the change on our own instance - but don't propagate the change to avoid loopin + [self setValue: note.userInfo[@"value"] forKey: note.userInfo[@"key"] stopPropagation: YES]; + } + + // regardless of which is more recent, we should really go get the new deal from disk + // in the near future (but debounce so we don't do this a million times for rapid changes) + if (self.debouncedChangeTimer != nil) { + dispatch_source_cancel(self.debouncedChangeTimer); + self.debouncedChangeTimer = nil; + } + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + double throttleSecs = 0.25f; + self.debouncedChangeTimer = [SCMiscUtilities createDebounceDispatchTimer: throttleSecs + queue: queue + block: ^{ + NSLog(@"Syncing settings due to propagated changes"); + [self synchronizeSettings]; + }]; +} + +- (void)resetAllSettingsToDefaults { + // we _basically_ just copy the default settings dict in, + // except we leave the settings version number and last settings update + // intact - that helps keep us in sync with any other instances + NSDictionary* defaultSettings = [self defaultSettingsDict]; + for (NSString* key in defaultSettings) { + if ([key isEqualToString: @"SettingsVersionNumber"] || [key isEqualToString: @"LastSettingsUpdate"]) { + continue; + } + + [self setValue: defaultSettings[key] forKey: key]; + } +} + +- (void)dealloc { + [self cancelSyncTimers]; +} + +@synthesize settingsDict = _settingsDict, lastSynchronizedWithDisk; + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCAuthorization.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCAuthorization.h new file mode 100644 index 00000000..893836f2 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCAuthorization.h @@ -0,0 +1,24 @@ +// +// SCXPCAuthorization.h +// SelfControl +// +// Created by Charlie Stigler on 1/4/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SCXPCAuthorization : NSObject + ++ (NSError *)checkAuthorization:(NSData *)authData command:(SEL)command; + ++ (NSString *)authorizationRightForCommand:(SEL)command; + // For a given command selector, return the associated authorization right name. + ++ (void)setupAuthorizationRights:(AuthorizationRef)authRef; + // Set up the default authorization rights in the authorization database. + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCAuthorization.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCAuthorization.m new file mode 100644 index 00000000..a6907931 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCAuthorization.m @@ -0,0 +1,186 @@ +// +// SCXPCAuthorization.m +// SelfControl +// +// Created by Charlie Stigler on 1/4/21. +// + +#import "SCXPCAuthorization.h" + +@implementation SCXPCAuthorization + +// all of these methods (basically this whole file) copied from Apple's Even Better Authorization Sample code + +static NSString * kCommandKeyAuthRightName = @"authRightName"; +static NSString * kCommandKeyAuthRightDefault = @"authRightDefault"; +static NSString * kCommandKeyAuthRightDesc = @"authRightDescription"; + +static NSDictionary* kAuthorizationRuleAuthenticateAsAdmin2MinTimeout; + +// copied from Apple's Even Better Authorization Sample code ++ (NSError *)checkAuthorization:(NSData *)authData command:(SEL)command + // Check that the client denoted by authData is allowed to run the specified command. + // authData is expected to be an NSData with an AuthorizationExternalForm embedded inside. +{ + #pragma unused(authData) + AuthorizationRef authRef; + + assert(command != nil); + + authRef = NULL; + + // First check that authData looks reasonable. + if ( (authData == nil) || ([authData length] != sizeof(AuthorizationExternalForm)) ) { + return [NSError errorWithDomain:NSOSStatusErrorDomain code:paramErr userInfo:nil]; + } + + // Create an authorization ref from that the external form data contained within. + OSStatus extFormStatus = AuthorizationCreateFromExternalForm([authData bytes], &authRef); + if (extFormStatus != errAuthorizationSuccess) { + return [NSError errorWithDomain: NSOSStatusErrorDomain code: extFormStatus userInfo: nil]; + } + + // Authorize the right associated with the command. + + AuthorizationItem oneRight = { NULL, 0, NULL, 0 }; + AuthorizationRights rights = { 1, &oneRight }; + AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed; + + oneRight.name = [[SCXPCAuthorization authorizationRightForCommand:command] UTF8String]; + assert(oneRight.name != NULL); + + OSStatus authStatus = AuthorizationCopyRights( + authRef, + &rights, + kAuthorizationEmptyEnvironment, + flags, + NULL + ); + if (authRef != NULL) { + AuthorizationFree(authRef, 0); + } + + if (authStatus != errAuthorizationSuccess) { + return [NSError errorWithDomain: NSOSStatusErrorDomain code: authStatus userInfo: nil]; + } + + return nil; +} + + ++ (NSDictionary *)commandInfo +{ + static dispatch_once_t sOnceToken; + static NSDictionary * sCommandInfo; + + // static var needs to bre defined before first use + if (kAuthorizationRuleAuthenticateAsAdmin2MinTimeout == nil) { + kAuthorizationRuleAuthenticateAsAdmin2MinTimeout = @{ + @"class": @"user", + @"group": @"admin", + @"timeout": @(120), // 2 minutes + @"shared": @(YES), + @"version": @1 // not entirely sure what this does TBH + }; + } + + dispatch_once(&sOnceToken, ^{ + #pragma clang diagnostic ignored "-Wundeclared-selector" + + + NSDictionary* startBlockCommandInfo = @{ + kCommandKeyAuthRightName : @"com.application.SelfControl.corebits.startBlock", + kCommandKeyAuthRightDefault : kAuthorizationRuleAuthenticateAsAdmin2MinTimeout, + kCommandKeyAuthRightDesc : NSLocalizedString( + @"SelfControl needs your username and password to start the block.", + @"prompt shown when user is required to authorize to start block" + ) + }; + NSDictionary* modifyBlockCommandInfo = @{ + kCommandKeyAuthRightName : @"com.application.SelfControl.corebits.modifyBlock", + kCommandKeyAuthRightDefault : kAuthorizationRuleAuthenticateAsAdmin2MinTimeout, + kCommandKeyAuthRightDesc : NSLocalizedString( + @"SelfControl needs your username and password to modify the block", + @"prompt shown when user is required to authorize to modify their block" + ) + }; + + sCommandInfo = @{ + NSStringFromSelector(@selector(startBlockWithControllingUID:blocklist:isAllowlist:endDate:blockSettings:authorization:reply:)) : startBlockCommandInfo, + NSStringFromSelector(@selector(updateBlocklist:authorization:reply:)) : modifyBlockCommandInfo, + NSStringFromSelector(@selector(updateBlockEndDate:authorization:reply:)) : modifyBlockCommandInfo + #pragma clang diagnostic pop + }; + }); + return sCommandInfo; +} + ++ (void)enumerateRightsUsingBlock:(void (^)(NSString * authRightName, id authRightDefault, NSString * authRightDesc))block + // Calls the supplied block with information about each known authorization right.. +{ + [self.commandInfo enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + #pragma unused(key) + #pragma unused(stop) + NSDictionary * commandDict; + NSString * authRightName; + id authRightDefault; + NSString * authRightDesc; + + // If any of the following asserts fire it's likely that you've got a bug + // in sCommandInfo. + + commandDict = (NSDictionary *) obj; + assert([commandDict isKindOfClass:[NSDictionary class]]); + + authRightName = [commandDict objectForKey:kCommandKeyAuthRightName]; + assert([authRightName isKindOfClass:[NSString class]]); + + authRightDefault = [commandDict objectForKey:kCommandKeyAuthRightDefault]; + assert(authRightDefault != nil); + + authRightDesc = [commandDict objectForKey:kCommandKeyAuthRightDesc]; + assert([authRightDesc isKindOfClass:[NSString class]]); + + block(authRightName, authRightDefault, authRightDesc); + }]; +} + ++ (void)setupAuthorizationRights:(AuthorizationRef)authRef + // See comment in header. +{ + assert(authRef != NULL); + [SCXPCAuthorization enumerateRightsUsingBlock:^(NSString * authRightName, id authRightDefault, NSString * authRightDesc) { + OSStatus blockErr; + + // First get the right. If we get back errAuthorizationDenied that means there's + // no current definition, so we add our default one. + + blockErr = AuthorizationRightGet([authRightName UTF8String], NULL); + if (blockErr == errAuthorizationDenied) { + NSLog(@"setting auth right default for %@: %@", authRightName, authRightDefault); + blockErr = AuthorizationRightSet( + authRef, // authRef + [authRightName UTF8String], // rightName + (__bridge CFTypeRef) authRightDefault, // rightDefinition + (__bridge CFStringRef) authRightDesc, // descriptionKey + NULL, // bundle (NULL implies main bundle) + CFSTR("SCXPCAuthorization") // localeTableName + ); + assert(blockErr == errAuthorizationSuccess); + } else { + // A right already exists (err == noErr) or any other error occurs, we + // assume that it has been set up in advance by the system administrator or + // this is the second time we've run. Either way, there's nothing more for + // us to do. + } + }]; +} + ++ (NSString *)authorizationRightForCommand:(SEL)command + // See comment in header. +{ + return [self commandInfo][NSStringFromSelector(command)][kCommandKeyAuthRightName]; +} + + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCClient.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCClient.h new file mode 100644 index 00000000..dd9f06c3 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCClient.h @@ -0,0 +1,28 @@ +// +// SCAppXPC.h +// SelfControl +// +// Created by Charlie Stigler on 7/4/20. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SCXPCClient : NSObject + +@property (readonly, getter=isConnected) BOOL connected; + +- (void)connectToHelperTool; +- (void)installDaemon:(void(^)(NSError*))callback; +- (void)refreshConnectionAndRun:(void(^)(void))callback; +- (void)connectAndExecuteCommandBlock:(void(^)(NSError *))commandBlock; + +- (void)getVersion:(void(^)(NSString* version, NSError* error))reply; +- (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings reply:(void(^)(NSError* error))reply; +- (void)updateBlocklist:(NSArray*)newBlocklist reply:(void(^)(NSError* error))reply; +- (void)updateBlockEndDate:(NSDate*)newEndDate reply:(void(^)(NSError* error))reply; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCClient.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCClient.m new file mode 100644 index 00000000..024f52fc --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/SCXPCClient.m @@ -0,0 +1,352 @@ +// +// SCAppXPC.m +// SelfControl +// +// Created by Charlie Stigler on 7/4/20. +// + +#import "SCXPCClient.h" +#import "SCDaemonProtocol.h" +#import +#import "SCXPCAuthorization.h" +#import "SCErr.h" + +@interface SCXPCClient () { + AuthorizationRef _authRef; +} + +@property (atomic, strong, readwrite) NSXPCConnection* daemonConnection; +@property (atomic, copy, readwrite) NSData* authorization; + +@end + +@implementation SCXPCClient + +- (void)setupAuthorization { + // this is mostly copied from Apple's Even Better Authorization Sample + + // Create our connection to the authorization system. + // + // If we can't create an authorization reference then the app is not going to be able + // to do anything requiring authorization. Generally this only happens when you launch + // the app in some wacky, and typically unsupported, way. + + // if we've already got an authorization session, no need to make another + if (self.authorization) { + return; + } + + AuthorizationRef authRef; + OSStatus errCode = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, 0, &authRef); + if (errCode) { + NSError* err = [NSError errorWithDomain: NSOSStatusErrorDomain code: errCode userInfo: nil]; + NSLog(@"Failed to set up initial authorization with error %@", err); + // [SCSentry captureError: err]; + } else { + [self updateStoredAuthorization: authRef]; + } +} + +- (void)updateStoredAuthorization:(AuthorizationRef)authRef { + self->_authRef = authRef; + if (!self->_authRef) { + self.authorization = nil; + return; + } + + AuthorizationExternalForm extForm; + OSStatus errCode = AuthorizationMakeExternalForm(self->_authRef, &extForm); + if (errCode) { + NSError* err = [NSError errorWithDomain: NSOSStatusErrorDomain code: errCode userInfo: nil]; + NSLog(@"Failed to update stored authorization with error %@", err); + // [SCSentry captureError: err]; + } else { + self.authorization = [[NSData alloc] initWithBytes: &extForm length: sizeof(extForm)]; + } + + // If we successfully connected to Authorization Services, add definitions for our default + // rights (unless they're already in the database). + [SCXPCAuthorization setupAuthorizationRights: self->_authRef]; +} + +// Ensures that we're connected to our helper tool +// should only be called from the main thread +// Copied from Apple's EvenBetterAuthorizationSample +- (void)connectToHelperTool { + assert([NSThread isMainThread]); + NSLog(@"Connecting to helper tool, daemon connection is %@", self.daemonConnection); + [self setupAuthorization]; + + if (self.daemonConnection == nil) { + self.daemonConnection = [[NSXPCConnection alloc] initWithMachServiceName: @"org.eyebeam.selfcontrold" options: NSXPCConnectionPrivileged]; + self.daemonConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(SCDaemonProtocol)]; + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Warc-retain-cycles" + NSXPCConnection* connection = self.daemonConnection; + connection.invalidationHandler = ^{ + connection.invalidationHandler = connection.interruptionHandler = nil; + + if (connection == self.daemonConnection) { + if ([NSThread isMainThread]) { + self.daemonConnection = nil; + } else { + NSLog(@"About to dispatch_sync"); + dispatch_sync(dispatch_get_main_queue(), ^{ + self.daemonConnection = nil; + }); + } + NSLog(@"CONNECTION INVALIDATED"); + } + }; + // our interruption handler is just our invalidation handler, except we retry afterward + connection.interruptionHandler = ^{ + NSLog(@"Helper tool connection interrupted"); + if (connection.invalidationHandler != nil) { + connection.invalidationHandler(); + } + + // interruptions may have happened because the daemon crashed + // so wait a second and try to reconnect + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + NSLog(@"Retrying helper tool connection!"); + [self connectToHelperTool]; + }); + }; + + #pragma clang diagnostic pop + [self.daemonConnection resume]; + + NSLog(@"Started helper connection!"); + } +} + +- (BOOL)isConnected { + return (self.daemonConnection != nil); +} + +- (void)installDaemon:(void(^)(NSError*))callback { + // make sure authorization is set up (if we haven't connected yet) + [self setupAuthorization]; + + AuthorizationItem blessRight = { + kSMRightBlessPrivilegedHelper, 0, NULL, 0 + }; + AuthorizationItem startBlockRight = { + "com.application.SelfControl.corebits.startBlock", 0, NULL, 0 + }; + AuthorizationItem rightsArr[] = { blessRight, startBlockRight }; + + AuthorizationRights authRights; + authRights.count = 2; + authRights.items = rightsArr; + + AuthorizationFlags myFlags = kAuthorizationFlagDefaults | + kAuthorizationFlagExtendRights | + kAuthorizationFlagInteractionAllowed; + OSStatus status; + + status = AuthorizationCopyRights( + self->_authRef, + &authRights, + kAuthorizationEmptyEnvironment, + myFlags, + NULL + ); + + if(status) { + if (status == AUTH_CANCELLED_STATUS) { + callback([SCErr errorWithCode: 1]); + } else { + NSLog(@"ERROR: Failed to authorize installing selfcontrold with status %d.", status); + NSError* err = [SCErr errorWithCode: 501]; + // [SCSentry captureError: err]; + callback(err); + } + return; + } + + CFErrorRef cfError = NULL; + + // In some cases, SMJobBless will fail if we don't first remove the currently running daemon. + // Attempt removal, but log detailed error info if it fails. + SILENCE_OSX10_10_DEPRECATION( + SMJobRemove(kSMDomainSystemLaunchd, CFSTR("org.eyebeam.selfcontrold"), self->_authRef, YES, &cfError); + ); + if (cfError) { + NSError *removeErr = CFBridgingRelease(cfError); + NSLog(@"WARNING: Failed to remove existing selfcontrold daemon. Domain=%@ Code=%ld UserInfo=%@", + removeErr.domain, (long)removeErr.code, removeErr.userInfo); + // reset for next use + cfError = NULL; + } + + BOOL result = (BOOL)SMJobBless( + kSMDomainSystemLaunchd, + CFSTR("org.eyebeam.selfcontrold"), + self->_authRef, + &cfError); + + if(!result) { + NSError* error = CFBridgingRelease(cfError); + // Log the full error so we can diagnose configuration/signing issues. + NSLog(@"WARNING: Authorized installation of selfcontrold returned failure. Domain=%@ Code=%ld UserInfo=%@", + error.domain, (long)error.code, error.userInfo); + + NSError* err = [SCErr errorWithCode: 500 subDescription: error.localizedDescription]; + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + // [SCSentry captureError: err]; + } + + callback(err); + return; + } else { + NSLog(@"Daemon installed successfully!"); + callback(nil); + } +} + +- (BOOL)connectionIsActive { + return (self.daemonConnection != nil); +} + +- (void)refreshConnectionAndRun:(void(^)(void))callback { + if (self.daemonConnection == nil) { + callback(); + return; + } + void (^standardInvalidationHandler)(void) = self.daemonConnection.invalidationHandler; + + __weak typeof(self) weakSelf = self; + self.daemonConnection.invalidationHandler = ^{ + __strong typeof(self) strongSelf = weakSelf; + if (standardInvalidationHandler != nil) { + standardInvalidationHandler(); + } + strongSelf.daemonConnection = nil; + callback(); + }; + + [self.daemonConnection performSelectorOnMainThread: @selector(invalidate) withObject: nil waitUntilDone: YES]; +} + +// Also copied from Apple's EvenBetterAuthorizationSample +// Connects to the helper tool and then executes the supplied command block on the +// main thread, passing it an error indicating if the connection was successful. +- (void)connectAndExecuteCommandBlock:(void(^)(NSError *))commandBlock { + [self performSelectorOnMainThread: @selector(connectToHelperTool) withObject:nil waitUntilDone: YES]; + commandBlock(nil); +} + +- (void)getVersion:(void(^)(NSString* version, NSError* error))reply { + [self connectAndExecuteCommandBlock:^(NSError * connectError) { + if (connectError != nil) { + NSLog(@"Failed to get daemon version with connection error: %@", connectError); + // [SCSentry captureError: connectError]; + reply(nil, connectError); + } else { + [[self.daemonConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError) { + NSLog(@"Failed to get daemon version with remote object proxy error: %@", proxyError); + // [SCSentry captureError: proxyError]; + reply(nil, proxyError); + }] getVersionWithReply:^(NSString * _Nonnull version) { + reply(version, nil); + }]; + } + }]; +} + +- (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings reply:(void(^)(NSError* error))reply { + [self connectAndExecuteCommandBlock:^(NSError * connectError) { + if (connectError != nil) { + // [SCSentry captureError: connectError]; + NSLog(@"Start block command failed with connection error: %@", connectError); + reply(connectError); + } else { + [[self.daemonConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError) { + NSLog(@"Start block command failed with remote object proxy error: %@", proxyError); + // [SCSentry captureError: proxyError]; + reply(proxyError); + }] startBlockWithControllingUID: controllingUID blocklist: blocklist isAllowlist:isAllowlist endDate:endDate blockSettings: blockSettings authorization: self.authorization reply:^(NSError* error) { + if (error != nil && ![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"Start block failed with error = %@\n", error); + // [SCSentry captureError: error]; + } + reply(error); + }]; + } + }]; +} + +- (void)updateBlocklist:(NSArray*)newBlocklist reply:(void(^)(NSError* error))reply { + [self connectAndExecuteCommandBlock:^(NSError * connectError) { + if (connectError != nil) { + NSLog(@"Blocklist update failed with connection error: %@", connectError); + // [SCSentry captureError: connectError]; + reply(connectError); + } else { + [[self.daemonConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError) { + NSLog(@"Blocklist update command failed with remote object proxy error: %@", proxyError); + // [SCSentry captureError: proxyError]; + reply(proxyError); + }] updateBlocklist: newBlocklist authorization: self.authorization reply:^(NSError* error) { + if (error != nil && ![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"Blocklist update failed with error = %@\n", error); + // [SCSentry captureError: error]; + } + reply(error); + }]; + } + }]; +} + +- (void)updateBlockEndDate:(NSDate*)newEndDate reply:(void(^)(NSError* error))reply { + [self connectAndExecuteCommandBlock:^(NSError * connectError) { + if (connectError != nil) { + NSLog(@"Block end date update failed with connection error: %@", connectError); + // [SCSentry captureError: connectError]; + reply(connectError); + } else { + [[self.daemonConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError) { + NSLog(@"Block end date update command failed with remote object proxy error: %@", proxyError); + // [SCSentry captureError: proxyError]; + reply(proxyError); + }] updateBlockEndDate: newEndDate authorization: self.authorization reply:^(NSError* error) { + if (error != nil && ![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"Block end date update failed with error = %@\n", error); + // [SCSentry captureError: error]; + } + reply(error); + }]; + } + }]; +} + +- (NSString*)selfControlHelperToolPath { + static NSString* path; + + // Cache the path so it doesn't have to be searched for again. + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSBundle* thisBundle = [NSBundle mainBundle]; + path = [thisBundle.bundlePath stringByAppendingString: @"/Contents/Library/LaunchServices/org.eyebeam.selfcontrold"]; + }); + + return path; +} + +- (char*)selfControlHelperToolPathUTF8String { + static char* path; + + // Cache the converted path so it doesn't have to be converted again + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + path = malloc(512); + [[self selfControlHelperToolPath] getCString: path + maxLength: 512 + encoding: NSUTF8StringEncoding]; + }); + + return path; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/LaunchctlHelper.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/LaunchctlHelper.h new file mode 100755 index 00000000..3dec6a77 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/LaunchctlHelper.h @@ -0,0 +1,35 @@ +// +// SCLaunchctlHelper.h +// SelfControl +// +// Created by Charlie Stigler on 2/13/09. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import + +// A simple utility class to deal with launchd jobs by calling the launchctl +// command-line tool. Each method corresponds to a launchctl subcommand. +@interface LaunchctlHelper : NSObject { +} + +// Calls the launchctl command-line tool installed on all newer Mac OS X +// systems to unload a job from the launchd system, which was loaded from the +// plist at the given path. Returns the exit status code of launchctl. ++ (int)unloadLaunchdJobWithPlistAt:(NSString*)pathToLaunchdPlist; + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/LaunchctlHelper.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/LaunchctlHelper.m new file mode 100755 index 00000000..2269fb98 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/LaunchctlHelper.m @@ -0,0 +1,38 @@ +// +// SCLaunchctlHelper.m +// SelfControl +// +// Created by Charlie Stigler on 2/13/09. +// Copyright 2009 Eyebeam. + +// This file is part of SelfControl. +// +// SelfControl is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#import "LaunchctlHelper.h" + +@implementation LaunchctlHelper + ++ (int)unloadLaunchdJobWithPlistAt:(NSString*)pathToLaunchdPlist { + NSTask* task = [[NSTask alloc] init]; + [task setLaunchPath: @"/bin/launchctl"]; + [task setArguments: @[@"unload", + @"-w", + pathToLaunchdPlist]]; + [task launch]; + [task waitUntilExit]; + return [task terminationStatus]; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCBlockUtilities.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCBlockUtilities.h new file mode 100644 index 00000000..3c90ce89 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCBlockUtilities.h @@ -0,0 +1,27 @@ +// +// SCBlockUtilities.h +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SCBlockUtilities : NSObject + +// uses the below methods as well as filesystem checks to see if the block is REALLY running or not ++ (BOOL)anyBlockIsRunning; ++ (BOOL)modernBlockIsRunning; ++ (BOOL)legacyBlockIsRunning; + ++ (BOOL)currentBlockIsExpired; + ++ (BOOL)blockRulesFoundOnSystem; + ++ (void)removeBlockFromSettings; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCBlockUtilities.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCBlockUtilities.m new file mode 100644 index 00000000..ad0efde5 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCBlockUtilities.m @@ -0,0 +1,76 @@ +// +// SCBlockUtilities.m +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#import "SCBlockUtilities.h" +#import "HostFileBlocker.h" +#import "PacketFilter.h" + +@implementation SCBlockUtilities + ++ (BOOL)anyBlockIsRunning { + BOOL blockIsRunning = [SCBlockUtilities modernBlockIsRunning] || [SCBlockUtilities legacyBlockIsRunning]; + + return blockIsRunning; +} + ++ (BOOL)modernBlockIsRunning { + SCSettings* settings = [SCSettings sharedSettings]; + + return [settings boolForKey: @"BlockIsRunning"]; +} + ++ (BOOL)legacyBlockIsRunning { + // first see if there's a legacy settings file from v3.x + // which could be in any user's home folder +// NSError* homeDirErr = nil; +// NSArray* homeDirectoryURLs = [SCMiscUtilities allUserHomeDirectoryURLs: &homeDirErr]; +// if (homeDirectoryURLs != nil) { +// for (NSURL* homeDirURL in homeDirectoryURLs) { +// NSString* relativeSettingsPath = [NSString stringWithFormat: @"/Library/Preferences/%@", SCSettings.settingsFileName]; +// NSURL* settingsFileURL = [homeDirURL URLByAppendingPathComponent: relativeSettingsPath isDirectory: NO]; +// +// if ([SCMigrationUtilities legacyBlockIsRunningInSettingsFile: settingsFileURL]) { +// return YES; +// } +// } +// } +// +// // nope? OK, how about a lock file from pre-3.0? +// if ([SCMigrationUtilities legacyLockFileExists]) { +// return YES; +// } + + // we don't check defaults anymore, though pre-3.0 blocks did + // have data stored there. That should be covered by the lockfile anyway + + return NO; +} + +// returns YES if the block should have expired active based on the specified end time (i.e. the end time is in the past), or NO otherwise ++ (BOOL)currentBlockIsExpired { + // the block should be running if the end date hasn't arrived yet + SCSettings* settings = [SCSettings sharedSettings]; + if ([[settings valueForKey: @"BlockEndDate"] timeIntervalSinceNow] > 0) { + return NO; + } else { + return YES; + } +} + ++ (BOOL)blockRulesFoundOnSystem { + return [PacketFilter blockFoundInPF] || [HostFileBlocker blockFoundInHostsFile]; +} + ++ (void) removeBlockFromSettings { + SCSettings* settings = [SCSettings sharedSettings]; + [settings setValue: @NO forKey: @"BlockIsRunning"]; + [settings setValue: nil forKey: @"BlockEndDate"]; + [settings setValue: nil forKey: @"ActiveBlocklist"]; + [settings setValue: nil forKey: @"ActiveBlockAsWhitelist"]; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCConstants.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCConstants.h new file mode 100644 index 00000000..3758d999 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCConstants.h @@ -0,0 +1,21 @@ +// +// SCConstants.h +// SelfControl +// +// Created by Charlie Stigler on 3/31/19. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +extern OSStatus const AUTH_CANCELLED_STATUS; + +@interface SCConstants : NSObject + +@property (class, readonly, nonatomic) NSArray* systemSoundNames; +@property (class, readonly, nonatomic) NSDictionary* const defaultUserDefaults; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCConstants.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCConstants.m new file mode 100644 index 00000000..79e07a07 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCConstants.m @@ -0,0 +1,75 @@ +// +// SCConstants.m +// SelfControl +// +// Created by Charlie Stigler on 3/31/19. +// + +#import "SCConstants.h" + +OSStatus const AUTH_CANCELLED_STATUS = -60006; + +@implementation SCConstants + ++ (NSArray*)systemSoundNames { + static NSArray* soundsArr = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + soundsArr = @[@"Basso", + @"Blow", + @"Bottle", + @"Frog", + @"Funk", + @"Glass", + @"Hero", + @"Morse", + @"Ping", + @"Pop", + @"Purr", + @"Sosumi", + @"Submarine", + @"Tink"]; + }); + + return soundsArr; +} + ++ (NSDictionary*)defaultUserDefaults { + static NSDictionary* defaultDefaultsDict = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + defaultDefaultsDict = @{ + @"Blocklist": @[], + @"BlockAsWhitelist": @NO, + @"HighlightInvalidHosts": @YES, + @"VerifyInternetConnection": @YES, + @"TimerWindowFloats": @NO, + @"BadgeApplicationIcon": @YES, + @"BlockDuration": @1, + @"MaxBlockLength": @1440, + @"WhitelistAlertSuppress": @NO, + @"GetStartedShown": @NO, + @"EvaluateCommonSubdomains": @YES, + @"IncludeLinkedDomains": @YES, + @"BlockSoundShouldPlay": @NO, + @"BlockSound": @5, + @"ClearCaches": @YES, + @"AllowLocalNetworks": @YES, + // if the user has checked the box to "send crash reports to third-party developers", we'll default Sentry on + // otherwise it defaults off, but we'll still prompt to ask them if we can send data +// @"EnableErrorReporting": @([SCMiscUtilities systemThirdPartyCrashReportingEnabled]), + @"ErrorReportingPromptDismissed": @NO, + @"SuppressLongBlockWarning": @NO, + @"SuppressRestartFirefoxWarning": @NO, + @"FirstBlockStarted": @NO, + + @"V4MigrationComplete": @NO + }; + }); + + return defaultDefaultsDict; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCHelperToolUtilities.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCHelperToolUtilities.h new file mode 100644 index 00000000..3efe4a09 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCHelperToolUtilities.h @@ -0,0 +1,45 @@ +// +// SCHelperToolUtilities.h +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// Utility methods athat are only used by the helper tools +// (i.e. selfcontrold, selfcontrol-cli, and SCKillerHelper) +// note that this is NOT included in SCUtility.h currently! +@interface SCHelperToolUtilities : NSObject + +// Reads the domain block list from the settings for SelfControl, and adds deny +// rules for all of the IPs (or the A DNS record IPS for doamin names) to the +// ipfw firewall. ++ (void)installBlockRulesFromSettings; + +// calls SMJobRemove to unload the daemon from launchd +// (which also kills the running process, synchronously) ++ (void)unloadDaemonJob; + +// Checks the settings system to see whether the user wants their web browser +// caches cleared, and deletes the specific cache folders for a few common +// web browsers if it is required. ++ (void)clearCachesIfRequested; + +// Clear only the caches for browsers ++ (NSError*)clearBrowserCaches; + +// Clear only the OS-level DNS cache ++ (void)clearOSDNSCache; + +// Removes block via settings, host file rules and ipfw rules, +// deleting user caches if requested, and migrating legacy settings. ++ (void)removeBlock; + ++ (void)sendConfigurationChangedNotification; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCHelperToolUtilities.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCHelperToolUtilities.m new file mode 100644 index 00000000..d526eaf0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCHelperToolUtilities.m @@ -0,0 +1,183 @@ +// +// SCHelperToolUtilities.m +// SelfControl +// +// Created by Charlie Stigler on 1/19/21. +// + +#import "SCHelperToolUtilities.h" +#import "BlockManager.h" +#import "SCBlockUtilities.h" + +#import + +@implementation SCHelperToolUtilities + ++ (void)installBlockRulesFromSettings { + SCSettings* settings = [SCSettings sharedSettings]; + BOOL shouldEvaluateCommonSubdomains = [settings boolForKey: @"EvaluateCommonSubdomains"]; + BOOL allowLocalNetworks = [settings boolForKey: @"AllowLocalNetworks"]; + BOOL includeLinkedDomains = [settings boolForKey: @"IncludeLinkedDomains"]; + + // get value for ActiveBlockAsWhitelist + BOOL blockAsAllowlist = [settings boolForKey: @"ActiveBlockAsWhitelist"]; + + BlockManager* blockManager = [[BlockManager alloc] initAsAllowlist: blockAsAllowlist allowLocal: allowLocalNetworks includeCommonSubdomains: shouldEvaluateCommonSubdomains includeLinkedDomains: includeLinkedDomains]; + + NSLog(@"About to run BlockManager commands"); + + [blockManager prepareToAddBlock]; + [blockManager addBlockEntriesFromStrings: [settings valueForKey: @"ActiveBlocklist"]]; + [blockManager finalizeBlock]; + +} + ++ (void)unloadDaemonJob { + NSLog(@"Unloading SelfControl daemon..."); + // [SCSentry addBreadcrumb: @"Daemon about to unload" category: @"daemon"]; + SCSettings* settings = [SCSettings sharedSettings]; + + // we're about to unload the launchd job + // this will kill this process, so we have to make sure + // all settings are synced before we unload + NSError* syncErr = [settings syncSettingsAndWait: 5.0]; + if (syncErr != nil) { + NSLog(@"WARNING: Sync failed or timed out with error %@ before unloading daemon job", syncErr); + // [SCSentry captureError: syncErr]; + } + + // uh-oh, looks like it's 5 seconds later and the sync hasn't completed yet. Bad news. + CFErrorRef cfError; + // this should block until the process is dead, so we should never get to the other side if it's successful + SILENCE_OSX10_10_DEPRECATION( + SMJobRemove(kSMDomainSystemLaunchd, CFSTR("org.eyebeam.selfcontrold"), NULL, YES, &cfError); + ); + if (cfError) { + NSLog(@"Failed to remove selfcontrold daemon with error %@", cfError); + } +} + ++ (void)clearCachesIfRequested { + SCSettings* settings = [SCSettings sharedSettings]; + if(![settings boolForKey: @"ClearCaches"]) { + return; + } + + NSError* err = [SCHelperToolUtilities clearBrowserCaches]; + if (err) { + NSLog(@"WARNING: Error clearing browser caches: %@", err); + // [SCSentry captureError: err]; + } + + [SCHelperToolUtilities clearOSDNSCache]; +} + ++ (NSError*)clearBrowserCaches { + NSFileManager* fileManager = [NSFileManager defaultManager]; + + NSError* homeDirErr = nil; + NSArray* homeDirectoryURLs = [SCMiscUtilities allUserHomeDirectoryURLs: &homeDirErr]; + if (homeDirectoryURLs == nil) return homeDirErr; + + NSArray* cacheDirPathComponents = @[ + // chrome + @"/Library/Caches/Google/Chrome/Default", + @"/Library/Caches/Google/Chrome/com.google.Chrome", + + // firefox + @"/Library/Caches/Firefox/Profiles", + + // safari + @"/Library/Caches/com.apple.Safari", + @"/Library/Containers/com.apple.Safari/Data/Library/Caches" // this one seems to fail due to permissions issues, but not sure how to fix + ]; + + + NSMutableArray* cacheDirURLs = [NSMutableArray arrayWithCapacity: cacheDirPathComponents.count * homeDirectoryURLs.count]; + for (NSURL* homeDirURL in homeDirectoryURLs) { + for (NSString* cacheDirPathComponent in cacheDirPathComponents) { + [cacheDirURLs addObject: [homeDirURL URLByAppendingPathComponent: cacheDirPathComponent isDirectory: YES]]; + } + } + + for (NSURL* cacheDirURL in cacheDirURLs) { + NSLog(@"Clearing browser cache folder %@", cacheDirURL); + // removeItemAtURL will return errors if the file doesn't exist + // so we don't track the errors - best effort is OK + [fileManager removeItemAtURL: cacheDirURL error: nil]; + } + + return nil; +} + ++ (void)clearOSDNSCache { + // no error checks - if it works it works! + NSTask* flushDsCacheUtil = [[NSTask alloc] init]; + [flushDsCacheUtil setLaunchPath: @"/usr/bin/dscacheutil"]; + [flushDsCacheUtil setArguments: @[@"-flushcache"]]; + [flushDsCacheUtil launch]; + [flushDsCacheUtil waitUntilExit]; + + NSTask* killResponder = [[NSTask alloc] init]; + [killResponder setLaunchPath: @"/usr/bin/killall"]; + [killResponder setArguments: @[@"-HUP", @"mDNSResponder"]]; + [killResponder launch]; + [killResponder waitUntilExit]; + + NSTask* killResponderHelper = [[NSTask alloc] init]; + [killResponderHelper setLaunchPath: @"/usr/bin/killall"]; + [killResponderHelper setArguments: @[@"mDNSResponderHelper"]]; + [killResponderHelper launch]; + [killResponderHelper waitUntilExit]; + + NSLog(@"Cleared OS DNS caches"); +} + ++ (void)playBlockEndSound { + SCSettings* settings = [SCSettings sharedSettings]; + if([settings boolForKey: @"BlockSoundShouldPlay"]) { + // Map the tags used in interface builder to the sound + NSArray* systemSoundNames = SCConstants.systemSoundNames; + NSSound* alertSound = [NSSound soundNamed: systemSoundNames[(NSUInteger)[[settings valueForKey: @"BlockSound"] intValue]]]; + if(!alertSound) + NSLog(@"WARNING: Alert sound not found."); + else { + [alertSound play]; + } + } +} + ++ (void)removeBlock { + [SCBlockUtilities removeBlockFromSettings]; + [[BlockManager new] clearBlock]; + + [SCHelperToolUtilities clearCachesIfRequested]; + + // play a sound letting + [SCHelperToolUtilities playBlockEndSound]; + + // always synchronize settings ASAP after removing a block to let everybody else know + // and wait until they're synced before we send the configuration change notification + // so the app has no chance of reading the data before we update it + NSError* syncErr = [[SCSettings sharedSettings] syncSettingsAndWait: 5.0]; + if (syncErr != nil) { + NSLog(@"WARNING: Sync failed or timed out with error %@ after removing block", syncErr); + // [SCSentry captureError: syncErr]; + } + + // let the main app know things have changed so it can update the UI! + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + NSLog(@"INFO: Block cleared."); +} + ++ (void)sendConfigurationChangedNotification { + // if you don't include the NSNotificationPostToAllSessions option, + // it will not deliver when run by launchd (root) to the main app being run by the user + [[NSDistributedNotificationCenter defaultCenter] postNotificationName: @"SCConfigurationChangedNotification" + object: nil + userInfo: nil + options: NSNotificationDeliverImmediately | NSNotificationPostToAllSessions]; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCMiscUtilities.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCMiscUtilities.h new file mode 100644 index 00000000..1658a467 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCMiscUtilities.h @@ -0,0 +1,33 @@ +// +// SCMiscUtilities.h +// SelfControl +// +// Created by Charles Stigler on 07/07/2018. +// + +#import + +// Holds utility methods for use throughout SelfControl + +@interface SCMiscUtilities : NSObject + ++ (dispatch_source_t)createDebounceDispatchTimer:(double) debounceTime queue:(dispatch_queue_t)queue block:(dispatch_block_t)block; + ++ (NSString *)getSerialNumber; ++ (NSString *)sha1:(NSString*)stringToHash; + ++ (BOOL)systemThirdPartyCrashReportingEnabled; + ++ (NSArray*)cleanBlocklistEntry:(NSString*)rawEntry; + ++ (NSArray*)cleanBlocklist:(NSArray*)blocklist; + ++ (NSDictionary*) defaultsDictForUser:(uid_t)controllingUID; + ++ (NSArray*)allUserHomeDirectoryURLs:(NSError**)errPtr; + ++ (BOOL)errorIsAuthCanceled:(NSError*)err; + ++ (NSString*)killerKeyForDate:(NSDate*)date; + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCMiscUtilities.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCMiscUtilities.m new file mode 100644 index 00000000..3b45490d --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/Common/Utilities/SCMiscUtilities.m @@ -0,0 +1,250 @@ +// +// SCMiscUtilities.m +// SelfControl +// +// Created by Charles Stigler on 07/07/2018. +// + +//#import "SCHelperToolUtilities.h" +//#import "SCSettings.h" +#import +#include + +@implementation SCMiscUtilities + +// copied from stevenojo's GitHub snippet: https://gist.github.com/stevenojo/e1dcc2b3e2fd4ed1f411eef88e254cb0 ++ (dispatch_source_t)createDebounceDispatchTimer:(double)debounceTime queue:(dispatch_queue_t)queue block:(dispatch_block_t)block { + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + + if (timer) { + dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, debounceTime * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, (1ull * NSEC_PER_SEC) / 10); + dispatch_source_set_event_handler(timer, block); + dispatch_resume(timer); + } + + return timer; +} + +// by Martin R et al on StackOverflow: https://stackoverflow.com/a/15451318 ++ (NSString *)getSerialNumber { + NSString *serial = nil; + io_service_t platformExpert = IOServiceGetMatchingService(kIOMainPortDefault, + IOServiceMatching("IOPlatformExpertDevice")); + if (platformExpert) { + CFTypeRef serialNumberAsCFString = + IORegistryEntryCreateCFProperty(platformExpert, + CFSTR(kIOPlatformSerialNumberKey), + kCFAllocatorDefault, 0); + if (serialNumberAsCFString) { + serial = CFBridgingRelease(serialNumberAsCFString); + } + + IOObjectRelease(platformExpert); + } + return serial; +} +// by hypercrypt et al on StackOverflow: https://stackoverflow.com/a/7571583 ++ (NSString *)sha1:(NSString*)stringToHash +{ + NSData *data = [stringToHash dataUsingEncoding:NSUTF8StringEncoding]; + uint8_t digest[CC_SHA1_DIGEST_LENGTH]; + + CC_SHA1(data.bytes, (CC_LONG)data.length, digest); + + NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2]; + + for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) + { + [output appendFormat:@"%02x", digest[i]]; + } + + return output; +} + ++ (BOOL)systemThirdPartyCrashReportingEnabled { + NSUserDefaults* appleCrashReporter = [NSUserDefaults standardUserDefaults]; + [appleCrashReporter addSuiteNamed: @"/Library/Application Support/CrashReporter/DiagnosticMessagesHistory.plist"]; + + return [appleCrashReporter boolForKey: @"ThirdPartyDataSubmit"]; +} + +// Standardize and clean up the input value so it'll block properly (and look good doing it) +// note that if the user entered line breaks, we'll split it into many entries, so this can return multiple +// cleaned entries in the NSArray it returns ++ (NSArray*) cleanBlocklistEntry:(NSString*)rawEntry { + if (rawEntry == nil) return @[]; + + // This'll remove whitespace and lowercase the string. + NSString* str = [[rawEntry stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] lowercaseString]; + + // if there are newlines in the string, split it and process it as many strings + if([str rangeOfCharacterFromSet: [NSCharacterSet newlineCharacterSet]].location != NSNotFound) { + NSArray* splitEntries = [str componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]]; + + NSMutableArray* returnArr = [NSMutableArray new]; + for (NSString* splitEntry in splitEntries) { + // recursion makes the rest of the code prettier + NSArray* cleanedSubEntries = [SCMiscUtilities cleanBlocklistEntry: splitEntry]; + [returnArr addObjectsFromArray: cleanedSubEntries]; + } + return returnArr; + } + + // if the user entered a scheme (https://, http://, etc) remove it. + // We only block hostnames so scheme is ignored anyway and it can gunk up the blocking + NSArray* separatedStr = [str componentsSeparatedByString: @"://"]; + str = [separatedStr lastObject]; + + // Remove URL login names/passwords (username:password@host) if a user tried to put that in + separatedStr = [str componentsSeparatedByString: @"@"]; + str = [separatedStr lastObject]; + + // now here's where it gets tricky. Besides just hostnames, we also support CIDR IP ranges, for example: 83.0.1.2/24 + // so we are gonna keep track of whether we might have received a valid CIDR IP range instead of hostname as we go... + // we also take port numbers, so keep track of whether we have one of those + int cidrMaskBits = -1; + int portNum = -1; + + // first pull off everything after a slash + // discard the end if it's just a path, but check to see if it might be our CIDR mask length + separatedStr = [str componentsSeparatedByString: @"/"]; + str = [separatedStr firstObject]; + + // if the part after a slash is an integer between 1 and 128, it could be our mask length + if (separatedStr.count > 1) { + int potentialMaskLen = [[separatedStr lastObject] intValue]; + if (potentialMaskLen > 0 && potentialMaskLen <= 128) cidrMaskBits = potentialMaskLen; + } + + // check for the port + separatedStr = [str componentsSeparatedByString: @":"]; + str = [separatedStr firstObject]; + + if (separatedStr.count > 1) { + int potentialPort = [[separatedStr lastObject] intValue]; + if (potentialPort > 0 && potentialPort <= 65535) { + portNum = potentialPort; + } + } + + // remove invalid characters from the hostname + // hostnames are 1-253 characters long, and can contain only a-z, A-Z, 0-9, -, and ., and maybe _ (mostly not but kinda) + // for some reason [NSCharacterSet URLHostAllowedCharacterSet] has tons of other characters that aren't actually valid + NSMutableCharacterSet* invalidHostnameChars = [[NSCharacterSet alphanumericCharacterSet] mutableCopy]; + [invalidHostnameChars addCharactersInString: @"-._"]; + [invalidHostnameChars invert]; + + NSMutableString* validCharsOnly = [NSMutableString stringWithCapacity: str.length]; + for (NSUInteger i = 0; i < str.length && i < 253; i++) { + unichar c = [str characterAtIndex: i]; + if (![invalidHostnameChars characterIsMember: c]) { + [validCharsOnly appendFormat: @"%C", c]; + } + } + str = validCharsOnly; + + // allow blocking an empty hostname IFF we're only blocking a single port number (i.e. :80) + // otherwise, empty hostname = nothing to do + if (str.length < 1 && portNum < 0) { + return @[]; + } + + NSString* maskString; + NSString* portString; + + // create a mask string if we have one + if (cidrMaskBits < 0) { + maskString = @""; + } else { + maskString = [NSString stringWithFormat: @"/%d", cidrMaskBits]; + } + + // create a port string if we have one + if (portNum < 0) { + portString = @""; + } else { + portString = [NSString stringWithFormat: @":%d", portNum]; + } + + // combine em together and you got something! + return @[[NSString stringWithFormat: @"%@%@%@", str, maskString, portString]]; +} + ++ (NSArray*)cleanBlocklist:(NSArray*)blocklist { + NSMutableArray* cleanedList = [NSMutableArray arrayWithCapacity: blocklist.count]; + + // for now, we just remove whitespace and then remove empty entries + // in the future, this method could do more thorough cleaning + for (NSString* blockString in blocklist) { + NSString* cleanedString = [blockString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + if (cleanedString.length > 0) { + [cleanedList addObject: cleanedString]; + } + } + + return cleanedList; +} + ++ (NSDictionary*) defaultsDictForUser:(uid_t) controllingUID { + if (geteuid() != 0) { + // if we're not root, we can't just get defaults for some arbitrary user + return nil; + } + + // pull up the user's defaults in the old legacy way + // to do that, we have to seteuid to the controlling UID so NSUserDefaults thinks we're them + seteuid(controllingUID); + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + [defaults addSuiteNamed: @"org.eyebeam.SelfControl"]; + [defaults registerDefaults: SCConstants.defaultUserDefaults]; + [defaults synchronize]; + NSDictionary* dictValue = [defaults dictionaryRepresentation]; + // reset the euid so nothing else gets funky + [NSUserDefaults resetStandardUserDefaults]; + seteuid(0); + + return dictValue; +} + ++ (BOOL)errorIsAuthCanceled:(NSError*)err { + if (err == nil) return NO; + + if ([err.domain isEqualToString: NSOSStatusErrorDomain] && err.code == AUTH_CANCELLED_STATUS) { + return YES; + } + if ([err.domain isEqualToString: kSelfControlErrorDomain] && err.code == 1) { + return YES; + } + + return NO; +} + ++ (NSArray*)allUserHomeDirectoryURLs:(NSError**)errPtr { + NSError* retErr = nil; + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSURL* usersFolderURL = [NSURL fileURLWithPath: @"/Users"]; + NSArray* homeDirectoryURLs = [fileManager contentsOfDirectoryAtURL: usersFolderURL + includingPropertiesForKeys: @[NSURLPathKey, NSURLIsDirectoryKey, NSURLIsReadableKey] + options: NSDirectoryEnumerationSkipsHiddenFiles + error: &retErr]; + if (homeDirectoryURLs == nil || homeDirectoryURLs.count == 0) { + if (retErr != nil) { + *errPtr = retErr; + } else { + *errPtr = [SCErr errorWithCode: 700]; + } + +// [SCSentry captureError: *errPtr]; + + return nil; + } + + return homeDirectoryURLs; +} + ++ (NSString*)killerKeyForDate:(NSDate*)date { + return [SCMiscUtilities sha1: [NSString stringWithFormat: @"SelfControlKillerKey%@%@", [SCMiscUtilities getSerialNumber], [date descriptionWithLocale: nil]]]; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.h new file mode 100644 index 00000000..bff468e6 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.h @@ -0,0 +1,18 @@ +// +// ConsoleLog.h +// SelfControl +// +// Created by Satendra Singh on 25/12/25. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ConsoleLog : NSObject + ++ (void)log:(NSString *)message; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.m new file mode 100644 index 00000000..dca0d2e1 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.m @@ -0,0 +1,17 @@ +// +// ConsoleLog.m +// SelfControl +// +// Created by Satendra Singh on 25/12/25. +// + +#import "ConsoleLog.h" +#include + +@implementation ConsoleLog + ++ (void)log:(NSString *)message { + os_log(OS_LOG_DEFAULT, "ConsoleLog: %@", message); +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/DaemonMain.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/DaemonMain.m new file mode 100644 index 00000000..89cec21b --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/DaemonMain.m @@ -0,0 +1,25 @@ +// +// DaemonMain.m +// SelfControl +// +// Created by Charlie Stigler on 5/28/20. +// + +#import +#import "SCDaemon.h" + +// Entry point for the SelfControl daemon process (selfcontrold) +int main(int argc, const char *argv[]) { +// // [SCSentry startSentry: @"org.eyebeam.selfcontrold"]; + + // get the daemon object going + SCDaemon* daemon = [SCDaemon sharedDaemon]; + [daemon start]; + + NSLog(@"running forever"); + + // never gonna give you up, never gonna let you down, never gonna run around and desert you... + [[NSRunLoop currentRunLoop] run]; + + return 0; +} diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.h new file mode 100644 index 00000000..1e857311 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.h @@ -0,0 +1,40 @@ +// +// SCDaemon.h +// SelfControl +// +// Created by Charlie Stigler on 5/28/20. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// SCDaemon is the top-level class that runs the SelfControl +// daemon process (selfcontrold). It runs from DaemonMain. +@interface SCDaemon : NSObject + +// Singleton instance of SCDaemon ++ (instancetype)sharedDaemon; + + +// Starts the daemon tasks, including accepting XPC connections +// and running block checkup jobs if necessary +- (void)start; + +// Starts checking up on the block on a regular basis +// to make sure it hasn't expired, been tampered with, etc +// (and will remove it or fix it if so) +- (void)startCheckupTimer; + +// Stops the checkup timer (this should only be called if there's +// no block running, because we should have checkups going for all blocks) +- (void)stopCheckupTimer; + +// Lets the daemon know that there was recent activity +// so we can reset our inactivity timer. +// The daemon will die if goes for too long without activity. +- (void)resetInactivityTimer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.m new file mode 100644 index 00000000..c85d1a99 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.m @@ -0,0 +1,195 @@ +// +// SCDaemon.m +// SelfControl +// +// Created by Charlie Stigler on 5/28/20. +// + +#import "SCDaemon.h" +#import "SCDaemonProtocol.h" +#import "SCDaemonXPC.h" +#import"SCDaemonBlockMethods.h" +#import "SCFileWatcher.h" +#import "SCBlockUtilities.h" +#import "SCHelperToolUtilities.h" + +static NSString* serviceName = @"org.eyebeam.selfcontrold"; +float const INACTIVITY_LIMIT_SECS = 60 * 2; // 2 minutes + +@interface NSXPCConnection(PrivateAuditToken) + +// This property exists, but it's private. Make it available: +@property (nonatomic, readonly) audit_token_t auditToken; + +@end + +@interface SCDaemon () + +@property (nonatomic, strong, readwrite) NSXPCListener* listener; +@property (strong, readwrite) NSTimer* checkupTimer; +@property (strong, readwrite) NSTimer* inactivityTimer; +@property (nonatomic, strong, readwrite) NSDate* lastActivityDate; + +@property (nonatomic, strong) SCFileWatcher* hostsFileWatcher; + +@end + +@implementation SCDaemon + ++ (instancetype)sharedDaemon { + static SCDaemon* daemon = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + daemon = [SCDaemon new]; + }); + return daemon; +} + +- (id) init { + _listener = [[NSXPCListener alloc] initWithMachServiceName: serviceName]; + _listener.delegate = self; + + return self; +} + +- (void)start { + [self.listener resume]; + + // if there's any evidence of a block (i.e. an official one running, + // OR just block remnants remaining in hosts), we should start + // running checkup regularly so the block gets found/removed + // at the proper time. + // we do NOT run checkup if there's no block, because it can result + // in the daemon actually unloading itself before the app has a chance + // to start the block + if ([SCBlockUtilities anyBlockIsRunning] || [SCBlockUtilities blockRulesFoundOnSystem]) { + [self startCheckupTimer]; + } + + [self startInactivityTimer]; + [self resetInactivityTimer]; + + self.hostsFileWatcher = [SCFileWatcher watcherWithFile: @"/etc/hosts" block:^(NSError * _Nonnull error) { + if ([SCBlockUtilities anyBlockIsRunning]) { + NSLog(@"INFO: hosts file changed, checking block integrity"); + [SCDaemonBlockMethods checkBlockIntegrity]; + } + }]; +} + +- (void)startCheckupTimer { + // this method must always be called on the main thread, so the timer will work properly + if (![NSThread isMainThread]) { + dispatch_sync(dispatch_get_main_queue(), ^{ + [self startCheckupTimer]; + }); + return; + } + + // if the timer's already running, don't stress it! + if (self.checkupTimer != nil) { + return; + } + + self.checkupTimer = [NSTimer scheduledTimerWithTimeInterval: 1 repeats: YES block:^(NSTimer * _Nonnull timer) { + [SCDaemonBlockMethods checkupBlock]; + }]; + + // run the first checkup immediately! + [SCDaemonBlockMethods checkupBlock]; +} +- (void)stopCheckupTimer { + if (self.checkupTimer == nil) { + return; + } + + [self.checkupTimer invalidate]; + self.checkupTimer = nil; +} + + +- (void)startInactivityTimer { + self.inactivityTimer = [NSTimer scheduledTimerWithTimeInterval: 15.0 repeats: YES block:^(NSTimer * _Nonnull timer) { + // we haven't had any activity in a while, the daemon appears to be idling + // so kill it to avoid the user having unnecessary processes running! + if ([[NSDate date] timeIntervalSinceDate: self.lastActivityDate] > INACTIVITY_LIMIT_SECS) { + // if we're inactive but also there's a block running, that's a bad thing + // start the checkups going again - unclear why they would've stopped + if ([SCBlockUtilities anyBlockIsRunning] || [SCBlockUtilities blockRulesFoundOnSystem]) { + [self startCheckupTimer]; + [SCDaemonBlockMethods checkupBlock]; + return; + } + + NSLog(@"Daemon inactive for more than %f seconds, exiting!", INACTIVITY_LIMIT_SECS); + [SCHelperToolUtilities unloadDaemonJob]; + } + }]; +} +- (void)resetInactivityTimer { + self.lastActivityDate = [NSDate date]; +} + +- (void)dealloc { + if (self.checkupTimer) { + [self.checkupTimer invalidate]; + self.checkupTimer = nil; + } + if (self.inactivityTimer) { + [self.inactivityTimer invalidate]; + self.inactivityTimer = nil; + } + if (self.hostsFileWatcher) { + [self.hostsFileWatcher stopWatching]; + self.hostsFileWatcher = nil; + } +} + +#pragma mark - NSXPCListenerDelegate + +- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { +#if DEBUG + // In Debug builds, skip code signature validation to ease development. + NSLog(@"[DEBUG] Accepting XPC connection without signature validation."); + // [SCSentry addBreadcrumb:@"DEBUG: accepted XPC connection without validation" category:@"daemon"]; +#else + // There is a potential security issue / race condition with matching based on PID, so we use the (technically private) auditToken instead + audit_token_t auditToken = newConnection.auditToken; + NSDictionary* guestAttributes = @{ + (id)kSecGuestAttributeAudit: [NSData dataWithBytes: &auditToken length: sizeof(audit_token_t)] + }; + SecCodeRef guest; + if (SecCodeCopyGuestWithAttributes(NULL, (__bridge CFDictionaryRef _Nullable)(guestAttributes), kSecCSDefaultFlags, &guest) != errSecSuccess) { + return NO; + } + + SecRequirementRef isSelfControlApp; + // versions before 4.0 didn't have hardened code signing, so aren't trustworthy to talk to the daemon + // (plus the daemon didn't exist before 4.0 so there's really no reason they should want to run it!) + SecRequirementCreateWithString(CFSTR("anchor apple generic and (identifier \"org.eyebeam.SelfControl\" or identifier \"org.eyebeam.selfcontrol-cli\" or identifier \"com.application.SelfControl.corebits\") and info [CFBundleVersion] >= \"407\""), kSecCSDefaultFlags, &isSelfControlApp); + OSStatus clientValidityStatus = SecCodeCheckValidity(guest, kSecCSDefaultFlags, isSelfControlApp); + + CFRelease(guest); + CFRelease(isSelfControlApp); + + if (clientValidityStatus) { + NSError* error = [NSError errorWithDomain: NSOSStatusErrorDomain code: clientValidityStatus userInfo: nil]; + NSLog(@"Rejecting XPC connection because of invalid client signing. Error was %@", error); + // [SCSentry captureError: error]; + return NO; + } +#endif + + SCDaemonXPC* scdXPC = [[SCDaemonXPC alloc] init]; + newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol: @protocol(SCDaemonProtocol)]; + newConnection.exportedObject = scdXPC; + + [newConnection resume]; + + NSLog(@"Accepted new connection!"); + // [SCSentry addBreadcrumb: @"Daemon accepted new connection" category: @"daemon"]; + + return YES; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonBlockMethods.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonBlockMethods.h new file mode 100644 index 00000000..ca1ccf6d --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonBlockMethods.h @@ -0,0 +1,36 @@ +// +// SCDaemonBlockMethods.h +// org.eyebeam.selfcontrold +// +// Created by Charlie Stigler on 7/4/20. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +// Top-level logic for different methods run by the SelfControl daemon +// these logics can be run by XPC methods, or elsewhere +@interface SCDaemonBlockMethods : NSObject + +@property (class, readonly) NSLock* daemonMethodLock; + +// Starts a block ++ (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + +// Checks whether the block is expired or compromised, and takes action to fix ++ (void)checkupBlock; + +// updates the blocklist for the currently running block +// (i.e. adds new sites to the list) ++ (void)updateBlocklist:(NSArray*)newBlocklist authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + +// updates the block end date for the currently running block +// (i.e. extends the block) ++ (void)updateBlockEndDate:(NSDate*)newEndDate authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + ++ (void)checkBlockIntegrity; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonBlockMethods.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonBlockMethods.m new file mode 100644 index 00000000..e8c9b2ea --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonBlockMethods.m @@ -0,0 +1,391 @@ +// +// SCDaemonBlockMethods.m +// org.eyebeam.selfcontrold +// +// Created by Charlie Stigler on 7/4/20. +// + +#import "SCDaemonBlockMethods.h" +#import "SCSettings.h" +#import "SCHelperToolUtilities.h" +#import "PacketFilter.h" +#import "BlockManager.h" +#import "SCDaemon.h" +#import "LaunchctlHelper.h" +#import "HostFileBlockerSet.h" +#import "SCBlockUtilities.h" +#import "SCErr.h" +//#import "SCMigrationUtilities.h" + +NSTimeInterval METHOD_LOCK_TIMEOUT = 5.0; +NSTimeInterval CHECKUP_LOCK_TIMEOUT = 0.5; // use a shorter lock timeout for checkups, because we'd prefer not to have tons pile up + +@implementation SCDaemonBlockMethods + ++ (NSLock*)daemonMethodLock { + static NSLock* lock = nil; + if (lock == nil) { + lock = [[NSLock alloc] init]; + } + return lock; +} + ++ (BOOL)lockOrTimeout:(void(^)(NSError* error))reply timeout:(NSTimeInterval)timeout { + // only run one request at a time, so we avoid weird situations like trying to run a checkup while we're starting a block + if (![self.daemonMethodLock lockBeforeDate: [NSDate dateWithTimeIntervalSinceNow: timeout]]) { + // if we couldn't get a lock within 10 seconds, something is weird + // but we probably shouldn't still run, because that's just unexpected at that point + // don't capture this error on Sentry because it's very usual for checkups to timeout + NSError* err = [SCErr errorWithCode: 300]; + NSLog(@"ERROR: Timed out acquiring request lock (after %f seconds)", timeout); + + if (reply != nil) { + reply(err); + } + return NO; + } + return YES; +} ++ (BOOL)lockOrTimeout:(void(^)(NSError* error))reply { + return [self lockOrTimeout: reply timeout: METHOD_LOCK_TIMEOUT]; +} + + ++ (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + if (![SCDaemonBlockMethods lockOrTimeout: reply]) { + return; + } + + // we reset at the _end_ of every method, but we'll also reset at the _start_ here + // because startBlock can sometimes take a while, and it'd be a shame if the daemon killed itself + // before we were done + [[SCDaemon sharedDaemon] resetInactivityTimer]; + + // [SCSentry addBreadcrumb: @"Daemon method startBlock called" category: @"daemon"]; + + if ([SCBlockUtilities anyBlockIsRunning]) { + NSLog(@"ERROR: Can't start block since a block is already running"); + NSError* err = [SCErr errorWithCode: 301]; + // [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + + // clear any legacy block information - no longer useful and could potentially confuse things + // but first, copy it over one more time (this should've already happened once in the app, but you never know) +// if ([SCMigrationUtilities legacySettingsFoundForUser: controllingUID]) { +// [SCMigrationUtilities copyLegacySettingsToDefaults: controllingUID]; +// [SCMigrationUtilities clearLegacySettingsForUser: controllingUID]; +// +// // if we had legacy settings, there's a small chance the old helper tool could still be around +// // make sure it's dead and gone +// [LaunchctlHelper unloadLaunchdJobWithPlistAt: @"/Library/LaunchDaemons/org.eyebeam.SelfControl.plist"]; +// } + + SCSettings* settings = [SCSettings sharedSettings]; + // update SCSettings with the blocklist and end date that've been requested + [settings setValue: blocklist forKey: @"ActiveBlocklist"]; + [settings setValue: @(isAllowlist) forKey: @"ActiveBlockAsWhitelist"]; + [settings setValue: endDate forKey: @"BlockEndDate"]; + + // update all the settings for the block, which we're basically just copying from defaults to settings + [settings setValue: blockSettings[@"ClearCaches"] forKey: @"ClearCaches"]; + [settings setValue: blockSettings[@"AllowLocalNetworks"] forKey: @"AllowLocalNetworks"]; + [settings setValue: blockSettings[@"EvaluateCommonSubdomains"] forKey: @"EvaluateCommonSubdomains"]; + [settings setValue: blockSettings[@"IncludeLinkedDomains"] forKey: @"IncludeLinkedDomains"]; + [settings setValue: blockSettings[@"BlockSoundShouldPlay"] forKey: @"BlockSoundShouldPlay"]; + [settings setValue: blockSettings[@"BlockSound"] forKey: @"BlockSound"]; + [settings setValue: blockSettings[@"EnableErrorReporting"] forKey: @"EnableErrorReporting"]; + + if(([blocklist count] <= 0 && !isAllowlist) || [SCBlockUtilities currentBlockIsExpired]) { + NSLog(@"ERROR: Blocklist is empty, or block end date is in the past"); + NSLog(@"Block End Date: %@ (%@), vs now is %@", [settings valueForKey: @"BlockEndDate"], [[settings valueForKey: @"BlockEndDate"] class], [NSDate date]); + NSError* err = [SCErr errorWithCode: 302]; + // [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + + NSLog(@"Adding firewall rules..."); + [SCHelperToolUtilities installBlockRulesFromSettings]; + [settings setValue: @YES forKey: @"BlockIsRunning"]; + + NSError* syncErr = [settings syncSettingsAndWait: 5]; // synchronize ASAP since BlockIsRunning is a really important one + if (syncErr != nil) { + NSLog(@"WARNING: Sync failed or timed out with error %@ after starting block", syncErr); + // [SCSentry captureError: syncErr]; + } + + NSLog(@"Firewall rules added!"); + + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + // Clear all caches if the user has the correct preference set, so + // that blocked pages are not loaded from a cache. + [SCHelperToolUtilities clearCachesIfRequested]; + + // [SCSentry addBreadcrumb: @"Daemon added block successfully" category: @"daemon"]; + NSLog(@"INFO: Block successfully added."); + reply(nil); + + [[SCDaemon sharedDaemon] resetInactivityTimer]; + [[SCDaemon sharedDaemon] startCheckupTimer]; + [self.daemonMethodLock unlock]; +} + ++ (void)updateBlocklist:(NSArray*)newBlocklist authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + if (![SCDaemonBlockMethods lockOrTimeout: reply]) { + return; + } + + // [SCSentry addBreadcrumb: @"Daemon method updateBlocklist called" category: @"daemon"]; + if ([SCBlockUtilities legacyBlockIsRunning]) { + NSLog(@"ERROR: Can't update blocklist because a legacy block is running"); + NSError* err = [SCErr errorWithCode: 303]; + // [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + if (![SCBlockUtilities modernBlockIsRunning]) { + NSLog(@"ERROR: Can't update blocklist since block isn't running"); + NSError* err = [SCErr errorWithCode: 304]; + // [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + + SCSettings* settings = [SCSettings sharedSettings]; + + if ([settings boolForKey: @"ActiveBlockAsWhitelist"]) { + NSLog(@"ERROR: Attempting to update active blocklist, but this is not possible with an allowlist block"); + NSError* err = [SCErr errorWithCode: 305]; + // [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + + NSArray* activeBlocklist = [settings valueForKey: @"ActiveBlocklist"]; + NSMutableArray* added = [NSMutableArray arrayWithArray: newBlocklist]; + [added removeObjectsInArray: activeBlocklist]; + NSMutableArray* removed = [NSMutableArray arrayWithArray: activeBlocklist]; + [removed removeObjectsInArray: newBlocklist]; + + // throw a warning if something got removed for some reason, since we ignore them + if (removed.count > 0) { + NSLog(@"WARNING: Active blocklist has removed items; these will not be updated. Removed items are %@", removed); + } + + BlockManager* blockManager = [[BlockManager alloc] initAsAllowlist: [settings boolForKey: @"ActiveBlockAsWhitelist"] + allowLocal: [settings boolForKey: @"EvaluateCommonSubdomains"] + includeCommonSubdomains: [settings boolForKey: @"AllowLocalNetworks"] + includeLinkedDomains: [settings boolForKey: @"IncludeLinkedDomains"]]; + [blockManager enterAppendMode]; + [blockManager addBlockEntriesFromStrings: added]; + [blockManager finishAppending]; + + [settings setValue: newBlocklist forKey: @"ActiveBlocklist"]; + + // make sure everyone knows about our new list + NSError* syncErr = [settings syncSettingsAndWait: 5]; + if (syncErr != nil) { + NSLog(@"WARNING: Sync failed or timed out with error %@ after updating blocklist", syncErr); + // [SCSentry captureError: syncErr]; + } + + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + // Clear all caches if the user has the correct preference set, so + // that blocked pages are not loaded from a cache. + [SCHelperToolUtilities clearCachesIfRequested]; + + // [SCSentry addBreadcrumb: @"Daemon updated blocklist successfully" category: @"daemon"]; + NSLog(@"INFO: Blocklist successfully updated."); + reply(nil); + + [[SCDaemon sharedDaemon] resetInactivityTimer]; + [self.daemonMethodLock unlock]; +} + ++ (void)updateBlockEndDate:(NSDate*)newEndDate authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + if (![SCDaemonBlockMethods lockOrTimeout: reply]) { + return; + } + + // [SCSentry addBreadcrumb: @"Daemon method updateBlockEndDate called" category: @"daemon"]; + + if ([SCBlockUtilities legacyBlockIsRunning]) { + NSLog(@"ERROR: Can't update block end date because a legacy block is running"); + NSError* err = [SCErr errorWithCode: 306]; + // [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + if (![SCBlockUtilities modernBlockIsRunning]) { + NSLog(@"ERROR: Can't update block end date since block isn't running"); + NSError* err = [SCErr errorWithCode: 307]; + // [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + return; + } + + SCSettings* settings = [SCSettings sharedSettings]; + + // this can only be used to *extend* the block end date - not shorten it! + // and we also won't let them extend by more than 24 hours at a time, for safety... + // TODO: they should be able to extend up to MaxBlockLength minutes, right? + NSDate* currentEndDate = [settings valueForKey: @"BlockEndDate"]; + if ([newEndDate timeIntervalSinceDate: currentEndDate] < 0) { + NSLog(@"ERROR: Can't update block end date to an earlier date"); + NSError* err = [SCErr errorWithCode: 308]; + // [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + } + if ([newEndDate timeIntervalSinceDate: currentEndDate] > 86400) { // 86400 seconds = 1 day + NSLog(@"ERROR: Can't extend block end date by more than 1 day at a time"); + NSError* err = [SCErr errorWithCode: 309]; + // [SCSentry captureError: err]; + reply(err); + [self.daemonMethodLock unlock]; + } + + [settings setValue: newEndDate forKey: @"BlockEndDate"]; + + // make sure everyone knows about our new end date + NSError* syncErr = [settings syncSettingsAndWait: 5]; + if (syncErr != nil) { + NSLog(@"WARNING: Sync failed or timed out with error %@ after extending block", syncErr); + // [SCSentry captureError: syncErr]; + } + + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + // [SCSentry addBreadcrumb: @"Daemon extended block successfully" category: @"daemon"]; + NSLog(@"INFO: Block successfully extended."); + reply(nil); + + [[SCDaemon sharedDaemon] resetInactivityTimer]; + [self.daemonMethodLock unlock]; +} + ++ (void)checkupBlock { + if (![SCDaemonBlockMethods lockOrTimeout: nil timeout: CHECKUP_LOCK_TIMEOUT]) { + return; + } + + // [SCSentry addBreadcrumb: @"Daemon method checkupBlock called" category: @"daemon"]; + + NSTimeInterval integrityCheckIntervalSecs = 15.0; + static NSDate* lastBlockIntegrityCheck; + if (lastBlockIntegrityCheck == nil) { + lastBlockIntegrityCheck = [NSDate distantPast]; + } + + BOOL shouldRunIntegrityCheck = NO; + if(![SCBlockUtilities anyBlockIsRunning]) { + // No block appears to be running at all in our settings. + // Most likely, the user removed it trying to get around the block. Boo! + // but for safety and to avoid permablocks (we no longer know when the block should end) + // we should clear the block now. + // but let them know that we noticed their (likely) cheating and we're not happy! + NSLog(@"INFO: Checkup ran, no active block found."); + + // [SCSentry captureMessage: @"Checkup ran and no active block found! Removing block, tampering suspected..."]; + + [SCHelperToolUtilities removeBlock]; + + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + // Temporarily disabled the TamperingDetection flag because it was sometimes causing false positives + // (i.e. people having the background set repeatedly despite no attempts to cheat) + // We will try to bring this feature back once we can debug it + // GitHub issue: https://github.com/SelfControlApp/selfcontrol/issues/621 + // [settings setValue: @YES forKey: @"TamperingDetected"]; + // [settings synchronizeSettings]; + // + + // once the checkups stop, the daemon will clear itself in a while due to inactivity + [[SCDaemon sharedDaemon] stopCheckupTimer]; + } else if ([SCBlockUtilities currentBlockIsExpired]) { + NSLog(@"INFO: Checkup ran, block expired, removing block."); + + [SCHelperToolUtilities removeBlock]; + + [SCHelperToolUtilities sendConfigurationChangedNotification]; + + // [SCSentry addBreadcrumb: @"Daemon found and cleared expired block" category: @"daemon"]; + + // once the checkups stop, the daemon will clear itself in a while due to inactivity + [[SCDaemon sharedDaemon] stopCheckupTimer]; + } else if ([[NSDate date] timeIntervalSinceDate: lastBlockIntegrityCheck] > integrityCheckIntervalSecs) { + lastBlockIntegrityCheck = [NSDate date]; + // The block is still on. Every once in a while, we should + // check if anybody removed our rules, and if so + // re-add them. + shouldRunIntegrityCheck = YES; + } + + [[SCDaemon sharedDaemon] resetInactivityTimer]; + [self.daemonMethodLock unlock]; + + // if we need to run an integrity check, we need to do it at the very end after we give up our lock + // because checkBlockIntegrity requests its own lock, and we don't want it to deadlock + if (shouldRunIntegrityCheck) { + [SCDaemonBlockMethods checkBlockIntegrity]; + } +} + ++ (void)checkBlockIntegrity { + if (![SCDaemonBlockMethods lockOrTimeout: nil timeout: CHECKUP_LOCK_TIMEOUT]) { + return; + } + + // [SCSentry addBreadcrumb: @"Daemon method checkBlockIntegrity called" category: @"daemon"]; + + SCSettings* settings = [SCSettings sharedSettings]; + PacketFilter* pf = [[PacketFilter alloc] init]; + HostFileBlockerSet* hostFileBlockerSet = [[HostFileBlockerSet alloc] init]; + if(![pf containsSelfControlBlock] || (![settings boolForKey: @"ActiveBlockAsWhitelist"] && ![hostFileBlockerSet.defaultBlocker containsSelfControlBlock])) { + NSLog(@"INFO: Block is missing in PF or hosts, re-adding..."); + // The firewall is missing at least the block header. Let's clear everything + // before we re-add to make sure everything goes smoothly. + + [pf stopBlock: false]; + + [hostFileBlockerSet removeSelfControlBlock]; + BOOL success = [hostFileBlockerSet writeNewFileContents]; + // Revert the host file blocker's file contents to disk so we can check + // whether or not it still contains the block after our write (aka we messed up). + [hostFileBlockerSet revertFileContentsToDisk]; + if(!success || [hostFileBlockerSet.defaultBlocker containsSelfControlBlock]) { + NSLog(@"WARNING: Error removing host file block. Attempting to restore backup."); + + if([hostFileBlockerSet restoreBackupHostsFile]) + NSLog(@"INFO: Host file backup restored."); + else + NSLog(@"ERROR: Host file backup could not be restored. This may result in a permanent block."); + } + + // Get rid of the backup file since we're about to make a new one. + [hostFileBlockerSet deleteBackupHostsFile]; + + // Perform the re-add of the rules + [SCHelperToolUtilities installBlockRulesFromSettings]; + + [SCHelperToolUtilities clearCachesIfRequested]; + + // [SCSentry addBreadcrumb: @"Daemon found compromised block integrity and re-added rules" category: @"daemon"]; + NSLog(@"INFO: Integrity check ran; readded block rules."); + } else NSLog(@"INFO: Integrity check ran; no action needed."); + + [self.daemonMethodLock unlock]; +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonProtocol.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonProtocol.h new file mode 100644 index 00000000..07ccbdef --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonProtocol.h @@ -0,0 +1,28 @@ +// +// SCDaemonProtocol.h +// selfcontrold +// +// Created by Charlie Stigler on 5/30/20. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol SCDaemonProtocol + +// XPC method to start block +- (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + +// XPC method to add to blocklist +- (void)updateBlocklist:(NSArray*)newBlocklist authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + +// XPC method to extend block +- (void)updateBlockEndDate:(NSDate*)newEndDate authorization:(NSData *)authData reply:(void(^)(NSError* error))reply; + +// XPC method to get version of the installed daemon +- (void)getVersionWithReply:(void(^)(NSString * version))reply; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.h b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.h new file mode 100644 index 00000000..720d6acf --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.h @@ -0,0 +1,19 @@ +// +// SCDaemonXPC.h +// selfcontrold +// +// Created by Charlie Stigler on 5/30/20. +// + +#import +#import "SCDaemonProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +// Implementations for SC XPC methods +// (see SCDaemonProtocol for all method prototypes) +@interface SCDaemonXPC : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.m new file mode 100644 index 00000000..7c3f1853 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.m @@ -0,0 +1,90 @@ +// +// SCDaemonXPC.m +// selfcontrold +// +// Created by Charlie Stigler on 5/30/20. +// + +#import "SCDaemonXPC.h" +#import "SCDaemonBlockMethods.h" +#import "SCXPCAuthorization.h" +#import "SCMiscUtilities.h" +#import "ConsoleLog.h" + +@implementation SCDaemonXPC + +- (void)startBlockWithControllingUID:(uid_t)controllingUID blocklist:(NSArray*)blocklist isAllowlist:(BOOL)isAllowlist endDate:(NSDate*)endDate blockSettings:(NSDictionary*)blockSettings authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + NSLog(@"SCDaemonXPC: XPC method called: startBlockWithControllingUID"); + [ConsoleLog log:[NSString stringWithFormat:@"SCDaemonXPC: XPC method called: startBlockWithControllingUID"]]; + + NSError* error = [SCXPCAuthorization checkAuthorization: authData command: _cmd]; + if (error != nil) { + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"SCDaemonXPC: ERROR: XPC authorization failed due to error %@", error); + // [SCSentry captureError: error]; + } + reply(error); + return; + } else { + NSLog(@"SCDaemonXPC: AUTHORIZATION ACCEPTED for startBlock with authData %@ and command %s", authData, sel_getName(_cmd)); + [ConsoleLog log:[NSString stringWithFormat:@"SCDaemonXPC: AUTHORIZATION ACCEPTED for startBlock with authData %@ and command %s", authData, sel_getName(_cmd)]]; + } + + [SCDaemonBlockMethods startBlockWithControllingUID: controllingUID blocklist: blocklist isAllowlist:isAllowlist endDate: endDate blockSettings:blockSettings authorization: authData reply: reply]; +} + +- (void)updateBlocklist:(NSArray*)newBlocklist authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + NSLog(@"SCDaemonXPC: XPC method called: updateBlocklist"); + [ConsoleLog log:[NSString stringWithFormat:@"SCDaemonXPC: XPC method called: updateBlocklist"]]; + + NSError* error = [SCXPCAuthorization checkAuthorization: authData command: _cmd]; + if (error != nil) { + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"SCDaemonXPC: ERROR: XPC authorization failed due to error %@", error); + [ConsoleLog log:[NSString stringWithFormat:@"SCDaemonXPC: ERROR: XPC authorization failed due to error %@", error]]; + // [SCSentry captureError: error]; + } + reply(error); + return; + } else { + NSLog(@"SCDaemonXPC: AUTHORIZATION ACCEPTED for updateBlocklist with authData %@ and command %s", authData, sel_getName(_cmd)); + [ConsoleLog log:[NSString stringWithFormat:@"SCDaemonXPC: AUTHORIZATION ACCEPTED for updateBlocklist with authData %@ and command %s", authData, sel_getName(_cmd)]]; + + } + + [SCDaemonBlockMethods updateBlocklist: newBlocklist authorization: authData reply: reply]; +} + +- (void)updateBlockEndDate:(NSDate*)newEndDate authorization:(NSData *)authData reply:(void(^)(NSError* error))reply { + NSLog(@"SCDaemonXPC: XPC method called: updateBlockEndDate"); + [ConsoleLog log:[NSString stringWithFormat:@"SCDaemonXPC: XPC method called: updateBlockEndDate"]]; + + NSError* error = [SCXPCAuthorization checkAuthorization: authData command: _cmd]; + if (error != nil) { + if (![SCMiscUtilities errorIsAuthCanceled: error]) { + NSLog(@"SCDaemonXPC: ERROR: XPC authorization failed due to error %@", error); + [ConsoleLog log:[NSString stringWithFormat:@"SCDaemonXPC: XPC method called: updateBlockEndDate: %@", error]]; + // [SCSentry captureError: error]; + } + reply(error); + return; + } else { + NSLog(@"SCDaemonXPC: AUTHORIZATION ACCEPTED for updateBlockENdDate with authData %@ and command %s", authData, sel_getName(_cmd)); + [ConsoleLog log:[NSString stringWithFormat:@"SCDaemonXPC: AUTHORIZATION ACCEPTED for updateBlockENdDate with authData %@ and command %s", authData, sel_getName(_cmd)]]; + } + + [SCDaemonBlockMethods updateBlockEndDate: newEndDate authorization: authData reply: reply]; +} + +// Part of the HelperToolProtocol. Returns the version number of the tool. Note that never +// requires authorization. +- (void)getVersionWithReply:(void(^)(NSString * version))reply { + NSLog(@"SCDaemonXPC: XPC method called: getVersionWithReply"); + [ConsoleLog log:[NSString stringWithFormat:@"SCDaemonXPC: XPC method called: getVersionWithReply"]]; + + // We specifically don't check for authorization here. Everyone is always allowed to get + // the version of the helper tool. + reply(@"4.0.2"); +} + +@end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon_Prefix.pch b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon_Prefix.pch new file mode 100755 index 00000000..55dbe0cc --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon_Prefix.pch @@ -0,0 +1,21 @@ +// +// Prefix header for all source files of the 'SelfControl' target in the 'SelfControl' project +// + +#ifdef __OBJC__ + +#define SELFCONTROL_VERSION_STRING @"4.0.2" + +#import +#import + +#import "DeprecationSilencers.h" +//#import "SCUtility.h" +#import "SCErr.h" +#import "SCConstants.h" +//#import "SCSentry.h" +#import "SCSettings.h" +#import "SCMiscUtilities.h" +#import "SCHelperToolUtilities.h" + +#endif diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/org.eyebeam.selfcontrold.plist b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/org.eyebeam.selfcontrold.plist new file mode 100644 index 00000000..91dc9b7a --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/org.eyebeam.selfcontrold.plist @@ -0,0 +1,19 @@ + + + + + Label + org.eyebeam.selfcontrold + RunAtLoad + + MachServices + + org.eyebeam.selfcontrold + + + KeepAlive + + Nice + 5 + + diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/selfcontrold-Info.plist b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/selfcontrold-Info.plist new file mode 100755 index 00000000..6b0e6667 --- /dev/null +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/selfcontrold-Info.plist @@ -0,0 +1,58 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + selfcontrold + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + selfcontrol + + CFBundleTypeIconFile + SelfControlBlocklist.icns + CFBundleTypeName + SelfControl Blocklist + CFBundleTypeRole + Editor + LSTypeIsPackage + + NSPersistentStoreTypeKey + Binary + + + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + SelfControlIcon + CFBundleIdentifier + org.eyebeam.selfcontrold + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 4.0.2 + CFBundleVersion + 410 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSHumanReadableCopyright + Free and open-source under the GPL. + SMAuthorizedClients + + anchor apple generic and (identifier "org.eyebeam.SelfControl" or identifier "org.eyebeam.selfcontrol-cli" or identifier "com.application.SelfControl.corebits") + + + From 4b19eb3973064f31a31fc60815f3c80b9247fba6 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 14 Jan 2026 21:04:32 +0530 Subject: [PATCH 05/64] added app delegate to handle different app states --- .../SelfControl/SelfControl/AppDelegate.swift | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/AppDelegate.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/AppDelegate.swift b/Self-Control-Extension/SelfControl/SelfControl/AppDelegate.swift new file mode 100644 index 00000000..0ac17838 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/AppDelegate.swift @@ -0,0 +1,29 @@ +// +// AppDelegate.swift +// SelfControl +// +// Created by Satendra Singh on 11/01/26. +// + + +import Cocoa + +class AppDelegate: NSObject, NSApplicationDelegate { + + var onAppClose: (() -> Void)? + + func applicationDidFinishLaunching(_ notification: Notification) { + print("App did finish launching") + } + + func applicationWillTerminate(_ notification: Notification) { + print("App will terminate") + onAppClose?() + // Save state, cleanup resources, stop services, etc. + } + + func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { + print("Should terminate") + return .terminateNow + } +} From a67a343d4cf0e8baa225e5522385a591d7139b4d Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 14 Jan 2026 21:04:58 +0530 Subject: [PATCH 06/64] add tool in the info plist configuration --- Self-Control-Extension/SelfControl/SelfControl/Info.plist | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Info.plist b/Self-Control-Extension/SelfControl/SelfControl/Info.plist index b0aaa9df..c7670546 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Info.plist +++ b/Self-Control-Extension/SelfControl/SelfControl/Info.plist @@ -34,5 +34,10 @@ NSSupportsSuddenTermination + SMPrivilegedExecutables + + org.eyebeam.selfcontrold + anchor apple generic and identifier "org.eyebeam.selfcontrold" + From 52672b0844b1c04c2eb2faf90c2caee81eade309 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 14 Jan 2026 21:05:19 +0530 Subject: [PATCH 07/64] integrated app delegate adaptop --- .../SelfControl/SelfControlApp.swift | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift index e7fa36f6..d0727897 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift @@ -10,29 +10,18 @@ import SwiftUI @main struct SelfControlApp: App { @StateObject var viewModel = FilterViewModel() + @NSApplicationDelegateAdaptor(AppDelegate.self) + var appDelegate + var body: some Scene { WindowGroup { ContentView() .environmentObject(viewModel) // Inject the object into the environment - - Button("Test", action: { - let iPAddress = resolveIPToHostname(ipAddress: "8.8.8.8") - print("IP: \(iPAddress ?? "Unknown")") - // Example: - if let host = reverseDNS(ipAddress: "8.8.8.8") { - print("Hostname: \(host)") - } else { - print("Could not resolve.") - } - // Example - if let domain = reverseDNSUsingGetNameInfo(ipAddress: "163.70.145.35") { - print("Domain: \(domain)") - } else { - print("Reverse DNS lookup failed.") - } - - }) - + .onAppear(perform: { + appDelegate.onAppClose = { + viewModel.stopFilter() + } + }) } Window("Preferences View", id: "preferences") { PreferencesView() // Your view to be presented in the new window From 60792da7c626adcc5163a2295c5d435a437bd0c4 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 14 Jan 2026 21:05:55 +0530 Subject: [PATCH 08/64] passed setting to activate tool --- AppController.m | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/AppController.m b/AppController.m index 700200cd..dc58f170 100755 --- a/AppController.m +++ b/AppController.m @@ -607,7 +607,15 @@ - (void)installBlock { // we're about to launch a helper tool which will read settings, so make sure the ones on disk are valid [self->settings_ synchronizeSettings]; [self->defaults_ synchronize]; - + NSDictionary *blockSettings = @{ + @"ClearCaches": [self->defaults_ valueForKey: @"ClearCaches"], + @"AllowLocalNetworks": [self->defaults_ valueForKey: @"AllowLocalNetworks"], + @"EvaluateCommonSubdomains": [self->defaults_ valueForKey: @"EvaluateCommonSubdomains"], + @"IncludeLinkedDomains": [self->defaults_ valueForKey: @"IncludeLinkedDomains"], + @"BlockSoundShouldPlay": [self->defaults_ valueForKey: @"BlockSoundShouldPlay"], + @"BlockSound": [self->defaults_ valueForKey: @"BlockSound"], + @"EnableErrorReporting": [self->defaults_ valueForKey: @"EnableErrorReporting"] + }; // ok, the new helper tool is installed! refresh the connection, then it's time to start the block [self.xpc refreshConnectionAndRun:^{ NSLog(@"Refreshed connection and ready to start block!"); @@ -615,15 +623,7 @@ - (void)installBlock { blocklist: [self->defaults_ arrayForKey: @"Blocklist"] isAllowlist: [self->defaults_ boolForKey: @"BlockAsWhitelist"] endDate: newBlockEndDate - blockSettings: @{ - @"ClearCaches": [self->defaults_ valueForKey: @"ClearCaches"], - @"AllowLocalNetworks": [self->defaults_ valueForKey: @"AllowLocalNetworks"], - @"EvaluateCommonSubdomains": [self->defaults_ valueForKey: @"EvaluateCommonSubdomains"], - @"IncludeLinkedDomains": [self->defaults_ valueForKey: @"IncludeLinkedDomains"], - @"BlockSoundShouldPlay": [self->defaults_ valueForKey: @"BlockSoundShouldPlay"], - @"BlockSound": [self->defaults_ valueForKey: @"BlockSound"], - @"EnableErrorReporting": [self->defaults_ valueForKey: @"EnableErrorReporting"] - } + blockSettings: blockSettings reply:^(NSError * _Nonnull error) { if (error != nil) { [SCUIUtilities presentError: error]; From 0594ffec753b198c7fd5f9e17cf4b54054c2b97f Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 14 Jan 2026 21:09:12 +0530 Subject: [PATCH 09/64] added new files in the project --- .../SelfControl.xcodeproj/project.pbxproj | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index 16032d30..ef51f53a 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 77BA53092D9D5E2A00863476 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77BA53082D9D5E2A00863476 /* NetworkExtension.framework */; }; 9A441CE32E36024E00A521CC /* com.application.SelfControl.corebits.network.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9AC9E59E2F04357E0004D39C /* org.eyebeam.selfcontrold in Copy Daemon Launch Service */ = {isa = PBXBuildFile; fileRef = 9AC9E4EA2F041D570004D39C /* org.eyebeam.selfcontrold */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 9AD524CE2EC9F15500AAD5BF /* SelfControlWebExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9AD524B12EC9B0EA00AAD5BF /* SelfControlWebExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -20,6 +21,13 @@ remoteGlobalIDString = 77BA53052D9D5E2A00863476; remoteInfo = SelfControlExtension; }; + 9AC9E59A2F0434F00004D39C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 77BA52CE2D9D5D7700863476 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AC9E4E92F041D570004D39C; + remoteInfo = org.eyebeam.selfcontrold; + }; 9AD524CF2EC9F15500AAD5BF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 77BA52CE2D9D5D7700863476 /* Project object */; @@ -30,6 +38,26 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 9AC9E4E82F041D570004D39C /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; + 9AC9E59D2F0435560004D39C /* Copy Daemon Launch Service */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = Contents/Library/LaunchServices; + dstSubfolderSpec = 1; + files = ( + 9AC9E59E2F04357E0004D39C /* org.eyebeam.selfcontrold in Copy Daemon Launch Service */, + ); + name = "Copy Daemon Launch Service"; + runOnlyForDeploymentPostprocessing = 0; + }; 9AD524D12EC9F15500AAD5BF /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -59,6 +87,7 @@ 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */ = {isa = PBXFileReference; explicitFileType = "wrapper.system-extension"; includeInIndex = 0; path = com.application.SelfControl.corebits.network.systemextension; sourceTree = BUILT_PRODUCTS_DIR; }; 77BA53082D9D5E2A00863476 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; 9A7FFA0B2E91950D007D4A0D /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 9AC9E4EA2F041D570004D39C /* org.eyebeam.selfcontrold */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = org.eyebeam.selfcontrold; sourceTree = BUILT_PRODUCTS_DIR; }; 9AD524B12EC9B0EA00AAD5BF /* SelfControlWebExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SelfControlWebExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -93,6 +122,20 @@ ); target = 77BA52D52D9D5D7700863476 /* SelfControl */; }; + 9AC9E5972F0422140004D39C /* Exceptions for "org.eyebeam.selfcontrold" folder in "SelfControl" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Common/SCErr.m, + Common/SCFileWatcher.m, + Common/SCSettings.m, + Common/SCXPCAuthorization.m, + Common/SCXPCClient.m, + Common/Utilities/SCConstants.m, + Common/Utilities/SCMiscUtilities.m, + org.eyebeam.selfcontrold.plist, + ); + target = 77BA52D52D9D5D7700863476 /* SelfControl */; + }; 9AD524C32EC9B0EA00AAD5BF /* Exceptions for "SelfControl Rules Extension" folder in "SelfControlWebExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( @@ -120,6 +163,14 @@ path = SelfControlExtension; sourceTree = ""; }; + 9AC9E4EB2F041D570004D39C /* org.eyebeam.selfcontrold */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 9AC9E5972F0422140004D39C /* Exceptions for "org.eyebeam.selfcontrold" folder in "SelfControl" target */, + ); + path = org.eyebeam.selfcontrold; + sourceTree = ""; + }; 9AD524B52EC9B0EA00AAD5BF /* SelfControl Rules Extension */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( @@ -147,6 +198,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9AC9E4E72F041D570004D39C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9AD524AE2EC9B0EA00AAD5BF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -163,6 +221,7 @@ 77BA52D82D9D5D7700863476 /* SelfControl */, 77BA530A2D9D5E2A00863476 /* SelfControlExtension */, 9AD524B52EC9B0EA00AAD5BF /* SelfControl Rules Extension */, + 9AC9E4EB2F041D570004D39C /* org.eyebeam.selfcontrold */, 77BA53072D9D5E2A00863476 /* Frameworks */, 77BA52D72D9D5D7700863476 /* Products */, ); @@ -174,6 +233,7 @@ 77BA52D62D9D5D7700863476 /* SelfControl.app */, 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */, 9AD524B12EC9B0EA00AAD5BF /* SelfControlWebExtension.appex */, + 9AC9E4EA2F041D570004D39C /* org.eyebeam.selfcontrold */, ); name = Products; sourceTree = ""; @@ -199,10 +259,12 @@ 77BA52D42D9D5D7700863476 /* Resources */, 9AFC129E2E355671003B5455 /* Embed System Extensions */, 9AD524D12EC9F15500AAD5BF /* Embed Foundation Extensions */, + 9AC9E59D2F0435560004D39C /* Copy Daemon Launch Service */, ); buildRules = ( ); dependencies = ( + 9AC9E59B2F0434F00004D39C /* PBXTargetDependency */, 9A441CE22E36024300A521CC /* PBXTargetDependency */, 9AD524D02EC9F15500AAD5BF /* PBXTargetDependency */, ); @@ -238,6 +300,28 @@ productReference = 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */; productType = "com.apple.product-type.system-extension"; }; + 9AC9E4E92F041D570004D39C /* org.eyebeam.selfcontrold */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9AC9E4EE2F041D570004D39C /* Build configuration list for PBXNativeTarget "org.eyebeam.selfcontrold" */; + buildPhases = ( + 9AC9E4E62F041D570004D39C /* Sources */, + 9AC9E4E72F041D570004D39C /* Frameworks */, + 9AC9E4E82F041D570004D39C /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 9AC9E4EB2F041D570004D39C /* org.eyebeam.selfcontrold */, + ); + name = org.eyebeam.selfcontrold; + packageProductDependencies = ( + ); + productName = org.eyebeam.selfcontrold; + productReference = 9AC9E4EA2F041D570004D39C /* org.eyebeam.selfcontrold */; + productType = "com.apple.product-type.tool"; + }; 9AD524B02EC9B0EA00AAD5BF /* SelfControlWebExtension */ = { isa = PBXNativeTarget; buildConfigurationList = 9AD524C42EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControlWebExtension" */; @@ -276,6 +360,9 @@ 77BA53052D9D5E2A00863476 = { CreatedOnToolsVersion = 16.2; }; + 9AC9E4E92F041D570004D39C = { + CreatedOnToolsVersion = 26.0; + }; 9AD524B02EC9B0EA00AAD5BF = { CreatedOnToolsVersion = 26.0; }; @@ -298,6 +385,7 @@ 77BA52D52D9D5D7700863476 /* SelfControl */, 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */, 9AD524B02EC9B0EA00AAD5BF /* SelfControlWebExtension */, + 9AC9E4E92F041D570004D39C /* org.eyebeam.selfcontrold */, ); }; /* End PBXProject section */ @@ -341,6 +429,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9AC9E4E62F041D570004D39C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9AD524AD2EC9B0EA00AAD5BF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -356,6 +451,11 @@ target = 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */; targetProxy = 9A441CE12E36024300A521CC /* PBXContainerItemProxy */; }; + 9AC9E59B2F0434F00004D39C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AC9E4E92F041D570004D39C /* org.eyebeam.selfcontrold */; + targetProxy = 9AC9E59A2F0434F00004D39C /* PBXContainerItemProxy */; + }; 9AD524D02EC9F15500AAD5BF /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 9AD524B02EC9B0EA00AAD5BF /* SelfControlWebExtension */; @@ -498,6 +598,7 @@ ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; + GCC_PREFIX_HEADER = org.eyebeam.selfcontrold/SCDaemon_Prefix.pch; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = SelfControl/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; @@ -512,6 +613,7 @@ PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Self Control app dev"; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Debug; @@ -532,6 +634,7 @@ ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; + GCC_PREFIX_HEADER = org.eyebeam.selfcontrold/SCDaemon_Prefix.pch; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = SelfControl/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = ""; @@ -546,6 +649,7 @@ PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Self Control app dev"; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Release; @@ -618,6 +722,62 @@ }; name = Release; }; + 9AC9E4EF2F041D570004D39C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 410; + DEVELOPMENT_TEAM = X6FQ433AWK; + ENABLE_HARDENED_RUNTIME = YES; + GCC_PREFIX_HEADER = org.eyebeam.selfcontrold/SCDaemon_Prefix.pch; + INFOPLIST_FILE = "org.eyebeam.selfcontrold/selfcontrold-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + "org.eyebeam.selfcontrold/selfcontrold-Info.plist", + "-sectcreate", + __TEXT, + __launchd_plist, + org.eyebeam.selfcontrold/org.eyebeam.selfcontrold.plist, + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.eyebeam.selfcontrold; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 9AC9E4F02F041D570004D39C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 410; + DEVELOPMENT_TEAM = X6FQ433AWK; + ENABLE_HARDENED_RUNTIME = YES; + GCC_PREFIX_HEADER = org.eyebeam.selfcontrold/SCDaemon_Prefix.pch; + INFOPLIST_FILE = "org.eyebeam.selfcontrold/selfcontrold-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 13.5; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "-sectcreate", + __TEXT, + __info_plist, + "org.eyebeam.selfcontrold/selfcontrold-Info.plist", + "-sectcreate", + __TEXT, + __launchd_plist, + org.eyebeam.selfcontrold/org.eyebeam.selfcontrold.plist, + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.eyebeam.selfcontrold; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; 9AD524C52EC9B0EA00AAD5BF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -748,6 +908,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 9AC9E4EE2F041D570004D39C /* Build configuration list for PBXNativeTarget "org.eyebeam.selfcontrold" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9AC9E4EF2F041D570004D39C /* Debug */, + 9AC9E4F02F041D570004D39C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 9AD524C42EC9B0EA00AAD5BF /* Build configuration list for PBXNativeTarget "SelfControlWebExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( From c6ecdc9d9cc00fa10c9e9352bc5a9cbbd204278d Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 14 Jan 2026 21:14:35 +0530 Subject: [PATCH 10/64] activate /deactivate legacy blocking conditionally --- .../org.eyebeam.selfcontrold.xcscheme | 79 +++++++++++++++++++ .../SelfControl/Main/FilterViewModel.swift | 25 ++++-- 2 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/org.eyebeam.selfcontrold.xcscheme diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/org.eyebeam.selfcontrold.xcscheme b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/org.eyebeam.selfcontrold.xcscheme new file mode 100644 index 00000000..fa398599 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/org.eyebeam.selfcontrold.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index f43a3c40..585d327d 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -21,6 +21,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques var blockedIPAddressed: [String] = [] private var cancellables = Set() @Published var isActiveBlocking: Bool = false + lazy var selfControlDaemon = SSCDaemonHelper() // Safari extension identifier used to query state private let safariExtensionIdentifier = "com.application.SelfControl.corebits.SelfControl-Safari-Extension" @@ -95,7 +96,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func onInit() { // On initialization load the filter configuration and register for changes. - loadFilterConfiguration { success in + Self.loadFilterConfiguration { success in guard success else { self.status = .stopped self.refreshExtensionState() @@ -129,12 +130,18 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques // MARK: - UI and Filter Management func setBlockedUrls(urls: [String]) { + IPCConnection.shared.enableURLBlocking(urls) Task { let ips: Set = await DNSResolverActor().resolve(hostURL: urls) print("Resolved app:\(ips)") setIPAddressesToBlock(addresses: Array(ips)) } + if status == .stopped { //If legacy blocking + if isActiveBlocking { //if is active blocking + updateLegacyBlockedList(newBlockedDomains: urls) + } + } // State might change due to Safari integration } @@ -168,7 +175,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques os_log("[SC] 🔍] %@", message) } - func loadFilterConfiguration(completionHandler: @escaping (Bool) -> Void) { + static func loadFilterConfiguration(completionHandler: @escaping (Bool) -> Void) { NEFilterManager.shared().loadFromPreferences { loadError in DispatchQueue.main.async { var success = true @@ -187,7 +194,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques registerWithProvider() return } - loadFilterConfiguration { success in + Self.loadFilterConfiguration { success in guard success else { self.status = .stopped self.refreshExtensionState() @@ -211,7 +218,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques self.refreshExtensionState() return } else { - self.enableDNSProxy() +// self.enableDNSProxy() } self.registerWithProvider() } @@ -300,7 +307,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques refreshExtensionState() return } - loadFilterConfiguration { success in + Self.loadFilterConfiguration { success in guard success else { self.status = .running self.refreshExtensionState() @@ -437,5 +444,13 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques timerFireDate = nil self.isActiveBlocking = false } + + func installLegacyLaunched(futureDuration: Date) { + selfControlDaemon.install(blockedDomains: ProxyPreferences.getBlockedDomains(), time: futureDuration) + } + + func updateLegacyBlockedList(newBlockedDomains: [String]) { + selfControlDaemon.updateBlocklist(newBlockedDomains) + } } From e3abb7f20aeb248672179813a44f65a2cb0c12d8 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 14 Jan 2026 21:16:44 +0530 Subject: [PATCH 11/64] updated messages and file constant --- .../SelfControl/Main/ContentView.swift | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift index 7c32ba2d..40cc7b8e 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift @@ -24,14 +24,15 @@ struct ContentView: View { Button { viewModel.activateExtension() } label: { - Text("Install and Start Block") + Text("Install and Start Network Extension") } // Start/Stop buttons. HStack { if viewModel.status == .stopped { - Button("Start") { - viewModel.startFilter() - } + Button("Start") { + // viewModel.startFilter() + viewModel.installLegacyLaunched(futureDuration: Date.now.addingTimeInterval(viewModel.delay*60)) + } } if viewModel.status == .running { Button("Stop") { @@ -53,10 +54,14 @@ struct ContentView: View { viewModel.deactivateNetworkBlocking() viewModel.cancelTimer() } else { - if viewModel.startTimerWithSelectedDelay() == false { - return + if viewModel.status == .stopped { + viewModel.installLegacyLaunched(futureDuration: Date.now.addingTimeInterval(viewModel.delay*60)) + } else { + if viewModel.startTimerWithSelectedDelay() == false { + return + } + viewModel.activateNetworkBlocking() } - viewModel.activateNetworkBlocking() } } label: { Text(viewModel.isActiveBlocking ? "Deactivate Block" : "Activate Block") From b247dcbb53735b4458b6512c90039958402ae926 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 14 Jan 2026 21:17:24 +0530 Subject: [PATCH 12/64] added daemon helper file --- .../SCDaemonSupport/SCDaemonHelper.swift | 69 +++++++++++++++++++ .../SelfControl-Bridging-Header.h | 5 ++ .../SelfControl/SCDaemonSupport/Test.h | 16 +++++ .../SelfControl/SCDaemonSupport/Test.m | 12 ++++ .../SafariExtensionSupport/Constants.swift | 13 ++++ 5 files changed, 115 insertions(+) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SCDaemonHelper.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.h create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.m create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/Constants.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SCDaemonHelper.swift b/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SCDaemonHelper.swift new file mode 100644 index 00000000..774576de --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SCDaemonHelper.swift @@ -0,0 +1,69 @@ +// +// SCDaemonHelper.swift +// SelfControl +// +// Created by Satendra Singh on 22/12/25. +// + +import Foundation + +final class SSCDaemonHelper { + let xpc = SCXPCClient() + + init() { + + } + + func install(blockedDomains: [String], time: Date) { + xpc.installDaemon { (error: Error?) in + if let error = error { + NSLog("ERROR: Failed to install daemon with error \(error)") + exit(EX_SOFTWARE) + } else { + // NSTimeInterval blockDurationSecs = MAX([[self->defaults_ valueForKey: @"BlockDuration"] intValue] * 60, 0); + // NSDate* newBlockEndDate = [NSDate dateWithTimeIntervalSinceNow: blockDurationSecs]; +// let date = Date().addingTimeInterval(5*60) //add second + let blockSettingsFromDefaults: [String: Any] = [ + "ClearCaches": 1, + "AllowLocalNetworks": 1, + "EvaluateCommonSubdomains": 1, + "IncludeLinkedDomains": 1, + "BlockSoundShouldPlay": 0, + "BlockSound": 5, + "EnableErrorReporting": 1 + ] + let blockSettings = blockSettingsFromDefaults + // ok, the new helper tool is installed! refresh the connection, then it's time to start the block + self.xpc.refreshConnectionAndRun { + NSLog("Refreshed connection and ready to start block!") + self.xpc.startBlock( + withControllingUID: getuid(), + blocklist: blockedDomains, + isAllowlist: false, + end: time, + blockSettings: blockSettings + ) { (error: Error?) in + if let error = error { + NSLog("ERROR: Daemon failed to start block with error \(error)") + exit(EX_SOFTWARE) + } + + NSLog("INFO: Block successfully added.") + // installingBlockSema.signal() + } + } + } + } + } + + func connect() { + xpc.connectToHelperTool() + } + + func updateBlocklist(_ domains: [String]) { + xpc.updateBlocklist(domains) { error in + print("\(error.localizedDescription )") + } + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h b/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h new file mode 100644 index 00000000..2f252b0e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "SCXPCClient.h" diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.h b/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.h new file mode 100644 index 00000000..5c8077e0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.h @@ -0,0 +1,16 @@ +// +// Test.h +// SelfControl +// +// Created by Satendra Singh on 22/12/25. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface Test : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.m b/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.m new file mode 100644 index 00000000..93ca8540 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.m @@ -0,0 +1,12 @@ +// +// Test.m +// SelfControl +// +// Created by Satendra Singh on 22/12/25. +// + +#import "Test.h" + +@implementation Test + +@end diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/Constants.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/Constants.swift new file mode 100644 index 00000000..58dd76f3 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/Constants.swift @@ -0,0 +1,13 @@ +// +// Constants.swift +// SelfControl +// +// Created by Satendra Singh on 09/10/25. +// + + + +enum Constants { + /// File name for the JSON file with Safari rules. + static let SAFARI_BLOCKER_FILE_NAME = "blockerList.json" +} From 8b065e0012a0aa515433d97efeb384cf7d5e9d70 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 14 Jan 2026 21:21:03 +0530 Subject: [PATCH 13/64] updated filter data logic --- .../FilterDataProvider.swift | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift index b49233fd..3f3ded1f 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift @@ -193,26 +193,27 @@ class FilterDataProvider: NEFilterDataProvider { readBytesStartOffset offset: Int, readBytes data: Data ) -> NEFilterDataVerdict { - + var result: NEFilterDataVerdict = .allow() + guard IPCConnection.shared.isServiceActive else { //Service is inactive - return .allow() + return result } guard IPCConnection.shared.blockedUrls.count > 0 else { //No signinficant urls to block - return .allow() + return result } if RequestSourceAppValidator.isAllowedHost(flow: flow) { - return .allow() + return result } guard let socketFlow = flow as? NEFilterSocketFlow else { os_log("[SC] 🔍] Not a socket flow. Allowing.", log: OSLog.default, type: .info) - return .allow() + return result } if socketFlow.direction != .outbound { os_log("[SC] 🔍] Not a inbound socket flow. Allowing.", log: OSLog.default, type: .info) - return .allow() + return result } // guard let socketFlow = flow as? NEFilterSocketFlow else { @@ -236,19 +237,20 @@ class FilterDataProvider: NEFilterDataProvider { } else if let sni = extractSNI(fromTLSData: data) { os_log("[SC] 🔍] data TLS SNI Host: %{public}@", sni) guard let hostDomain = TLDURLToDomain.getURLDomain(from: sni) else { - return .allow() + return result } os_log("[SC] 🔍] data hostDomain: %{public}@", hostDomain) for url in IPCConnection.shared.blockedUrls { if url.contains(hostDomain) { os_log("[SC] 🔍] data Blocking flow Host match SNI:%{public}@, host:%{public}@", sni, hostDomain) - return .drop() + result = .drop() + break } } } - return .allow() + return result } // Called for each new flow. @@ -295,10 +297,10 @@ class FilterDataProvider: NEFilterDataProvider { filterOutbound: true, peekOutboundBytes: Int.max) } - + os_log("[SC] 🔍] Host SNI domain:%{public}@", hostDomain) for url in IPCConnection.shared.blockedUrls { if url.contains(hostDomain) { - os_log("[SC] 🔍] data Blocking flow Host:%{public}@", hostDomain) + os_log("[SC] 🔍] Blocking flow Host:%{public}@", hostDomain) return .drop() } } @@ -398,7 +400,7 @@ class FilterDataProvider: NEFilterDataProvider { override func handleInboundData(from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes: Data) -> NEFilterDataVerdict { if let requestString = String(data: readBytes, encoding: .utf8) { - print("Outbound data: \(requestString)") + print("Inbound data: \(requestString)") os_log("[SC] 🔍] handleInboundData: %{public}@", requestString) if requestString.contains("facebook.com/friends") { From d8e34209906c2df852685e036aa2a36bc61a08ca Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Fri, 30 Jan 2026 23:24:12 +0530 Subject: [PATCH 14/64] integrated New UI flow --- .../SelfControl/Main/FilterViewModel.swift | 34 +++-- .../SelfControl/SelfControl/NewViewUI.swift | 107 ++++++++++++++ .../BlockListManager.swift | 4 +- .../SelfControl/SelfControlApp.swift | 82 ++++++++++- .../Helpers/ScheduleBlockManager.swift | 39 ++++++ .../Models/BlockedURLStore.swift | 131 ++++++++++++++++++ 6 files changed, 381 insertions(+), 16 deletions(-) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/NewViewUI.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/ScheduleBlockManager.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/BlockedURLStore.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index 585d327d..33bb05e6 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -17,6 +17,8 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques @Published var status: Status = .stopped @State private var domains = ProxyPreferences.getBlockedDomains() private let chromeService = ChromeExtensionRequestListner() + @State var blockedURLs: [BlockedURL] = [] + var blockerStorage: BlockedURLStore? @Published var delay: Double = 5.0 var blockedIPAddressed: [String] = [] private var cancellables = Set() @@ -29,8 +31,8 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques // Timer to manage delayed actions based on `delay` (in minutes) private var blockTimer: Timer? private var timerFireDate: Date? - - // Date formatter used to log entries + + // Date formatter used to log entries lazy var dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" @@ -84,6 +86,9 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques self.refreshExtensionState() } .store(in: &cancellables) + Task { @MainActor in + self.blockerStorage = BlockedURLStore() + } } deinit { @@ -132,11 +137,11 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func setBlockedUrls(urls: [String]) { IPCConnection.shared.enableURLBlocking(urls) - Task { - let ips: Set = await DNSResolverActor().resolve(hostURL: urls) - print("Resolved app:\(ips)") - setIPAddressesToBlock(addresses: Array(ips)) - } +// Task { +// let ips: Set = await DNSResolverActor().resolve(hostURL: urls) +// print("Resolved app:\(ips)") +// setIPAddressesToBlock(addresses: Array(ips)) +// } if status == .stopped { //If legacy blocking if isActiveBlocking { //if is active blocking updateLegacyBlockedList(newBlockedDomains: urls) @@ -452,5 +457,18 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func updateLegacyBlockedList(newBlockedDomains: [String]) { selfControlDaemon.updateBlocklist(newBlockedDomains) } + + func updateBlockList(newBlockedDomains: [BlockedURL], time: Double ) { + + let urls = newBlockedDomains.compactMap(\.urls) + let flattened: [String] = urls.flatMap { $0 } + + ProxyPreferences.setBlockedDomains(flattened) + setBlockedUrls(urls: flattened) + self.delay = time + if startTimerWithSelectedDelay() == false { + return + } + activateNetworkBlocking() + } } - diff --git a/Self-Control-Extension/SelfControl/SelfControl/NewViewUI.swift b/Self-Control-Extension/SelfControl/SelfControl/NewViewUI.swift new file mode 100644 index 00000000..1d29c70e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/NewViewUI.swift @@ -0,0 +1,107 @@ +// +// NewViewUI.swift +// SelfControl +// +// Created by Satendra Singh on 16/01/26. +// + +import SwiftUI + +struct NewViewUI { + static var body: some Scene { + let windowGroup = WindowGroup { + NewContentView() + .background(WindowTitleBarHider()) + } +// .commands { +// CommandGroup(replacing: .newItem) { } +// CommandGroup(replacing: .appInfo) { +// Button(Strings.AppMenu.aboutSelfControl) { +// NotificationCenter.default.post(name: NSNotification.Name("ShowAboutScreen"), object: nil) +// } +// +// Divider() +// +// Button(Strings.AppMenu.editSiteList) { +// NotificationCenter.default.post(name: NSNotification.Name("ShowEditListScreen"), object: nil) +// } +// .keyboardShortcut("e", modifiers: .command) +// +// Button(Strings.AppMenu.editBlockSchedule) { +// NotificationCenter.default.post(name: NSNotification.Name("ShowBlockScheduleScreen"), object: nil) +// } +// .keyboardShortcut("b", modifiers: .command) +// +// Button(Strings.AppMenu.moreSettings) { +// NotificationCenter.default.post(name: NSNotification.Name("ShowAdvancedSettingsScreen"), object: nil) +// } +// .keyboardShortcut(",", modifiers: .command) +// +// +// +// Divider() +// +// Button(Strings.AppMenu.donate) { +// if let url = URL(string: Strings.AppMenu.URLs.donate) { +// NSWorkspace.shared.open(url) +// } +// } +// } +// CommandGroup(replacing: .help) { +// Button(Strings.AppMenu.gettingStartedTips) { +// NotificationCenter.default.post(name: NSNotification.Name("ShowTipsScreen"), object: nil) +// } +// .keyboardShortcut("t", modifiers: .command) +// +// Divider() +// +// Button(Strings.AppMenu.selfControlHelp) { +// if let url = URL(string: Strings.AppMenu.URLs.help) { +// NSWorkspace.shared.open(url) +// } +// } +// .keyboardShortcut("?", modifiers: .command) +// +// Button(Strings.AppMenu.faq) { +// if let url = URL(string: Strings.AppMenu.URLs.faq) { +// NSWorkspace.shared.open(url) +// } +// } +// } +// } + + return windowGroup + } +} + +//// MARK: - Window Configuration Helper (macOS 11 compatible) +struct WindowTitleBarHider: NSViewRepresentable { + func makeNSView(context: Context) -> NSView { + let view = NSView() + DispatchQueue.main.async { + if let window = view.window { + configure(window: window) + } else if let window = NSApplication.shared.windows.first { + configure(window: window) + } + } + return view + } + + func updateNSView(_ nsView: NSView, context: Context) { + if let window = nsView.window { + configure(window: window) + } + } + + private func configure(window: NSWindow) { + window.titleVisibility = .hidden + window.titlebarAppearsTransparent = true + window.isMovableByWindowBackground = true + window.styleMask.insert(.fullSizeContentView) + // Set a slightly smaller default content size + if window.contentView?.frame.size.width ?? 0 > 0 { + window.setContentSize(NSSize(width: 600, height: 450)) + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift index 9eea843a..b221f304 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift @@ -64,7 +64,7 @@ enum BlockListManager { // Ensure folder exists before writing try fileManager.createDirectory(at: containerURL, withIntermediateDirectories: true, attributes: nil) - let fileURL = containerURL.appendingPathComponent(SafariConst.SAFARI_BLOCKER_FILE_NAME) + let fileURL = containerURL.appendingPathComponent(Constants.SAFARI_BLOCKER_FILE_NAME) try data.write(to: fileURL, options: .atomic) print("✅ Wrote blockerList.json to: \(fileURL.path)") @@ -73,7 +73,7 @@ enum BlockListManager { SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: SafariConst.identifier) { state, error in os_log("[SC] 🔍 Safari Extension State Error: %{public}@", error?.localizedDescription ?? "") os_log("[SC] 🔍 Safari Extension State: %{public}d", state?.isEnabled ?? false) - print("Safari Extension State Error :\(error, default: "nil")") + print("Safari Extension State Error :\(String(describing: error))") print("Safari Extension State :\(state?.isEnabled ?? false)") Task { @MainActor in if NetworkExtensionState.shared.isEnabled == true { diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift index d0727897..30eec309 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift @@ -15,13 +15,67 @@ struct SelfControlApp: App { var body: some Scene { WindowGroup { - ContentView() - .environmentObject(viewModel) // Inject the object into the environment - .onAppear(perform: { - appDelegate.onAppClose = { - viewModel.stopFilter() + if viewModel.status == .running { + newView + } else { + oldView + } + } + .commands { + CommandGroup(replacing: .newItem) { } + CommandGroup(replacing: .appInfo) { + Button(Strings.AppMenu.aboutSelfControl) { + NotificationCenter.default.post(name: NSNotification.Name("ShowAboutScreen"), object: nil) + } + + Divider() + + Button(Strings.AppMenu.editSiteList) { + NotificationCenter.default.post(name: NSNotification.Name("ShowEditListScreen"), object: nil) + } + .keyboardShortcut("e", modifiers: .command) + + Button(Strings.AppMenu.editBlockSchedule) { + NotificationCenter.default.post(name: NSNotification.Name("ShowBlockScheduleScreen"), object: nil) + } + .keyboardShortcut("b", modifiers: .command) + + Button(Strings.AppMenu.moreSettings) { + NotificationCenter.default.post(name: NSNotification.Name("ShowAdvancedSettingsScreen"), object: nil) + } + .keyboardShortcut(",", modifiers: .command) + + + + Divider() + + Button(Strings.AppMenu.donate) { + if let url = URL(string: Strings.AppMenu.URLs.donate) { + NSWorkspace.shared.open(url) } - }) + } + } + CommandGroup(replacing: .help) { + Button(Strings.AppMenu.gettingStartedTips) { + NotificationCenter.default.post(name: NSNotification.Name("ShowTipsScreen"), object: nil) + } + .keyboardShortcut("t", modifiers: .command) + + Divider() + + Button(Strings.AppMenu.selfControlHelp) { + if let url = URL(string: Strings.AppMenu.URLs.help) { + NSWorkspace.shared.open(url) + } + } + .keyboardShortcut("?", modifiers: .command) + + Button(Strings.AppMenu.faq) { + if let url = URL(string: Strings.AppMenu.URLs.faq) { + NSWorkspace.shared.open(url) + } + } + } } Window("Preferences View", id: "preferences") { PreferencesView() // Your view to be presented in the new window @@ -29,6 +83,22 @@ struct SelfControlApp: App { } .windowStyle(.automatic) } + + private var oldView: some View { + ContentView() + .environmentObject(viewModel) // Inject the object into the environment + .onAppear(perform: { + appDelegate.onAppClose = { + viewModel.stopFilter() + } + }) + } + + private var newView: some View { + NewContentView() + .environmentObject(viewModel) // Inject the object into the environment + .background(WindowTitleBarHider()) + } func resolveIPToHostname(ipAddress: String) -> String? { let hostRef = CFHostCreateWithName(nil, ipAddress as CFString).takeRetainedValue() diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/ScheduleBlockManager.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/ScheduleBlockManager.swift new file mode 100644 index 00000000..93df0a72 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/ScheduleBlockManager.swift @@ -0,0 +1,39 @@ +// +// ScheduleBlockManager.swift +// SelfControl +// +// Created by Satendra Singh on 29/01/26. +// + +final class ScheduleBlockManager { + private var schedules: [Schedule] = [] + @MainActor + final class BlockedURLStore: ObservableObject { + + @Published + private(set) var schedules: [Schedule] = [] + + init() { + _ = loadSchedules() + } + + // Load schedules from UserDefaults + private func loadSchedules() -> [Schedule] { + if let data = UserDefaults.standard.data(forKey: "schedules"), + let decoded = try? JSONDecoder().decode([Schedule].self, from: data) { + schedules = decoded + } else { + // Initialize with one empty schedule if no saved data + schedules = [Schedule(timeSlots: [], enabledDays: [])] + } + return schedules + } + + // Save schedules to UserDefaults + private func saveSchedules(schedules: [Schedule]) { + if let encoded = try? JSONEncoder().encode(schedules) { + UserDefaults.standard.set(encoded, forKey: "schedules") + } + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/BlockedURLStore.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/BlockedURLStore.swift new file mode 100644 index 00000000..a60d0430 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/BlockedURLStore.swift @@ -0,0 +1,131 @@ +import Foundation + +// A store that persists [BlockedURL] in UserDefaults using JSON encoding. +@MainActor +final class BlockedURLStore: ObservableObject { + @Published private(set) var items: [BlockedURL] = [] + + private let defaults: UserDefaults + private let key: String + private let encoder = JSONEncoder() + private let decoder = JSONDecoder() + + // If you need an app group, pass: UserDefaults(suiteName: "group.your.identifier")! + init( + key: String = "BlockedURLItems", + defaults: UserDefaults = .standard + ) { + self.key = key + self.defaults = defaults + load() + } + + func load() { + guard let data = defaults.data(forKey: key) else { + items = [] + return + } + do { + items = try decoder.decode([BlockedURL].self, from: data) + } catch { + #if DEBUG + print("Failed to decode [BlockedURL]: \(error)") + #endif + items = [] + } + } + + func save() { + do { + let data = try encoder.encode(items) + defaults.set(data, forKey: key) + } catch { + #if DEBUG + print("Failed to encode [BlockedURL]: \(error)") + #endif + } + } + + // Replace entire array + func set(_ newItems: [BlockedURL]) { + items = newItems + save() + } + + // Append a new entry + func append(_ item: BlockedURL) { + items.append(item) + save() + } + + // Insert or update by id + func upsert(_ item: BlockedURL) { + if let idx = items.firstIndex(where: { $0.id == item.id }) { + items[idx] = item + } else { + items.append(item) + } + save() + } + + // Remove by id + func remove(id: UUID) { + if let idx = items.firstIndex(where: { $0.id == id }) { + items.remove(at: idx) + save() + } + } + + // Remove via IndexSet (useful for List.onDelete) + func remove(at offsets: IndexSet) { + items.remove(atOffsets: offsets) + save() + } + + // Toggle enable/disable for a specific id + func setEnabled(_ isEnabled: Bool, for id: UUID) { + guard let idx = items.firstIndex(where: { $0.id == id }) else { return } + items[idx].isEnabled = isEnabled + save() + } + + // Convenience: check if a given URL string matches any enabled blocked entry. + // This compares against the computed urls strings of enabled items. + func isBlocked(urlString: String) -> Bool { + let normalized = Self.normalize(urlString: urlString) + for item in items where item.isEnabled { + guard let itemURLs = item.urls else { continue } + if itemURLs.contains(where: { Self.normalize(urlString: $0) == normalized }) { + return true + } + } + return false + } + + // Optional normalization helper for consistent comparisons. + private static func normalize(urlString: String) -> String { + // If it's a full URL, normalize via URLComponents; otherwise, lowercased trim. + if let url = URL(string: urlString), + var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) { + + // Clear fragment and query + comps.fragment = nil + comps.query = nil + + // Remove trailing slash from path unless it's just "/" + var path = comps.path + if path.count > 1, path.hasSuffix("/") { + path.removeLast() + comps.path = path + } + + // Default scheme if missing + if comps.scheme == nil { + comps.scheme = "http" + } + + // Continue with your logic using `comps`... + } + return urlString.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + } +} From 9d0f2d6e242f61d7cf5f92a9ac9e2bb3ed4305d6 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Fri, 30 Jan 2026 23:24:32 +0530 Subject: [PATCH 15/64] added new ui --- .../Components/CustomComponents.swift | 176 ++ .../Helpers/BlockedURLsHelpers.swift | 117 ++ .../Helpers/BlockingStateHelpers.swift | 24 + .../Helpers/IntensityHelpers.swift | 32 + .../SelfControlUI/Helpers/TimeHelpers.swift | 338 ++++ .../SelfControlUI/Models/AppModels.swift | 242 +++ .../SelfControlUI/NewContentView.swift | 1499 +++++++++++++++++ .../SelfControlUI/SelfControlUIApp.swift | 109 ++ .../SelfControlUI/Utils/DesignSystem.swift | 177 ++ .../SelfControlUI/Utils/DomainConstants.swift | 29 + .../SelfControlUI/Utils/Strings.swift | 321 ++++ .../SelfControlUI/Views/AboutView.swift | 149 ++ .../Views/AdvancedSettingsView.swift | 257 +++ .../Views/BlockScheduleView.swift | 487 ++++++ .../Views/BlocklistEditorView.swift | 1352 +++++++++++++++ .../Views/DomainDetailView.swift | 385 +++++ .../Views/ExtendTimerOverlayView.swift | 331 ++++ .../Views/IntensityDropdownOverlay.swift | 93 + .../Views/NonDraggableBackgroundView.swift | 54 + .../Views/StopConfirmationOverlayView.swift | 127 ++ .../SelfControlUI/Views/TipsOverlayView.swift | 94 ++ 21 files changed, 6393 insertions(+) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/CustomComponents.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/BlockedURLsHelpers.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/BlockingStateHelpers.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/IntensityHelpers.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/TimeHelpers.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/SelfControlUIApp.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DesignSystem.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DomainConstants.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AboutView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/ExtendTimerOverlayView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/IntensityDropdownOverlay.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/NonDraggableBackgroundView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/StopConfirmationOverlayView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/TipsOverlayView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/CustomComponents.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/CustomComponents.swift new file mode 100644 index 00000000..d22274d6 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/CustomComponents.swift @@ -0,0 +1,176 @@ +// +// CustomComponents.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import SwiftUI +import AppKit + +// MARK: - Arrow Key TextField +struct ArrowKeyTextField: NSViewRepresentable { + @Binding var text: String + var onUpArrow: () -> Void + var onDownArrow: () -> Void + var onSubmit: () -> Void + var onFocusChange: ((Bool) -> Void)? = nil + + func makeNSView(context: Context) -> NSTextField { + let textField = ArrowKeyNSTextField() + textField.delegate = context.coordinator + textField.isBordered = false + textField.backgroundColor = .clear + textField.drawsBackground = false + textField.alignment = .right + textField.font = .systemFont(ofSize: 32, weight: .bold) + textField.textColor = NSColor(red: 0.95, green: 0.95, blue: 0.97, alpha: 1.0) + textField.onUpArrow = onUpArrow + textField.onDownArrow = onDownArrow + textField.onSubmit = onSubmit + textField.onFocusChange = onFocusChange + return textField + } + + func updateNSView(_ nsView: NSTextField, context: Context) { + if nsView.stringValue != text { + nsView.stringValue = text + } + } + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + class Coordinator: NSObject, NSTextFieldDelegate { + var parent: ArrowKeyTextField + + init(_ parent: ArrowKeyTextField) { + self.parent = parent + } + + func controlTextDidChange(_ obj: Notification) { + if let textField = obj.object as? NSTextField { + parent.text = textField.stringValue + } + } + + func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { + if commandSelector == #selector(NSResponder.insertNewline(_:)) { + parent.onSubmit() + return true + } + return false + } + } +} + +class ArrowKeyNSTextField: NSTextField { + var onUpArrow: (() -> Void)? + var onDownArrow: (() -> Void)? + var onSubmit: (() -> Void)? + var onFocusChange: ((Bool) -> Void)? + + override func becomeFirstResponder() -> Bool { + let result = super.becomeFirstResponder() + if result { + onFocusChange?(true) + } + return result + } + + override func resignFirstResponder() -> Bool { + let result = super.resignFirstResponder() + if result { + onFocusChange?(false) + } + return result + } + + override func keyDown(with event: NSEvent) { + switch event.keyCode { + case 126: // Up arrow + onUpArrow?() + case 125: // Down arrow + onDownArrow?() + default: + super.keyDown(with: event) + } + } +} + +// MARK: - Checkbox Toggle Style +struct CheckboxToggleStyle: ToggleStyle { + func makeBody(configuration: Configuration) -> some View { + HStack { + Image(systemName: configuration.isOn ? "checkmark.square.fill" : "square") + .font(DesignSystem.font(size: DesignSystem.fontSizeHeading)) + .foregroundColor(configuration.isOn ? Color.white : Color(white: 0.5)) + .onTapGesture { + configuration.isOn.toggle() + } + + configuration.label + } + } +} + +// MARK: - Custom Switch Toggle Style +struct SwitchToggleStyle: ToggleStyle { + let tint: Color + @State private var isHovered = false + + init(tint: Color = Color(red: 0.25, green: 0.48, blue: 0.95)) { + self.tint = tint + } + + func makeBody(configuration: Configuration) -> some View { + HStack { + // Switch pill + ZStack(alignment: configuration.isOn ? .trailing : .leading) { + // Background pill + Capsule() + .fill(configuration.isOn ? Color.white : Color(white: 0.25)) + .frame(width: 44, height: 24) + .overlay( + Capsule() + .stroke(configuration.isOn ? DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium) : DesignSystem.borderPrimary, lineWidth: 1) + ) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityMedium), radius: 2, x: 0, y: 1) + + // Thumb/knob - warm white when off, dark gray when on + Circle() + .fill(configuration.isOn ? Color(red: 0.2, green: 0.2, blue: 0.22) : Color(red: 0.96, green: 0.95, blue: 0.96)) + .frame(width: 20, height: 20) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityMedium), radius: 2, x: 0, y: 1) + .padding(DesignSystem.spacingXXSmall) + } + .animation(.spring(response: 0.25, dampingFraction: 0.75, blendDuration: 0), value: configuration.isOn) + .scaleEffect(isHovered ? 1.05 : 1.0) + .animation(.spring(response: 0.2, dampingFraction: 0.7, blendDuration: 0), value: isHovered) + .onTapGesture { + withAnimation(.spring(response: 0.25, dampingFraction: 0.75)) { + configuration.isOn.toggle() + } + } + .onHover { hovering in + isHovered = hovering + } + + configuration.label + } + } +} + +// MARK: - Hover Hand Cursor Modifier (macOS 11 compatible) +extension View { + func handCursorOnHover() -> some View { + self.onHover { hovering in + if hovering { + NSCursor.pointingHand.push() + } else { + NSCursor.pop() + } + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/BlockedURLsHelpers.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/BlockedURLsHelpers.swift new file mode 100644 index 00000000..84b46241 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/BlockedURLsHelpers.swift @@ -0,0 +1,117 @@ +// +// BlockedURLsHelpers.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import Foundation + +// MARK: - Blocked URLs Helpers +struct BlockedURLsHelpers { + /// Get enabled URLs from the blocked URLs list + private static func enabledURLs(_ blockedURLs: [BlockedURL]) -> [BlockedURL] { + blockedURLs.filter { $0.isEnabled } + } + + /// Count active blocked domains (sites) + static func activeBlockedCount(_ blockedURLs: [BlockedURL]) -> Int { + enabledURLs(blockedURLs).count + } + + /// Count active paths across all enabled domains + static func activePathCount(_ blockedURLs: [BlockedURL]) -> Int { + enabledURLs(blockedURLs).reduce(0) { $0 + $1.paths.count } + } + + /// Format blocking count message with separate domain and path counts + static func blockingCountMessage(_ blockedURLs: [BlockedURL]) -> String { + let domainCount = activeBlockedCount(blockedURLs) + let pathCount = activePathCount(blockedURLs) + + if domainCount == 0 && pathCount == 0 { + return "0 \(Strings.BlocklistEditor.sites.lowercased())" + } + + var parts: [String] = [] + if domainCount > 0 { + parts.append(Strings.BlockedURLs.domainsText(domainCount)) + } + if pathCount > 0 { + parts.append(Strings.BlocklistEditor.pathCount(pathCount)) + } + + return parts.joined(separator: ", ") + } + + /// Generate unique identifier for blockedURLs state to force view updates + static func blockedURLsStateId(_ blockedURLs: [BlockedURL]) -> String { + let domainCount = activeBlockedCount(blockedURLs) + let pathCount = activePathCount(blockedURLs) + return "\(domainCount)_\(pathCount)_\(blockedURLs.count)" + } + + /// Format blocked sites text for display + static func blockedSitesText(_ blockedURLs: [BlockedURL]) -> String { + if blockedURLs.isEmpty { + return Strings.BlockedURLs.noSites + } + + let enabled = enabledURLs(blockedURLs) + + if enabled.isEmpty { + return Strings.BlockedURLs.allSitesDisabled + } + + // Count domains and paths separately + let domainCount = enabled.count + let totalPathCount = enabled.reduce(0) { $0 + $1.paths.count } + + // Only show path name if there's exactly 1 domain and exactly 1 path + if totalPathCount == 1 && domainCount == 1 { + if let url = enabled.first, let firstPath = url.paths.first { + // Remove leading slash if present for display + let displayPath = firstPath.hasPrefix("/") ? String(firstPath.dropFirst()) : firstPath + return "\(url.domain)/\(displayPath)" + } + } + + // Build domain count text + let domainText: String + if domainCount == 1 { + domainText = enabled.first!.domain + } else if domainCount != blockedURLs.count { + domainText = Strings.BlockedURLs.domainCount(domainCount, total: blockedURLs.count) + } else { + domainText = Strings.BlockedURLs.domainsText(domainCount) + } + + // Combine domain and path counts + if totalPathCount > 0 { + return "\(domainText), \(Strings.BlocklistEditor.pathCount(totalPathCount))" + } else { + return domainText + } + } + + /// Parse domain and path from input like "instagram.com/reels" + static func parseDomainAndPath(from input: String) -> (domain: String, path: String?) { + let cleaned = input.trimmingCharacters(in: .whitespaces).lowercased() + + // Check if input contains a path (has a / after the domain) + if let slashIndex = cleaned.firstIndex(of: "/") { + let domainPart = String(cleaned[.. Bool { + intensityLevel == .low || intensityLevel == .medium + } + + /// Check if stop confirmation is required based on intensity level + static func requiresStopConfirmation(intensityLevel: IntensityLevel) -> Bool { + intensityLevel == .medium + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/IntensityHelpers.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/IntensityHelpers.swift new file mode 100644 index 00000000..331376ca --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/IntensityHelpers.swift @@ -0,0 +1,32 @@ +// +// IntensityHelpers.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import Foundation + +// MARK: - Intensity Helpers +struct IntensityHelpers { + /// Get next intensity level in the cycle + static func nextIntensity(current: IntensityLevel) -> IntensityLevel { + let allLevels = IntensityLevel.allCases + if let currentIndex = allLevels.firstIndex(of: current) { + let nextIndex = (currentIndex + 1) % allLevels.count + return allLevels[nextIndex] + } + return current + } + + /// Get previous intensity level in the cycle + static func previousIntensity(current: IntensityLevel) -> IntensityLevel { + let allLevels = IntensityLevel.allCases + if let currentIndex = allLevels.firstIndex(of: current) { + let previousIndex = (currentIndex - 1 + allLevels.count) % allLevels.count + return allLevels[previousIndex] + } + return current + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/TimeHelpers.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/TimeHelpers.swift new file mode 100644 index 00000000..4f7e1c27 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/TimeHelpers.swift @@ -0,0 +1,338 @@ +// +// TimeHelpers.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import Foundation + +// MARK: - Time Formatting Helpers +struct TimeHelpers { + /// Format time display string from minutes, supporting easter egg mode with days + static func timeDisplay(minutes: Double, isEasterEggUnlocked: Bool, days: Int) -> String { + if isEasterEggUnlocked { + // Extract days, hours, and minutes + let daysInMinutes = days * 24 * 60 + let totalMins = Int(minutes) + let hoursAndMins = totalMins - daysInMinutes + let hours = max(0, hoursAndMins) / 60 + let mins = max(0, hoursAndMins) % 60 + + var parts: [String] = [] + if days > 0 { + parts.append("\(days)d") + } + if hours > 0 { + parts.append("\(hours)h") + } + if mins > 0 { + parts.append("\(mins)m") + } + return parts.isEmpty ? "0m" : parts.joined(separator: " ") + } + + let hours = Int(minutes) / 60 + let mins = Int(minutes) % 60 + + if hours > 0 { + return mins > 0 ? "\(hours)h \(mins)m" : "\(hours)h" + } + return "\(Int(minutes))m" + } + + /// Format countdown display string from remaining time + static func countdownDisplay(remainingTime: TimeInterval, hideSeconds: Bool) -> String { + let hours = Int(remainingTime) / 3600 + let mins = (Int(remainingTime) % 3600) / 60 + let secs = Int(remainingTime) % 60 + + if hideSeconds { + return String(format: "%02d:%02d", hours, mins) + } else { + return String(format: "%02d:%02d:%02d", hours, mins, secs) + } + } + + /// Format countdown time as mm:ss (for medium intensity stop confirmation) + static func formatCountdownTime(_ time: TimeInterval) -> String { + let minutes = Int(time) / 60 + let seconds = Int(time) % 60 + return String(format: "%02d:%02d", minutes, seconds) + } + + /// Extract time components from minutes for editing + static func extractTimeComponents(minutes: Double, isEasterEggUnlocked: Bool, days: Int) -> (days: Int, hours: Int, minutes: Int) { + if isEasterEggUnlocked { + let daysInMinutes = days * 24 * 60 + let totalMins = Int(minutes) + let hoursAndMins = totalMins - daysInMinutes + let hours = max(0, hoursAndMins) / 60 + let mins = max(0, hoursAndMins) % 60 + return (days: days, hours: hours, minutes: mins) + } + + let hours = Int(minutes) / 60 + let mins = Int(minutes) % 60 + return (days: 0, hours: hours, minutes: mins) + } + + // MARK: - Time Input Calculation Helpers + + /// Result structure for time calculations + struct TimeCalculationResult { + let days: Int + let daysInput: String + let hours: Int + let hoursInput: String + let minutes: Int + let minutesInput: String + let totalMinutes: Double + } + + /// Calculate minutes from hours and minutes inputs (non-easter egg mode) + static func calculateMinutesFromInputs(hoursInput: String, minutesInput: String, isEasterEggUnlocked: Bool) -> (hours: Int, hoursInput: String, minutes: Int, minutesInput: String, totalMinutes: Double) { + var hours = Int(hoursInput) ?? 0 + var mins = Int(minutesInput) ?? 0 + + if !isEasterEggUnlocked { + // For non-easter egg version, handle minutes overflow first + if mins >= 60 { + let additionalHours = mins / 60 + hours += additionalHours + mins = mins % 60 + } + + // Then clamp hours to 24 max + hours = min(24, max(0, hours)) + } + + let finalHoursInput = hours == 0 ? "0" : String(hours) + let finalMinutesInput = mins == 0 ? "0" : String(mins) + + let totalMinutes = Double(hours * 60 + mins) + let maxMinutes = isEasterEggUnlocked ? Double.greatestFiniteMagnitude : 1440 + let clampedMinutes = max(0, min(maxMinutes, totalMinutes)) + + return (hours: hours, hoursInput: finalHoursInput, minutes: mins, minutesInput: finalMinutesInput, totalMinutes: clampedMinutes) + } + + /// Calculate minutes from days, hours, and minutes inputs (easter egg mode) + static func calculateMinutesFromAllInputs(daysInput: String, hoursInput: String, minutesInput: String, currentDays: Int, isEasterEggUnlocked: Bool) -> TimeCalculationResult { + var inputDays = Int(daysInput) ?? currentDays + var inputHours = Int(hoursInput) ?? 0 + var inputMins = Int(minutesInput) ?? 0 + + if isEasterEggUnlocked { + // First, handle minutes overflow -> convert to hours + if inputMins >= 60 { + let additionalHours = inputMins / 60 + inputHours += additionalHours + inputMins = inputMins % 60 + } + + // Then, handle hours overflow -> convert to days + if inputHours >= 24 { + let additionalDays = inputHours / 24 + let newTotalDays = inputDays + additionalDays + + // If total would exceed 1000, cap at 1000 and keep excess in hours + if newTotalDays > 1000 { + let daysToAdd = 1000 - inputDays + inputDays = 1000 + inputHours = inputHours - (daysToAdd * 24) + } else { + inputDays = newTotalDays + inputHours = inputHours % 24 + } + } + + // Final clamp to ensure we never exceed 1000 + inputDays = min(1000, max(0, inputDays)) + inputHours = max(0, inputHours) + inputMins = max(0, min(59, inputMins)) + + let totalMinutes = Double(inputDays * 24 * 60 + inputHours * 60 + inputMins) + + return TimeCalculationResult( + days: inputDays, + daysInput: inputDays == 0 ? "0" : String(inputDays), + hours: inputHours, + hoursInput: inputHours == 0 ? "0" : String(inputHours), + minutes: inputMins, + minutesInput: inputMins == 0 ? "0" : String(inputMins), + totalMinutes: max(0, totalMinutes) + ) + } else { + // Fall back to non-easter egg calculation + let result = calculateMinutesFromInputs(hoursInput: hoursInput, minutesInput: minutesInput, isEasterEggUnlocked: false) + return TimeCalculationResult( + days: 0, + daysInput: "0", + hours: result.hours, + hoursInput: result.hoursInput, + minutes: result.minutes, + minutesInput: result.minutesInput, + totalMinutes: result.totalMinutes + ) + } + } + + /// Adjust days by amount (easter egg mode) + static func adjustDays(currentDaysInput: String, currentDays: Int, amount: Int) -> (days: Int, daysInput: String) { + let currentDays = Int(currentDaysInput) ?? currentDays + let newDays = min(1000, max(0, currentDays + amount)) + return (days: newDays, daysInput: newDays == 0 ? "0" : String(newDays)) + } + + /// Adjust hours by amount + static func adjustHours(daysInput: String, hoursInput: String, minutesInput: String, currentDays: Int, currentMinutes: Double, amount: Int, isEasterEggUnlocked: Bool) -> TimeCalculationResult { + if isEasterEggUnlocked { + var currentDays = Int(daysInput) ?? currentDays + var currentHours = Int(hoursInput) ?? (Int(currentMinutes) / 60 % 24) + + currentHours += amount + + // Auto-calculate days from hours + if currentHours >= 24 { + let additionalDays = currentHours / 24 + currentDays += additionalDays + currentHours = currentHours % 24 + } else if currentHours < 0 && currentDays > 0 { + // Borrow from days if hours go negative + let borrowDays = (-currentHours + 23) / 24 + currentDays = max(0, currentDays - borrowDays) + currentHours = currentHours + (borrowDays * 24) + } + + // Clamp days to max 1000 + currentDays = min(1000, max(0, currentDays)) + + return calculateMinutesFromAllInputs( + daysInput: currentDays == 0 ? "0" : String(currentDays), + hoursInput: currentHours == 0 ? "0" : String(max(0, currentHours)), + minutesInput: minutesInput, + currentDays: currentDays, + isEasterEggUnlocked: true + ) + } else { + // Original behavior for editing mode + let currentHours = Int(hoursInput) ?? 0 + let newHours = max(0, min(24, currentHours + amount)) + let result = calculateMinutesFromInputs( + hoursInput: String(newHours), + minutesInput: minutesInput, + isEasterEggUnlocked: false + ) + return TimeCalculationResult( + days: 0, + daysInput: "0", + hours: result.hours, + hoursInput: result.hoursInput, + minutes: result.minutes, + minutesInput: result.minutesInput, + totalMinutes: result.totalMinutes + ) + } + } + + /// Adjust minutes by amount + static func adjustMinutes(daysInput: String, hoursInput: String, minutesInput: String, currentDays: Int, currentMinutes: Double, amount: Int, isEasterEggUnlocked: Bool) -> TimeCalculationResult { + if isEasterEggUnlocked { + var currentDays = Int(daysInput) ?? currentDays + var currentHours = Int(hoursInput) ?? (Int(currentMinutes) / 60 % 24) + var currentMins = Int(minutesInput) ?? (Int(currentMinutes) % 60) + + currentMins += amount + var carryHours = 0 + + if currentMins >= 60 { + carryHours = currentMins / 60 + currentMins = currentMins % 60 + } else if currentMins < 0 { + carryHours = (currentMins - 59) / 60 + currentMins = ((currentMins % 60) + 60) % 60 + } + + currentHours += carryHours + + // Auto-calculate days from hours + if currentHours >= 24 { + let additionalDays = currentHours / 24 + currentDays += additionalDays + currentHours = currentHours % 24 + } else if currentHours < 0 && currentDays > 0 { + let borrowDays = (-currentHours + 23) / 24 + currentDays = max(0, currentDays - borrowDays) + currentHours = currentHours + (borrowDays * 24) + } + + // Clamp days to max 1000 + currentDays = min(1000, max(0, currentDays)) + + return TimeCalculationResult( + days: currentDays, + daysInput: currentDays == 0 ? "0" : String(currentDays), + hours: currentHours, + hoursInput: currentHours == 0 ? "0" : String(max(0, currentHours)), + minutes: currentMins, + minutesInput: currentMins == 0 ? "0" : String(max(0, currentMins)), + totalMinutes: Double(currentDays * 24 * 60 + currentHours * 60 + currentMins) + ) + } else { + // Original behavior for editing mode + let currentMins = Int(minutesInput) ?? 0 + var newMins = currentMins + amount + var carryHours = 0 + + if newMins >= 60 { + carryHours = 1 + newMins = 0 + } else if newMins < 0 { + carryHours = -1 + newMins = 59 + } + + // If there's a carry, adjust hours directly + var newHours = Int(hoursInput) ?? 0 + if carryHours != 0 { + newHours += carryHours + newHours = max(0, min(24, newHours)) + } + + // Calculate final result + let result = calculateMinutesFromInputs( + hoursInput: String(newHours), + minutesInput: String(newMins), + isEasterEggUnlocked: false + ) + return TimeCalculationResult( + days: 0, + daysInput: "0", + hours: result.hours, + hoursInput: result.hoursInput, + minutes: result.minutes, + minutesInput: result.minutesInput, + totalMinutes: result.totalMinutes + ) + } + } + + // MARK: - Date/Time Conversion Helpers + + /// Create a Date from hour and minute components + static func dateFromTime(hour: Int, minute: Int) -> Date { + var components = DateComponents() + components.hour = hour + components.minute = minute + return Calendar.current.date(from: components) ?? Date() + } + + /// Extract hour and minute from a Date + static func timeFromDate(_ date: Date) -> (hour: Int, minute: Int) { + let components = Calendar.current.dateComponents([.hour, .minute], from: date) + return (components.hour ?? 0, components.minute ?? 0) + } + +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift new file mode 100644 index 00000000..6fe4f82e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift @@ -0,0 +1,242 @@ +// +// AppModels.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import Foundation + +// MARK: - Blocked URL Model +struct BlockedURL: Identifiable, Codable, Equatable { + let id: UUID + var domain: String + var paths: [String] + var isEnabled: Bool + + init(id: UUID = UUID(), domain: String, paths: [String] = [], isEnabled: Bool = true) { + self.id = id + self.domain = domain + self.paths = paths + self.isEnabled = isEnabled + } + + var urls: [String]? { + if isEnabled == false { return nil } + if paths.count == 0 { + return [domain] + } else { + return paths.map { path in + let path = path.hasPrefix("/") ? path : "/\(path)" + return "\(domain)\(path)" + } + } + } +} + +// MARK: - App Navigation +enum AppScreen: Equatable { + case main + case editList + case domainDetail(UUID) + case advancedSettings + case blockSchedule + case about + + static func == (lhs: AppScreen, rhs: AppScreen) -> Bool { + switch (lhs, rhs) { + case (.main, .main): + return true + case (.editList, .editList): + return true + case (.domainDetail(let lhsId), .domainDetail(let rhsId)): + return lhsId == rhsId + case (.advancedSettings, .advancedSettings): + return true + case (.blockSchedule, .blockSchedule): + return true + case (.about, .about): + return true + default: + return false + } + } +} + +// MARK: - Blocking Mode +enum BlockingMode: String { + case blocklist + case allowlist +} + +// MARK: - Intensity Level +enum IntensityLevel: String, Codable, CaseIterable { + case low = "low" + case medium = "medium" + case high = "high" + + var displayName: String { + switch self { + case .low: + return "Low" + case .medium: + return "Medium" + case .high: + return "High" + } + } + + var description: String { + switch self { + case .low: + return "Stop blocking anytime with one click." + case .medium: + return "Added friction with a 10 minute wait." + case .high: + return "No way to stop blocking early." + } + } +} + +// MARK: - Schedule Models +struct TimeSlot: Identifiable, Codable, Equatable { + let id: UUID + var startHour: Int + var startMinute: Int + var endHour: Int + var endMinute: Int + + init(id: UUID = UUID(), startHour: Int, startMinute: Int, endHour: Int, endMinute: Int) { + self.id = id + self.startHour = startHour + self.startMinute = startMinute + self.endHour = endHour + self.endMinute = endMinute + } + + // Helper to format hour in 12-hour format + private func formatHour12(_ hour24: Int) -> (hour: Int, isPM: Bool) { + if hour24 == 0 { + return (12, false) + } else if hour24 < 12 { + return (hour24, false) + } else if hour24 == 12 { + return (12, true) + } else { + return (hour24 - 12, true) + } + } + + var startTimeString: String { + let (hour12, isPM) = formatHour12(startHour) + let amPm = isPM ? "PM" : "AM" + return String(format: "%d:%02d %@", hour12, startMinute, amPm) + } + + var endTimeString: String { + let (hour12, isPM) = formatHour12(endHour) + let amPm = isPM ? "PM" : "AM" + return String(format: "%d:%02d %@", hour12, endMinute, amPm) + } + + var displayString: String { + "\(startTimeString)-\(endTimeString)" + } +} + +// MARK: - Weekday +enum Weekday: Int, CaseIterable, Codable, Hashable { + case monday = 0 + case tuesday = 1 + case wednesday = 2 + case thursday = 3 + case friday = 4 + case saturday = 5 + case sunday = 6 + + var letter: String { + switch self { + case .monday: return "M" + case .tuesday: return "T" + case .wednesday: return "W" + case .thursday: return "T" + case .friday: return "F" + case .saturday: return "S" + case .sunday: return "S" + } + } + + var fullName: String { + switch self { + case .monday: return "Monday" + case .tuesday: return "Tuesday" + case .wednesday: return "Wednesday" + case .thursday: return "Thursday" + case .friday: return "Friday" + case .saturday: return "Saturday" + case .sunday: return "Sunday" + } + } +} + +// MARK: - Schedule +struct Schedule: Identifiable, Codable, Equatable { + let id: UUID + var timeSlots: [TimeSlot] + var enabledDays: Set + var isExpanded: Bool + var isEnabled: Bool + + init(id: UUID = UUID(), timeSlots: [TimeSlot] = [], enabledDays: Set = [], isExpanded: Bool = false, isEnabled: Bool = false) { + self.id = id + self.timeSlots = timeSlots + self.enabledDays = enabledDays + self.isExpanded = isExpanded + self.isEnabled = isEnabled + } + + var summaryString: String { + timeSlots.map { $0.displayString }.joined(separator: " ") + } + + var enabledDaysString: String { + Weekday.allCases.compactMap { enabledDays.contains($0) ? $0.letter : nil }.joined() + } +} + +// MARK: - About Page Item +enum AboutItemType { + case text + case link +} + +struct AboutItem { + let type: AboutItemType + let text: String + let url: String? + + init(text: String) { + self.type = .text + self.text = text + self.url = nil + } + + init(text: String, url: String) { + self.type = .link + self.text = text + self.url = url + } +} + +// MARK: - Tip Model +struct Tip: Identifiable { + let id: UUID + let title: String + let description: String + + init(id: UUID = UUID(), title: String, description: String) { + self.id = id + self.title = title + self.description = description + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift new file mode 100644 index 00000000..b031afa9 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift @@ -0,0 +1,1499 @@ +// +// ContentView.swift +// SelfControlUI +// +// Created by Vidya Giri on 10/30/25. +// + +import SwiftUI +import AppKit + +struct NewContentView: View { + + @EnvironmentObject var viewModel: FilterViewModel + + @State private var minutes: Double = 11 + @State private var blockedURLs: [BlockedURL] = [] + @State private var currentScreen: AppScreen = .main + @State private var isBlocking = false + @State private var isHoveringButton = false + @State private var remainingTime: TimeInterval = 0 + @State private var timer: Timer? + @State private var focusedField: FocusField? + @State private var showingConfirmation = false + @AppStorage("dontShowConfirmation") private var dontShowConfirmation = false + @AppStorage("blockingMode") private var blockingMode: BlockingMode = .blocklist + @AppStorage("hideSeconds") private var hideSeconds = false + @AppStorage("hasSeenTips") private var hasSeenTips = false + @AppStorage("intensityLevel") private var intensityLevel: IntensityLevel = .low + @State private var showingTips = false + @State private var isHoveringIntensity = false + @State private var isEditingTime = false + @State private var hoursInput = "" + @State private var minutesInput = "" + @State private var isHoveringTime = false + @State private var isAdjustingTime = false + @State private var logoRotation: Double = 0 + @State private var isEasterEggUnlocked: Bool = false + @State private var days: Int = 1 + @State private var daysInput: String = "1" + @State private var logoDragOffset: CGSize = .zero + @State private var hasReachedEdge: Bool = false + @State private var isHoveringEditButton = false + @State private var isHoveringDoneButton = false + @State private var showingStopConfirmation = false + @State private var stopCountdownTime: TimeInterval = 600 + @State private var stopCountdownTimer: Timer? + @State private var isHoveringStopButton = false + @State private var isPressingStartButton = false + @State private var showingIntensityDropdown = false + @State private var showingExtendTimer = false + @State private var extensionMinutes: Double = 60 + @State private var isHoveringExtendTimer = false + @State private var isHoveringAddToList = false + @AppStorage("isScheduleActive") private var isScheduleActive = false + @State private var isHoveringSchedule = false + + enum FocusField: Hashable { + case slider + case timeField + case startButton + case editButton + case addToBlocklist + } + + var activeBlockedCount: Int { + BlockedURLsHelpers.activeBlockedCount(blockedURLs) + } + + var activePathCount: Int { + BlockedURLsHelpers.activePathCount(blockedURLs) + } + + // Format blocking count message with separate domain and path counts + var blockingCountMessage: String { + BlockedURLsHelpers.blockingCountMessage(blockedURLs) + } + + // Unique identifier for the blockedURLs state to force view updates + var blockedURLsStateId: String { + BlockedURLsHelpers.blockedURLsStateId(blockedURLs) + } + + var timeDisplay: String { + TimeHelpers.timeDisplay(minutes: minutes, isEasterEggUnlocked: isEasterEggUnlocked, days: days) + } + + var countdownDisplay: String { + TimeHelpers.countdownDisplay(remainingTime: remainingTime, hideSeconds: hideSeconds) + } + + @ViewBuilder + var currentScreenView: some View { + switch currentScreen { + case .main: + mainView + case .editList: + BlocklistEditorView( + blockedURLs: $blockedURLs, isBlockingMode: isBlocking, + blockingMode: $blockingMode, + currentScreen: $currentScreen, + showingTips: $showingTips + ) + case .domainDetail(let domainId): + DomainDetailView( + blockedURLs: $blockedURLs, + domainId: domainId, + isBlockingMode: isBlocking, + currentScreen: $currentScreen, + blockingMode: $blockingMode + ) + case .advancedSettings: + AdvancedSettingsView(currentScreen: $currentScreen, blockingMode: $blockingMode, isBlockingMode: isBlocking) + case .blockSchedule: + BlockScheduleView(currentScreen: $currentScreen, blockingMode: $blockingMode, isScheduleActive: $isScheduleActive) + case .about: + AboutView(currentScreen: $currentScreen) + } + } + + var body: some View { + currentScreenView + .frame(maxWidth: .infinity, maxHeight: .infinity) + .frame(minWidth: 520, minHeight: 420) + .id(blockedURLsStateId) // Force view refresh when blockedURLs changes + .alert(isPresented: $showingConfirmation) { + let messageText: Text = { + let countText = blockingCountMessage + return Text(Strings.MainScreen.startBlockingMessage(blockingMode: blockingMode, countText: countText, timeDisplay: timeDisplay)) + }() + return Alert( + title: Text(Strings.MainScreen.startBlockingTitle), + message: messageText, + primaryButton: .default(Text(Strings.MainScreen.startBlock), action: { startBlocking() }), + secondaryButton: .cancel(Text(Strings.Common.cancel)) + ) + } + .overlay( + Group { + if showingTips { + TipsOverlayView( + onDismiss: { + showingTips = false + hasSeenTips = true + } + ) + .zIndex(1000) + .allowsHitTesting(true) + } + if showingStopConfirmation { + StopConfirmationOverlayView( + countdownTime: stopCountdownTime, + onResume: { + resumeBlocking() + }, + onStopBlocking: { + stopBlocking() + } + ) + .zIndex(1000) + .allowsHitTesting(true) + } + if showingIntensityDropdown { + IntensityDropdownOverlay( + selectedLevel: $intensityLevel, + onDismiss: { + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { + showingIntensityDropdown = false + } + } + ) + .zIndex(999) + .allowsHitTesting(true) + } + if showingExtendTimer { + ExtendTimerOverlayView( + extensionMinutes: $extensionMinutes, + onConfirm: { minutes in + extendTimer(by: minutes) + withAnimation(DesignSystem.animationNormal) { + showingExtendTimer = false + } + }, + onDismiss: { + withAnimation(DesignSystem.animationNormal) { + showingExtendTimer = false + } + } + ) + .zIndex(998) + .allowsHitTesting(true) + } + } + ) + .onAppear { + // Show tips on first launch only + if !hasSeenTips { + // Small delay to ensure view is fully loaded + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + showingTips = true + // Clear focus when tips modal shows + focusedField = nil + } + } else { + // Focus slider on startup + focusedField = .slider + } + blockedURLs = viewModel.blockerStorage?.items ?? [] + } + .onChange(of: showingTips) { isShowing in + // Clear focus when tips modal is shown + if isShowing { + focusedField = nil + } else { + // Set focus to slider after tips are dismissed (if not editing time) + if !isEditingTime { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + focusedField = .slider + } + } + } + } + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ShowAboutScreen"))) { _ in + currentScreen = .about + } + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ShowEditListScreen"))) { _ in + currentScreen = .editList + } + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ShowAdvancedSettingsScreen"))) { _ in + currentScreen = .advancedSettings + } + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ShowBlockScheduleScreen"))) { _ in + currentScreen = .blockSchedule + } + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("ShowTipsScreen"))) { _ in + showingTips = true + } + } + + var mainView: some View { + ZStack { + // Dark gradient background + DesignSystem.backgroundGradient + .ignoresSafeArea(.all) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .onTapGesture { + if showingIntensityDropdown { + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { + showingIntensityDropdown = false + } + } + } + + if isBlocking { + // Countdown mode - large timer display + VStack(spacing: 20) { + Spacer() + + // Logo + if let nsImage = NSImage(named: "AppIcon") { + Image(nsImage: nsImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 64, height: 64) + } else { + Image(systemName: "exclamationmark.triangle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplayXXXL)) + .foregroundColor(DesignSystem.accentOrange) + } + + // Large countdown timer + Text(countdownDisplay) + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplayHuge, weight: DesignSystem.fontWeightBold, design: .monospaced)) + .overlay( + LinearGradient( + gradient: Gradient(colors: [ + DesignSystem.textPrimary.opacity(DesignSystem.opacityAlmost), + DesignSystem.textPrimary.opacity(DesignSystem.opacityHigher) + ]), + startPoint: .top, + endPoint: .bottom + ) + .mask( + Text(countdownDisplay) + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplayHuge, weight: DesignSystem.fontWeightBold, design: .monospaced)) + ) + ) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityHigh), radius: 2, x: 0, y: 2) + + // Blocking info + VStack(spacing: DesignSystem.spacingXSmall) { + if blockingMode == .blocklist { + if activeBlockedCount == 0 { + Text(Strings.MainScreen.noSitesBlocked) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textSecondary) + Text(Strings.MainScreen.blocklistEmpty) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textTertiary) + } else { + Text("\(Strings.MainScreen.blocking) \(blockingCountMessage)") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textSecondary) + } + } else { + if activeBlockedCount == 0 { + Text(Strings.MainScreen.noSitesAllowed) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textSecondary) + Text(Strings.MainScreen.allWebsitesBlocked) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textTertiary) + } else { + Text("\(Strings.MainScreen.allowing) \(blockingCountMessage)") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textSecondary) + Text(Strings.MainScreen.allOtherSitesBlocked) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textTertiary) + } + } + } + .padding(.bottom, DesignSystem.spacingMedium) + + // Add to list and Extend Timer buttons (chip style) + HStack(spacing: DesignSystem.spacingSmall) { + Button(action: { + showingExtendTimer = true + }) { + HStack(spacing: 4) { + Image(systemName: "clock") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Text("Extend Timer") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(.white) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringExtendTimer ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle(color: .white)) + } + .buttonStyle(.plain) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringExtendTimer = hovering + } + } + + Button(action: { + currentScreen = .editList + }) { + HStack(spacing: 4) { + Image(systemName: "plus.circle") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Text(blockingMode == .blocklist ? Strings.MainScreen.addToBlocklist : Strings.MainScreen.addToAllowlist) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(.white) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringAddToList ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle(color: .white)) + } + .buttonStyle(.plain) + .focusable(false) + .keyboardShortcut("a", modifiers: .command) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringAddToList = hovering + } + } + } + .padding(.bottom, DesignSystem.spacingSmall) + + // Stop Blocking button (only for low and medium intensity) + if intensityLevel == .low || intensityLevel == .medium { + Button(action: { + if intensityLevel == .low { + // Low intensity: stop immediately + stopBlocking() + } else { + // Medium intensity: show confirmation with countdown + showStopConfirmation() + } + }) { + Text(Strings.MainScreen.stopBlocking) + .font(DesignSystem.font(size: DesignSystem.fontSizeXLarge, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .frame(maxWidth: .infinity) + .frame(height: 44) + .contentShape(Rectangle()) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusXXLarge) + .fill(isHoveringStopButton ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle(color: .white)) + .clipShape(RoundedRectangle(cornerRadius: DesignSystem.radiusXXLarge)) + } + .buttonStyle(.plain) + .help(intensityLevel == .low ? Strings.Help.stopBlockingImmediately : Strings.Help.stopBlockingWithConfirmation) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringStopButton = hovering + } + } + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.bottom, DesignSystem.spacingXLargePlusMedium) + } else { + // Add spacer for high intensity to maintain consistent layout + Spacer() + .frame(height: DesignSystem.spacingXLargePlusMedium) + } + } + } else { + // Configuration mode + VStack(spacing: 0) { + // Logo at top (draggable easter egg) + GeometryReader { geometry in + HStack { + Spacer() + ZStack { + // Background view to prevent window dragging + NonDraggableBackgroundView() + .frame(width: 56, height: 56) + + Group { + if let nsImage = NSImage(named: "AppIcon") { + Image(nsImage: nsImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 48, height: 48) + } else { + Image(systemName: "shield.lefthalf.filled") + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplayXL)) + .foregroundColor(DesignSystem.textPrimary) + } + } + .rotationEffect(.degrees(logoRotation)) + .offset(logoDragOffset) + .contentShape(Rectangle()) + .gesture( + isEasterEggUnlocked ? nil : DragGesture() + .onChanged { value in + let logoSize: CGFloat = 48 + let windowWidth = geometry.size.width + + // Get window bounds for vertical edge detection + let windowHeight: CGFloat + if let window = NSApplication.shared.windows.first { + windowHeight = window.contentView?.bounds.height ?? 450 + } else { + windowHeight = 450 // fallback + } + + // Calculate where logo center would be (starts at window center horizontally, near top vertically) + let centerX = windowWidth / 2 + let logoCenterX = centerX + value.translation.width + + // For vertical, logo starts near top (approximately 40-50px from top) + // Check if it reaches top or bottom edge + let approximateTopOffset: CGFloat = 50 + let logoCenterY = approximateTopOffset + value.translation.height + + // Check if logo reaches any edge + let halfLogo = logoSize / 2 + let reachedLeftEdge = logoCenterX - halfLogo <= 0 + let reachedRightEdge = logoCenterX + halfLogo >= windowWidth + let reachedTopEdge = logoCenterY - halfLogo <= 0 + let reachedBottomEdge = logoCenterY + halfLogo >= windowHeight + let reachedAnyEdge = reachedLeftEdge || reachedRightEdge || reachedTopEdge || reachedBottomEdge + + if reachedAnyEdge && !hasReachedEdge { + hasReachedEdge = true + } + + // Apply rubberbanding effect when dragging beyond edge + var newOffset = value.translation + + // Horizontal rubberbanding + if reachedLeftEdge { + let edgePosition = -centerX + halfLogo + let overshoot = abs(value.translation.width - edgePosition) + newOffset.width = edgePosition - overshoot * 0.3 + } else if reachedRightEdge { + let edgePosition = centerX - halfLogo + let overshoot = abs(value.translation.width - edgePosition) + newOffset.width = edgePosition + overshoot * 0.3 + } + + // Vertical rubberbanding + if reachedTopEdge { + let edgePosition = -approximateTopOffset + halfLogo + let overshoot = abs(value.translation.height - edgePosition) + newOffset.height = edgePosition - overshoot * 0.3 + } else if reachedBottomEdge { + let edgePosition = windowHeight - approximateTopOffset - halfLogo + let overshoot = abs(value.translation.height - edgePosition) + newOffset.height = edgePosition + overshoot * 0.3 + } + + logoDragOffset = newOffset + } + .onEnded { value in + guard !isEasterEggUnlocked else { return } + + // If we reached the edge, bounce back and unlock + if hasReachedEdge { + // Bounce back to center + withAnimation(.spring(response: 0.5, dampingFraction: 0.6)) { + logoDragOffset = .zero + } + + // After bounce back, spin and unlock + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + // Spin animation + withAnimation(.easeOut(duration: 1.5)) { + logoRotation = 1080 // 3 full spins + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + withAnimation(.spring(response: 0.6, dampingFraction: 0.7)) { + isEasterEggUnlocked = true + logoRotation = 0 + logoDragOffset = .zero + hasReachedEdge = false + // Initialize to 1 day when unlocked + days = 1 + daysInput = "1" + hoursInput = "0" + minutesInput = "0" + minutes = Double(days * 24 * 60) + } + } + } + } else { + // Normal drag end - bounce back to center + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { + logoDragOffset = .zero + hasReachedEdge = false + } + } + } + ) + } + .frame(width: 56, height: 56) + Spacer() + } + .offset(y: -16) + .padding(.top, DesignSystem.spacingLarge) + .padding(.bottom, DesignSystem.spacingXXSmall) + } + .frame(height: 70) + + // Schedule indicator pill below logo + HStack { + Spacer() + Button(action: { + currentScreen = .blockSchedule + }) { + HStack(spacing: 4) { + Circle() + .fill(isScheduleActive ? Color.green.opacity(0.7) : Color.gray.opacity(0.5)) + .frame(width: 6, height: 6) + Text(isScheduleActive ? "Schedule On" : "Schedule Off") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall)) + .foregroundColor(Color.gray.opacity(0.7)) + } + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + Capsule() + .stroke(Color.gray.opacity(isHoveringSchedule ? 0.4 : 0.25), lineWidth: 1) + .background( + Capsule() + .fill(isHoveringSchedule ? Color.gray.opacity(0.1) : Color.clear) + ) + ) + } + .buttonStyle(.plain) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringSchedule = hovering + } + } + Spacer() + } + .padding(.bottom, DesignSystem.spacingSmall) + + Spacer() + + // Main content with consistent spacing + VStack(spacing: DesignSystem.spacingMedium) { + // Time slider card + VStack(spacing: DesignSystem.spacingMedium) { + HStack(alignment: .top, spacing: DesignSystem.spacingXSmall) { + Image(systemName: "clock.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeTitle)) + .overlay( + LinearGradient( + gradient: Gradient(colors: [ + DesignSystem.textPrimary.opacity(DesignSystem.opacityAlmost), + DesignSystem.textPrimary.opacity(DesignSystem.opacityHigher) + ]), + startPoint: .top, + endPoint: .bottom + ) + .mask( + Image(systemName: "clock.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeTitle)) + ) + ) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityHigh), radius: 2, x: 0, y: 2) + .padding(.top, DesignSystem.spacingSmallMedium) + + if isEditingTime && !isEasterEggUnlocked { + HStack(spacing: 8) { + HStack(spacing: 6) { + // Hours + HStack(spacing: 2) { + ArrowKeyTextField( + text: $hoursInput, + onUpArrow: { + isAdjustingTime = true + adjustHours(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onDownArrow: { + isAdjustingTime = true + adjustHours(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onSubmit: { + updateMinutesFromInputs() + commitTimeEdit() + }, + onFocusChange: { hasFocus in + if !hasFocus { + updateMinutesFromInputs() + } + } + ) + .frame(width: max(40, CGFloat(hoursInput.count) * 20 + 10)) + + VStack(spacing: 2) { + Button(action: { + focusedField = nil + isAdjustingTime = true + adjustHours(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.up") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + + Button(action: { + focusedField = nil + isAdjustingTime = true + adjustHours(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.down") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + } + + Text(Strings.MainScreen.hours) + .font(DesignSystem.font(size: DesignSystem.fontSizeHeading, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + .padding(.top, DesignSystem.spacingXSmall) + } + + // Minutes + HStack(spacing: 2) { + ArrowKeyTextField( + text: $minutesInput, + onUpArrow: { + isAdjustingTime = true + adjustMinutes(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onDownArrow: { + isAdjustingTime = true + adjustMinutes(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onSubmit: { + updateMinutesFromInputs() + commitTimeEdit() + }, + onFocusChange: { hasFocus in + if !hasFocus { + updateMinutesFromInputs() + } + } + ) + .frame(width: max(40, CGFloat(minutesInput.count) * 20 + 10)) + + VStack(spacing: 2) { + Button(action: { + focusedField = nil + isAdjustingTime = true + adjustMinutes(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.up") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + + Button(action: { + focusedField = nil + isAdjustingTime = true + adjustMinutes(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.down") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + } + + Text(Strings.MainScreen.minutes) + .font(DesignSystem.font(size: DesignSystem.fontSizeHeading, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + .padding(.top, DesignSystem.spacingXSmall) + } + } + + // Done button + Button(action: { + commitTimeEdit() + }) { + Text(Strings.Common.done) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.vertical, DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringDoneButton ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringDoneButton = hovering + } + } + } + .frame(height: 42, alignment: .center) + } else if isEditingTime && isEasterEggUnlocked { + // Show days/hours/minutes editor when unlocked and editing + HStack(spacing: 8) { + HStack(spacing: 6) { + // Days + HStack(spacing: 2) { + ArrowKeyTextField( + text: $daysInput, + onUpArrow: { adjustDays(by: 1) }, + onDownArrow: { adjustDays(by: -1) }, + onSubmit: { updateMinutesFromAllInputs() }, + onFocusChange: { hasFocus in + if !hasFocus { + updateMinutesFromAllInputs() + } + } + ) + .frame(width: max(40, CGFloat(daysInput.count) * 20 + 10)) + + VStack(spacing: 2) { + Button(action: { adjustDays(by: 1) }) { + Image(systemName: "chevron.up") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + + Button(action: { adjustDays(by: -1) }) { + Image(systemName: "chevron.down") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + } + + Text(Strings.MainScreen.days) + .font(DesignSystem.font(size: DesignSystem.fontSizeHeading, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + .padding(.top, DesignSystem.spacingXSmall) + } + + // Hours + HStack(spacing: 2) { + ArrowKeyTextField( + text: $hoursInput, + onUpArrow: { + isAdjustingTime = true + adjustHours(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onDownArrow: { + isAdjustingTime = true + adjustHours(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onSubmit: { updateMinutesFromAllInputs() }, + onFocusChange: { hasFocus in + if !hasFocus { + updateMinutesFromAllInputs() + } + } + ) + .frame(width: max(40, CGFloat(hoursInput.count) * 20 + 10)) + + VStack(spacing: 2) { + Button(action: { + focusedField = nil + isAdjustingTime = true + adjustHours(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.up") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + + Button(action: { + focusedField = nil + isAdjustingTime = true + adjustHours(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.down") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + } + + Text(Strings.MainScreen.hours) + .font(DesignSystem.font(size: DesignSystem.fontSizeHeading, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + .padding(.top, DesignSystem.spacingXSmall) + } + + // Minutes + HStack(spacing: 2) { + ArrowKeyTextField( + text: $minutesInput, + onUpArrow: { + isAdjustingTime = true + adjustMinutes(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onDownArrow: { + isAdjustingTime = true + adjustMinutes(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onSubmit: { updateMinutesFromAllInputs() }, + onFocusChange: { hasFocus in + if !hasFocus { + updateMinutesFromAllInputs() + } + } + ) + .frame(width: max(40, CGFloat(minutesInput.count) * 20 + 10)) + + VStack(spacing: 2) { + Button(action: { + focusedField = nil + isAdjustingTime = true + adjustMinutes(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.up") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + + Button(action: { + focusedField = nil + isAdjustingTime = true + adjustMinutes(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.down") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + } + + Text(Strings.MainScreen.minutes) + .font(DesignSystem.font(size: DesignSystem.fontSizeHeading, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + .padding(.top, DesignSystem.spacingXSmall) + } + + } + + // Done button + Button(action: { + commitTimeEdit() + }) { + Text(Strings.Common.done) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.vertical, DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringDoneButton ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringDoneButton = hovering + } + } + } + .frame(height: 42, alignment: .center) + } else { + // Display mode - show formatted time (works for both unlocked and locked) + Text(timeDisplay) + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplayXL, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingMediumSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(isHoveringTime ? DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium) : Color.clear) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .stroke(isHoveringTime ? DesignSystem.hoverBorder : Color.clear, lineWidth: 1) + ) + ) + .frame(height: 42, alignment: .center) + .animation(DesignSystem.animationFast, value: isHoveringTime) + .onTapGesture { + startEditingTime() + } + .onHover { hovering in + isHoveringTime = hovering + if hovering { + NSCursor.pointingHand.push() + } else { + NSCursor.pop() + } + } + .help(Strings.Help.clickToEditTime) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + + // Show slider only if easter egg is not unlocked + if !isEasterEggUnlocked { + // Custom styled slider (original, only shown when easter egg not unlocked) + VStack(spacing: DesignSystem.spacingXSmall) { + ZStack(alignment: .center) { + // Tick marks integrated into slider + GeometryReader { geometry in + HStack(spacing: 0) { + ForEach(0..<97) { index in + let minuteValue = index * 15 + if minuteValue <= 1440 { + VStack(spacing: 0) { + Rectangle() + .fill(DesignSystem.textPrimary.opacity(DesignSystem.opacitySubtle)) + .frame(width: 0.5, height: index % 4 == 0 ? 8 : 4) + } + .frame(maxWidth: .infinity) + } + } + } + } + .frame(height: 8) + .allowsHitTesting(false) + + Group { + Group { + if showingTips { + Slider(value: $minutes, in: 0...1440, step: 15) + .accentColor(.white) + .disabled(true) + .opacity(1.0) + .allowsHitTesting(false) + } else { + Slider(value: $minutes, in: 0...1440, step: 15) + .accentColor(.white) + .disabled(isEditingTime) + .opacity(isEditingTime ? 0.4 : 1.0) + .focusable(false) + } + } + } + } + + HStack { + Text(Strings.MainScreen.oneMinute) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textTertiary) + Spacer() + Text(Strings.MainScreen.sixHours) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textTertiary) + Spacer() + Text(Strings.MainScreen.twelveHours) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textTertiary) + Spacer() + Text(Strings.MainScreen.eighteenHours) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textTertiary) + Spacer() + Text(Strings.MainScreen.twentyFourHours) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textTertiary) + } + } + } + } + .padding(DesignSystem.spacingXLarge) + .modifier(DesignSystem.cardStyle()) + .padding(.horizontal, DesignSystem.spacingLarge) + + // Site section with intensity integrated + VStack(spacing: DesignSystem.spacingMedium) { + HStack(spacing: DesignSystem.spacingSmall) { + Image(systemName: blockingMode == .blocklist ? "hand.raised.slash" : "hand.raised.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge)) + .overlay( + LinearGradient( + gradient: Gradient(colors: [ + DesignSystem.textPrimary.opacity(DesignSystem.opacityAlmost), + DesignSystem.textPrimary.opacity(DesignSystem.opacityHigher) + ]), + startPoint: .top, + endPoint: .bottom + ) + .mask( + Image(systemName: blockingMode == .blocklist ? "hand.raised.slash" : "hand.raised.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge)) + ) + ) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityHigh), radius: 2, x: 0, y: 2) + + VStack(alignment: .leading, spacing: 2) { + Text(blockingMode == .blocklist ? Strings.MainScreen.willBlock : Strings.MainScreen.willAllow) + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(blockingMode == .allowlist ? DesignSystem.textPrimary : DesignSystem.textTertiary) + .textCase(.uppercase) + Text(blockedSitesText) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .lineLimit(1) + .padding(.top, DesignSystem.spacingXXSmall) + .padding(.bottom, DesignSystem.spacingXXSmall) + } + + Spacer() + + // Intensity selector (centered in middle, aligned with Will Block) + VStack(alignment: .center, spacing: 2) { + // Intensity label row (centered above dropdown) + Text(Strings.MainScreen.intensity) + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textTertiary) + .textCase(.uppercase) + + // Custom intensity dropdown button + Button(action: { + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { + showingIntensityDropdown.toggle() + } + }) { + HStack(spacing: 4) { + Text(intensityLevel.displayName) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + Image(systemName: "chevron.down") + .font(.system(size: 8, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(isHoveringIntensity ? DesignSystem.textPrimary : DesignSystem.textSecondary) + .rotationEffect(.degrees(showingIntensityDropdown ? 180 : 0)) + } + .padding(.horizontal, DesignSystem.spacingXSmall) + .padding(.vertical, DesignSystem.spacingXXSmallPlus) + .frame(minWidth: 100) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(isHoveringIntensity ? DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium) : Color.clear) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .stroke(isHoveringIntensity ? DesignSystem.hoverBorder : Color.clear, lineWidth: 1) + ) + ) + } + .buttonStyle(.plain) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringIntensity = hovering + } + } + } + + Spacer() + + // Edit button + Button(action: { + currentScreen = .editList + }) { + HStack(spacing: DesignSystem.spacingXSmall) { + Image(systemName: "list.bullet.rectangle") + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + Text(Strings.Common.edit) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.vertical, DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringEditButton ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringEditButton = hovering + } + } + .keyboardShortcut("e", modifiers: .command) + } + } + .padding(DesignSystem.spacingMedium) + .modifier(DesignSystem.cardStyle()) + .padding(.horizontal, DesignSystem.spacingLarge) + } + // End of main content VStack + .padding(.bottom, DesignSystem.spacingMedium) + + Spacer() + + // Start button at bottom + Button(action: { + if intensityLevel == .high { + showingConfirmation = true + } else { + startBlocking() + } + }) { + Text(Strings.MainScreen.startBlock) + .font(.system(size: 15, weight: .medium)) + .foregroundColor(Color(hex: "#4A4A4A")) + .frame(maxWidth: .infinity) + .frame(height: 44) + .contentShape(Rectangle()) + .background( + ZStack { + RoundedRectangle(cornerRadius: DesignSystem.radiusXXLarge) + .fill( + LinearGradient( + gradient: Gradient(stops: [ + .init(color: Color(hex: "#F0F5F5"), location: 0.0), + .init(color: Color(hex: "#8A9A98"), location: 0.5), + .init(color: Color(hex: "#B5C9C5"), location: 1.0) + ]), + startPoint: .top, + endPoint: .bottom + ) + ) + + RoundedRectangle(cornerRadius: DesignSystem.radiusXXLarge) + .fill( + LinearGradient( + gradient: Gradient(colors: [ + Color(hex: "#E8EDED"), + Color(hex: "#A8B5B3") + ]), + startPoint: .top, + endPoint: .bottom + ) + ) + .padding(DesignSystem.spacingTiny) + + if isPressingStartButton { + RoundedRectangle(cornerRadius: DesignSystem.radiusXXLarge) + .fill(DesignSystem.overlayBackground.opacity(DesignSystem.opacityLow)) + .padding(DesignSystem.spacingTiny) + } + + // Metallic border - smoother blended gradient + RoundedRectangle(cornerRadius: DesignSystem.radiusXXLarge) + .stroke( + LinearGradient( + gradient: Gradient(stops: [ + .init(color: DesignSystem.textPrimary.opacity(DesignSystem.opacityHigh), location: 0.0), + .init(color: Color.gray.opacity(DesignSystem.opacityMedium), location: 0.2), + .init(color: Color.gray.opacity(DesignSystem.opacityMedium), location: 0.4), + .init(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityMedium), location: 0.6), + .init(color: Color.gray.opacity(DesignSystem.opacityMedium), location: 0.8), + .init(color: Color.gray.opacity(DesignSystem.opacityMedium), location: 1.0) + ]), + startPoint: .top, + endPoint: .bottom + ), + lineWidth: 1.5 + ) + } + ) + .scaleEffect(isHoveringButton ? 1.01 : 1.0) + .scaleEffect(isPressingStartButton ? 0.99 : 1.0) + } + .buttonStyle(.plain) + .help(Strings.Help.startBlocking) + .focusable(false) + .simultaneousGesture( + DragGesture(minimumDistance: 0) + .onChanged { _ in + if !isPressingStartButton { + withAnimation(DesignSystem.animationFast) { + isPressingStartButton = true + } + } + } + .onEnded { _ in + withAnimation(DesignSystem.animationFast) { + isPressingStartButton = false + } + } + ) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringButton = hovering + } + } + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.bottom, DesignSystem.spacingXLargePlusMedium) + } + } + } + .onAppear { + // Don't focus slider on startup - let tips modal show first if needed + // Focus will be set after tips are dismissed + } + } + + private func incrementIntensity() { + intensityLevel = IntensityHelpers.nextIntensity(current: intensityLevel) + } + + private func decrementIntensity() { + intensityLevel = IntensityHelpers.previousIntensity(current: intensityLevel) + } + + private func startBlocking() { + withAnimation(DesignSystem.animationNormal) { + isBlocking = true + viewModel.updateBlockList(newBlockedDomains: blockedURLs, time: minutes) + // Calculate total time including days if easter egg is unlocked + remainingTime = minutes * 60 + } + + // Start countdown timer + timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in + if remainingTime > 0 { + remainingTime -= 1 + } else { + stopBlocking() + } + } + } + + private func stopBlocking() { + withAnimation(DesignSystem.animationNormal) { + isBlocking = false + } + timer?.invalidate() + timer = nil + remainingTime = 0 + cancelStopCountdown() + } + + private func showStopConfirmation() { + stopCountdownTime = 600 + showingStopConfirmation = true + + // Start the countdown timer + stopCountdownTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in + if stopCountdownTime > 0 { + stopCountdownTime -= 1 + } else { + // Countdown finished, just stop the timer (user must click Yes to stop) + stopCountdownTimer?.invalidate() + stopCountdownTimer = nil + } + } + } + + private func cancelStopCountdown() { + stopCountdownTimer?.invalidate() + stopCountdownTimer = nil + stopCountdownTime = 600 + showingStopConfirmation = false + } + + private func resumeBlocking() { + cancelStopCountdown() + } + + private func extendTimer(by minutes: Double) { + remainingTime += minutes * 60 + } + + private var blockedSitesText: String { + BlockedURLsHelpers.blockedSitesText(blockedURLs) + } + + private func startEditingTime() { + let components = TimeHelpers.extractTimeComponents(minutes: minutes, isEasterEggUnlocked: isEasterEggUnlocked, days: days) + daysInput = String(components.days) + hoursInput = String(components.hours) + minutesInput = String(components.minutes) + isEditingTime = true + focusedField = .timeField + } + + private func commitTimeEdit() { + // Update minutes from the current input values + if isEasterEggUnlocked { + updateMinutesFromAllInputs() + } else { + updateMinutesFromInputs() + } + isEditingTime = false + focusedField = nil + } + + private func updateMinutesFromInputs() { + let result = TimeHelpers.calculateMinutesFromInputs( + hoursInput: hoursInput, + minutesInput: minutesInput, + isEasterEggUnlocked: isEasterEggUnlocked + ) + hoursInput = result.hoursInput + minutesInput = result.minutesInput + minutes = result.totalMinutes + } + + private func updateMinutesFromAllInputs() { + let result = TimeHelpers.calculateMinutesFromAllInputs( + daysInput: daysInput, + hoursInput: hoursInput, + minutesInput: minutesInput, + currentDays: days, + isEasterEggUnlocked: isEasterEggUnlocked + ) + days = result.days + daysInput = result.daysInput + hoursInput = result.hoursInput + minutesInput = result.minutesInput + minutes = result.totalMinutes + } + + private func adjustDays(by amount: Int) { + if isEasterEggUnlocked { + let result = TimeHelpers.adjustDays( + currentDaysInput: daysInput, + currentDays: days, + amount: amount + ) + days = result.days + daysInput = result.daysInput + updateMinutesFromAllInputs() + } + } + + private func adjustHours(by amount: Int) { + let result = TimeHelpers.adjustHours( + daysInput: daysInput, + hoursInput: hoursInput, + minutesInput: minutesInput, + currentDays: days, + currentMinutes: minutes, + amount: amount, + isEasterEggUnlocked: isEasterEggUnlocked + ) + days = result.days + daysInput = result.daysInput + hoursInput = result.hoursInput + minutesInput = result.minutesInput + minutes = result.totalMinutes + } + + private func adjustMinutes(by amount: Int) { + let result = TimeHelpers.adjustMinutes( + daysInput: daysInput, + hoursInput: hoursInput, + minutesInput: minutesInput, + currentDays: days, + currentMinutes: minutes, + amount: amount, + isEasterEggUnlocked: isEasterEggUnlocked + ) + days = result.days + daysInput = result.daysInput + hoursInput = result.hoursInput + minutesInput = result.minutesInput + minutes = result.totalMinutes + } +} + +#Preview { + NewContentView() +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/SelfControlUIApp.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/SelfControlUIApp.swift new file mode 100644 index 00000000..b92f9f2b --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/SelfControlUIApp.swift @@ -0,0 +1,109 @@ +// +// SelfControlUIApp.swift +// SelfControlUI +// +// Created by Vidya Giri on 10/30/25. +// +// +//import SwiftUI +//import AppKit +// +//@main +//struct SelfControlUIApp: App { +// var body: some Scene { +// let windowGroup = WindowGroup { +// ContentView() +// .background(WindowTitleBarHider()) +// } +// .commands { +// CommandGroup(replacing: .newItem) { } +// CommandGroup(replacing: .appInfo) { +// Button(Strings.AppMenu.aboutSelfControl) { +// NotificationCenter.default.post(name: NSNotification.Name("ShowAboutScreen"), object: nil) +// } +// +// Divider() +// +// Button(Strings.AppMenu.editSiteList) { +// NotificationCenter.default.post(name: NSNotification.Name("ShowEditListScreen"), object: nil) +// } +// .keyboardShortcut("e", modifiers: .command) +// +// Button(Strings.AppMenu.editBlockSchedule) { +// NotificationCenter.default.post(name: NSNotification.Name("ShowBlockScheduleScreen"), object: nil) +// } +// .keyboardShortcut("b", modifiers: .command) +// +// Button(Strings.AppMenu.moreSettings) { +// NotificationCenter.default.post(name: NSNotification.Name("ShowAdvancedSettingsScreen"), object: nil) +// } +// .keyboardShortcut(",", modifiers: .command) +// +// +// +// Divider() +// +// Button(Strings.AppMenu.donate) { +// if let url = URL(string: Strings.AppMenu.URLs.donate) { +// NSWorkspace.shared.open(url) +// } +// } +// } +// CommandGroup(replacing: .help) { +// Button(Strings.AppMenu.gettingStartedTips) { +// NotificationCenter.default.post(name: NSNotification.Name("ShowTipsScreen"), object: nil) +// } +// .keyboardShortcut("t", modifiers: .command) +// +// Divider() +// +// Button(Strings.AppMenu.selfControlHelp) { +// if let url = URL(string: Strings.AppMenu.URLs.help) { +// NSWorkspace.shared.open(url) +// } +// } +// .keyboardShortcut("?", modifiers: .command) +// +// Button(Strings.AppMenu.faq) { +// if let url = URL(string: Strings.AppMenu.URLs.faq) { +// NSWorkspace.shared.open(url) +// } +// } +// } +// } +// +// return windowGroup +// } +//} +// +//// MARK: - Window Configuration Helper (macOS 11 compatible) +//private struct WindowTitleBarHider: NSViewRepresentable { +// func makeNSView(context: Context) -> NSView { +// let view = NSView() +// DispatchQueue.main.async { +// if let window = view.window { +// configure(window: window) +// } else if let window = NSApplication.shared.windows.first { +// configure(window: window) +// } +// } +// return view +// } +// +// func updateNSView(_ nsView: NSView, context: Context) { +// if let window = nsView.window { +// configure(window: window) +// } +// } +// +// private func configure(window: NSWindow) { +// window.titleVisibility = .hidden +// window.titlebarAppearsTransparent = true +// window.isMovableByWindowBackground = true +// window.styleMask.insert(.fullSizeContentView) +// // Set a slightly smaller default content size +// if window.contentView?.frame.size.width ?? 0 > 0 { +// window.setContentSize(NSSize(width: 600, height: 450)) +// } +// } +//} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DesignSystem.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DesignSystem.swift new file mode 100644 index 00000000..7e8be519 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DesignSystem.swift @@ -0,0 +1,177 @@ +// +// DesignSystem.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import SwiftUI + +struct DesignSystem { + // MARK: - Corner Radii + static let radiusXSmall: CGFloat = 4 + static let radiusSmall: CGFloat = 8 + static let radiusMedium: CGFloat = 12 + static let radiusLarge: CGFloat = 16 + static let radiusXLarge: CGFloat = 20 + static let radiusXXLarge: CGFloat = 24 + + // MARK: - Spacing + static let spacingTiny: CGFloat = 1.5 + static let spacingXXSmall: CGFloat = 2 + static let spacingXXSmallPlus: CGFloat = 3 + static let spacingXSmall: CGFloat = 6 + static let spacingSmall: CGFloat = 10 + static let spacingSmallMedium: CGFloat = 11 + static let spacingMediumSmall: CGFloat = 12 + static let spacingMedium: CGFloat = 14 + static let spacingMediumLarge: CGFloat = 16 + static let spacingLarge: CGFloat = 18 + static let spacingLargeMinus: CGFloat = 16 + static let spacingXLarge: CGFloat = 24 + static let spacingXLargePlusMedium: CGFloat = 28 + static let spacingXXLarge: CGFloat = 32 + + // MARK: - Colors + // Base backgrounds + static let backgroundPrimary = Color(white: 0.15) + static let backgroundSecondary = Color(white: 0.12) + static let backgroundTertiary = Color(white: 0.2) + + // Border colors + static let borderPrimary = Color.white.opacity(0.1) + static let borderSecondary = Color.white.opacity(0.05) + + // Text colors + static let textPrimary = Color.white + static let textSecondary = Color(white: 0.7) + static let textTertiary = Color(white: 0.5) + static let textQuaternary = Color(white: 0.4) + + // Interactive states + static let hoverBackground = Color.white.opacity(0.2) + static let hoverBorder = Color.white.opacity(0.4) + + // Modal/Overlay backgrounds + static let overlayBackground = Color.black.opacity(0.6) + + // Disabled state colors + static let disabledBackground = Color(white: 0.4) + static let disabledText = Color(white: 0.4) + + // Accent colors + static let accentOrange = Color.orange + static let accentOrangeMuted = Color.orange.opacity(0.8) + static let accentPurple = Color.purple + static let accentPurpleMuted = Color.purple.opacity(0.8) + static let toggleTint = Color(red: 0.85, green: 0.95, blue: 1.0) + + // MARK: - Typography + static let fontSizeXSmall: CGFloat = 10 + static let fontSizeSmall: CGFloat = 11 + static let fontSizeBase: CGFloat = 12 + static let fontSizeMedium: CGFloat = 13 + static let fontSizeLarge: CGFloat = 14 + static let fontSizeXLarge: CGFloat = 15 + static let fontSizeXXLarge: CGFloat = 16 + static let fontSizeTitle: CGFloat = 18 + static let fontSizeHeading: CGFloat = 20 + static let fontSizeDisplay: CGFloat = 24 + static let fontSizeDisplayLarge: CGFloat = 28 + static let fontSizeDisplayXL: CGFloat = 32 + static let fontSizeDisplayXXL: CGFloat = 36 + static let fontSizeDisplayXXXL: CGFloat = 52 + static let fontSizeDisplayHuge: CGFloat = 64 + + // Font weights + static let fontWeightRegular = Font.Weight.regular + static let fontWeightMedium = Font.Weight.medium + static let fontWeightSemibold = Font.Weight.semibold + static let fontWeightBold = Font.Weight.bold + + // MARK: - Opacities + static let opacitySubtle: Double = 0.05 + static let opacityLow: Double = 0.1 + static let opacityMedium: Double = 0.2 + static let opacityHigh: Double = 0.4 + static let opacityHigher: Double = 0.6 + static let opacityAlmost: Double = 0.8 + static let opacityFull: Double = 1.0 + + // Semantic opacity aliases for common use cases + static let disabledOpacity: Double = 0.5 + + // MARK: - Animations + static let animationFast = Animation.easeInOut(duration: 0.2) + static let animationNormal = Animation.easeInOut(duration: 0.3) + + // MARK: - Gradients + static let backgroundGradient = LinearGradient( + gradient: Gradient(colors: [ + Color(red: 0.10, green: 0.10, blue: 0.12), + Color(red: 0.14, green: 0.14, blue: 0.17), + Color(red: 0.11, green: 0.11, blue: 0.14) + ]), + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + + // MARK: - Mode Colors + static func modeColor(for mode: BlockingMode) -> Color { + mode == .blocklist ? Color(red: 0.25, green: 0.48, blue: 0.95) : Color(red: 0.9, green: 0.3, blue: 0.3) + } + + // Muted destructive color + static let destructiveColor = Color(red: 0.8, green: 0.3, blue: 0.3) + + // MARK: - Card Style Modifier + static func cardStyle() -> some ViewModifier { + CardStyleModifier() + } + + // MARK: - Button Style Modifier + static func buttonStyle(color: Color? = nil, isDestructive: Bool = false) -> some ViewModifier { + ButtonStyleModifier(color: color, isDestructive: isDestructive) + } + + // MARK: - Typography Helpers + static func font(size: CGFloat, weight: Font.Weight = .regular) -> Font { + .system(size: size, weight: weight) + } + + static func font(size: CGFloat, weight: Font.Weight, design: Font.Design = .default) -> Font { + .system(size: size, weight: weight, design: design) + } +} + +// MARK: - Style Modifiers +struct CardStyleModifier: ViewModifier { + func body(content: Content) -> some View { + content + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusLarge) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusLarge) + .stroke(DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + } +} + +struct ButtonStyleModifier: ViewModifier { + let color: Color? + let isDestructive: Bool + + func body(content: Content) -> some View { + content + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isDestructive ? DesignSystem.destructiveColor.opacity(DesignSystem.opacityLow) : (color?.opacity(DesignSystem.opacityMedium) ?? DesignSystem.backgroundTertiary)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(isDestructive ? DesignSystem.destructiveColor.opacity(DesignSystem.opacityMedium) : (color?.opacity(DesignSystem.opacityMedium) ?? DesignSystem.borderPrimary), lineWidth: 1) + ) + ) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DomainConstants.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DomainConstants.swift new file mode 100644 index 00000000..d63c6e0e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DomainConstants.swift @@ -0,0 +1,29 @@ +// +// Constants.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import Foundation + +struct DomainConstants { + // MARK: - Suggested Sites + static let suggestedSites = [ + "instagram.com", + "tiktok.com", + "x.com", + "discord.com", + "linkedin.com", + "facebook.com", + "youtube.com", + "netflix.com", + "twitch.tv", + "reddit.com", + "hulu.com", + "amazon.com", + "chatgpt.com", + "claude.ai", + "perplexity.ai" + ] +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift new file mode 100644 index 00000000..edd5c7b4 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift @@ -0,0 +1,321 @@ +// +// Strings.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import Foundation + +struct Strings { + // MARK: - Common + struct Common { + static let back = "Back" + static let cancel = "Cancel" + static let done = "Done" + static let delete = "Delete" + static let add = "Add" + static let edit = "Edit" + static let importText = "Import" + static let export = "Export" + static let selectAll = "Select All" + static let deselectAll = "Deselect All" + } + + // MARK: - Main Screen + struct MainScreen { + static let startBlock = "Start Block" + static let stopBlocking = "Stop Blocking" + static let intensity = "INTENSITY" + static let willBlock = "Will Block" + static let willAllow = "Will Allow (Blocks ALL Else)" + static let noSitesBlocked = "No sites blocked" + static let blocklistEmpty = "Blocklist is empty" + static let blocking = "Blocking" + static let noSitesAllowed = "No sites allowed" + static let allWebsitesBlocked = "All websites are blocked" + static let allowing = "Allowing" + static let allOtherSitesBlocked = "All other sites are blocked" + static let addToBlocklist = "Add to Blocklist" + static let addToAllowlist = "Add to Allowlist" + + // Time presets + static let oneMinute = "1 min" + static let sixHours = "6h" + static let twelveHours = "12h" + static let eighteenHours = "18h" + static let twentyFourHours = "24h" + + // Time units + static let hours = "h" + static let minutes = "m" + static let days = "d" + + // Confirmation alert + static let startBlockingTitle = "Start Blocking?" + static func startBlockingMessage(blockingMode: BlockingMode, countText: String, timeDisplay: String) -> String { + if blockingMode == .allowlist { + return "⚠️ ALLOWLIST MODE: You will block ALL websites EXCEPT \(countText) for \(timeDisplay).\n\nOnly sites on your list will be accessible. You won't be able to stop until the timer expires." + } else { + return "You will block \(countText) for \(timeDisplay). You won't be able to stop until the timer expires." + } + } + } + + // MARK: - Blocklist Editor + struct BlocklistEditor { + static let title = "Blocklist" + static let allowlist = "Allowlist" + static let allowlistModeActive = "ALLOWLIST MODE ACTIVE" + static let allowlistModeDescription = "Only sites on this list will be accessible. Everything else will be blocked." + static let quickAdd = "Quick Add" + static let suggested = "Suggested" + static let tips = "Tips" + static let bulkEdit = "Bulk Edit" + static let sites = "SITE" + static let sitesPlural = "SITES" + static let noBlockedSites = "No blocked sites yet" + static let noResultsFound = "No results found" + static let locked = "(locked)" + static let path = "path" + static let paths = "paths" + static let selected = "selected" + + // Placeholders + static let searchPlaceholder = "Search..." + static let addWebsitePlaceholder = "Add website..." + static let addWebsiteExamplePlaceholder = "Add website (e.g., facebook.com)" + static let searchBlockedSitesPlaceholder = "Search blocked sites" + + static func siteCount(_ count: Int) -> String { + "\(count) \(count == 1 ? sites : sitesPlural)" + } + static func pathCount(_ count: Int) -> String { + "\(count) \(count == 1 ? path : paths)" + } + static func selectedCount(_ count: Int) -> String { + "\(count) \(selected)" + } + + // Delete confirmation + static func deleteSiteTitle(_ count: Int) -> String { + "Delete \(count) Site\(count == 1 ? "" : "s")?" + } + static func deleteSiteMessage(_ count: Int) -> String { + "Are you sure you want to delete \(count == 1 ? "this site" : "these sites")? This action cannot be undone." + } + + // Add site confirmation + static let addToExistingBlockTitle = "Add to Existing Block?" + static func addToExistingBlockMessage(_ site: String, blockingMode: BlockingMode) -> String { + "Are you sure you want to add \"\(site)\" to your \(blockingMode == .blocklist ? "blocklist" : "allowlist") while a block is active?" + } + static let addSite = "Add Site" + } + + // MARK: - Domain Detail + struct DomainDetail { + static let siteSettings = "Site Settings" + static let blockingMode = "BLOCKING MODE" + static let blockEntireDomain = "Block Entire Domain" + static let specificPathsOnly = "Specific Paths Only" + static let blockEntireDomainDescription = "Blocks all pages on this domain" + static let specificPathsDescription = "Only blocks specific paths/subdomains you add below" + static let pathsAndSubdomains = "PATHS & SUBDOMAINS" + static let addPathOrSubdomain = "Add Path or Subdomain" + static let paths = "Paths" + static func pathsCount(_ count: Int) -> String { + "Paths (\(count))" + } + static let noPathsAdded = "No paths added yet" + static let deleteSite = "Delete Site" + static let managePathsDescription = "Manage specific paths and subdomains for this website" + + // Placeholders + static let addPathPlaceholder = "e.g., /explore, /reels" + static let addPathExamplePlaceholder = "e.g., /explore, /reels, or m.domain.com" + } + + // MARK: - Advanced Settings + struct AdvancedSettings { + static let title = "More Settings" + static let blocking = "BLOCKING" + static let general = "GENERAL" + static let networkAndCache = "NETWORK & CACHE" + static let displayAndTimer = "DISPLAY & TIMER" + static let mode = "MODE" + static let allowlistMode = "Allowlist Mode" + static let allowlistModeDescription = "Blocks ALL sites except those on the list" + + // Allowlist confirmation + static let enableAllowlistTitle = "⚠️ Enable Allowlist Mode?" + static let enableAllowlistMessage = "WARNING: Allowlist mode blocks EVERYTHING except sites on your list.\n\nOnly the sites you explicitly add will be accessible. All other websites, apps, and services will be blocked. Use this mode with caution." + static let enableAllowlist = "Enable Allowlist" + + // Setting titles + static let blockCommonSubdomains = "Block common subdomains" + static let highlightInvalidHosts = "Highlight invalid hosts" + static let allowlistLinkedSites = "Allowlist includes linked sites (slow)" + static let autoCheckUpdates = "Automatically check for updates" + static let autoSendErrorReports = "Automatically send anonymized error reports" + static let playSoundOnCompletion = "Play sound on completion" + static let timerFloatsOnTop = "Timer window should float on top" + static let showCountdownInDock = "Show countdown in Dock" + static let hideSecondsInCountdown = "Hide seconds in countdown" + static let verifyInternetConnection = "Verify internet connection" + static let clearBrowserCache = "Clear browser cache" + static let allowLocalNetworks = "Allow local networks" + } + + // MARK: - Block Schedule + struct BlockSchedule { + static let title = "Block Schedule" + static let noTimeSlots = "No time slots" + static let addTimeSlot = "Add Time Slot" + static let deleteSchedule = "Delete Schedule" + static let to = "to" + } + + // MARK: - About + struct About { + static let title = "About SelfControl" + static let appName = "SelfControl" + static let version = "Version 4.0.2 (410)" + static let freeAndOpenSource = "Free and open-source under the GPL." + + // Main website link + static let mainWebsite = AboutItem(text: "http://selfcontrolapp.com", url: "http://selfcontrolapp.com") + + // Credits sections as arrays (each array represents one line/row) + static let developers: [AboutItem] = [ + AboutItem(text: "Developed by"), + AboutItem(text: "Charlie Stigler", url: "https://github.com/cstigler"), + AboutItem(text: ","), + AboutItem(text: "Steve Lambert", url: "http://visitsteve.com"), + AboutItem(text: ", and you?") + ] + + static let iconAndContributorsLine1: [AboutItem] = [ + AboutItem(text: "Icon by"), + AboutItem(text: "Joseph Fusco", url: "http://josephfus.co/"), + AboutItem(text: ", contributions by all of") + ] + + static let iconAndContributorsLine2: [AboutItem] = [ + AboutItem(text: "these lovely folks", url: "https://github.com/SelfControlApp/selfcontrol/graphs/contributors"), + AboutItem(text: ", and translations by"), + AboutItem(text: "these troopers", url: "https://www.transifex.com/selfcontrol/selfcontrol/"), + AboutItem(text: ".") + ] + + static let errorReporting: [AboutItem] = [ + AboutItem(text: "Error reporting generously provided by"), + AboutItem(text: "Sentry", url: "https://sentry.io"), + AboutItem(text: ".") + ] + + static let license: [AboutItem] = [ + AboutItem(text: "Free Software created at"), + AboutItem(text: "Eyebeam", url: "http://eyebeam.org"), + AboutItem(text: "under the"), + AboutItem(text: "GNU GPL", url: "http://www.gnu.org/copyleft/gpl.html"), + AboutItem(text: ".") + ] + + static let sourceCode: [AboutItem] = [ + AboutItem(text: "Source code", url: "https://github.com/SelfControlApp/selfcontrol/"), + AboutItem(text: "is available on GitHub.") + ] + } + + // MARK: - Tips + struct Tips { + static let title = "Getting Started with SelfControl" + static let gotIt = "Got it" + + // Tips as an array + static let items: [Tip] = [ + Tip( + title: "1. Add Sites to Block", + description: "Click 'Edit' to add website domains or paths you want to block." + ), + Tip( + title: "2. Adjust Blocking Time and Intensity", + description: "Set how long you want to block sites and the intensity of the blocking." + ), + Tip( + title: "3. Start Blocking", + description: "Click 'Start Block' to begin." + ) + ] + } + + // MARK: - Stop Confirmation + struct StopConfirmation { + static let title = "Stop Blocking?" + static let seconds = "seconds" + static let yesStopBlocking = "Yes, Stop Blocking" + static let resumeBlocking = "Resume Blocking" + static func countdownMessage(_ countdownTime: TimeInterval) -> String { + if countdownTime > 0 { + let minutes = Int(countdownTime) / 60 + let seconds = Int(countdownTime) % 60 + if minutes > 0 { + return "Wait for \(minutes) minute\(minutes == 1 ? "" : "s") and \(seconds) second\(seconds == 1 ? "" : "s") before you can stop blocking" + } else { + return "Wait for \(seconds) second\(seconds == 1 ? "" : "s") before you can stop blocking" + } + } else { + return "Confirm below to stop blocking" + } + } + } + + // MARK: - Help Text + struct Help { + static let closeSearch = "Close search" + static let addSite = "Add site" + static let search = "Search" + static let showTips = "Show getting started tips" + static let cannotDisableDuringBlock = "Cannot disable sites while a block is active" + static let stopBlockingImmediately = "Stop blocking immediately" + static let stopBlockingWithConfirmation = "Stop blocking with confirmation" + static let clickToEditTime = "Click to edit time" + static let startBlocking = "Start blocking" + static let cannotChangeModeDuringBlock = "Cannot change blocking mode while a block is active" + } + + // MARK: - Blocked URLs Helpers + struct BlockedURLs { + static let noSites = "No sites" + static let allSitesDisabled = "All sites disabled" + static let domain = "domain" + static let domains = "domains" + static func domainCount(_ count: Int, total: Int) -> String { + "\(count)/\(total) domain\(count == 1 ? "" : "s")" + } + static func domainsText(_ count: Int) -> String { + "\(count) domain\(count == 1 ? "" : "s")" + } + } + + // MARK: - App Menu + struct AppMenu { + static let aboutSelfControl = "About SelfControl" + static let editSiteList = "Edit Site List" + static let editBlockSchedule = "Edit Block Schedule" + static let moreSettings = "More Settings" + static let donate = "Donate" + static let gettingStartedTips = "Getting Started Tips" + static let selfControlHelp = "SelfControl Help" + static let faq = "FAQ" + + // URLs + struct URLs { + static let donate = "https://selfcontrolapp.com/donate" + static let help = "https://github.com/SelfControlApp/selfcontrol/wiki/SelfControl-Support-Hub" + static let faq = "https://github.com/SelfControlApp/selfcontrol/wiki/FAQ" + } + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AboutView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AboutView.swift new file mode 100644 index 00000000..45aeaffc --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AboutView.swift @@ -0,0 +1,149 @@ +// +// AboutView.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import SwiftUI +import AppKit + +// MARK: - About View +struct AboutView: View { + @Binding var currentScreen: AppScreen + + var body: some View { + ZStack { + // Dark gradient background + DesignSystem.backgroundGradient + .ignoresSafeArea() + + VStack(spacing: 0) { + // Header with back button + HStack(alignment: .center) { + Button(action: { + currentScreen = .main + }) { + HStack(spacing: 4) { + Image(systemName: "chevron.left") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + Text(Strings.Common.back) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textPrimary) + } + .buttonStyle(.plain) + .keyboardShortcut(.cancelAction) + + Spacer() + + Text(Strings.About.title) + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + Spacer() + + Text("") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + .frame(width: 60, alignment: .trailing) + } + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + + Divider() + .background(DesignSystem.borderPrimary) + + ScrollView { + VStack(spacing: DesignSystem.spacingSmall) { + // Icon and Title + VStack(spacing: 1) { + if let nsImage = NSImage(named: "AppIcon") { + Image(nsImage: nsImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 80, height: 80) + } + Text(Strings.About.appName) + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplayLarge, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + + Text(Strings.About.version) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.textSecondary) + + AboutItemView(item: Strings.About.mainWebsite) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + } + .padding(.top, DesignSystem.spacingLarge) + + // Credits section + VStack(spacing: DesignSystem.spacingSmall) { + // Developers + AboutItemsRow(items: Strings.About.developers) + + // Icon and contributors + AboutItemsRow(items: Strings.About.iconAndContributorsLine1) + AboutItemsRow(items: Strings.About.iconAndContributorsLine2) + + // Error reporting + AboutItemsRow(items: Strings.About.errorReporting) + + // License info + AboutItemsRow(items: Strings.About.license) + + // Source code + AboutItemsRow(items: Strings.About.sourceCode) + + // Footer + Text(Strings.About.freeAndOpenSource) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textSecondary) + .multilineTextAlignment(.center) + } + } + .frame(maxWidth: .infinity) + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.bottom, DesignSystem.spacingLarge) + } + .frame(maxWidth: .infinity) + } + } + } +} + +// MARK: - About Item View +struct AboutItemView: View { + let item: AboutItem + + var body: some View { + switch item.type { + case .text: + Text(item.text) + .foregroundColor(DesignSystem.textPrimary) + case .link: + if let urlString = item.url, let url = URL(string: urlString) { + Link(item.text, destination: url) + } else { + Text(item.text) + .foregroundColor(DesignSystem.textPrimary) + } + } + } +} + +// MARK: - About Items Row +struct AboutItemsRow: View { + let items: [AboutItem] + + var body: some View { + HStack(spacing: 4) { + ForEach(Array(items.enumerated()), id: \.offset) { _, item in + AboutItemView(item: item) + } + } + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .multilineTextAlignment(.center) + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift new file mode 100644 index 00000000..0b1949f4 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift @@ -0,0 +1,257 @@ +// +// AdvancedSettingsView.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import SwiftUI + +// MARK: - Advanced Settings View +struct AdvancedSettingsView: View { + @Binding var currentScreen: AppScreen + @Binding var blockingMode: BlockingMode + let isBlockingMode: Bool + + // Network Settings + @AppStorage("verifyConnection") private var verifyConnection = true + @AppStorage("clearCache") private var clearCache = true + @AppStorage("allowLocalNetworks") private var allowLocalNetworks = true + + // Blocking Settings + @AppStorage("blockCommonSubdomains") private var blockCommonSubdomains = true + @AppStorage("highlightInvalidHosts") private var highlightInvalidHosts = true + @AppStorage("allowlistLinkedSites") private var allowlistLinkedSites = true + + // General Settings + @AppStorage("autoCheckUpdates") private var autoCheckUpdates = true + @AppStorage("sendErrorReports") private var sendErrorReports = false + @AppStorage("playSoundOnCompletion") private var playSoundOnCompletion = true + @AppStorage("timerFloatsOnTop") private var timerFloatsOnTop = false + @AppStorage("showCountdownInDock") private var showCountdownInDock = true + + // Display Settings + @AppStorage("hideSeconds") private var hideSeconds = false + + @State private var showingAllowlistWarning = false + + var body: some View { + ZStack { + // Dark gradient background + DesignSystem.backgroundGradient + .ignoresSafeArea() + + VStack(spacing: 0) { + // Compact header + HStack(alignment: .center) { + Button(action: { + currentScreen = .editList + }) { + HStack(spacing: 4) { + Image(systemName: "chevron.left") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + Text(Strings.Common.back) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(.white) + } + .buttonStyle(.plain) + .keyboardShortcut(.cancelAction) + + Spacer() + + Text(Strings.AdvancedSettings.title) + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + Spacer() + + Text("") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + .frame(width: 60, alignment: .trailing) + } + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + + Divider() + .background(DesignSystem.borderPrimary) + + ScrollView { + VStack(spacing: 10) { + // Blocking Settings + VStack(alignment: .leading, spacing: 8) { + Text(Strings.AdvancedSettings.blocking) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textTertiary) + .tracking(1.2) + + VStack(spacing: 0) { + settingRow(title: Strings.AdvancedSettings.blockCommonSubdomains, isOn: $blockCommonSubdomains, showDivider: true) + settingRow(title: Strings.AdvancedSettings.highlightInvalidHosts, isOn: $highlightInvalidHosts, showDivider: true) + settingRow(title: Strings.AdvancedSettings.allowlistLinkedSites, isOn: $allowlistLinkedSites, showDivider: false) + } + } + .padding(DesignSystem.spacingMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) + ) + + // General Settings + VStack(alignment: .leading, spacing: 8) { + Text(Strings.AdvancedSettings.general) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textTertiary) + .tracking(1.2) + + VStack(spacing: 0) { + settingRow(title: Strings.AdvancedSettings.autoCheckUpdates, isOn: $autoCheckUpdates, showDivider: true) + settingRow(title: Strings.AdvancedSettings.autoSendErrorReports, isOn: $sendErrorReports, showDivider: true) + settingRow(title: Strings.AdvancedSettings.playSoundOnCompletion, isOn: $playSoundOnCompletion, showDivider: false) + } + } + .padding(DesignSystem.spacingMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) + ) + + // Network & Cache + VStack(alignment: .leading, spacing: 8) { + Text(Strings.AdvancedSettings.networkAndCache) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textTertiary) + .tracking(1.2) + + VStack(spacing: 0) { + settingRow(title: Strings.AdvancedSettings.verifyInternetConnection, isOn: $verifyConnection, showDivider: true) + settingRow(title: Strings.AdvancedSettings.clearBrowserCache, isOn: $clearCache, showDivider: true) + settingRow(title: Strings.AdvancedSettings.allowLocalNetworks, isOn: $allowLocalNetworks, showDivider: false) + } + } + .padding(DesignSystem.spacingMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) + ) + + // Display & Timer + VStack(alignment: .leading, spacing: 8) { + Text(Strings.AdvancedSettings.displayAndTimer) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textTertiary) + .tracking(1.2) + + VStack(spacing: 0) { + settingRow(title: Strings.AdvancedSettings.timerFloatsOnTop, isOn: $timerFloatsOnTop, showDivider: true) + settingRow(title: Strings.AdvancedSettings.showCountdownInDock, isOn: $showCountdownInDock, showDivider: true) + settingRow(title: Strings.AdvancedSettings.hideSecondsInCountdown, isOn: $hideSeconds, showDivider: false) + } + } + .padding(DesignSystem.spacingMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) + ) + + // Mode Settings + VStack(alignment: .leading, spacing: 8) { + Text(Strings.AdvancedSettings.mode) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textTertiary) + .tracking(1.2) + + // Allowlist mode checkbox with warning + VStack(alignment: .leading, spacing: 8) { + Toggle(isOn: Binding( + get: { blockingMode == .allowlist }, + set: { newValue in + if !isBlockingMode { + if newValue { + showingAllowlistWarning = true + } else { + withAnimation(DesignSystem.animationFast) { + blockingMode = .blocklist + } + } + } + } + )) { + HStack(spacing: 8) { + Image(systemName: "exclamationmark.shield.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(blockingMode == .allowlist ? Color.white : DesignSystem.textSecondary) + VStack(alignment: .leading, spacing: 2) { + Text(Strings.AdvancedSettings.allowlistMode) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + Text(Strings.AdvancedSettings.allowlistModeDescription) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textSecondary) + } + } + } + .toggleStyle(SwitchToggleStyle(tint: DesignSystem.toggleTint)) + .disabled(isBlockingMode) + .opacity(isBlockingMode ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + .padding(DesignSystem.spacingMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(blockingMode == .allowlist ? DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium) : DesignSystem.backgroundTertiary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(blockingMode == .allowlist ? DesignSystem.hoverBorder.opacity(DesignSystem.opacityHigher) : DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + } + } + .padding(DesignSystem.spacingMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) + ) + } + .padding(DesignSystem.spacingLarge) + } + } + } + .alert(isPresented: $showingAllowlistWarning) { + Alert( + title: Text(Strings.AdvancedSettings.enableAllowlistTitle), + message: Text(Strings.AdvancedSettings.enableAllowlistMessage), + primaryButton: .default(Text(Strings.AdvancedSettings.enableAllowlist), action: { + withAnimation(DesignSystem.animationFast) { + blockingMode = .allowlist + } + }), + secondaryButton: .cancel(Text(Strings.Common.cancel)) + ) + } + } + + private func settingRow(title: String, isOn: Binding, showDivider: Bool) -> some View { + VStack(spacing: 0) { + HStack(alignment: .center) { + Toggle(isOn: isOn) { + Text(title) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textPrimary) + .multilineTextAlignment(.leading) + } + .toggleStyle(CheckboxToggleStyle()) + Spacer() + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, DesignSystem.spacingMediumSmall) + .padding(.vertical, DesignSystem.spacingSmall) + + if showDivider { + Divider() + .background(DesignSystem.borderPrimary) + .padding(.leading, 12) + } + } + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift new file mode 100644 index 00000000..4e7d44d2 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift @@ -0,0 +1,487 @@ +// +// BlockScheduleView.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import SwiftUI +import AppKit + +// MARK: - Block Schedule View +struct BlockScheduleView: View { + @Binding var currentScreen: AppScreen + @Binding var blockingMode: BlockingMode + @Binding var isScheduleActive: Bool + + @State private var schedules: [Schedule] = [] + + // Helper to check if any schedule is active + private func updateScheduleStatus() { + isScheduleActive = schedules.contains { schedule in + schedule.isEnabled && + !schedule.timeSlots.isEmpty && + !schedule.enabledDays.isEmpty + } + } + + // Load schedules from UserDefaults + private func loadSchedules() { + if let data = UserDefaults.standard.data(forKey: "schedules"), + let decoded = try? JSONDecoder().decode([Schedule].self, from: data) { + schedules = decoded + } else { + // Initialize with one empty schedule if no saved data + schedules = [Schedule(timeSlots: [], enabledDays: [])] + } + } + + // Save schedules to UserDefaults + private func saveSchedules() { + if let encoded = try? JSONEncoder().encode(schedules) { + UserDefaults.standard.set(encoded, forKey: "schedules") + } + } + + var body: some View { + ZStack { + // Dark gradient background + DesignSystem.backgroundGradient + .ignoresSafeArea() + + VStack(spacing: 0) { + // Header + HStack(alignment: .center) { + Button(action: { + currentScreen = .main + }) { + HStack(spacing: 4) { + Image(systemName: "chevron.left") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + Text(Strings.Common.back) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(.white) + } + .buttonStyle(.plain) + .keyboardShortcut(.cancelAction) + + Spacer() + + Text(Strings.BlockSchedule.title) + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + Spacer() + + // Add schedule button + Button(action: { + withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) { + schedules.append(Schedule(timeSlots: [], enabledDays: [])) + saveSchedules() + updateScheduleStatus() + } + }) { + Image(systemName: "plus") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(.white) + } + .buttonStyle(.plain) + .frame(width: 60, alignment: .trailing) + } + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + + Divider() + .background(DesignSystem.borderPrimary) + + ScrollView { + VStack(spacing: DesignSystem.spacingMedium) { + + // Schedule list + VStack(spacing: DesignSystem.spacingMedium) { + ForEach($schedules) { $schedule in + ScheduleRow( + schedule: $schedule, + blockingMode: blockingMode, + onDelete: { + withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) { + schedules.removeAll { $0.id == schedule.id } + saveSchedules() + updateScheduleStatus() + } + } + ) + .transition(.asymmetric( + insertion: .move(edge: .top).combined(with: .opacity), + removal: .move(edge: .bottom).combined(with: .opacity) + )) + } + + // Invisible spacer to anchor animations (always present) + Color.clear + .frame(height: 1) + .allowsHitTesting(false) + } + .padding(.horizontal, DesignSystem.spacingLarge) + } + .padding(.vertical, DesignSystem.spacingLarge) + } + } + } + .onChange(of: schedules) { _ in + saveSchedules() + updateScheduleStatus() + } + .onAppear { + loadSchedules() + updateScheduleStatus() + } + } +} + +// MARK: - Schedule Row +struct ScheduleRow: View { + @Binding var schedule: Schedule + let blockingMode: BlockingMode + let onDelete: () -> Void + + var body: some View { + VStack(spacing: 0) { + // Header row with time slots summary and weekdays + VStack(spacing: DesignSystem.spacingSmall) { + HStack(spacing: DesignSystem.spacingMedium) { + // Toggle switch to enable/disable schedule + Toggle("", isOn: $schedule.isEnabled) + .toggleStyle(SwitchToggleStyle(tint: Color(red: 0.85, green: 0.95, blue: 1.0))) + .labelsHidden() + + // Time slots summary or placeholder + if schedule.timeSlots.isEmpty { + Text(Strings.BlockSchedule.noTimeSlots) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + .foregroundColor(DesignSystem.textTertiary) + } else { + VStack(alignment: .leading, spacing: 4) { + ForEach(schedule.timeSlots) { slot in + Text(slot.displayString) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + .foregroundColor(DesignSystem.textSecondary) + } + } + } + + Spacer() + + // Weekday circles + HStack(spacing: 6) { + ForEach(Weekday.allCases, id: \.self) { weekday in + WeekdayCircle( + weekday: weekday, + isEnabled: schedule.enabledDays.contains(weekday), + blockingMode: blockingMode, + onToggle: { + withAnimation(DesignSystem.animationFast) { + if schedule.enabledDays.contains(weekday) { + schedule.enabledDays.remove(weekday) + } else { + schedule.enabledDays.insert(weekday) + } + } + } + ) + } + } + + // Expand/collapse chevron (on the right) + Button(action: { + withAnimation(DesignSystem.animationFast) { + schedule.isExpanded.toggle() + // Auto-add first time slot when expanded and no slots exist + if schedule.isExpanded && schedule.timeSlots.isEmpty { + let defaultSlot = TimeSlot( + startHour: 9, + startMinute: 0, + endHour: 17, + endMinute: 0 + ) + schedule.timeSlots.append(defaultSlot) + } + } + }) { + Image(systemName: schedule.isExpanded ? "chevron.down" : "chevron.right") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textSecondary) + .frame(width: 20) + } + .buttonStyle(.plain) + } + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.vertical, DesignSystem.spacingMedium) + + // Expanded time slots + if schedule.isExpanded { + VStack(spacing: 4) { + ForEach(schedule.timeSlots) { slot in + if let index = schedule.timeSlots.firstIndex(where: { $0.id == slot.id }) { + TimeSlotRow( + timeSlot: $schedule.timeSlots[index], + blockingMode: blockingMode, + onDelete: { + withAnimation(DesignSystem.animationFast) { + schedule.timeSlots.removeAll { $0.id == slot.id } + } + }, + showDelete: schedule.timeSlots.count > 1 + ) + } + } + + // Add time slot button + Button(action: { + withAnimation(DesignSystem.animationFast) { + let lastEndHour = schedule.timeSlots.last?.endHour ?? 17 + let lastEndMinute = schedule.timeSlots.last?.endMinute ?? 0 + + let defaultSlot = TimeSlot( + startHour: schedule.timeSlots.isEmpty ? 9 : lastEndHour, + startMinute: schedule.timeSlots.isEmpty ? 0 : lastEndMinute, + endHour: schedule.timeSlots.isEmpty ? 17 : min(23, lastEndHour + 2), + endMinute: schedule.timeSlots.isEmpty ? 0 : lastEndMinute + ) + schedule.timeSlots.append(defaultSlot) + } + }) { + HStack(spacing: 6) { + Image(systemName: "plus.circle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Text(Strings.BlockSchedule.addTimeSlot) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(Color(white: 0.85)) + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.vertical, DesignSystem.spacingSmallMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.textPrimary.opacity(DesignSystem.opacityLow)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .stroke(DesignSystem.textPrimary.opacity(DesignSystem.opacityLow), lineWidth: 1) + ) + ) + } + .buttonStyle(.plain) + .padding(.vertical, DesignSystem.spacingSmallMedium) + + // Delete schedule button (chip style) + Button(action: onDelete) { + HStack(spacing: 6) { + Image(systemName: "trash.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Text(Strings.BlockSchedule.deleteSchedule) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.destructiveColor) + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.vertical, DesignSystem.spacingSmallMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.destructiveColor.opacity(DesignSystem.opacityLow)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .stroke(DesignSystem.destructiveColor.opacity(DesignSystem.opacityMedium), lineWidth: 1) + ) + ) + } + .buttonStyle(.plain) + .padding(.top, DesignSystem.spacingSmall) + } + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.bottom, DesignSystem.spacingMedium) + } + } + } + .modifier(DesignSystem.cardStyle()) + } +} + +// MARK: - Weekday Circle +struct WeekdayCircle: View { + let weekday: Weekday + let isEnabled: Bool + let blockingMode: BlockingMode + let onToggle: () -> Void + + var body: some View { + Button(action: onToggle) { + Text(weekday.letter) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(isEnabled ? Color(red: 0.15, green: 0.15, blue: 0.17) : DesignSystem.textSecondary) + .frame(width: 28, height: 28) + .background( + Circle() + .fill(isEnabled ? DesignSystem.textPrimary : DesignSystem.backgroundSecondary) + .overlay( + Circle() + .stroke(isEnabled ? DesignSystem.textPrimary : DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + } + .buttonStyle(.plain) + } +} + +// MARK: - Time Slot Row +struct TimeSlotRow: View { + @Binding var timeSlot: TimeSlot + let blockingMode: BlockingMode + let onDelete: () -> Void + let showDelete: Bool + + var body: some View { + HStack(spacing: 6) { + // Start time picker + DatePicker( + "", + selection: Binding( + get: { TimeHelpers.dateFromTime(hour: timeSlot.startHour, minute: timeSlot.startMinute) }, + set: { newDate in + let time = TimeHelpers.timeFromDate(newDate) + timeSlot.startHour = time.hour + timeSlot.startMinute = time.minute + } + ), + displayedComponents: .hourAndMinute + ) + .datePickerStyle(.compact) + .labelsHidden() + .styleDatePicker(cornerRadius: DesignSystem.radiusSmall, backgroundColor: DesignSystem.backgroundPrimary, borderColor: DesignSystem.borderPrimary) + .frame(width: 105) + + Text(Strings.BlockSchedule.to) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textTertiary) + .padding(.horizontal, DesignSystem.spacingXXSmall) + + // End time picker + DatePicker( + "", + selection: Binding( + get: { TimeHelpers.dateFromTime(hour: timeSlot.endHour, minute: timeSlot.endMinute) }, + set: { newDate in + let time = TimeHelpers.timeFromDate(newDate) + timeSlot.endHour = time.hour + timeSlot.endMinute = time.minute + } + ), + displayedComponents: .hourAndMinute + ) + .datePickerStyle(.compact) + .labelsHidden() + .styleDatePicker(cornerRadius: DesignSystem.radiusSmall, backgroundColor: DesignSystem.backgroundPrimary, borderColor: DesignSystem.borderPrimary) + .frame(width: 105) + + Spacer() + + // Delete button + if showDelete { + Button(action: onDelete) { + Image(systemName: "trash.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(Color(white: 0.7)) + .padding(DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.textPrimary.opacity(DesignSystem.opacityLow)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .stroke(DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium), lineWidth: 1) + ) + ) + } + .buttonStyle(.plain) + } + } + .padding(.vertical, DesignSystem.spacingXSmall) + .padding(.horizontal, DesignSystem.spacingMedium) + } +} + +// MARK: - DatePicker Styler +struct DatePickerStyler: NSViewRepresentable { + let cornerRadius: CGFloat + let backgroundColor: NSColor + let borderColor: NSColor + + func makeNSView(context: Context) -> NSView { + let view = NSView() + view.wantsLayer = true + + DispatchQueue.main.async { + if let superview = view.superview { + self.styleDatePicker(in: superview) + } + } + + return view + } + + func updateNSView(_ nsView: NSView, context: Context) { + DispatchQueue.main.async { + if let superview = nsView.superview { + self.styleDatePicker(in: superview) + } + } + } + + private func styleDatePicker(in view: NSView) { + // Find the main container view (usually an NSStackView or similar) + if let stackView = view as? NSStackView { + stackView.wantsLayer = true + stackView.layer?.cornerRadius = cornerRadius + stackView.layer?.backgroundColor = backgroundColor.cgColor + stackView.layer?.borderWidth = 1 + stackView.layer?.borderColor = borderColor.cgColor + } + + // Style text fields + for subview in view.subviews { + if let textField = subview as? NSTextField { + textField.drawsBackground = false + textField.backgroundColor = .clear + textField.isBordered = false + textField.focusRingType = .none + } + + // Apply rounded corners to subviews + if subview.wantsLayer { + subview.layer?.cornerRadius = cornerRadius + subview.layer?.backgroundColor = backgroundColor.cgColor + } + + styleDatePicker(in: subview) + } + } +} + +extension Color { + var nsColor: NSColor { + if let cgColor = self.cgColor { + return NSColor(cgColor: cgColor) ?? NSColor(white: 0.5, alpha: 1.0) + } + return NSColor(white: 0.5, alpha: 1.0) + } +} + +extension View { + func styleDatePicker(cornerRadius: CGFloat, backgroundColor: Color, borderColor: Color) -> some View { + self.background( + DatePickerStyler( + cornerRadius: cornerRadius, + backgroundColor: backgroundColor.nsColor, + borderColor: borderColor.nsColor + ) + ) + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift new file mode 100644 index 00000000..7341e0fa --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift @@ -0,0 +1,1352 @@ +// +// BlocklistEditorView.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import SwiftUI +struct BlocklistEditorView: View { + @EnvironmentObject var viewModel: FilterViewModel + + @Binding var blockedURLs: [BlockedURL] { + didSet { + saveChanges() + } + } + let isBlockingMode: Bool + @Binding var blockingMode: BlockingMode + @Binding var currentScreen: AppScreen + @Binding var showingTips: Bool + @State private var newSite: String = "" + @State private var newPath: String = "" + @State private var searchText: String = "" + @AppStorage("showSuggestedSites") private var showSuggested: Bool = false + @State private var selectedDomain: UUID? = nil + @State private var focusedField: EditorFocusField? + @State private var showingAddSiteConfirmation = false + @State private var pendingSiteToAdd: String? = nil + @State private var isBulkEditMode: Bool = false + @State private var selectedURLs: Set = [] + @State private var lastSelectedIndex: Int? = nil + @State private var showingDeleteConfirmation: Bool = false + @State private var isSearchMode: Bool = false + @State private var isHoveringSuggestedChip = false + @State private var isHoveringBulkEdit = false + @State private var isHoveringImport = false + @State private var isHoveringExport = false + @State private var isHoveringAdd = false + + enum EditorFocusField: Hashable { + case addWebsite + case search + case addPath + case doneButton + } + + // Suggested sites for quick adding + let suggestedSites = DomainConstants.suggestedSites + + var filteredURLs: [BlockedURL] { + if searchText.isEmpty { + return blockedURLs.sorted { $0.domain < $1.domain } + } + return blockedURLs.filter { url in + url.domain.localizedCaseInsensitiveContains(searchText) || + url.paths.contains { $0.localizedCaseInsensitiveContains(searchText) } + }.sorted { $0.domain < $1.domain } + } + + var totalCount: Int { + blockedURLs.count + blockedURLs.reduce(0) { $0 + $1.paths.count } + } + + func handleCheckboxClick(for urlId: UUID, at index: Int, isShiftPressed: Bool = false) { + if isShiftPressed, let lastIndex = lastSelectedIndex { + // Shift-select: select range from lastSelectedIndex to current index + let startIndex = min(lastIndex, index) + let endIndex = max(lastIndex, index) + let range = startIndex...endIndex + + let isCurrentlySelected = selectedURLs.contains(urlId) + + // Toggle all items in range + for i in range { + let itemId = filteredURLs[i].id + if isCurrentlySelected { + selectedURLs.remove(itemId) + } else { + selectedURLs.insert(itemId) + } + } + } else { + // Normal click: toggle single item + if selectedURLs.contains(urlId) { + selectedURLs.remove(urlId) + } else { + selectedURLs.insert(urlId) + } + lastSelectedIndex = index + } + } + + func deleteSelectedURLs() { + withAnimation(DesignSystem.animationFast) { + // Create a new array without the selected items + blockedURLs = blockedURLs.filter { !selectedURLs.contains($0.id) } + selectedURLs.removeAll() + + // Clear bulk edit mode if no URLs left + if blockedURLs.isEmpty { + isBulkEditMode = false + } + } + } + + var headerSubtitleText: String { + let enabledCount = blockedURLs.filter { $0.isEnabled }.count + let pathCount = blockedURLs.reduce(0) { $0 + $1.paths.count } + + if enabledCount != blockedURLs.count { + return "\(enabledCount)/\(blockedURLs.count) enabled" + (pathCount > 0 ? ", \(pathCount) paths" : "") + } else if pathCount > 0 { + return "\(blockedURLs.count) domain\(blockedURLs.count == 1 ? "" : "s"), \(pathCount) path\(pathCount == 1 ? "" : "s")" + } else { + return "\(blockedURLs.count) website\(blockedURLs.count == 1 ? "" : "s")" + } + } + + var headerView: some View { + HStack(alignment: .center) { + // Back button + Button(action: { + selectedDomain = nil + currentScreen = .main + }) { + HStack(spacing: 4) { + Image(systemName: "chevron.left") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + Text(Strings.Common.back) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textPrimary) + } + .buttonStyle(.plain) + .keyboardShortcut(.cancelAction) + + Spacer() + + // Import button + Button(action: { + // TODO: Import functionality + }) { + HStack(spacing: 4) { + Image(systemName: "square.and.arrow.down") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Text(Strings.Common.importText) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringImport ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringImport = hovering + } + } + + // Export button + Button(action: { + // TODO: Export functionality + }) { + HStack(spacing: 4) { + Image(systemName: "square.and.arrow.up") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Text(Strings.Common.export) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringExport ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringExport = hovering + } + } + } + .overlay( + Text(blockingMode == .blocklist ? Strings.BlocklistEditor.title : Strings.BlocklistEditor.allowlist) + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(maxWidth: .infinity, alignment: .center) + ) + } + + var body: some View { + ZStack { + // Dark gradient background + DesignSystem.backgroundGradient + .ignoresSafeArea(.all) + .frame(maxWidth: .infinity, maxHeight: .infinity) + + VStack(spacing: 0) { + // Compact header + headerView + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + + Divider() + .background(DesignSystem.borderPrimary) + + // Allowlist mode warning banner + if blockingMode == .allowlist { + HStack(spacing: 10) { + Image(systemName: "exclamationmark.triangle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.textPrimary) + + VStack(alignment: .leading, spacing: 2) { + Text(Strings.BlocklistEditor.allowlistModeActive) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + Text(Strings.BlocklistEditor.allowlistModeDescription) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textSecondary) + } + + Spacer() + } + .padding(DesignSystem.spacingMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.hoverBorder.opacity(DesignSystem.opacityHigher), lineWidth: 1) + ) + ) + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.top, DesignSystem.spacingMedium) + } + + // Combined add and search section + HStack(spacing: DesignSystem.spacingSmall) { + // Add website / Search (toggles based on isSearchMode) + HStack(spacing: 8) { + // Icon - animated transition + Group { + if isSearchMode { + Image(systemName: "magnifyingglass") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.textPrimary) + } else { + Image(systemName: "plus.circle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.textPrimary) + } + } + .transition(.opacity.combined(with: .scale(scale: 0.8))) + + // TextField - animated transition + Group { + if isSearchMode { + TextField(Strings.BlocklistEditor.searchPlaceholder, text: $searchText) + .textFieldStyle(.plain) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textPrimary) + } else { + TextField(Strings.BlocklistEditor.addWebsitePlaceholder, text: $newSite, onCommit: { + addWebsite() + }) + .textFieldStyle(.plain) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textPrimary) + } + } + .transition(.opacity.combined(with: .move(edge: .trailing))) + + // Spacer to push button to the right + if isSearchMode { + Spacer() + .frame(minWidth: 8) + } + + // Close/Add button area - maintains consistent width + Group { + if isSearchMode { + Button(action: { + withAnimation(DesignSystem.animationNormal) { + isSearchMode = false + searchText = "" + focusedField = .addWebsite + } + }) { + Text(Strings.Common.cancel) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textTertiary) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringAdd ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .help(Strings.Help.closeSearch) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringAdd = hovering + } + } + } else { + Button(action: { + addWebsite() + }) { + Text(Strings.Common.add) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textTertiary) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringAdd ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .disabled(newSite.trimmingCharacters(in: .whitespaces).isEmpty) + .opacity(newSite.trimmingCharacters(in: .whitespaces).isEmpty ? DesignSystem.opacityHigh : DesignSystem.opacityFull) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringAdd = hovering + } + } + .help(Strings.Help.addSite) + } + } + .transition(.opacity.combined(with: .scale(scale: 0.9))) + } + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingSmallMedium) + .frame(height: 44) // Fixed height to prevent size changes + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke((isSearchMode && focusedField == .search) || (!isSearchMode && focusedField == .addWebsite) ? DesignSystem.textPrimary : DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + .animation(DesignSystem.animationNormal, value: isSearchMode) + .animation(DesignSystem.animationNormal, value: searchText.isEmpty) + + // Search icon (replaces gear icon) - minimal spacing + Button(action: { + withAnimation(DesignSystem.animationNormal) { + isSearchMode.toggle() + if isSearchMode { + focusedField = .search + } else { + searchText = "" + focusedField = .addWebsite + } + } + }) { + Image(systemName: "magnifyingglass") + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge)) + .foregroundColor(isSearchMode ? DesignSystem.textPrimary : DesignSystem.textTertiary) + .frame(width: 32, height: 32) + } + .buttonStyle(.plain) + .help(isSearchMode ? Strings.Help.closeSearch : Strings.Help.search) + .padding(.leading, DesignSystem.spacingXXSmall) + } + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + + Divider() + .background(DesignSystem.borderPrimary) + + // Hierarchical tree list + urlListView + + // Bottom action bar + bottomActionBar + } + + // Delete confirmation modal overlay - placed last to appear on top + if showingDeleteConfirmation { + DesignSystem.overlayBackground + .ignoresSafeArea() + .onTapGesture { + showingDeleteConfirmation = false + } + + VStack(spacing: DesignSystem.spacingMedium) { + let count = selectedURLs.count + + VStack(spacing: 8) { + Image(systemName: "exclamationmark.triangle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplay)) + .foregroundColor(DesignSystem.destructiveColor) + + Text(Strings.BlocklistEditor.deleteSiteTitle(count)) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + Text(Strings.BlocklistEditor.deleteSiteMessage(count)) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textSecondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + } + + HStack(spacing: DesignSystem.spacingSmall) { + Button(action: { + showingDeleteConfirmation = false + }) { + Text(Strings.Common.cancel) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 80) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundTertiary) + ) + } + .buttonStyle(.plain) + + Button(action: { + deleteSelectedURLs() + showingDeleteConfirmation = false + }) { + Text(Strings.Common.delete) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 80) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.destructiveColor) + ) + } + .buttonStyle(.plain) + } + } + .padding(DesignSystem.spacingMedium) + .frame(maxWidth: 320) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityHigh), radius: 15, x: 0, y: 5) + .zIndex(999) + } + } + .alert(isPresented: $showingAddSiteConfirmation) { + let site = pendingSiteToAdd ?? "this site" + return Alert( + title: Text(Strings.BlocklistEditor.addToExistingBlockTitle), + message: Text(Strings.BlocklistEditor.addToExistingBlockMessage(site, blockingMode: blockingMode)), + primaryButton: .default(Text(Strings.BlocklistEditor.addSite), action: { + confirmAddWebsite() + }), + secondaryButton: .cancel(Text(Strings.Common.cancel), action: { + pendingSiteToAdd = nil + }) + ) + } + .onAppear { + // Reset to list view and focus on add website field when editor opens + selectedDomain = nil + focusedField = .addWebsite + } + } + + var addWebsiteSection_UNUSED: some View { + VStack(spacing: DesignSystem.spacingMedium) { + HStack(spacing: DesignSystem.spacingSmall) { + Image(systemName: "plus.circle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeHeading)) + .foregroundColor(DesignSystem.textPrimary) + + TextField(Strings.BlocklistEditor.addWebsiteExamplePlaceholder, text: $newSite, onCommit: { addWebsite() }) + .textFieldStyle(.plain) + .font(DesignSystem.font(size: DesignSystem.fontSizeXLarge)) + .foregroundColor(DesignSystem.textPrimary) + .padding(DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .stroke(focusedField == .addWebsite ? DesignSystem.textPrimary : DesignSystem.borderPrimary, lineWidth: focusedField == .addWebsite ? 2 : 1) + ) + ) + + + Button(action: addWebsite) { + Text(Strings.Common.add) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.textPrimary) + ) + } + .buttonStyle(.plain) + .disabled(newSite.isEmpty) + .opacity(newSite.isEmpty ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + } + + // Suggested chip + if !suggestedSites.isEmpty { + VStack(alignment: .leading, spacing: DesignSystem.spacingSmall) { + Text(Strings.BlocklistEditor.quickAdd) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textTertiary) + .textCase(.uppercase) + + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 10) { + Button(action: { + showSuggested.toggle() + }) { + HStack(spacing: 6) { + Image(systemName: "lightbulb") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Text(Strings.BlocklistEditor.suggested) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(showSuggested ? DesignSystem.textTertiary : DesignSystem.textSecondary) + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(showSuggested ? DesignSystem.backgroundPrimary : DesignSystem.backgroundTertiary) + ) + } + .buttonStyle(.plain) + } + } + + // Show all suggested sites + if showSuggested { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + ForEach(suggestedSites, id: \.self) { site in + let isAlreadyBlocked = blockedURLs.contains { $0.domain == site } + Button(action: { + if !isAlreadyBlocked { + // Add without animation to prevent blink + // showSuggested is @AppStorage so it persists automatically + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + blockedURLs.append(BlockedURL(domain: site)) + } + } + }) { + HStack(spacing: 6) { + Text(site) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Image(systemName: isAlreadyBlocked ? "checkmark.circle.fill" : "plus.circle") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + } + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .stroke(isAlreadyBlocked ? DesignSystem.hoverBorder.opacity(DesignSystem.opacityHigher) : DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + } + .buttonStyle(.plain) + .disabled(isAlreadyBlocked) + } + } + } + } + } + } + } + } + + var searchBarView_UNUSED: some View { + HStack(spacing: DesignSystem.spacingSmall) { + Image(systemName: "magnifyingglass") + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge)) + .foregroundColor(DesignSystem.textTertiary) + + TextField(Strings.BlocklistEditor.searchBlockedSitesPlaceholder, text: $searchText) + .textFieldStyle(.plain) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.textPrimary) + + + if !searchText.isEmpty { + Button(action: { searchText = "" }) { + Image(systemName: "xmark.circle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge)) + .foregroundColor(DesignSystem.textQuaternary) + } + .buttonStyle(.plain) + } + } + .padding(DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .stroke(focusedField == .search ? DesignSystem.textPrimary : Color.clear, lineWidth: 2) + ) + ) + .padding(.horizontal, DesignSystem.spacingXLarge) + .padding(.vertical, DesignSystem.spacingMedium) + } + + var urlListView: some View { + domainListView + } + + var domainListView: some View { + ScrollView { + VStack(spacing: 0) { + // Count header with Suggested and Bulk Edit buttons + if !filteredURLs.isEmpty || (!suggestedSites.isEmpty && searchText.isEmpty && !isSearchMode) { + ZStack { + // Centered text + if !filteredURLs.isEmpty { + Text(Strings.BlocklistEditor.siteCount(blockedURLs.count)) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textTertiary) + .tracking(1.2) + } + + // Left and right aligned buttons + HStack { + // Left-aligned Suggested button + if !suggestedSites.isEmpty && searchText.isEmpty && !isSearchMode { + Button(action: { + showSuggested.toggle() + }) { + HStack(spacing: 4) { + Image(systemName: showSuggested ? "lightbulb" : "lightbulb") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Text(Strings.BlocklistEditor.suggested) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(showSuggested ? DesignSystem.textTertiary : DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringSuggestedChip ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringSuggestedChip = hovering + } + } + } + + Spacer() + + // Right-aligned Bulk Edit button + if !filteredURLs.isEmpty { + Button(action: { + withAnimation { + isBulkEditMode.toggle() + if !isBulkEditMode { + selectedURLs.removeAll() + lastSelectedIndex = nil + } + } + }) { + HStack(spacing: 4) { + Image(systemName: isBulkEditMode ? "checkmark.circle.fill" : "checkmark.circle") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Text(isBulkEditMode ? Strings.Common.done : Strings.BlocklistEditor.bulkEdit) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringBulkEdit ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringBulkEdit = hovering + } + } + } + } + .padding(.horizontal, DesignSystem.spacingLarge) + } + .frame(maxWidth: .infinity) + .padding(.top, DesignSystem.spacingMedium) + + // Show all suggested sites below the header line + if showSuggested && !suggestedSites.isEmpty && searchText.isEmpty && !isSearchMode { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 6) { + ForEach(Array(suggestedSites), id: \.self) { site in + let isAlreadyBlocked = blockedURLs.contains { $0.domain == site } + + Button(action: { + guard !isAlreadyBlocked else { return } + + if isBlockingMode { + // Show confirmation when blocking is active + pendingSiteToAdd = site + // Small delay to ensure state is set before showing alert + DispatchQueue.main.async { + showingAddSiteConfirmation = true + } + } else { + // Add directly when not blocking - no animation to prevent blink + // showSuggested is @AppStorage so it persists automatically + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + blockedURLs.append(BlockedURL(domain: site)) + } + } + }) { + HStack(spacing: 4) { + Text(site) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + if isAlreadyBlocked { + Image(systemName: "checkmark.circle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall)) + } + } + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingSmallMedium) + .padding(.vertical, DesignSystem.spacingXXSmall) + .background( + Capsule() + .fill(isAlreadyBlocked ? DesignSystem.hoverBackground : DesignSystem.backgroundTertiary) + .overlay( + Capsule() + .stroke(isAlreadyBlocked ? DesignSystem.textPrimary.opacity(DesignSystem.disabledOpacity) : Color.clear, lineWidth: 1) + ) + ) + } + .buttonStyle(.plain) + .disabled(isAlreadyBlocked) + } + } + .padding(.horizontal, DesignSystem.spacingLarge) + } + .padding(.top, DesignSystem.spacingSmall) + .padding(.bottom, DesignSystem.spacingSmall) + } + } + + LazyVStack(spacing: 6) { + if filteredURLs.isEmpty { + VStack(spacing: DesignSystem.spacingSmall) { + Image(systemName: searchText.isEmpty ? "shield.slash" : "magnifyingglass") + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplayXXL)) + .foregroundColor(DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium)) + Text(searchText.isEmpty ? Strings.BlocklistEditor.noBlockedSites : Strings.BlocklistEditor.noResultsFound) + .font(DesignSystem.font(size: DesignSystem.fontSizeXLarge)) + .foregroundColor(DesignSystem.textTertiary) + } + .frame(maxWidth: .infinity) + .padding(.top, DesignSystem.spacingXLargePlusMedium) + } else { + let enumeratedURLs = Array(filteredURLs.enumerated()) + ForEach(enumeratedURLs, id: \.element.id) { index, url in + HStack(alignment: .center, spacing: DesignSystem.spacingSmall) { + // Checkbox in bulk edit mode, toggle otherwise + Group { + if isBulkEditMode { + let isSelected = selectedURLs.contains(url.id) + + Button(action: { + // Check shift key from modifier flags + let isShiftPressed = NSEvent.modifierFlags.contains(.shift) + handleCheckboxClick(for: url.id, at: index, isShiftPressed: isShiftPressed) + }) { + Image(systemName: isSelected ? "checkmark.square.fill" : "square") + .font(DesignSystem.font(size: DesignSystem.fontSizeTitle)) + .foregroundColor(isSelected ? DesignSystem.textPrimary : DesignSystem.textQuaternary) + .frame(width: 20, height: 20, alignment: .center) + } + .buttonStyle(.plain) + } else { + // Toggle switch (can only enable during blocking, not disable) + let isDisabledDuringBlock = isBlockingMode && url.isEnabled + + Toggle("", isOn: Binding( + get: { + url.isEnabled + }, + set: { newValue in + // Only allow enabling during active block, not disabling + if isBlockingMode && !newValue { + // Prevent disabling - don't do anything + return + } + + // Allow enabling during block or any toggle when not blocking + if let index = blockedURLs.firstIndex(where: { $0.id == url.id }) { + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + blockedURLs[index].isEnabled = newValue + } + } + } + )) + .toggleStyle(SwitchToggleStyle(tint: Color(red: 0.85, green: 0.95, blue: 1.0))) + .labelsHidden() + .disabled(isDisabledDuringBlock) + .opacity(isDisabledDuringBlock ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + .help(isDisabledDuringBlock ? Strings.Help.cannotDisableDuringBlock : "") + .buttonStyle(.plain) + } + } + .frame(width: 44, height: 24, alignment: .center) + + Button(action: { + if isBulkEditMode { + // Check shift key from modifier flags + let isShiftPressed = NSEvent.modifierFlags.contains(.shift) + handleCheckboxClick(for: url.id, at: index, isShiftPressed: isShiftPressed) + } else { + currentScreen = .domainDetail(url.id) + } + }) { + HStack(spacing: DesignSystem.spacingSmall) { + VStack(alignment: .leading, spacing: 2) { + HStack(spacing: 4) { + Text(url.domain) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(url.isEnabled ? DesignSystem.textPrimary : DesignSystem.textTertiary) + + // Show lock icon when disabled during active block + if isBlockingMode && url.isEnabled { + Image(systemName: "lock.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall)) + .foregroundColor(DesignSystem.accentOrangeMuted) + + Text(Strings.BlocklistEditor.locked) + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightRegular)) + .foregroundColor(DesignSystem.accentOrangeMuted) + } + } + + if !url.paths.isEmpty { + Text(Strings.BlocklistEditor.pathCount(url.paths.count)) + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall)) + .foregroundColor(DesignSystem.textTertiary) + } + } + + Spacer() + + Image(systemName: "chevron.right") + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textQuaternary) + } + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + } + .frame(minHeight: 20) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundPrimary.opacity(url.isEnabled ? 0.6 : 0.3)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(url.isEnabled ? DesignSystem.borderSecondary : DesignSystem.textPrimary.opacity(DesignSystem.opacitySubtle), lineWidth: 1) + ) + ) + .opacity(url.isEnabled ? DesignSystem.opacityFull : DesignSystem.opacityAlmost) + } + } + } + .padding(DesignSystem.spacingXLarge) + } + } + } + + var domainDetailView_UNUSED: some View { + VStack(spacing: 0) { + if let domainId = selectedDomain, + let domain = blockedURLs.first(where: { $0.id == domainId }) { + // Header with back button + HStack { + Button(action: { + withAnimation(DesignSystem.animationFast) { + selectedDomain = nil + } + }) { + HStack(spacing: 6) { + Image(systemName: "chevron.left") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + Text(Strings.Common.back) + .font(DesignSystem.font(size: DesignSystem.fontSizeXLarge, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textPrimary) + } + .buttonStyle(.plain) + + Spacer() + } + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + + Divider() + .background(DesignSystem.borderPrimary) + + ScrollView { + VStack(spacing: DesignSystem.spacingMedium) { + // Domain info + VStack(alignment: .leading, spacing: DesignSystem.spacingSmall) { + HStack { + Image(systemName: "globe") + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplay)) + .foregroundColor(DesignSystem.textPrimary) + + Text(domain.domain) + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplay, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + + Spacer() + + if !isBlockingMode { + Button(action: { + withAnimation { + blockedURLs.removeAll { $0.id == domainId } + selectedDomain = nil + } + }) { + HStack(spacing: 6) { + Image(systemName: "trash") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + Text(Strings.Common.delete) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textSecondary) + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.vertical, DesignSystem.spacingSmall) + .modifier(DesignSystem.buttonStyle(isDestructive: true)) + } + .buttonStyle(.plain) + } + } + + Text(Strings.DomainDetail.managePathsDescription) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textTertiary) + } + .padding(DesignSystem.spacingXLarge) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusLarge) + .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) + ) + + // Add path section + VStack(alignment: .leading, spacing: DesignSystem.spacingMedium) { + Text(Strings.DomainDetail.addPathOrSubdomain) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + HStack(spacing: DesignSystem.spacingSmall) { + TextField(Strings.DomainDetail.addPathExamplePlaceholder, text: $newPath, onCommit: { addPath(to: domainId) }) + .textFieldStyle(.plain) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.textPrimary) + .padding(DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .stroke(focusedField == .addPath ? DesignSystem.textPrimary : DesignSystem.borderPrimary, lineWidth: focusedField == .addPath ? 2 : 1) + ) + ) + + + Button(action: { + addPath(to: domainId) + }) { + Text(Strings.Common.add) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + .background(Color.green) + .cornerRadius(DesignSystem.radiusSmall) + } + .buttonStyle(.plain) + .disabled(newPath.isEmpty) + .opacity(newPath.isEmpty ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + } + } + .padding(DesignSystem.spacingXLarge) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusLarge) + .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigh)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusLarge) + .stroke(DesignSystem.hoverBorder.opacity(DesignSystem.opacityHigher), lineWidth: 1) + ) + ) + + // Paths list + if !domain.paths.isEmpty { + VStack(alignment: .leading, spacing: DesignSystem.spacingMedium) { + Text(Strings.DomainDetail.pathsCount(domain.paths.count)) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + VStack(spacing: DesignSystem.spacingXSmall) { + ForEach(domain.paths, id: \.self) { path in + HStack(spacing: DesignSystem.spacingSmall) { + Image(systemName: "doc.text") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.accentPurpleMuted) + + Text(path) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.textPrimary) + + Spacer() + + if !isBlockingMode { + Button(action: { + withAnimation { + if let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { + blockedURLs[index].paths.removeAll { $0 == path } + saveChanges() + } + } + }) { + Image(systemName: "trash") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.destructiveColor.opacity(DesignSystem.opacityHigher)) + } + .buttonStyle(.plain) + } + } + .padding(DesignSystem.spacingMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundSecondary.opacity(DesignSystem.opacityHigher)) + ) + } + } + } + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusLarge) + .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigh)) + ) + } + } + .padding(DesignSystem.spacingXLarge) + } + } + } + } + + var bottomActionBar: some View { + Group { + if isBulkEditMode { + VStack(spacing: 0) { + // Divider + Divider() + .background(DesignSystem.borderPrimary) + + // Floating action bar + ZStack { + HStack(spacing: DesignSystem.spacingMedium) { + // Select All button + Button(action: { + withAnimation(DesignSystem.animationFast) { + let filteredIDs = Set(filteredURLs.map { $0.id }) + let allFilteredSelected = !filteredURLs.isEmpty && filteredIDs.isSubset(of: selectedURLs) + + if allFilteredSelected { + // Deselect all filtered items (but keep any selections outside filtered view) + for id in filteredIDs { + selectedURLs.remove(id) + } + } else { + // Select all filtered items (and keep any existing selections) + for id in filteredIDs { + selectedURLs.insert(id) + } + } + } + }) { + let filteredIDs = Set(filteredURLs.map { $0.id }) + let allFilteredSelected = !filteredURLs.isEmpty && filteredIDs.isSubset(of: selectedURLs) + + HStack(spacing: 6) { + Image(systemName: allFilteredSelected ? "checkmark.square.fill" : "square") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + Text(allFilteredSelected ? Strings.Common.deselectAll : Strings.Common.selectAll) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.vertical, DesignSystem.spacingSmall) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .disabled(filteredURLs.isEmpty) + .opacity(filteredURLs.isEmpty ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + + Spacer() + + // Delete button + Button(action: { + showingDeleteConfirmation = true + }) { + HStack(spacing: 6) { + Image(systemName: "trash") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + Text(Strings.Common.delete) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.destructiveColor) + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.vertical, DesignSystem.spacingSmall) + .modifier(DesignSystem.buttonStyle(isDestructive: true)) + } + .buttonStyle(.plain) + .disabled(selectedURLs.isEmpty) + .opacity(selectedURLs.isEmpty ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + } + + // Selected count - centered + if !selectedURLs.isEmpty { + Text(Strings.BlocklistEditor.selectedCount(selectedURLs.count)) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + } + } + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingMedium) + .background( + ZStack(alignment: .top) { + Rectangle() + .fill(DesignSystem.backgroundPrimary) + Rectangle() + .fill(DesignSystem.borderPrimary) + .frame(height: 1) + } + ) + } + } else { + EmptyView() + } + } + } + + private func addWebsite() { + let input = newSite.trimmingCharacters(in: .whitespaces) + if input.isEmpty { + return + } + + let (domain, path) = BlockedURLsHelpers.parseDomainAndPath(from: input) + + if domain.isEmpty { + return + } + + // Find existing domain or create new one + var domainId: UUID + if let existingDomain = blockedURLs.first(where: { $0.domain == domain }) { + domainId = existingDomain.id + } else { + // Domain doesn't exist, check if we should add it + if isBlockingMode { + // Show confirmation when blocking is active + pendingSiteToAdd = input + showingAddSiteConfirmation = true + return + } else { + // Add new domain + let newDomain = BlockedURL(domain: domain) + domainId = newDomain.id + withAnimation { + blockedURLs.append(newDomain) + } + } + } + + // If there's a path, add it to the domain + if let pathToAdd = path { + if let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { + let formattedPath = pathToAdd.hasPrefix("/") || pathToAdd.contains(".") ? pathToAdd : "/\(pathToAdd)" + if !blockedURLs[index].paths.contains(formattedPath) { + withAnimation { + blockedURLs[index].paths.append(formattedPath) + saveChanges() + newSite = "" + // Navigate to domain detail view to show the path was added + currentScreen = .domainDetail(domainId) + } + return + } else { + // Path already exists, navigate to detail view anyway + withAnimation { + newSite = "" + currentScreen = .domainDetail(domainId) + } + return + } + } + } + + // If no path, navigate to detail view if domain was new + let domainExisted = blockedURLs.contains(where: { $0.id == domainId && $0.domain == domain }) + if !domainExisted { + // New domain was added, navigate to detail view + withAnimation { + newSite = "" + currentScreen = .domainDetail(domainId) + } + } else { + newSite = "" + } + } + + private func confirmAddWebsite() { + guard let input = pendingSiteToAdd else { return } + let (domain, path) = BlockedURLsHelpers.parseDomainAndPath(from: input) + + if domain.isEmpty { + pendingSiteToAdd = nil + return + } + + // Find existing domain or create new one + var domainId: UUID + if let existingDomain = blockedURLs.first(where: { $0.domain == domain }) { + domainId = existingDomain.id + } else { + // Add new domain + let newDomain = BlockedURL(domain: domain) + domainId = newDomain.id + withAnimation { + blockedURLs.append(newDomain) + } + } + + // If there's a path, add it to the domain + if let pathToAdd = path { + if let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { + let formattedPath = pathToAdd.hasPrefix("/") || pathToAdd.contains(".") ? pathToAdd : "/\(pathToAdd)" + if !blockedURLs[index].paths.contains(formattedPath) { + withAnimation { + blockedURLs[index].paths.append(formattedPath) + saveChanges() + newSite = "" + pendingSiteToAdd = nil + // Navigate to domain detail view to show the path was added + currentScreen = .domainDetail(domainId) + } + return + } else { + // Path already exists, navigate to detail view anyway + withAnimation { + newSite = "" + pendingSiteToAdd = nil + currentScreen = .domainDetail(domainId) + } + return + } + } + } + + // If no path, navigate to detail view if domain was new + let domainExisted = blockedURLs.contains(where: { $0.id == domainId && $0.domain == domain }) + if !domainExisted { + // New domain was added, navigate to detail view + withAnimation { + newSite = "" + pendingSiteToAdd = nil + currentScreen = .domainDetail(domainId) + } + } else { + withAnimation { + newSite = "" + pendingSiteToAdd = nil + } + } + } + + + private func addPath(to urlId: UUID) { + let cleanPath = newPath.trimmingCharacters(in: .whitespaces) + if !cleanPath.isEmpty, let index = blockedURLs.firstIndex(where: { $0.id == urlId }) { + // Ensure path starts with / (unless it's a subdomain) + let formattedPath = cleanPath.hasPrefix("/") || cleanPath.contains(".") ? cleanPath : "/\(cleanPath)" + + if !blockedURLs[index].paths.contains(formattedPath) { + withAnimation { + blockedURLs[index].paths.append(formattedPath) + saveChanges() + newPath = "" + } + } + } + } + + private func saveChanges() { + viewModel.blockerStorage?.set(blockedURLs) + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift new file mode 100644 index 00000000..bf2422e3 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift @@ -0,0 +1,385 @@ +// +// DomainDetailView.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import SwiftUI + +// MARK: - Domain Detail View +struct DomainDetailView: View { + @EnvironmentObject var viewModel: FilterViewModel + + @Binding var blockedURLs: [BlockedURL] { + didSet { + saveChanges() + } + } + + let domainId: UUID + let isBlockingMode: Bool + @Binding var currentScreen: AppScreen + @Binding var blockingMode: BlockingMode + @State private var newPath: String = "" + @State private var blockEntireDomain: Bool = true + @State private var isHoveringAdd: Bool = false + + var domain: BlockedURL? { + blockedURLs.first(where: { $0.id == domainId }) + } + + // Get the storage key for this domain's blocking mode preference + private var storageKey: String { + "blockEntireDomain_\(domainId.uuidString)" + } + + // Initialize blockEntireDomain from UserDefaults or based on domain state + private func initializeBlockEntireDomain() { + if let domain = domain { + // If domain has paths, always show paths tab (override stored preference) + if !domain.paths.isEmpty { + blockEntireDomain = false + // Update stored preference to match + UserDefaults.standard.set(false, forKey: storageKey) + } else { + // No paths - check stored preference or default to true + if UserDefaults.standard.object(forKey: storageKey) != nil { + blockEntireDomain = UserDefaults.standard.bool(forKey: storageKey) + } else { + // No stored preference - default to true (block entire domain) + blockEntireDomain = true + UserDefaults.standard.set(true, forKey: storageKey) + } + } + } + } + + // Save the preference when it changes + private func saveBlockEntireDomainPreference() { + UserDefaults.standard.set(blockEntireDomain, forKey: storageKey) + } + + var body: some View { + ZStack { + DesignSystem.backgroundGradient + .ignoresSafeArea() + + if let domain = domain { + VStack(spacing: 0) { + // Header + HStack(alignment: .center) { + Button(action: { + currentScreen = .editList + }) { + HStack(spacing: DesignSystem.spacingXXSmall) { + Image(systemName: "chevron.left") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + Text(Strings.Common.back) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(.white) + } + .buttonStyle(.plain) + .keyboardShortcut(.cancelAction) + + Spacer() + + Text(Strings.DomainDetail.siteSettings) + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + Spacer() + + Text("") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + .frame(width: 60, alignment: .trailing) + } + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + + Divider() + .background(DesignSystem.textPrimary.opacity(DesignSystem.opacityLow)) + + ScrollView { + VStack(spacing: DesignSystem.spacingMedium) { + // Domain info + HStack(spacing: 8) { + Image(systemName: "globe") + .font(DesignSystem.font(size: DesignSystem.fontSizeXLarge)) + .foregroundColor(DesignSystem.textPrimary) + + Text(domain.domain) + .font(DesignSystem.font(size: DesignSystem.fontSizeXLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + Spacer() + } + .padding(DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.hoverBorder.opacity(DesignSystem.opacityHigher), lineWidth: 1) + ) + ) + + // Blocking mode selector + VStack(alignment: .leading, spacing: 10) { + Text(Strings.DomainDetail.blockingMode) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textTertiary) + + HStack(spacing: 0) { + Button(action: { + if !isBlockingMode { + withAnimation(DesignSystem.animationFast) { + blockEntireDomain = true + saveBlockEntireDomainPreference() + } + } + }) { + HStack(spacing: 6) { + Image(systemName: "globe") + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + Text(Strings.DomainDetail.blockEntireDomain) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(blockEntireDomain ? DesignSystem.backgroundPrimary : DesignSystem.textSecondary) + .frame(maxWidth: .infinity) + .padding(.vertical, DesignSystem.spacingSmall) + .background(blockEntireDomain ? Color.white : Color.clear) + .clipShape(RoundedRectangle(cornerRadius: DesignSystem.radiusSmall)) + } + .buttonStyle(.plain) + .disabled(isBlockingMode) + .opacity(isBlockingMode ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + .help(isBlockingMode ? Strings.Help.cannotChangeModeDuringBlock : "") + + Button(action: { + if !isBlockingMode { + withAnimation(DesignSystem.animationFast) { + blockEntireDomain = false + saveBlockEntireDomainPreference() + } + } + }) { + HStack(spacing: 6) { + Image(systemName: "list.bullet") + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + Text(Strings.DomainDetail.specificPathsOnly) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(!blockEntireDomain ? DesignSystem.backgroundPrimary : DesignSystem.textSecondary) + .frame(maxWidth: .infinity) + .padding(.vertical, DesignSystem.spacingSmall) + .contentShape(Rectangle()) + .background(!blockEntireDomain ? Color.white : Color.clear) + .clipShape(RoundedRectangle(cornerRadius: DesignSystem.radiusSmall)) + } + .buttonStyle(.plain) + .disabled(isBlockingMode) + .opacity(isBlockingMode ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + .help(isBlockingMode ? Strings.Help.cannotChangeModeDuringBlock : "") + } + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundTertiary) + ) + + Text(blockEntireDomain ? Strings.DomainDetail.blockEntireDomainDescription : Strings.DomainDetail.specificPathsDescription) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + .foregroundColor(DesignSystem.textTertiary) + } + .padding(DesignSystem.spacingMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + + // Paths section (only shown when not blocking entire domain) + if !blockEntireDomain { + VStack(alignment: .leading, spacing: DesignSystem.spacingMedium) { + Text(Strings.DomainDetail.pathsAndSubdomains) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textTertiary) + + // Add path + HStack(spacing: 8) { + Image(systemName: "plus.circle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.textPrimary) + + TextField(Strings.DomainDetail.addPathPlaceholder, text: $newPath, onCommit: { + addPath() + }) + .textFieldStyle(.plain) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textPrimary) + + Spacer() + .frame(minWidth: 8) + + Button(action: { + addPath() + }) { + Text(Strings.Common.add) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textTertiary) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringAdd ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .disabled(newPath.trimmingCharacters(in: .whitespaces).isEmpty) + .opacity(newPath.trimmingCharacters(in: .whitespaces).isEmpty ? DesignSystem.opacityHigh : DesignSystem.opacityFull) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringAdd = hovering + } + } + } + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingSmallMedium) + .frame(height: 44) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + + // Paths list + if !domain.paths.isEmpty { + VStack(spacing: 6) { + ForEach(domain.paths, id: \.self) { path in + HStack(spacing: 10) { + Image(systemName: "doc.text") + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textPrimary.opacity(DesignSystem.opacityAlmost)) + + Text(path) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textPrimary) + + Spacer() + + if !isBlockingMode { + Button(action: { + withAnimation { + if let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { + blockedURLs[index].paths.removeAll { $0 == path } + saveChanges() + } + } + }) { + Image(systemName: "xmark.circle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge)) + .foregroundColor(DesignSystem.textQuaternary) + } + .buttonStyle(.plain) + } + } + .padding(.horizontal, DesignSystem.spacingMediumSmall) + .padding(.vertical, DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundSecondary.opacity(DesignSystem.opacityHigher)) + ) + } + } + } else { + Text(Strings.DomainDetail.noPathsAdded) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textTertiary) + .frame(maxWidth: .infinity) + .padding(.vertical, DesignSystem.spacingMediumLarge) + } + } + .padding(DesignSystem.spacingMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + } + + Spacer() + .frame(height: 20) + + // Delete button at bottom + if !isBlockingMode { + Button(action: { + withAnimation { + blockedURLs.removeAll { $0.id == domainId } + currentScreen = .editList + saveChanges() + } + }) { + HStack(spacing: 8) { + Image(systemName: "trash") + .font(.system(size: 14)) + Text(Strings.DomainDetail.deleteSite) + .font(.system(size: 14, weight: .medium)) + } + .foregroundColor(DesignSystem.textSecondary) + .frame(maxWidth: .infinity) + .padding(.vertical, DesignSystem.spacingMediumSmall) + .modifier(DesignSystem.buttonStyle(isDestructive: true)) + } + .buttonStyle(.plain) + } + } + .padding(DesignSystem.spacingLarge) + } + } + } + } + .onAppear { + initializeBlockEntireDomain() + } + .onChange(of: domain?.paths.count ?? 0) { pathCount in + // If paths are added while viewing, switch to paths tab + if pathCount > 0 && blockEntireDomain { + blockEntireDomain = false + saveBlockEntireDomainPreference() + } + } + } + + private func addPath() { + let cleanPath = newPath.trimmingCharacters(in: .whitespaces) + let formattedPath = cleanPath.hasPrefix("/") ? cleanPath : "/" + cleanPath + + if !formattedPath.isEmpty, let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { + if !blockedURLs[index].paths.contains(formattedPath) { + withAnimation { + blockedURLs[index].paths.append(formattedPath) + newPath = "" + saveChanges() + } + } + } + } + + private func saveChanges() { + viewModel.blockerStorage?.set(blockedURLs) + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/ExtendTimerOverlayView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/ExtendTimerOverlayView.swift new file mode 100644 index 00000000..9993e9b8 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/ExtendTimerOverlayView.swift @@ -0,0 +1,331 @@ +// +// ExtendTimerOverlayView.swift +// SelfControlUI +// +// Created on 2026-01-08. +// + +import SwiftUI + +struct ExtendTimerOverlayView: View { + @Binding var extensionMinutes: Double + let onConfirm: (Double) -> Void + let onDismiss: () -> Void + @State private var isHoveringCancel = false + @State private var isHoveringConfirm = false + @State private var hoursInput: String = "0" + @State private var minutesInput: String = "0" + @State private var isAdjustingTime = false + + var timeDisplay: String { + let totalMinutes = Int(extensionMinutes) + let hours = totalMinutes / 60 + let minutes = totalMinutes % 60 + + if hours == 0 { + return "\(minutes)m" + } else if minutes == 0 { + return "\(hours)h" + } else { + return "\(hours)h \(minutes)m" + } + } + + private func updateMinutesFromInputs() { + let hours = Int(hoursInput.filter { $0.isNumber }) ?? 0 + let mins = Int(minutesInput.filter { $0.isNumber }) ?? 0 + + let clampedHours = max(0, min(24, hours)) + let clampedMinutes = max(0, min(59, mins)) + + hoursInput = String(clampedHours) + minutesInput = String(clampedMinutes) + + extensionMinutes = Double(clampedHours * 60 + clampedMinutes) + } + + private func adjustHours(by amount: Int) { + let currentHours = Int(hoursInput) ?? 0 + let newHours = max(0, min(24, currentHours + amount)) + hoursInput = String(newHours) + updateMinutesFromInputs() + } + + private func adjustMinutes(by amount: Int) { + let currentHours = Int(hoursInput) ?? 0 + let currentMins = Int(minutesInput) ?? 0 + var totalMinutes = currentHours * 60 + currentMins + amount + + totalMinutes = max(0, min(1440, totalMinutes)) + + let newHours = totalMinutes / 60 + let newMins = totalMinutes % 60 + + hoursInput = String(newHours) + minutesInput = String(newMins) + + extensionMinutes = Double(totalMinutes) + } + + var body: some View { + ZStack { + // Semi-transparent background + DesignSystem.overlayBackground + .ignoresSafeArea() + .onTapGesture { + onDismiss() + } + + // Modal content + VStack(spacing: DesignSystem.spacingMedium) { + // Header + VStack(spacing: 8) { + Image(systemName: "clock.badge.plus") + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplay)) + .foregroundColor(DesignSystem.textPrimary) + + Text("Extend Timer") + .font(DesignSystem.font(size: DesignSystem.fontSizeHeading, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + Text("Add more time to your current session") + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textSecondary) + .multilineTextAlignment(.center) + } + + // Timer input with arrow buttons + HStack(spacing: 6) { + Image(systemName: "clock.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeTitle)) + .overlay( + LinearGradient( + gradient: Gradient(colors: [ + DesignSystem.textPrimary.opacity(DesignSystem.opacityAlmost), + DesignSystem.textPrimary.opacity(DesignSystem.opacityHigher) + ]), + startPoint: .top, + endPoint: .bottom + ) + .mask( + Image(systemName: "clock.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeTitle)) + ) + ) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityHigh), radius: 2, x: 0, y: 2) + .padding(.top, DesignSystem.spacingSmallMedium) + + HStack(spacing: 6) { + // Hours + HStack(spacing: 2) { + ArrowKeyTextField( + text: $hoursInput, + onUpArrow: { + isAdjustingTime = true + adjustHours(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onDownArrow: { + isAdjustingTime = true + adjustHours(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onSubmit: { + updateMinutesFromInputs() + }, + onFocusChange: { hasFocus in + if !hasFocus { + updateMinutesFromInputs() + } + } + ) + .frame(width: max(40, CGFloat(hoursInput.count) * 20 + 10)) + + VStack(spacing: 2) { + Button(action: { + isAdjustingTime = true + adjustHours(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.up") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + + Button(action: { + isAdjustingTime = true + adjustHours(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.down") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + } + + Text(Strings.MainScreen.hours) + .font(DesignSystem.font(size: DesignSystem.fontSizeHeading, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + .padding(.top, DesignSystem.spacingXSmall) + } + + // Minutes + HStack(spacing: 2) { + ArrowKeyTextField( + text: $minutesInput, + onUpArrow: { + isAdjustingTime = true + adjustMinutes(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onDownArrow: { + isAdjustingTime = true + adjustMinutes(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }, + onSubmit: { + updateMinutesFromInputs() + }, + onFocusChange: { hasFocus in + if !hasFocus { + updateMinutesFromInputs() + } + } + ) + .frame(width: max(40, CGFloat(minutesInput.count) * 20 + 10)) + + VStack(spacing: 2) { + Button(action: { + isAdjustingTime = true + adjustMinutes(by: 1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.up") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + + Button(action: { + isAdjustingTime = true + adjustMinutes(by: -1) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isAdjustingTime = false + } + }) { + Image(systemName: "chevron.down") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 20, height: 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .focusable(false) + } + + Text(Strings.MainScreen.minutes) + .font(DesignSystem.font(size: DesignSystem.fontSizeHeading, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + .padding(.top, DesignSystem.spacingXSmall) + } + } + } + .frame(maxWidth: .infinity, alignment: .center) + .padding(.top, DesignSystem.spacingSmall) + .padding(.bottom, DesignSystem.spacingMedium) + + // Action buttons + HStack(spacing: DesignSystem.spacingSmall) { + Button(action: { + onDismiss() + }) { + Text(Strings.Common.cancel) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .frame(maxWidth: .infinity) + .padding(.vertical, DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringCancel ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringCancel = hovering + } + } + + Button(action: { + onConfirm(extensionMinutes) + }) { + Text("Extend") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .frame(maxWidth: .infinity) + .padding(.vertical, DesignSystem.spacingSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringConfirm ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .disabled(extensionMinutes == 0) + .opacity(extensionMinutes == 0 ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringConfirm = hovering + } + } + } + .padding(.top, DesignSystem.spacingSmall) + } + .padding(DesignSystem.spacingXLarge) + .frame(maxWidth: 400) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityHigh), radius: 15, x: 0, y: 5) + } + .onAppear { + // Initialize inputs from extensionMinutes + let totalMinutes = Int(extensionMinutes) + let hours = totalMinutes / 60 + let minutes = totalMinutes % 60 + hoursInput = String(hours) + minutesInput = String(minutes) + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/IntensityDropdownOverlay.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/IntensityDropdownOverlay.swift new file mode 100644 index 00000000..0881c6a7 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/IntensityDropdownOverlay.swift @@ -0,0 +1,93 @@ +// +// IntensityDropdownOverlay.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import SwiftUI + +// MARK: - Intensity Dropdown Overlay +struct IntensityDropdownOverlay: View { + @Binding var selectedLevel: IntensityLevel + let onDismiss: () -> Void + @State private var hoveredLevel: IntensityLevel? + + var body: some View { + GeometryReader { geometry in + ZStack { + // Transparent background to catch taps + Color.clear + .contentShape(Rectangle()) + .onTapGesture { + onDismiss() + } + + // Dropdown menu positioned below button + VStack(spacing: 6) { + ForEach(IntensityLevel.allCases, id: \.self) { level in + Button(action: { + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { + selectedLevel = level + onDismiss() + } + }) { + HStack(alignment: .top, spacing: 12) { + VStack(alignment: .leading, spacing: 4) { + Text(level.displayName) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + Text(level.description) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textSecondary) + .fixedSize(horizontal: false, vertical: true) + .lineLimit(2) + } + + Spacer() + + if selectedLevel == level { + Image(systemName: "checkmark") + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + .padding(.top, DesignSystem.spacingXXSmall) + } + } + .padding(.horizontal, DesignSystem.spacingMedium) + .padding(.vertical, DesignSystem.spacingMediumSmall) + .contentShape(Rectangle()) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(hoveredLevel == level ? DesignSystem.textPrimary.opacity(DesignSystem.opacityLow) : DesignSystem.textPrimary.opacity(DesignSystem.opacitySubtle)) + ) + } + .buttonStyle(.plain) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + hoveredLevel = hovering ? level : nil + } + } + } + } + .padding(DesignSystem.spacingXSmall) + .frame(width: 300) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityHigh), radius: 16, x: 0, y: 8) + .position(x: geometry.size.width / 2, y: 180) + .transition(.asymmetric( + insertion: .scale(scale: 0.95, anchor: .top).combined(with: .opacity), + removal: .scale(scale: 0.95, anchor: .top).combined(with: .opacity) + )) + } + } + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/NonDraggableBackgroundView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/NonDraggableBackgroundView.swift new file mode 100644 index 00000000..ffc832eb --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/NonDraggableBackgroundView.swift @@ -0,0 +1,54 @@ +// +// NonDraggableBackgroundView.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import SwiftUI +import AppKit + +// MARK: - Non-Draggable Background View +class NonDraggableNSView: NSView { + override var mouseDownCanMoveWindow: Bool { + return false + } +} + +struct NonDraggableBackgroundView: NSViewRepresentable { + func makeNSView(context: Context) -> NSView { + return NonDraggableNSView() + } + + func updateNSView(_ nsView: NSView, context: Context) { + // No update needed + } +} + +// MARK: - Color Extension for Hex Support +extension Color { + init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + let a, r, g, b: UInt64 + switch hex.count { + case 3: // RGB (12-bit) + (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: // RGB (24-bit) + (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: // ARGB (32-bit) + (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + (a, r, g, b) = (255, 0, 0, 0) + } + self.init( + .sRGB, + red: Double(r) / 255, + green: Double(g) / 255, + blue: Double(b) / 255, + opacity: Double(a) / 255 + ) + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/StopConfirmationOverlayView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/StopConfirmationOverlayView.swift new file mode 100644 index 00000000..e01016a7 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/StopConfirmationOverlayView.swift @@ -0,0 +1,127 @@ +// +// StopConfirmationOverlayView.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import SwiftUI + +// MARK: - Stop Confirmation Overlay View +struct StopConfirmationOverlayView: View { + let countdownTime: TimeInterval + let onResume: () -> Void + let onStopBlocking: () -> Void + @State private var isHoveringStop = false + @State private var isHoveringResume = false + + var body: some View { + ZStack { + // Semi-transparent background + DesignSystem.overlayBackground + .ignoresSafeArea() + + // Confirmation card + VStack(spacing: DesignSystem.spacingLarge) { + VStack(spacing: 12) { + // Title + Text(Strings.StopConfirmation.title) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + // Circular countdown loader + ZStack { + // Background circle + Circle() + .stroke( + DesignSystem.textPrimary.opacity(DesignSystem.opacityLow), + lineWidth: 8 + ) + .frame(width: 120, height: 120) + + // Progress circle (white) + Circle() + .trim(from: 0, to: CGFloat(countdownTime / 600.0)) + .stroke( + Color.white.opacity(DesignSystem.opacityAlmost), + style: StrokeStyle(lineWidth: 8, lineCap: .round) + ) + .frame(width: 120, height: 120) + .rotationEffect(.degrees(-90)) + .animation(.linear(duration: 1.0), value: countdownTime) + + // Countdown text (mm:ss format) + VStack(spacing: 2) { + Text(TimeHelpers.formatCountdownTime(countdownTime)) + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplayXL, weight: DesignSystem.fontWeightBold, design: .monospaced)) + .foregroundColor(DesignSystem.textPrimary) + } + } + + // Message - single line with dynamic text + Text(Strings.StopConfirmation.countdownMessage(countdownTime)) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textSecondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + } + + // Buttons + HStack(spacing: DesignSystem.spacingSmall) { + // Resume button (more prominent) + Button(action: onResume) { + Text(Strings.StopConfirmation.resumeBlocking) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .frame(maxWidth: .infinity) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(isHoveringResume ? DesignSystem.hoverBackground : DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium)) + ) + } + .buttonStyle(.plain) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringResume = hovering + } + } + + // Subtle Yes button + Button(action: onStopBlocking) { + Text(Strings.StopConfirmation.yesStopBlocking) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textSecondary) + .frame(maxWidth: .infinity) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(isHoveringStop ? DesignSystem.textPrimary.opacity(DesignSystem.opacityLow) : DesignSystem.backgroundTertiary) + ) + } + .buttonStyle(.plain) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringStop = hovering + } + } + .disabled(countdownTime > 0) + .opacity(countdownTime > 0 ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + } + } + .padding(DesignSystem.spacingLarge) + .frame(maxWidth: 340) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityHigh), radius: 15, x: 0, y: 5) + .offset(y: -20) + } + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/TipsOverlayView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/TipsOverlayView.swift new file mode 100644 index 00000000..1181b51e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/TipsOverlayView.swift @@ -0,0 +1,94 @@ +// +// TipsOverlayView.swift +// SelfControlUI +// +// Created on 2025-10-31. +// + +import SwiftUI + +// MARK: - Tips Overlay View +struct TipsOverlayView: View { + let onDismiss: () -> Void + + var body: some View { + ZStack { + // Semi-transparent background + DesignSystem.overlayBackground + .ignoresSafeArea() + .onTapGesture { + onDismiss() + } + + // Tips card + VStack(alignment: .leading, spacing: DesignSystem.spacingLarge) { + VStack(spacing: 12) { + // Header + Text(Strings.Tips.title) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + + // Tips content + VStack(alignment: .leading, spacing: DesignSystem.spacingMedium) { + ForEach(Strings.Tips.items) { tip in + TipItem( + title: tip.title, + description: tip.description + ) + } + } + } + + // Dismiss button + HStack(spacing: DesignSystem.spacingSmall) { + Spacer() + Button(action: onDismiss) { + Text(Strings.Tips.gotIt) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(.white) + .frame(width: 80) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundTertiary) + ) + } + .buttonStyle(.plain) + Spacer() + } + } + .padding(DesignSystem.spacingLarge) + .frame(maxWidth: 340) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityHigh), radius: 15, x: 0, y: 5) + } + } +} + +// MARK: - Tip Item +struct TipItem: View { + let title: String + let description: String + + var body: some View { + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + Text(description) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textSecondary) + .fixedSize(horizontal: false, vertical: true) + } + } +} + From 5c7ffbd5a44fd52c4bf02b8eca3eb4583158bbed Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 22 Feb 2026 23:05:18 +0530 Subject: [PATCH 16/64] added new icons in the app --- .../AppIcon.appiconset/Contents.json | 50 ++++++++++++------ .../AppIcon.appiconset/icon_1024.png | Bin 0 -> 418608 bytes .../AppIcon.appiconset/icon_128.png | Bin 0 -> 12661 bytes .../AppIcon.appiconset/icon_16.png | Bin 0 -> 804 bytes .../AppIcon.appiconset/icon_256.png | Bin 0 -> 35515 bytes .../AppIcon.appiconset/icon_32.png | Bin 0 -> 1940 bytes .../AppIcon.appiconset/icon_512.png | Bin 0 -> 115407 bytes .../AppIcon.appiconset/icon_64.png | Bin 0 -> 4949 bytes .../Image.imageset/Contents.json | 9 ++++ .../Assets.xcassets/SelfControlIcon.icns | Bin 0 -> 776926 bytes .../icon.imageset/Contents.json | 12 +++++ .../icon.imageset/SelfControlIcon 2.png | Bin 0 -> 227995 bytes 12 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_1024.png create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_128.png create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_16.png create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_256.png create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_32.png create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_512.png create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_64.png create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/Image.imageset/Contents.json create mode 100755 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/SelfControlIcon.icns create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/icon.imageset/Contents.json create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/icon.imageset/SelfControlIcon 2.png diff --git a/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/Contents.json b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/Contents.json index 3f00db43..e09f0fd2 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,58 +1,74 @@ { "images" : [ { - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" + "filename" : "icon_1024.png", + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" }, { + "scale" : "1x", "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" + "size" : "16x16", + "filename" : "icon_16.png" }, { + "scale" : "2x", "idiom" : "mac", + "size" : "16x16", + "filename" : "icon_32.png" + }, + { "scale" : "1x", + "filename" : "icon_32.png", + "idiom" : "mac", "size" : "32x32" }, { + "filename" : "icon_64.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { + "size" : "128x128", "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" + "filename" : "icon_128.png", + "scale" : "1x" }, { + "filename" : "icon_256.png", "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" + "size" : "128x128", + "scale" : "2x" }, { - "idiom" : "mac", "scale" : "1x", + "idiom" : "mac", + "filename" : "icon_256.png", "size" : "256x256" }, { + "size" : "256x256", + "filename" : "icon_512.png", "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" + "scale" : "2x" }, { + "filename" : "icon_512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { + "size" : "512x512", "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" + "filename" : "icon_1024.png", + "scale" : "2x" } ], "info" : { - "author" : "xcode", - "version" : 1 + "version" : 1, + "author" : "xcode" } -} +} \ No newline at end of file diff --git a/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_1024.png b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..3e2456d81d67477356bba44e0b967bc57bd3d456 GIT binary patch literal 418608 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

    XUxsQt;Bl|4aD-`hsymw8=%h~ZH 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

    dXT%e^+Hyua8|6#`6_yL3J()K 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 z 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%0DpN(h$Rak>D?7Po(R2G%1f0+sk>H|d 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 JnpKXacei+q_h{yIE&)gVmU=wIPOa8! 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�`YIo;{+}UK{qFO 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 literal 0 HcmV?d00001 diff --git a/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_256.png b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_256.png new file mode 100644 index 0000000000000000000000000000000000000000..3816f3dd7a022c20c19e1dc6e49d5e4818d7822a GIT binary patch literal 35515 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Í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%0DaQuhxw^##AtrV{jqx!(4a)6B2IL!CN|9 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%0DN$H4 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®WCa7i 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

    `e=^DDdKmfyrL28}nH_k$>Ja&r__u!wode1Ng~8 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⁣p~1dfe&afc zR1%icxg8YNlCFw>`3trC_RATrOt{*A;mVwe=Ff3)pvEE)**kJA@C4>ehUUjIv^cSL zxAUV|MFt&QwGYCGf`l*xIp_L}*A>_2gqjbp%)_bxRlrb&|3E4PRMzVpu~bQ~57qS2=TYU2Q?Y2}Zlnx@qoM8VNoUrQk45xy_Ja=*M+~}O&TGi>H4?f*) zEu?&0NmkgQ1hNQ((b1VPx0A>0L9WEh^kAV!F&1#{mpCIM;K6YrBTczIstlg5>mD@{ zMV^TLQH&nmcxG&y?OV}<_7g*SykBHi(_$RZd2nc<7OSPysP-Vuav%$t>a|{T^{rx= z+Xrdq;1ckw9?+{Q=vxusG}Xd_5hb>_Wbg-i+~_vZ_{SJB_13`lOu=)q&v+H#2#yw= z<9GcrZ|M%e+c#hdETWCil<%=Hj_|z|)fNOXHxKA@NLO1cxC8MbcycmPW@hQ~OiGx9 zH#*|sZY6Q%$*z#MOGMqG&}-|m)2tW9X4|wiUwq!}PdgWXR5xC_MnCB;-kl=(&P_D$ zicAKEE4A|To*Yrws-d0X8+LgfrN|!6Z52h0A(;mfry^JW>uokjDeUy4t#m77dgNJB z)_jYZy$96v9Iqi74eOl6thl7VDRv9C;-a!$@#ElTY}d2RlZg5Qv%TQrxeP-PFdIraxo>TKHXP;L{zieK%wqxZMR!1Gy%tR1YV1oPgOg(b;6{A>m;8Vss*wXlM=fymd&DNc6?y^vZN)Dkd?qc_`)!J?V5#92 zEUMh zZaTuw%}xjwVv5&|tZ`cceADc)vC?&FXUTl2^T#KS;2*`k5BjS>WV^JtCx8r!p>4O6 z;%3IsCl;PD$4$-RK+w)d>@$3?iq(~Sa(hE5bUlPq=|>x-XuAUr`~Yn#>2-TZTG510I9O?^ChI z6(P*W9^*oDJ#Jd_wFvT$90C>nPhM~RkyW4H38~OH(bDH}F+2wfmWg3q{&IWREt^Qw zSOIejyX#5w4%=Gb?vhK1HT8u((T8Q~#>lVvt)Iw<^{Q-Iv6aKqw?*Sj4vxnWQVV(6 zFd5!<(K183N(!A~wk#qvbBMIslUkJ=bD00zX`QcFt;hHV-BrUEgx-fQmABsLs&ggi z?h~?{Y9Evq5DQtXj~ARD^0IJ-$tvOU;8#81{%ZVEnA@%#-^40tSW5=u6xp6?o}~af zO0~g0_|mTsvJy1c6IXZKTJ7mcpPvk&muhSu-X`gwA$YLp2mK)S35Dcw z076lK_%rxVPFANZYkoe@e)GtZ9!;)a% zuz#~*J*KuT7xh$HxpSbV(A3Jz%yOb+mJ1aPw+NM$nXB?~Br27enJZUraGN>E1?EB= zpyFVN0y18{@B91x#le3Z9QSeE_jO(8`8lmsqSe06+USV%r_?~Wep6~2+10N-qdpp0 zCT3;e1q^|h0T2v{kU`l%;qX)Vl3yZxXsLeRFH&5BT^5b82fhMXL9 z;%$o!mHujCu${=1=k`YJeM-6JGB0XU?gHW3j8j(a06lDnl{veM#*^?|97OHZtjkU= zdUK4Zu75H{AM;EdDFvb|rWZ~pj#GsE02N#{2FZ+FDS{n8X~u$|xB&u)Ja=xQ(=omP)j2p-Y&U;C8CO*y{vqg@1ao7-XErzn-%+k(wuvjAa9dva2-)W3wW5`HuBS8p( zznS2diQkFUHAv! z&l=C!!{p#Deivx>bsVF9MVNenDuZy)3SRr0jWwnPZ3c0PL?&$63t6qn_*v7m!uaio zV==NE@qC5@hs>0a#UsAC|6$x07c4>r%xDpoRfvr$OBGG638yIcB|$rJBvJpGib!${ z$Iy3D$xL)2$Yg7oAX;vtEt#%+C`6Ao^&?IOMeRiD!d4T@VPM?IT=rdK)~DcQI8EfOrnn5jrZ_Msr5FmpfQ6(+ zp{CRvWjmzr!)PI7;=*A}NoQnk5_mRVs~?iL(EiG4;}Bk8?ppVC#-_;vrw0>C_y82% zDd?l~t92@_^vQ0Ii3;xud6>w6p7F;Wg(TXU6KSWa@7pM^WLG};gNqX&*lWH$w4cR} z#5~dgXqkAJUlP@;i?{$2&0H%g`@4RvX1!6w9peWe)7Vpt0{15uCO2{q-XePCz_1BD zoHj5Yzub7604G)Zy2D8^uWUym*@QT$NOp=vbur8Bd%3&g$n6*@#%IN*1tyepAIixp zx)~%P0kbsFGveyRQfQ*CVmvVQq5G+TIjti_{T)^b1m*r*=017lgZ`cV?LCajU=1j1 z;M7n)D`ha5hHn-$&HCL=hta1ayN`|QwmOTQ+2kH9B(2LdcIbhDnA>w;eE8w{+mWu5 zVFt-~_Q+-(NEcnz5Rg@Qdyt+v>tH1)zuqT0&4QcsUen%6Lv0Y+<(f0WF)pm?>Z8vN zqB?(mfQ5HzT%Z-1@bbvQPaE}KV2;#x(_>InNb{he6aR!ShtHYaX;8;5k$RZivw2^< z1XaaRVK^#7N_e4asv^+2vgs%&$lK#{VMVJUxa(Y9j3i@>eNU>tb8RwBm_)EXA|I-mQoi#KD5V39=AnAb+$aW1^?XAa!`mC zaX*hUo5;a5j|*!~*wC#K33I${b3ws@wdo;Hhq5SZX3>gT)Sf;}qSXwpxL6vY9(fi7E*<~lr@yL#((8q9ELJglV_myc zG~iEuX4Y3e7DANkWp(KfR@z&M5!BSRdnn$4xc880pLSzf`xNtSV`+hanbTp5djJ%C zN2j0@6~U*!h|Wn;E2e{qUyI9Q4GAPO$EsOtwPIy6Kg2C4ZW!cg0jIQ_LeuQaT*=^$ zl#O=9uw-il5M2HV$%=TP(9Ajfgm_kvr^Y!06>I!UDk|6-a^=Vr^O~9@lSl<=ZZ|Ov zDX|7tM|U95gy^X&1EinKkq-Yq{%=yg_1zfo{~>XIh`Ywxha8XoU?eJf1sd}r&VMmI zl4vn*tOv{9b-o?na)59x(FTgj@_fn6W+Jln(d^zM zdrSEkPKGW$gZ-%1(6$fH6iQ!h#-S?Hzb~Fg65hIeceur$0^U!dhu^Vd-P3`@cX+ua zp6EB*lX6X2MLY7ja{pt|u)2GdZ3(!(@bu3rZ#BJc$LdoV^oRSvKgT`@=FYA3r#$Dq zX%JOdq>H>nI*)sSqGG^frurzDk}sq}FR7&?&ZrFWIsq_QdiX3`GmbZK6^deS3wPs>+-6?T5lm~QRKFcAFT@9-MAz`X zJ2Abv@Qz71;ujr!N~Q9@%x>F@+F9$8gnG&WPthCk%HC(Gd?3#;pm}h-_%Tnntk(7QI(moSJ>?WTy#S8gBU)~?3-() z&K&_MH`c_*xt7baDR&%qIMfEq=HNqTw5! z9h#crQgoYPprN97I6O&Py!%YDZI`*^*ZC)^-7P9lQ^a!0vQpGcqlN2C(~2B;-SOJh zVrVC|IYURO@uD(b>P`%+n+6nC06r<=5_y|XMGaC*cnYTsjEqG=`I4B^4N4Dgxkn%- zdAo;t^{;A40#*<=k3!ua%(`}Q|Lj#9cTJ%v=m@79@LdN|6s+5@vHIx5grBH=`#@nO zry~w##2p6Y>-UJJgWwZ|4jq>^)!CA%59WP8;YX`R#1LRykmD|K>(BQ*GQt) zWl7eO{Rlvn%^WbpeoC&JvSM99!0Nl<77>1?=)_{>qFUy$9CZ*5-mQNQ=KdTm&Khp;W}RT?^&FYQ5a zekPfARQe}N z30h|?{Ehf7V!^){X2M*Vb8P;OTiPSp*20M$20->V>`|)`_}G4w35FL3|EzX5ya@j4 zpB@vMsU;G?d{} zWO>#bdt&FmV~y5b?z^paIZmu%I3vn2?!&wSQLnR7BV$X3Bb2@`9G_#4KX4W`j}ZhC zxlS%So2HU0S}(BwT}X}0VadmFG!=3WHzc1t2&SE*B^(t7MvEJrX1Yw`J=vF}Vv>gs z1`3W?oBYyWrrOb|PXFkKR*9LQ~t?%gze{8|oOedw7kZ$Tzi+Q2=4 z_7%{d`w~B;{5&D7bCZa(6_0PNHmq$Zi%3nYp>brQqkwl7&ql!QpCdb7lGNK|%g#zd@ywyphXLW;&%S`M*nbSj$X6*)VV$vc=|c5#XFnn`kxah? zfK=Nf{7jp|k}l9C#9;pXrg=}j&b)JEaY{^lnvHghC^D(02IXATo_UwGmHk+|&@l!k zxmn)UrJr82l+}XBDnetTH@I(BSOZQ^t|3w@gSsRAgaaSnxqc;Z{!8Jy3%NIAv!YP6 z01zFvtd2o=h!}y~VLe)5E$?L;+D_sMFc4QbM6=lvT?AD|x$~+Hzc{ zSm`jKoD^E>txZnfhoo`i65StM+%3@uurNXb)7#ufM$X?;687{I1>;iBzLcsOrBdKassH zY=h6864_*5lUV9_Wb=rObi30@_uZ}YZQDdK@gzrFgG6pBD3iv1Rgl6hVsML$b`8+} z>bw$~%p+Alf8pUPiCGb!rU+L|V1i69tavPCb;$}tRcx$ip;!YjAW<&3e1t^XtG01(499f&Zj$rr^`NXsJ4)rMb3nn%Pye(N`Yj(hWPPw43*7jiu?gmvI)`;m$I`yBo8vlH z^X`fU0tn#ZxQ7yxir^%oL?)QmXuKZ!^ai`OtVnub+=rt3o|j*_bnI>vjoFg>L9%HL zBw|5x?V3=m2E~)^ox9ERHrvROcYt6PDv_;JKTtvT+ehT0U1jHh^y=ZYb(Rf|-+hpr z&)uDFNz^s{oX|%Hw{i3c=n-%9;v-S6=C+MPwT7IMF3}nJPap7Q83GIZ*1)kZ`scKF zbsA3g10U;kdM=GoWQaVNC}9{(!$!VKMfEJ!&uvCGpTgyC2(5m*WKef6`)^8AI2X~6 z*3wZ?!5hQ5dxuwfS~M#x$&Hl<2{EaYc73hgqM#dgI33 zBvAFe<73J9kttxr8(JOYIiC8<5Gp+L1>yfgMPWp8bS)PVQ02 z(s(v!h^con5Dedoab@hX3mbuY(ud$kWDd<-6B+^Zm{bCXzU$$~%gudU)UPc*Z8`~W z{C$4u9e6eyEnfdBSdToBki0c_Urp@y^x<$@+_uJ1Pa$`^otQ1j(v15bof(XrcSOxo zu;{UZ>VF8UQGq*|SzfAhJtmn>WreJb^rN`-Cix4q+?KCoZK2Q8Pvlvy4^#5u%LXx3 zxPSH-XZm!_;C+FbL6y>5ns4bldfug@1au6lGZK;O^uwuisNJy-v`f)l;Dl_tW`Jvo zc*t)`{@$szHl0=eaDX~4`GJx8=$>Gw*K)J0;}bI0%q}t#EF7RuR;QfWv}4erVGoSthPkHJu5Gg zVT4bY1`qM41_7VRg9=jaTVo`5x`GsUJ-(E z;%S3W}{6Z=D470(8Fitdq=1o&Fi)^{!fF2 zgE*4Mo_nA@Un8H+NuBJ49`{q$BvuyT`>Q)YSl6TykS)P~h=Q;H7^@B#VeVutXyO%o z9WBPf_ILe!^4Uw%fU-Ef^IzSp#e?V$$zxCM7Wgii!BmV$KN03(;VOG1sXcGImN4*2T-$)rUD?d_0XOn!wD9}J z!h>kJ2cMt06WX|6CySf={>gTK2GO{>fCzGXdS z6?L@@HWaD~auyf!9&^8p4nOlk?)actLQSzaoKjXyp&SOX!ReYXDl(|svCpD&-c}?T zJkR*^eO_NEw>D?X7Ieqqj@0%Nxbd(fB5zSQ0KAGFpneuKmpxUu_GvVIU-r<8jjI98_IA06|CZsp8AhkSxzh=+d)FXMFD-8@}w6QvN)0#5T7M^+%J6$>k$#&RKOG za3B9|8%R3Mc+9h6lj`4-VdF$o({ct7RvP)mvNh1Crnn0N-s$X3njqbe!E-eM=73ge z>{Fiz)lhAUWM48B6d~ad(t#b4okxL~EV0&_Q})<0#Q&*wKm-G%t1USNL@CG4mi8#n z@TAMb+CgZrFe$oArR|!U)=?#;#>EGXYVAI5x)To!j2Tpl*UmtPW=T1Gw@sMXkOVe{ ziwstc&7#9N!2w@Cy(4Zh-O_~3GN2%C4_;u69}pV|>qju_GJGx&iHs2gmPD6zNS#fV zn@7lQ3q7`Js3CXHAJP|&G(0@I1v+)XGa`D zQ}!_`49snQDfz{-zu6L&0ZYrMMBv^-HH|5OR&#m4(EMG_G|!1AkEf=C3OdOqAndw+I|;{=&V41j1(?AlbjX${SE@+{Fku2FOLJE1wD*`M9UsT zv9&vCL<*MUjG>FHq&Ux3V>XqgszpfI>6R4;q)3M>JxzZcIA-E+NOt;r)!)nr{_W?s zIMS!`a>`TX2hbnMoq&?TlVLYq^=bRlWv7mUmpnLWIuz$|d4MV@O)!v}gE+Hd&(TFK z&8r(4bp!t~8Tg$0bw#C6Her`a^=NM&Zy@|m-nvn;*EJenDu?ZTu}I0YM02xO#xk^a z4{RkdSVurniartgNG?&kk^Qlz-_FzTR==~=e2DOr$KKmAFq_i38&mzESY&=*rl?S` za7djvnv3UK$oH9kdmLvQFg(vE%$%%hE9l=p=I$NMxe(VJd#PY!W}k_(2DPk>?r3U3 z;!b~XuWob1fOjgjG3iqC1z}U%RQAco1DlPUXHsa$8|Z$i*tG#kdOGD8A)=E~XdAOc zxnUdiUYzp)b@~1vO=&&42g%4s?`^XcKY=bPtuqf6T=QNr*p&g^l?hyL(Rk&Sf~=J? zZd~y2g-y0avm+{{ftqg*Pb+nYz8Cq`C7aIJ;-Tb$i{UGXl`wS5g`k%T=*@66t!$Nj zFh;ZwfG;8_$Dry73fCdlleYK18IU{i)VpB)Y#jNE4g-d#K0+t7T{(JmC3@^+Pge^14+VcOWc)zjbMXkE$TaaB34pm(6a_1Z3RaxHoL7P0w0Nk zYQ!ab_%bpD?I4=A(3SNkS@5z?*4f+o3kqxeII!;aKVRc#Y3LspINNX7IOFTwzor<; z%vN{Tf6PZcafwsu_`ref{mPz%@_gi4{iQg#Pr?9-4zniUUsSBRfmZoW8?&jQgiGgP%&EUp6sq$!_ru_>+aCcxxv0 zEA%7m%-L8K-xc4xMdE`KJKTHv`EpM+wOQ$??N4upvcTUCJ_!IzaYs{xPU!hwj3uYy zLt|l)u7QhjjitYei-~_@T^)?AzAD@5V=-)L2#w*Y98)H8{m0VQ%oo0ef)uZsTIIee z6ZkxzEP;w3KOWp1$r{L_Unv>>1$mDTu1NoRB=vG%{3p7MZYk}hds2C+!X#@pua7?` zlUi>%{FG?Xm(l#X4cHi%!s!W&1z)yaMZfZOxlqO|Br)cu}s-}~}e5~?TX3y8aJ zjX?~QVI`Og0V#A}I%1|wx1|6IG8BJ8{kxN7I=9vHzmB!f_(k3pEJXqep)S+*)An zdZY^{tqK>WMlpwL@ny|XlR*~qN`2KGoV1g;>4(l%{Gt_pymH7+g@r{hNOe7lz#I`u zWVdY?dXfmbSRj}{$4ts*&;eWTqVllO4LyG>LT9+f`x}Qz{DI?hWg-$XmLOEo5enp_ zwt8fY$!A_eKQUXRqV5-wZaV4tB7}3{vZ5UikSv1f(*%db=wehZ{#fdG$>w~svds+f zT3HD5ny#?$beFThlQ*!+*`(P%3D%)TnVg+Z;k&^`91OA$*4mQ1mSJZ>pUZ#7oi6#x zC|Lqm$+~~Iq5(CKvBH6`cb9CB@yq5MIm5~MAigd%&~V9LF*~LxL$5o7YmvYZZT7!6 zTcqnv#j-SH#(WnYdxOAxZ@qO0IC{KDz`RMW^!uc)!X&Bs|+5 z4^sR>`o47X)`k3P#SDT`ZW_J{d2(BUC(ij5!RaXJ@=Vp>qlF08tSuPHbHLH=xkyL} z<&5(DI?5^AgodKHxr(%1{J7m-rJ&wQ)2r6q zWJAAmAQ=+tZND#7iLR$mVk7)%Nf#j0K9)$l91k%T_E8HojY|*Ml&3M--W0CmvuSSk zVO_Nr{jc++*V%F9$#~LQC5}2$25NMPgB=zHOOP&c4eOiRb3eoP{Lio#*JV!C$6zz_ zLfx&Cr;i#yUHDykk*R8d{)Ctbv3hMYpyzZ6q1eXY)1xXOYw#_18s>zaKQub#t1}zYZ>AL zvfa0%{1rbQZusmNqaUpRy*&o5*_;9MH$#)(qJw`o_J&+}3>I z{=&(!&!euNT5RX2)L&nhlwaWvZ1!a&jAP=R0?scP{A5=0dTq^rvY6D?l86DC^v5;X zPkM!?BVXrqY+idNzvqasu!*Q}1Ry8hxqV_hEAb5|bMV|o_seX40>EZ`W-mV;=b45z z`*woo*$t%GLgHZ8Qo}}&r$T$p=SB zWyBU8HES1s1r#Uq?EihJG!)Tt152L*X^FjV%ArD*Jt3$lLG&eebNqU~uF+xS}hLEM%Jvb*sP@vMOkdoe8?|X~*R^vDg1_aZQ?#kinmU+Pn#=6MA!Ey2zPlg;lccgEa%KjYj@aMGT58?b5=+~pvTtE&e z?_v+#sQ`(5u4&-xk-pE%9Yz$jjBjfA*3l#&wJ}tm)Tx`s79alW(&n->iT-SP(|g z`sPJd{_yyF(KjGF;jPVwq@Bf|XghCAptvIrkbjACyIoZnEz`KYU>;3HQsGW!n%6zJ z3&VmYv;um5XxLz$G11vqZ(q6A8jCT|Y-$8{VP>aT;`(-T1 zpISHkxn}gSh~M_PGw?l(nw>251|OOdY4A>>0ckyUkw|Y>33rKjz-qZ<*dSLr{wq`% zbfBSJG8jTbo}q+~Mcn=iwYz*(6;T4o?1&pK=p(C4eTO>Fz_!B4F`JYW-WU#*MOa(h zLS<|n-@LlN&Y@0b9;XZle*@`Cez&7v9w2VC$}5OlVEpzCmg)y>F4>~~{#{4~ zSn+bu(!a^;)cR{Os1EHs8Tz%nCwjNkXihw(f{N*H+OT2XdXn~v5!p<9EW@2vCoh|U z57|-()A8B^%})*g6jlUg?V(&TbxRt#M=TQy}}OT>cGY4&47nJ8`?Rj1g4g zsPd4FddTQDvk*`ex)1@FRVF(8i-|4o`f}^4t7zL(^=6=y1R1gYDTHgjKeEkE8*>(O zC+wl0z1%MonZGZlB088aD21~T0YR{I-7Lm0An(eAC;MQREg*093_yEqC!1zDD_}&G zENx=KmjFNBX#kZR9XNKb5kE`RYpaIQ=g=L+GG)|DZU*4Uo0VR$Y*SfqsLH&Rby&)p zAPq(Z=b=dU)q}tSuFpV(`Afvbcdu6H5yhfp@ORBV8}08W>1%99&Ryxg(a~asXWy}_ z|Le?8TBdFc@DJa%>14e%ecUw1s?)*4N+R7Y7v&iN(N*5d#=dPWmUyZ5yU3T!g-2qpuhWp@wVut-x-l`GzSFEvSF3(VxDOh{NDH_CxFIQ?DU#xOsg3%}?9qh2 zYQx3_-54#h34b0cdQ7&jEo10%Kp(N|LrCzD(bO7@m%~f>wsmB4;8>Z0MFYXCHH3a? ztNCK8@+PLUDUPoVjZ)3ImTo!0>!70XP3|hX<4A+{? zLoVqG=177ll;Qb;^%z{3z}cR0(4HRhKZG0nkr16r){d&Z50DBfo#5`e$R`a*X6OD@ z{(GVK{A;Vm-X0qxT4sHF6$b!^>Ac`Gqi5e3bhY1X6D-++c;7qXXtz)!!L5nUiyuZF zsHh=9>+N03?|b%_k1TTXZp#ZSUX^9h@fT5T13nWzpN+wWQfDAbp?e*97LNR??~8}X zpFaKQ!cKhOxmVjXFF_Yc{x5bBq4zED>v!+iqD)TvC4AQq_&&N1APz)@jCeKe{Z>e9 z6XA?oKFbCcpib%ZfDQ0-{Y7L`S;Z3f20|$Hw6#{2vpwugrwc#+QgZ1wlwa(nygqcB z@96l_%aSrV4lVSS4tMbKxHc{aC`O)n0Ga=C8@0KShF`aigGO~|dM(QfGoB%>r{isr zGZed&sC&b@{#%X5`?6fYneFGjRQVVIw|vp6G6jK6*olxZbf!f(XV}I8GR#{x^wd3vU)7%vgMbjn4$0$`M@Mv_5_ySsXLpm?$JVtD8e8M&(!MUj_8=qmRC)<+w%ung zp!y4*YDy*h+~}%&qn`c>HUGm(#0l+xdSCd))vABNUR+$wRE0-29yxY0E6hQ((}4X8 zYH)`aDxaqotyLK~MwoVN*+7 z^b7K*)q<%V=hfl-H}AGa1y?%i!(ctdReZ2R}dTaed@aK=l2C`l>$&7V_TrK>Ify(JR-lzPbY#O$h@`i9Q$c!rJDsAyP~(76jl z=~Ck7j8sn7uC;DJ?Ty@bsB9~Km6~TZq8D?(1vNv+gl@HVTB#sis_J#im7^=AH!}|Z zGWw5xg57^~Tw&&zjB)}odWxOfCNUn^s^BCsHE59ssPxjqIwC7oDC#uVpZX6A$)C+~ zxJ)i`*gHFXLoPXi%rvlS*w~p(2rZmEvR8&T46$0IEQUsEY4Ml}oy|MOwX;eUb7oGH z8pXQ$FqxuE*AXhJ$f!=@GYBBIzdvPzh{C{Mz~E z@#N(ebv(>})ZX+^k9jMWZ}Q(sc5)6gN6=nBl?9Gc&B#)>H8QoTauG%gxc-H_-DFOI zSCC)-5H+*$MbP*VX%mm2@ZxtQ2aQsVSN@B8Zlw zY9@0`_Zrj69)>9bY)Dz0GsQ6-k`g!421x~Td(nL@nWJ7M)@Y8ZWWUqliRz9yA#&yo z6iIsgt_%}S!DAucN!Y6&HGBlH9O<4s0!5g=Q)SP!J)QcRoW0mk&&aox>rb-P^|z?m zkakQie^pV%u!qkGU8T22nlwt{twDkrNeB%^F~rG{2eLum(<2{{_(pSsBIZu&iff#5 z#z(3m@5jtE9xCn@PS&-&yG~RWuEyCyHcs-4wi*4AynsO9O+(L{UB3qD zkS8B+m~Y;D^4a5d$FxbZJM2NP73NBbWH~1di zV%wLF&D7sjWTlgANWGU?`?}LgTrZZBib+jSkUC#DdW+^h?kbXemkMfAz0+c-)40{Ja_i7&Yq z2Y$VLMV-s&S8_#cBp6IAJfw}Sn>zMkCU!yf!1dFJRq4tR9>6HHxorF z=6pzAQj+}psy@M?&D0wLNgT*d&#F>zc5+$WQHYVZPXD#znS(i7-{v*E{q*yHxB)}w z;}2t8di9+Ya*96bx2mr_<{{*)!`rMf8eziPbDP;{WUJXLccNwadWj-li!*dr+Blxh^-AB~rS3Id|+E=q=k6OP1gvds2Qy6srvFblhHP z-4!hh5mdB{Vw5uY_EuyJrus~dX;&q;q||QD$TvGj>XJu-Lm!4G3%eRof#Jx?Xs?g( z>q|Kb7tem2jfkp1Nh3*gg9^b2@Xa+_UsuYmgoTSuuNd|fIti*=@v zY1fm7Z;R{E+b@DgyCqx1b*E=j-pG{k5`omyf5pstv=l>6B=^Gj80KMMqY~V~8PlQL z47*Yh`Fcy*lb3KML(UtorL9~sn`fid$2-eQFmj<8a zOzzC4`Ff1|qqZB!CopY_TTMZ}Xa_ zd`^5$7amGlbkvI34)a%uSsy+c*Mmz*0j?IFgios`kbl{^a+B?q%^HJrn|QEo^Wl&f zk(1&vds)HUkR$R$!WYRRm3-d`} z`1<_O%`@+)j;xW*va|E3d**wbmE|QIPx1PQnz_qygI%_oC{_q=HXl=`PAJ|FvZ6dJ z{C?hP*DWN|b5{8y(+ADyl~FMrh5*9$F4ycb@#wYDet7~I7feY{Qt)wM8HC|x%+vy$ zNq5FN3*NR{pfjVt-BBw4Tdx3KXt>x+WvxA7|MZRf6Di=py zQ+h-}5_aW~%Tk5rQMu261&FI&ONTL%*oy2hAiJXt8*FfLP^qQ75+0DQ@UaYV#boj; z@v-Q6qJtoEwZXk_dtWK+;o?mAL0!qf2g@%UM;Xr3__qDimAg{yO$ho(!xTmf$LvBN zRgqM(M9_nDQtF_gUV$yjdC0&PJz}t=lVS9S&spbJ(^*{8%ZJm?9&a65I%=7mo;Z^_RwQNv$?4_K}2P8j8I0YbMSL@2L6hxXt)o# zKrmnQJZ$D5p39)xp;*=gcE{lFKvqweiHiY#&n#tF>8dWmvsmOMJmU7BN`d|8+?Uez z9KbDDG|vs;@kI1F78C<)@>zust6FC(4s)czx~1Lo3SfTrpp5m(?w%UnkBiW4>Jl#+ z(CC;WZ&BP2gGNWJz{MMX_H7J^m$9m=gZSfu^$Tn3_~Ub~(p{pYBHH!Ik@I0{OZF0N zTChk*iXQ#{bveSZ>sX^jVdCr%*}+T__@Qee0PDsSl84z|Q+RqA3{&(5rYYf6nRzB& z=f&vWMUO4fk}mu~iCeG}#N)U&1&#U~Pbh7mY^${@e-DArSRC?Y0mQclfrou$j=m3n_;m=XL;fI9pEk8jC& zK_vZMemNAzLsKwUJ zPDpJ4t()@;?JkVffec;fxP)=|Nuu+2+hNDNgp65wHHTlQo1LZ$UO5B>(aKy@bX5;d z-vg08<2K!^q}~;Af5zN`=!e8&OzQ(@n$tz<=$Q)Qp7i>Kq5sG*LrV<=aa83;)9UyGOJikF+{ z?rF15Wvi|tW~?rTpWLOIvgj$(~AZ$(LPqBEn;g#Gf>Pje)} zo1LE3=H}I2oePv<$Zow9V>G0+u1dA5Aeg1xdPQk;P#=i$$5?X_;?HNV10+R5hsMk~ z#~XO^TVJE=TTvFG8rN_?yq~JPjAlQ zOS<>q>2gVqE@d14<3}Jp-GOh4caN1mNS~Ex1o6(#6ljNkg{GT&sf-Jm9{8;Wd~_oZ z4&`kErIuo)N;RPNp$hD#;#F;K$$e7JmRuLdR+j8pTh%|H=Te7%E7Pljc{IK7$t|LXnfoqb^CSPPJ?M4U?V>45InR1g)Emlc3;CBA zugqtNeNoK>4BX;RM}|W1_1X^@j2gSsK}H3rR#h5vz}$Rh<*-}FiA^~n!vN^G+fp0Z zUPR3tmllo*mY3H|OL-}EuXGU0^WxKc9?AWuM-uJid(QVutDn{kllbc_t2RHIX{gByNXw z6ljb&q_Fy0*V|Lu&?tP~RLogxg?^?LofJ zX+|0|oI59?Pk}cP;Bd~YL>)4Y6_60CSpJs1lsS}cD`M_!b&W1HkDcryq|BLpRS2^` zc&Wup!FJ>}^wwDD6mfWUhMJU-)IX&g<8#_e^^}|J$;|1oey8w6>P^LEex-~kTXzpq zJLXTP+4`--Z1Xv0>S#!FYE$;ydLap(`${L*=ReQWKr7+mHd`jeO4n>xL6&+)d%_J| z@PpwpI4EYAY)rcX4K6u5amCu?`|ZGumVJ5sE+ssvzw29`(3$qpS>oY9PsWdWlra3q z<|Vr}T&aB{@3Zeux>^th|DxdQR)?3XustnF^bBgcP)K_25n8g*xn+CfA9@F<$+nU6 zf+U*5ki8!Q;rSiAXsGFte{5Iv&otalpq>}0K;# zo!>6VapM>-anNs>IP>9yYxx@F52zmvwbLz;iYO8k`S&k?42mzpMj1XxDe}9UknT(l z)^oqSVrpb3iC~k8XsUmuC)?{XJj@dITUsQMN9-}5QKoJ7o@*5id{;&SQM4lqJx|3g zO}n#GRD#Q1u4HaSiVD^M$XLXP;w3=~P^@%9fIw{!XDchlMs#U5i5!sN`Ql%ClRSIDTL5#8?vO}U$K}4WR1zCxT zh|GWxHiV6=1SEu=Kp24pNFZddH^29~-uFMqb6qF*bDnd5&*!7iCZJcBC6>mr`_Gdi zzm$FU48L-NU34ZDt#LW&+?{|CoyCkN$4fakoJTt=0>KW+o)JtcXdLL7=$tTSVXEU# zH_5f5T|@pprh3aq|3=|tQlPQqH~6L`W|F5P8UIxBNgQFWb}7+dd#Lt~K6q*( z`TSw&YOF4AoL$fV*W!TZQKr`KAdZWDb#)w93x?r8ziuan{T3UPdJ;e} z_$f798!}aDR>MFol;y ziFK73J?dB|go|l{$|iypL0{eg|BX|L6u9O-P^0R#Va?MZQMC;m|c@ciOX&~gd5LtLZh zudo>T!Y$BN`9)wwkSZo)ezQ)QmlZu2i(VhVlt}fu4?Iw2puYp+sfQ8kqa)bVIe`l> z=xd4GK&$hxP|zLiUG4YTimp5F*IdZ~EWHGSb^@zXbKbOL54MnaU+v82{d~iad=KhI zUyiz%bnU7M6+2z}MedeLvG|$m4xwWpyGjbHv^b_0BN=&M(J(xI*n#1MAmS7k*>sz0 zR)Jv`oZBib3}4M(_If%+{PbEglpHr0#-D#nG>4Y@pI}(3bZeNXj>?k9^VC z!KJr73D?U1+ErFvCnZL9IkdN>e_GyT|6~!)E%MS>w664`(QFCAxdS%4L4O-FQ{!ii zBz_66X!~t;%1*_>-1&7H!MJo8Fyd42vUfT4e&kQ?Evk8J|LT|jB2Uk#l(4Ifb#VmABBu9Tjv%W4F*;N_V?tB2$ zqfx-5P1lB2$iRbf__$?Lf{#0TluL#It8oa+ktZ}nwt3QPfX ztkms(xd!gYuOr6%!SiH@-_My$9W5~O+)r&-*^;jCJ^aaQwxP1&d_2K%cpR@K4Q60f z`S)!0p|jy?8B0mk-?ht$&+Q2eFG>rl%XZn8;VrSfV}TZIWDmD2vy%z`!TbNRl_(W? z@96-M;=;VQ8|ee(Pi(;hvvUa{;~cB}*T>nglP^2GXBtrtwC%nMP9HRf`eJP|>pbp{ zvSKAm!HE0b2o6O22F=Fs=20J4$CM=}Uq~Z<0Is|)JzPo(lN#3dOaQT|QdTjN^DyMr z>I&iIe;M$EFX5p}d4k=^RY56e>a*VbdejZ87GV5N1rTeGjY`q9E-{gtW`PE{N;*NxlPO2+l~Uj-r(lr^`CfDEQB_qu+qLBf~LSX-;* zHwi&-9Av5ev0g5IZ-b2QJH67brEzECpNg=})r0_vQSm^VP*XwW(oPp{-(#UV1ik~( zdGrGOhZH>tOzLyWE+}i>?Ya^N%bp~s-X-%(YZ2@Akxhf@h8FA%Wo=X1sD#wu0r11f4X$`?(5ooIf{!h|he$Fi!<*g_>s0j@rYGe$`x-_&bQ zhQFj;6=mmptLmOI#y;JU!qlRUg2L5=_`UEMrvOo_U^Jj>0%HXw6d`u$`@td~!&=X( z5O%=?znV~~Eh+8+JR^*Z){${FyrWnuw#ZXB?pd1##nn4&wNGa^BBz`sgHyw9@uOHv zP2;IBqt43hLk{(Blu6am6^~FT@``t^P><47^B)?JtYRTF8bNm?sae3|FX0{yPMqQd zu>0V5_n!2e4sDWL{|9o}ou)4Bm1OsG^kmCNvwB}J1?s*{LZECC_u>L2yb>WK4bOup z;$ZIo>d$H)dcq1rIOrx*M6wjw;%X=sO@Wh$bziLkxb(W9^amAP&}akk8uq4W5AH4t&NkKnhZMu4PzS z>zZ5?M=*>xiD(|;8MqOBC&5PXj~C2=9RKdQ_Ln79;+e~+?;9Rtk7i|-!6H5cPnuCK zc0^S;E?+E-=|FC(p7RanE}ZqcrCT9=rY{|kDm@mq%-{gH%s~7jU8z0%fZvM@*|dB* zh2D+5Mo(d_J+IQ7El4?YV)+J2#hcE2=ODHWZvZAD1aF5^Ggc}B>!uF)1~p(e4%(QV8tR1q8#MPYxuyEe1mY%pze^7(*6{)?A!qI;%v{G9kWs;LC@HF!uMd|9|Es3kp|| z4+fy!YyuF;`QL@;6y+-eI!c?O!B9SNt7BX?PNcPuDBv9QoZ5-lfolxtrE2p-Con6T zxXsQjKy76*j^YepQ;|E0?fqn3PR zWcEju3W3Xj^rk3Xaet<(iDVi8v`Q-*;9b4!>kfJjTDXbiuNFzbm6!LjP-)@7Q_DUT zWF@zgh3Y>Z3^D}VH+;D>jY4u&p6hVWyrb~bGldre$7we(hP97km!b)zP%Uw- z>=lfA(@>bMxAQ}&u|Ni8}IX351j){Wxf9^zsGCO-T01Zey^Sm z2c*EVQ8=4lkUD8$V?p%nbzi>FO^`Lk;0s{-Z3xa!FaK(6T6)}2eHs!^Rr2NrQtgwa z2Rb&U4ln@h=8e-c_wi&0Jp|j|!v(poLGXHp@KA+bWUS?YYBmN$4nNg}2|5h47rSaT zaG}*$XR!$l&=BA4)8=k8oe`MwWA-T~uhNuKw_pioPgi#>OhzM@L37B-@PlplSH(k) zWEf*U%-R1zg?>xR+6kSGG#n9$Z?ypmr?W{($N$tyHN*GW*?J7{R3um;&T{pK+;S4- zt?SVa`BFc2*$3^ta~^ujX1RYh0Y$CdO_T3WppW&0W~W7bpf6+y zD&IoHGI0~X&67Nla$F<3{;2lk)113?`3};=UL-D zuIFu?LW{bqp#lA^U8>8efOlQT15PsQXYzU)!nOWBmu;$}9F*!DTx)?hd;GIGCi-ix zj-JHDduOE}K-daUWfuCO6mSd(hb`gVYLVbSBZKSOkrBtiv;lWCt;&R8wNl4G^a%I3a{bHeQXf{Z$`oNzvFv2x*7RV??*1#2QuH_o!*?AQNg zPnWIw-A1oVQl+Ym!iiu5tBpV);k*@fLDkV|cjPaIc^linmUE4huIoM;yikqAF$*7F zZOExgJQz}VvJrCMshnw(_$}4ilJTd{K(|ly(iUpzz}%^tpj+By9iO`hL2d0%%iM!1 zlq>nqiWSwHZCGVD&;Pq!vGAR_5kFs_=HTSR?JvLBz#TuFq7|2$KOL)6kiS;t6vb}; zU~5-4nfmm1!vNt1dL*YHlArp)Db>XAF-gPj%=Du&P6{G;3^L4))!CY$njVF$^mw@0 zB7|@C+wn!le_oQxv1f*6ewpAC5`VmVLR0)`hrHuz1v`$&7oLO8>Gpnpn8Fb_Dsg(7 z*8n>v$Qmrj=k8dpFTav>z3|Oc*_+q*szraPD#tPMMXVnfzDtV8#e)aJAHB&4+wzIK ze~KT4RtD0cNVA}gu6(vBggOGY&I6iov2jI(U3UU}UT}q7aj1x`paSE1(nRIM~SGrH!DR2 z%n$1Xts{SVpA0%L-;jac3@YEQm7VC61F+8_>#v66V?L)Y>d%nn!RVg&^5|l%)FCCe_^=z{f3Wz?@0*V}WG3)PrAA#zTP1N}vo#?aVD!=U`|Xk^ zHtDc(+6U=50?%ir}T{tfPxXPV+E5^o_q((3B z(spu%v1mmHfmV1sX@58}Q_ee_l0t{TBPMX*gg&MffLQ^Ts!TFa$V!pZUyc=dwZ@VLFu z+-dW8whRODKBo0ugqL;MQJl=!wg5XXK&s&0I&H_p zR;HLIIOYsFvHrktTuw~jc?`sA`!j6sx5Zs4ecJ$!ih9L1b!dy^%-2Nxouz3#c?+2& zYP|5Sat1wH7Qn%@m~K(Cp#EFkDoVXM7vLn@(phKtZsTH4Y;SB*)S0HWdT^>S9z=#^ zpa7awdXEYOT^G4kNO8Zs{7i>&Yi#Qz-Rjz*OVHl|T<@9Wz`Nr_+LuE%BwF|RwadV=>tn7k{LmIzkL82r@2eoW9zbc(?vvI);$z{J z24>V`o37HSl@Q?74$$JIMQl`L!l(W+JK6;Qf1R5Of|ZPPFRoms*Ty)^*XD+9i!>En zp8p0Z$MtYO%f9cVO=Ue@>1Qe`EzMtUO?@BG{G@;M?U|zvTgqUwU=^^B?dAUIwS5|P z*~RyJe>rMVdk*2at3jR&STuE@xmZH-v+}~$6k#{<+-B`yiK{Qy9V&xIJ(#I)64}>A+2}Y?(8L+1Z?xA^j*fa}pMDY; zb1>LJXL|^|%yg84*}lhXP+AGku?a~OF#d6?MAh2n zf>`3Ux+hQ#xG&>S;Zz)%fRd%Dchnp_EcLJIBz2MEmt)DN(D%LRlb!|ejmG;+mu`e| zN5B$lETwOG^9N7WT_x4rADs>5H1zteVXSb_-k9%0sww@n>|Xs0O2Kvwzdyvgva?~r z+VE-Pf^cQJhV=(r91E11I%1msJOP#KcXLcPu&$4s`aU>m<#_T(@7k!T1-0F9YTm#F zeOFU%UK<*l@hrJ-{%uQrw(JA|s?b5o_riaPdf3zaCj3-Hv|+nlal#$s%i3`7gl`y* z9zc|f&U<;Nne)*Ytj4eI3e&!HF05bS2TZ!B73xRa{ZP0tH7dYUVxuehku%@)_gZ=W zgFLoNXa4Loc$5a;ruilbOOe*C4y|O#b}Rw6pY@Zq?yjbSBP$gsUUHDADnM9=Aphce zUMxnlbC|M^CkUfM83TQzZu0K_)M?No*;xqP`lJ@X)-N~|;}s$Zv1#m4H!_P|ESp(s zRZr@xK<=uCc)8;CR3F0qSH6oMbn-D=rgl}k)275L%KVIbd%E$$FT^M4_=cY_bC+I9 zvcr`l-9_a}P8Bg!kPw)L2wo4!+PnU~oi&+4NlyqayhCkdMLdDM%2q)GDx++xUphYBI~AB!XI5V&UiNZvTk53 zL0_laOPI`&H}*-gO{F)&fC8{>Jl3yk7;VWx4QdOEn}fHE^_m|<8*RF8c8;)*Li&p2 zA$|$zWe`?{ziqm+=lMPHY5R3#ln+BNSfuvG#s(PR85}U_RS<#NgCyyfZkK3#KR0K1 ztZsTTZl7DFX90J%&`UjAVvmz&m1SH*$C=N@p+3VSA{ylusL3cjMNdA?uVXv=c5LaMh z!cQzQmVV}l%lasjkBW{hY0j5Q3V^nYtw618O`u~^am)s0KPQ?KbHC~g?6IQgAYYHJ z63d2?sScuVAhhRTw5@2#*v_ZG^pthq9U8nhN9C@eXc_1#QyO7NrBz8pFedc-@BON; z%SO|2_dmNoDg%~e7>)FTD1g7VS$l^EK9sg88tts;k%hhK`$cjZ#V}Y`_yew2ZD2wQ=fR$;` z)?hjYkzOH=*t_LuA3SvWM{o{*wvfvdZ{nJ^j)e2RFHN*^$nv{bt|2aqFEVLfbDw>( z=el;K12*m=Rf6-JI>dYE_QQyWp*m)FvkzF@PuACf>Hva9YN64io4E6ToJ`kxHhj$g zMyBt|W*(qOT?J^iWV!P0KO4L8K@GcJ)Y2C2vYtE+6Y8jM=k7OD3bcD2?r58onDUdx z)cVuXv;4{Q{FXb#pT2a2r>9!)1a&#XLmQR7;Hf1s)R{UwOI?UbJnRkIV)bi)l$G*I z#ATsTW!Yj)GXKW3?p8VaKk?ZA;Bw-?BO?z~&hG(m`yy@!NL0kQ3@jw-zQAH5eLiTK5>=>@10H!ojXu(|gT)QZeLqQ$;#fzmOoIgDGFHli*4WI%JEo)(O#Zd>sq@FzbhEe1PWmu9%6mhtp+4GYCA(g#5^Nry`1fWS%jrk4p>V4>`hH@XMOL)5ka1$3_7#D$V85;$Fk}acgIUc%$vnCdiwJDhGRUx(9fgK)YkkXQ4^W*M`+@U zjQmk=WZn75rEbGH0ah?QFd;bcC z&@*h*1r~taa0Mc+&_cF{HgW$0`mk|9KQ8HOxWZ3Yrg#V@Pa5Cup__QfT31W>Os=A6 ze&3dM9PiZY)Nf7xy-ZWZ?L~{Z{c^Ukey+tT-u!S|v`t$i@P}^?5;jofyJk;XsZ!W^cXasHW|fcC;{`=$lC5mJV*p%)-FFNgH$HR->}%m1<&_%j=vh5Y z-4*v-zs62eN{R5X!IuHAzX{b2G1a?)F0A_xgp6t&Rr&8tOT%y@vahJXlN1(CFvLE( zuB#P<0$@jVr$?|zxn6B>vhG6v@Nh58ym|3~ZevGsdy!^=r`2#-Y%d(?!oP%D8Bx|C zBI@f#=3lmZN{LUIR}lBhYTbrKAB+bAY98#O@`S9c+=Qycm!{fgRy|CTg+kCfj??22 zs;OJfyeD5Sgn-^>^$%_U4wrj(7TgK3YJAX6$k+8s`^9}bG zy;sNXrD<$U(hky>>Q zv6_7FZAmi}d55|j{-igo2PD;`L6V+V0U~0MmKl0(q8ap^COx3ZWzr#vIL&3O?sg9| zDRHEtW8Y=uKEKS5oP-tLS=kxfz8Y0NHQo5iSg{YmjyI2O=(zpe%7qHeVVA7c8-dtS zCun8&W>;XB*$&@Fztm#Q?B1eH)8&ke>Y?WBpxYrmSSOpsL_jJSC2I43+D9w=zGS?;4jVTds7JZB7Vd z*_tlUDF?!H6Oo*?AV6;zqY5b{(mn?raxv-G8_dlOh`OVp4SWiN0;Q|4$3|}T9tH{P z!VL!-FTe?Um)}`p-JB%{cNAmst?aIUEA+LvKi_6Nk{V)tyAI981le%B(%+U;Eq5JD zYNTUYqJhTO3${a+mgvpTi}1jjrVd%xw`811{s?J=P4U%V`;Z&ChzBZSjCWVG zG(q^LXiQ3x1|Dt}Y`lRIfD<$2Gi_q*{i27YU1*B~%o)fDws=9%s2?QSKPe20 z4l1bB!jm~PK+H&_wJzAfy|Z>W5wCdFY>kf${zqF-0a^uYacu$~RArW>-WF_~OrjYDTj#Zt$Qr(VG-SN!M5%=^`MHk=y6+ zx#T2EF5H`)37-=3#_T ziz^m!l*;e!y^FV}+I+M)Z4v1I=4ij9;RtF(&LmAsJ0H^v4oPLUnM6F8Zb2_P=3-kt z9-DFh##tvA{W&FmGc2C=O)q%sep$_LiC!pYA&g|v^b~jct90JalGA_r>?v3iDe{!< zjh|vqG!E=DhN_22`hISrjS6%*U~StCQ%)?1%1ztKUq`oG+73}&&ii_Iv<(>}nY{8S z-p!q55HRxJvue^>-f^^N%azrmXrJ%H2f%|DrFUf2XwN9Ab}0_-PP-IaYGrDcQfPVu zrxm!Nn#r}x9f6proeG-vq3asdE%-5}{4*TDPgxL4=|l@hIO=3E`H?XgXG zG=a8c62uj!N#$AX*nZ#8iVM+A;8szdg~O&n_*O+AVLRTRGn{?yq5x6N&o%!dMD+0l zqo!mY-HnLx)38X~z!})`QanS}I+z94oA)+*4pR`r4s}|AYy<}t2LL@^o-3Ac5NZzS z5w6bgO|4_Oh`Yt15hX~bx?bSkMldIQo0Wq;Z($YSwf5=$a-e4WLq;4c+g|bp0!=}t zG|D0N|q_jh?MF(}qky~PZ3zVeo2ZITvS#hI=X7;`B3ioX|! zB~T)x7=+XulS{QaHiIywP%8T5Huq7Lw^rSg5|Ud3SgtjH~d4#|3P=G zzODnBkiPwD^d$2U%KdDF{Gk8?zN{y~n%YF-*@pO;unw##r7%5lF*&4Y$ENiDW`1M* zyy(nOS(>3QJ|ub*0^(knH5U1W)n$y>Z$*v~O4V$yQSc zAKy3sG?#W$WGv|`+ji$pZuW~$W*1nw={Ok})XAxKMah@e?6_wUFBeEaII9y3uJ;Sh z^FkE<&TAq5>5H6A>w&&v=I?C@qHvb69!zxM^{$r0U$(nNs{5?wsJky^OyNa^c&z4` z*B|;rH$6hYECtPkwWJWcpiZ5n0MC{6;0&j9nSq*yw~+o#_zV}lJsLc?-1;ee(_?l< z_IW(~-YK9bd)=%whKnKXTGiraYXpaX1)}5C;%`)pQCg2L6cJ1rTD_l#gK_@`w_#(K z)U_)44TBpX#}6W7N3yH*n;G2SZON&u78u@_Y}W-1yQ;P0TA)SJp$;dgjZd3a3d%~| z@crpGt=bl1Cu45m-TimF1~2JPRlXVMxS06Mue$$_1yGf{_fRY4Bz)%u=olhl24fIS zZAX3{)!?&^wF%{ipGFW_y0HYW3-7Z!R|W!+V;}%O?SM-nsTRr7 z!>*Ua(BJY|^=d(3L>(5Comxyc53DiuWl$QXYRef)*Yyj2X-4f#0JkQpQHk5lZ;&tX zX!$x40j=P75R>>u^aDk~QkDPB0kHduS9D&-F=XL~=DGrn z2VD%lh@;mRyZKtbx{}m1;TKlnPHPlK+v+AfQ*7-iK<;mY{s@n}{zl?ekc2HOGON1V z;7Xt-S6psJZcemRO*-qqML{l8l2usdIa2#PLz?uPB$t(!{xhs-#Mn6zEGt2-&yITh z-YQtY99{wVmo3(`p{Q_fHip1n;fS)cF|WLBN@*bB=CSb+4WV5r2Y8hj7dknG z(Tfx5QgE7WH9rE9%8~Rrsa)w2QbKPlkiA~`rQvjCvl@>@uGJIOF}|q}lkiqLzMN6z zjw!sUp;Y|av>AmfW3p2E14 zw#>6>b^UB(N(n9Yz#iiWp#8U*W26UqPfSE$ymY)5K?G51tVf?r>CpftqJ4m};!??K zNtjQ?#*l`G-IfFOhRoFmPyDTo$3?SkRLm-1f_~KlKzt+>JeO7)sAsfPjFj_%E(@D) zP^^F(o2hPR9FBSkyKEj+v)2*hvy1QjViBe~a9A~}jSyVN84X#M(8O!vI>fKbMhl9z zH$}-1E%~lP<8Qu=>=%vg&mAb$2;#dp^WVk+=NAVL%|ZWwR~KGvF2fzYFH5^d@D7Hy zHQ53gpKlr;m@P(FT|!HVt|6Qsz~=A_%1}$WzehxT=&Pi*n6gb2*Q;%T*DOE#=o9n; zg{=6bMY+*YQ>3a;Siv+lmns7JU6jNTMX7K~4x6n5it1q5)w0EeWte@WYQj0vPvi&W zdm}m5Y*LvS15TRA-c1t*Yaux4-#+l5^Kj+4Ro}$kiiaGq*XZF3DsKTIPguX}|eW%#oAd&(?b_ExY&OP0t-( zmpq10A%yz1K1#%q^7LN(gV0Ts8H^G?C>a=(^{kg&6H zs3V87e(Rq&pW)I6ZL$!L6Qvi zvX7cfiRsSK2{v;ZXr%((m%@lcpw8^9pM>NHmSQp&m#`S@x&w1_P$v`rB=C4Ts15Tb zIE+!VR_SB+2lvBaa-JiTYHG2w^wFZC()4iMb&D-|)ixv~7>KU{p?wgHAhxjc7rJo+VucnrIK>y+S63YmmeX z>4lQwTJB<|ByEs}{B8>VrpJO{9=!KQC}Vd-t%I5B_la`#=p27pu(gJJ475SU^|+oQ z95NFB4^V|otc3O)MsCMGBlxL~7H4^iyKg74(;X!N8SYi}13LZ8xmkzN-8$d|U5n&( z06$&7YZsiJVl_uBy&v>a(`MEKR_G{&+7;xNng_^1$jDcO{buh?(8eP}eH}((PW-sY z@00NnEHuu)GA&P ztUx912LwG26FD2=<+!)hlSS^odJm^71a}A~fub@>$LtIc`mGJh1MMn!&RIX}za@e4 zSQ8TYO>gjb(r-@W9mb8P3F z^Di9jVqMXmwp~)qimyf7qyc=nGS4ofD!xD2PZ>W0jD%`K<^IT;Fu61C>MIvqj(e4d`sjk*&NuTN(l$L)xVq>4 zRh?Fd-1)teW7M7k;0~TJtq$FU8-F*Wr_p*igj)3wC5Yx-x}X9K^QCjtB*c%%fbHI% z^RaV#CM3aZm}k8UQeY!_pcA~S7^a*-^7mrf;x3<7X*&C$+&ykb9}z6;pGz#Okl(BY zo5YNQ;1By!vyUzqR3^RDrUqA6kHXG7YnC&lU00dMRV14#FEos!m~jI(QEp{hxdK~b zJYJip8jD7dfrQ}oJDM?w<7vpY`x$Umm8u;P5h3>u-n}Jn2Za;PRm73i#1BQy&?S%^ zK43)hzf2FNnU!WrsaG|P9ySRebb|>y-ZMa%m_1k52iy2IK!SHQqG>gh#zA%2DIj$z zS8w}0blIg?&EI>`eYU9pusrWnM$hl_jY~3oyHN*$eJSg^6_KLu9G59^$)h^yiEgWI ztuxIMb&^DS$__rgFH_T-;9JA6g}~sLPrzAa{@)W7TFuXwR2}C#W`8_p=;IJ)XgLo0 zRjXlY+dzMqDj8rfIDRz@87<(e+2U&u(x0cGkjf7nbt`_ zF>ayu>)x6I8SRy-bMGM%wN3KMOvbaAQGmT%P^j<`z+o6w2&tIpU*5`U)Jzm-~g3hbKq?A+m_lMp8DBD=b%p0tc_M8LqU(MGSVH% zPB?dmXq)y#kK|a*@>Rs_j0S3-SC!wBn;1dgPkmbuhVAL@EN=8&N(_+VOZFo-w@zmy zn@Y+m-05@OwfqgePgbgQ{wgy{kCtZWcSX zAw8}>Q#$@PKMkhLG!@-RjFhhZXXBQXQT%hEHbkr_kvd!utX-!(l+BTWwR@Ass102j z#EK5u)4T6IC$wvvJ^euH02k+3*VBpQPemHJaq ze(8^q5p%+gI9#x9;b;MBP5%N=P!h0Gss=ZMa$8w@x{lTIxXk_4@>J=!A$YH%x)J(J zuZ~E*o2kAg!~dJPvi!?{pj4B zWZpf5YBuG$_T5N)ogJn?xSD2UzV|>%tko+dt`&d`1`Rv*PE)SVt)g!ZS-oqg ze|W^vsymD<95QEqM#s}Ha6jw5jlXy&Ks=2jM4^Ve-_+qTk?g|7lBsV~-iyWiQm?~1 zkpEa`u6iat&%(-I`5;L~cH&qsHY+~#bsvt4A2;8*FjwTVN|P1NhVPtu;0SL=t}^3P zM0o|Y0jq8Pr1f$?k*TAdl$nm!0uNjqL`=InZXRUf_Sam?$C&prEM@_d%za-4zwLGE zqZiZ@Sl6jy{}WFA^`@UNF_qG1YCH`9{)$MK_HHMXa_}wZMIpQ&^{gm~Gd_@?C@vGN z6WT2ehU+FS$S3pfwu}M7@sd!xto1vM8X`e7>YpU?9GN3d!No7@vRcKJ#d+A1kfciu z;@bfso)V6g%4+9gwQF161ZwZ0tU-_^Ty3-v&n?tMUqKhKztjT5m^~SXX#*I2L3v)= zuWN;63}K1v{mw{n$M#7dnDhP>ce|}^N<}LtrZ`C$ZRTu|9Oss z;d>VAn$X?c;A~?FD4lt~l%H1z86b&9bc&Kjn+pHi#HUNfZWc|wOe8OzmdtqN7D_Ve zn9*CIb}!QjzVMmeEIt`j?E-(bMb*=ALu$njM`5}MRx?mCayLj0qU$)A;?PRRAa;}# z`aK#qyKoj;Jc-idi>F?}p6Ajj=5R{;MWn*nj(v+6{}ntdsMIy*l7FptUU(Vp^S>k` zOrie*O4Hq0<%n~5NEEk5&Dp=ATIAhr50&kOIrVz9O4kaTdGvR&mgei<4g;ILm#=wa zSKeYp3ni4g*97VG-fy~K_W7{P#q~EDzeRc?m($A9d>xscUa;eoCe~Q;A=#prQ2(Ul z;w=4J9o8$?m*{4DH2p$|&kEG3{8Csuji%#h*Kpc`+?6*wG@*wj#_AfjCqn_G-YF8m z(d&h|4&mqbUaS>8U&FK+Tzsr#yc+SyH6tNs>lAC!-Z}L3n>SfWY`m!_jTp^!6h9Q& zgR(rQ*XiuMovOt(QECJ6eb1qe=(GapGxRg($YQc+pc&gLIMei4_Mh;n?lB*d@!?8$ z$wBv7yt?J&uMi{NtOt_PVOQh3_cjt2(TnmSd0ND0CREw=LpG#$2&boF4=2zA76z=^ zKHKp6&+sZ6f-LLsDQa+BbC);Yor-^0G^hqMH+>Ruy#*RzF^dwnO5pcgjMYgGosB&$Y7kqv4icBVBzSflwaF zkmzyH)`;E3j?2{g4W$kk9o_!*I4D!eRH>cq3sa+&f~Gg#BYyeK1*46Ik2D4CB$gm~ zPLk)#hf}M9OR)(xl)g{Wp<1<$C{lUg5_8>Hx%IyP4e;_377cCS?lJ53b5ktZmgqof zCE@kb?OEXzgzVF8DU5$VX(w&36iE+v{`kxtEEd{?GR;`0w&ad+ns*YOh6oFrE(s?` z9By73;@xm=(mfU)71kSF;ntoQK#UBCs(lG74u5iTa8!kSy|0!qq%1ayS%(pI4YM1? ziw+PG?e3&g(&nx#oVOICkOd=JzJW1#WR zioSn2DgcuaDE-$NOS)6FnX@e4~=@(!JMS zdI6V*F5D+i*EQfjtMLSfJJa5-jBl|Q2;|`;M9+lx_~rPXLTU#$qt(ac#&=K;S@wV!7qyBEY3tdo@D zbmgIzb-gf;1gzROs6L#|^rU6E?F16sSZ|uf(PVE}QN&-h20Z$BaWA4ZIs$V&6QdjRphLhbzJfm9QV z@uhvk?~?4dBTgRBMRf&+3?6jsFb)e683Ce*uk?Y|L-)r^wJ_S!N@R~e{Bx%Tq0p-; zin->X($v-f5asoJJ1_^}Y>Zn{dpALrbq!dzLR(@PG_{d(L}#SCIJFAQ%=M+7ERd8z z_))Qvo3ChguM??rin?0Z>K9fx$?J0dw+Vn9Db+`?k#?RCB_*9f*G)sbFE1ch^M^Gm z0cIQ5^gA;$q`MO!$BwB`{@@v%x2GyQH0*~fD@p&(8`99<5^k!PV^=1x%7l(7~^Jz7aqpx1^cg*m{o`y@bYQ zKw!`97ANf&Y-C%wQBrP1>&3+REMjs?opFb{6Q>p)X%&EYZyRv&vpStXWjABF07G$5 zPHg{(6GJNqK))azYne=_r?=@lFNmp-LelBgyh#5|NY&}de~OqVMOnKT1!7_g%Jk{H z7lkumaQ2?~?7B z^x4O5(<^2w*&n7Ewd@?X{58ZUdv+V!R6)v*=&?@1E>b52B;2wt?kA& zgCVxw4yGJC})PguvnhQ)6A4ez8js;kwqKml)tWS2mA zu8K`{QqVuZ4P1SUtSb7$?HiQQ4Z+f9Mu;{cOCx};7n1^(ncyPyXqbk=o4jv?nQJj^ zNFws>zWJ4-sC>Q7Id;Fu!m{T8S^}*WFN+=Umu9wmssmN@qCM;x^t+WI^|cs%S{5)z zGSLX*<9{!EPCQn*ox!g7r*l61foz*YqLh)(HLH!VF9Ej> zcgD?$>yQbLH>G}h0AV@OpZG?_w@|2h4*%g3Q&I4pgNql{V22B;^d$mCr73lfTg?W~ zIR|6b54t#J*hmJwY14PwdkFb!)(lGERNU1&S?32&*D>pE+9j$r*M7q>KEK7$LjSYV zXp|1kDkAu)NIt=FOzWPISI&`F>bqZmXI6&`a>C<5eSo`N0rj%;IK_A2pJbgG%Nc!_ zu@H+#Tg~s?Dq+X8Zf(I#%@Iq_!N)c$4>(AaY?nUXsg1ld{Ipec3r=0pcTj=-L;O!F z7p7L@tT;TBRj+?>=*l-(Z`mMS$FA>_lH^ODpoZCR3;;T6`$m20I| zuJBG}Nr{S=$O{TNS7zR7&deKBmS!q=iM)g6EmI*=!8=|-K}1FFw=etQ`v>sLdAx!5 zd0n2bSA4l0Dc{)-k$jHtMcJh$#o})5qjCyvm3}W|&I^{W5fLa2^aw@EK)mfL9W7s(!5*K8c!Vp7}oRPH5 z=!&cO`#0|P**6vfT!iIa9xr)ABBYI^lw7n+`8YP~TW22&H*>7%Tt%sO;s`;!b8?G5 z+t@tfLpe}oY@SS7__6GxCNkK%&~?Qq4FAd&(FwGE zAGILc?u%}gjbVr1T9MqR3CV7m!;$U959yk8!0x;}{oUNI!sb_$#!8*$`6}0%nI6Z> zb#sf4>Pg=-Tgr{&{&?4FzzlG2kMv*=i3N6e9Ces4h4ge+D34x94Oxuwek zK#JV9?9BOAGIznM$UD{bt0U)=TUOk9jgb+ByUt(H|43i>vFRgq5i#FPUQ@=dA=Vg!N{@=igC~`4 zKB&7u;fZY2wa_tGQ@>Zby7D>y+4wTL^r~xOJKsoIM;+}<&#xJSv-lH~s%*zF<>{p< z_^x!D*_r|#YCFv)rHDOJsg)aMeI5u=RG~4G%8{=e_*WY~wD-~I-woLvGUNvjh z!$a(eWt$1gusw|YpV*~4?jH)mdiGHotnn`<&wT%9T9uPz?mF`LebJB4T}Y@Z^G#$T zXXftel2L#tP2;Mo*0Ky;&~g-ilUN;p{EpHG)jmiT6f&;V%4YPBy%=^qi#;;)YfjT9 zjhbzC!FBxjA9DqW*+Cy8HlW2R?cp(^*DzDl_3=wB$SjK$o@Z{WLYJV&ou7{rtagFT z0-N)3Rnek@VEAcrhxa$sKABIM>kwX*bZ)pDVrB?W>q+fUzE#bBz{b3UB^D*@#%Hv8 za^87)L}6ImpjZv3fJZsrRq_GYvi9jMK+Tq{Cb?=DD5XA0$22`LvO zir`d$EY6{;N@d{%{NQJx+#aY-{bsUGMJ7^R)*VxD#>Jw?#jRSto42#O*%`M5kZIi>qTi~1x@);6GQE6R1H?(VE4*~)DLy) z6}O^z@cLZ(1RIW*uS6z zCz2d>tD84f%(2;nr+ygPE=P);uinYWNs= z_~Q8p19wIY&^?0!)}?muciM+q`6u>n3RG! zls~n>9Hf?J9xk!lkoo=Ew%8w$ojbpQifSf{x_8VBY~O3nNi<42@%!!z$L)_rjK1LN zH`^)ZcHHS$o^H!|iRBJ`i()VnyGphfHI-uKhD8Ct%)hC^17L8 z^^Z(5c{|uvKc9X<9T5L4xHjo$f179-CJb^=W~Gb`s1M0TzP09cuinM-YAEb_RzXAi zSorIt5p$vUy*8^u4ae#ryTDC4nB_8k_7MCKyF~}er+0!;*Ei?GnAClcw5?pf{uQO3OYOQAv^etKlCd0hdqj3tVh>3 z<*4eHv#05AE#W>5uT@XnRrBf$&CY>ZuJ~RnIBvJ-a~4<*{a~)?qV0%zI;z3ZB>h*< zcq}@Xc0J-s#P3K>d<+NbG5u;fX6SZy)rDgDo|Y&)|cyLlQ>WePBrx|8q!$K^|SAQyJDUv&Kj=Myu0K}1sXL%J<p|Cq=Rz5Z9*sfgmO z2|N(zq^1;L*(o)$!xM2{^x8NY%rn5~$F=7#28jA+M zKE*{sS4QzK5=GE@N84ql>z7J(>1?X0L4D97sSI9XKRx>P)jEQSa!k<};1)x5qD z0}31wl&_7zkE2D>D%^gr+fpMM<*^IIVg0t(WYFkp)Vz}N5*wAN+8k|FPuWE&9463U zdBt{Xuq|pg1>Pb!cO{0VvNN9=mVo~yI6}k|1(^b^eooVJnfumjuD5^MM9(Y!emn$c zK+=?TzX8_#kA|7Dmw?dF!q|$+OiAss*By%s>IUKK5Kb}ENj9Gjd+~}{F!RgORvWng zeJawN%Rt$QMim;yHGacNEBdFmz#hAm);};ynl_vHpz%^T5^`cE7$MBonD z!$ig#x4X9;iH$yca^dIRN0Yo-$Q>SMudPKq5-QFuoe5;>7c3+lEhaRd`lmjGbGkH_ zPv)(}FI<}#zP|F`8a9!vVXVqII+z(9!nAx^Z2k%)%9%g6u|-?Z8g)&*IsGv-=A#kU zovrDt>cei(o}=K7H${#AQizhvJw@}u8ogf_bI}cdd3Asc@y(uY+v8Tq;%_Xff_v@RneszA{DVXPyT4%-csURIN{I^Y;7Ac|CgObFS0#zRE_vsx* z^T+(y2Wg8xWA9=bac?L^j!Y*z$K~d@_s|X0HhPYGid_DwgX?-j-S0wW;zT_J!TzQ+ zM}1l6VLjGY>w`f{TeBs9A3bM=&-((9*OdXJZ_~_4r*BR27I%8BJjmAICtcu@_Xc>& zDnS3IWe^zE`R;ggdDpVzmk{B~L@CT#oC}lYHh&GC7{)Td8$70p2RIhky#9!4zwx;B zXYqfUKwMXo`_cubD4Z#>5_;`Vlo*y{uCGBg1MM++i`zUZHe$R*yI7gv+YVtjOCq`% zXa2-=TW!OeDOcvTZP$kzKaIGi_3l?3jHt4&P+bDa%e?SCorkA^*3=Fih70ArZ(>Qd zKdKu426^$uP6DSj_Y3I8?Gmn2d3`aA zXNjTa3f?xcZzH`v$>xe74hVNFx1CjK&5o}MT6^MggR(}hZriLSKChNleJcfQ)do=F zQUL2is_X3k>F-<5jloE-@zYGn(zg_NfPAh#q}>nBDpL00mF-h)HvbIRLfE_b@9IMS zK3z|$^-ahmoujh1O`DNq;R47-)7j^C6x3u;W*QKI{UC?fYa$Io{)1dl%oCztRE`Rq zB&`X3fbHhR_6xe;N-<)@&&pgcp43y&NI^1x%!qme zs9qcaC3b)Ul6qnjdq)1lVz<2${QN+_$M!VZ1parpbra`;@FV{$v-s6~#yo-;UOC@!H7&5-GiJqqX>ZM;Dp8Y}-zQ0lyv}eE0{9)*Dy`|%V zNa1b4yo~+Tl&F#9rX{PYyJp>(N1b<#$cSv=+y@rF)6V-icbyUcXT+R0+cCFzUv$iW ziCGKJV^Iw(jw?wQyOiUAf{*U@@9!=eN}33lA$DJYrw-fF)J;vaCt8Wob-bV1Tk*H2 z-toS*9)wT0T@(W#f1yb@Te7ADhKKzc!*fQTkA<$G|3TgV$v0Tfqgm>n z>DPAqRf*inJ#tm(oYkFc#Xd`pS$jA%UYoYAd$mw!avlHOPLMQ*bPBhyYWMe}620JY z%}7HrRKoyPmO<4*rm`6dlapX*AybutjTF-B$*9>*V$6|b$=m?5ETSQ+MRW>|6+?Vr zn`!_OD<3Ia!;GLi9;#U&ij-4GUM}1yWS66Qvr(sm=`Mf2WxQRy$f^E_N!;Mz74z6c zp+Xn<8&~%~Dpw#=7zkwwT$y$6vV26i{KWCC30iPb8#`KWe0|r0W7JpCxdHOwM*id{ zzx3Nz0~36+s}NA+{%bCmM90u!&AXVG*j`VV+4H_Q+5HSvr(JB%l3UnaL^1u(Q0|o7 zEeQKiDBj{n zm3hmDWrL-J@*2`0O?)S6sBCU#*lm}p921IIZ0XzPJ=z6|+f&jYy^`MT=Nz&2%S7}r zQR>|)bezk1eJzn;0#oX^H~1rC{o`V~^SbG7I5WSPklRxw?dZCRaQHX-;fF>x*GAw% zuybPE(e|^)MmptRaAuM$`=M+8jfP0ViZkJy?ceK+%0L}f9Oisxg8au+H_d;!3HCfB z#?!^D2e--YQO|0seX6Ywm^Mj{-8TS#Zn$AJ;5b-{t0u2Tu{X#bU^8n}?}@VapZ!3{ zso!LJCy_2UeJ5{C{)>ugPCKucm1QhGv%~W2#Qv|VkJtxKM0VC$xzmDXSCbS5EkckC zAT(kpge_G1G+mw$BD@IfjG}Vu6wm4eM(O(%-Y+^(yKDaOkf0l62HX&5wNJc+v!!2|r2 zTxwKsuBg%wF3v8nn=qKM0=0G`kK=3AUyY|WiEpPhaE!jCbQK}WGqj4atw%RqhC{Tf zQ{0rAoZ1SbU#r+hn zK7Ml`FKPaon!ewYTCve1edQ|^;eP-!JBKK9y87`|UD#I;wyhBPP772oSbKzC$^S)R z!IsY$ckQ)IPcRb?f!xO(TF+4~@bQVETfo7cWvT`4!%>dmX9o&BZk)26y7p!!W3LdD z$G&YtVpRvA^sojhm0GhZj#ogk#!t^@?31Gd^`*NWOzONn&7b;Uzf_{yuI1@K(a|c4 zFpv28(G05yF@3KyN6a$S$Ail4hov*UrWc4oj3AwD{9?)6cFHZE^+lUi+dQYriKOb2 z7v{TeG89XzE-sa0*pY~|1y=)k=;D|V__tD{oD~FZk{}-$B>J^@3~?w)`%XsuwBeBL z=X{)mkKz@v3DH<&M& z%3@}(Z=`w9o3$9)C?$9o`o}uwlGPdc-^y<_HA#`*D~L6=Dy!#O#={ z!fsgQKo=HHJhBG|i)T9u?bwxw8hl=3eaqr;D3A_=MeZd_Xhwb1YtNB;s^8rxk6#sf zul))Xq4Xxc%GD*SQyA1#ZS8a;=%~@5JC(OAE+4EJH6cFv_qU7Rr#Bd!owU5!iEZ}} z{`An&aym|?dunp#q545nd)47yy@;{HKV35W^7_fqCr(#%-Wlw@emt~hg1^jTxTBft zm|$un-7Lq~HJ&n!M$iNw%7lDnw;vPK)Ba*#w8C1AAhv!4e?4*$SN4Pru2iape65#b zi45xJetUU}*G83bpuO|9X$^{M6oTj(QlwW%6R8|L_re(a;!EY>GRAmYmr?8c93S3@ z&Odc`)H+oniu3{6#Q?B!Rk`6H9iCL?SQpp}i)*JQg1Gr7w`b{8=i+JB1zGW!w2BdX zJt6=1!CR)}qH%vXV@Vcw2|@37jMZzE06is{OYdm%?^6- zdC(}SzkuZ*!HA{Q)TxW#{QAp1j}HL`aH5I;x!Kl1dtD>57%`e0=yAUb_rl5a=J(s# zNsEX$0`)JSIL^uWa&GDHS7wltNCTAf01;OdtFe>`eXY%JAQ+?dz}NJJfd3yW zIB5~0CCP$_*_>&=kMWZr4Xt=JvSZJ?-H$-!KFDuR_$dc;2qD3}J!eoI9Krls>^!8TKk(hl*YBS%)PL$bBLuf ztD4;dA@k33nteWOo@B*^s}|m1mJIg`g;D&Xw)ZJs1QA5~tBdKn2ij6KAfx+jy<*YC zn}4N>Rf3HYqb{e85WEP2jL$>b{=~1><-*4Tu(+z0b{^(gor~_6GFj{UmC4F$mK6&;qV{f>b7{eIPTzN!4I=KPRMn zXj#gXtRCJ>s4f-nu(O>ngeG_ih#{dV5UJNLE*JPl*i;=NF~mO2_CzELg0|}nm~1d? zk|VzjeYX?n)FEb{5bsQB68>cOzMcM{AN-pnVSDcQE&wPY2uJHzCd zV9o>;fJ@ph;wk@uDAOzA1&;X6FagBA?67O!3@611Vwt8iM}-`$Nn*64|=|MJEjTy*cZMuEzmDTm14Om zkfIzRdRjWp#~hi0dNis>d$JX|J~R}Yn)u0hk$;fv0(Zz0=J*g@X_igD)Fz5f293O6 zN4M1nS(=y9o(U4S`+^W3QC%QG98Ql7nJZ_)kLVvqZL%-wO*P`VDS#-jJJl|t60Xv@ zBTl86_~R%0ts1BQTvhp{Dto}9nzi-9x_8Bb*CO@1qPc^u4^lTz-kY@Z0#E`0*A_A{ zUByX5YE+_DTm1`Y7Pd-BQFmY;&n*~S$7W@I1BTt*g}(g4l>tl7lV>RDCNJx{eGG&ne@3}w{t-it0fGv0Hz zTPIQ3iQq3s=V?(33Py3ck(!>Q$ z;&XDy8hpRJ**$>WniNn3lNBglrBALF4zy{AtMc!hdfbHV;TZfD+Yj(o($pVYkNh?d zf|<{loY5b!v$1c>Bo7CC7f2M$eR5{hXT^Nql=Jrbs8gp>IqQNP!2?Dr9M;%y*~`eXsK7p6$!=*y#+=%`SwY>8l#iua_t9q6G2y* zTh?+_nW?}xX+1wmJFpUch|~_tqE!|WBi=B$tUv4~|4a%VyA<)2EZqRuRJX!e+issO zg#~jKLazB^l1JWnp7`N`c1lEeqb0;~UfciG&T6{XOYVSQXZvKwwZxviM;u%ZPq*YA zg&St~5YD*RictBQA3EGft8DYrRk!I^`Z2O|CuhKFmVvJN-x4gU%Z$3v4{$v|YTZqY zTJw6T%%4!^oMaZ@y2iXCFB}LO{CsshVH!1Z{G!>^dsuwKGATr2+~3tZz0cu9!2aO< zuq6Y8d5nTlS@zfT`q=OT{JIvz?^7t*7jGss<^{{$DK@Upm`lN-Ve|$K_9+Rc+ zQ_w#v=g9~kUHAf9N2}tufa;6If!5FR#Mc8u2uu#7i&cni5p@3p8i;qhMo$dx2GTWq zxyI;96jgC3A_-lN1on@mOhKb+A5E-#VOe&U3ktlgdod@#!N7#@UCRE}8nuENTbix3 zFuDB~dH{f2Yt&F;Rm&O!o4#4e%5d#27>5co8T|8KVy?Vw9C-K)^RY!1b;8U%A`SWw-$E^bnuY)Ohss#c&%`Ro}5yjT_zxkUT(2 zX!%bO`t8Z}&}jR(mRQX7&aEG)T9chmqJp02TG$BvQ+^pOxIeYAI}B~FznO^XUFLqm zCJdtoaKDgzI>b^q(Vaa|)}Z+GmdNUH>-mTb-J`8dBCe$EY9VbJ07~e4j{IA*BY+O{ z`0$Z^IVFV6IRkY@L~uV`xAlzZt!XSDIHRi6)QZ0XfMlw1Ab zc#}+limmiJH^5X*F>ie^v|A3vEG%Ku4gvkQ*fJ>T77quZ}<6>@Hr^w(HjY^vtbN+3B&80b9>} z)iLf0G^3X-j6D5+7Qn0*Eym;EHX&j>u_4pW+-&`r?8fzP2J-ID%yl{ig=s&C?&8N; znkR$-JJi(K+c;~qdzXIjhH31YenV2A%qrV*vfDQtjBVT3oWK~De|gAoYJa|(m2Y*q z!(^<8Tj3E9)^qK8jlgH$r94ytz!UOYROl`;u1G9!`rdpFPh$uQr&sS@0cfat*OOEc zB9v%I&YMsTpN)ys10#oZumtQ3YS)Rm%GogDm=UH&=xy7eVA{_d_xXKp?K`VuIPnO6T56c7GU(x@3$wGuK< z9TtZHLiwG#0q+J@3YrMN2y--f)y1I4fCLWqGPRJkn#|_L2raY}qaBK_N6>3!qN}R2 zoi5Ggw>4iKuf#b6>B9_B4w{fK0r>V60ppj$qL&_2ko{Oe^h846^!i&MCW>-jc-O{m zlw+MTCUn$$i85_tf%&KFJm4`7ZuPi?7dfa?@@0E7bn?2 z-JovuZeyhtlBnCn7K_)qpA&%dy%pc~QUSD0V#ojY}s7;Z-7cS_~ z{U)s1S;@O_`&3r<3B7i0!z9N}6L(l@+*>lYnXKDs?Fv?{XR8y9H-E^7eTl2o*P^n* z?|^*Nvt&7O-B|f#MUbx5SR;GX@f=vxIU3s!Jlek2wp2J+D$aC_;Fw*UovCB#CfmQ5 z{VcYmfFRb8=T~a3*}v;@k&fA1OswWxt8@!x{j7p)&zmt|JZ#HKd3#0dsXcAf;w+z^ zQ)R(kdY(RXu8+qAgd1n};_p2{_O#Xk*>%7iYy_`>uLD&<$7yf&nA+hqhtp5SR+~IC zRo3gdMBNT`o|TNcJN_Qm~?)tXK?1Il`$| zuQS7<^_*c*n7c30_ApjhxQ1K-+N5l%#7K4g5bV6{r4HfV7?+l`8uM!7-~(Dp!RiQ@ zpGkS_%kwQXhbzi3oU$uB)C-9bwU*sF`-{P_B+*K%Bx<5@E-f-`tB68{9}#V$6kWAE`4y1mRRPww9LY& zt5xe4jjMY6^a->&zh=yqN@D7;u~1AFrsTPA**0xAGOw*_zx5z0Fs7k@=_0sQ1Nydi zbi5uQHrrLLjsI6Z{vbK85uK6c!XCW@sP6Vw8ie6t$@k=;cC_K3L&%a_xCT^j@4AwtL-sagr zMYilxd28?rFKYAPBO;(rIhU;TlW5KKXq2)zh~fia%S*YDljbEw3!koZ$&*)B%tBSp zTGQ)Uv6ZITrd2jUTI3&Ewr4Iv0%ln6T0s#fSv|YR@nvS9d!eU9F9T{;Obw85Ft`ak z>Q+qeEL`s&LJQ`#$#Qze{hoU83){yN5XsEr5yMt%bqd~uXD?Z`Es*@`K84HPM5)Sk z85LYkFx$$Q*A#uO6=MQuVRvnuQ704Fac4S0;z4Vg{Wg5=h(Y7PcNc83xyg@5Z<&^q zvVV|mNRi<9gzX`5PDOyHT0L$zyi0QcCF@So(39?>Rq_=|->m}S`qMr(g&k+d zxb>l7rg?RER&_XU3qH9t<%Qgj=?wW$SRXUsG4O>an>&i!`A(REBpBFEea|rI{s{V^ z*rL?3rjV_ZI=$d%z<&mMQK!aqNC#DVT2amCwz}xYWZScM_SFeTy#T{PcC~ABOTHQM zNE#pX!ceV&{kHIMq~CoIDbovczME_7Y1tb2J%mEdbqf|VFbb_-lhBTQ<{Krv)Zgf@ zARhx-s!e5lP!rJwSlJVEwO4Vo)r$e#dF!PeX;0!pYK*Ix&Hs3S%0Jd7Fc)2O&0RMP z**hPm_gh!x?ttKeq%wo0ccfv#k^3QOjc^?F_rTN!p9e-%Z8tV4FL@|f_ zkEwU-m6TVi@v5FOx>#{e*$Zz~W`AFE2YQhys%c%W$Lk9Q|As>!&eUdUZk^h=P$L@M;aqJswyXb=(qL-p1u2s)@1|^e z5$?E)z8!qd-Vp{%)f^q>DWL6}gMc?7*pjeMnW$OC*vCVQ@aE1&v`SsIm8ugTw{e`g zaeO6cY<+}?5rR~+5Jf8fNXq0M!KauAgT2|d|2^9-6KzE>bvr=O4tu-abBA;Gu4)~k z>cO`Ny_Dsgmz9h~{+1UYXOFivU(oaUOdu{f?b_6f>kVz6EmEHYchne;+lL%mXCd`B ztZkkLgl3F3lBL7$4d-quUr)&1-oUm$@SkyIr=RTs?ZtPl&HUQz>XF%-?{~eOmsmA8 zVbrulBB*9>O_3hFYR@%GrcW#WrW<`uJ5P=-WC}rmszHW_`uesklhBvN$*^;b$ND$d z5)O4ITyMoL#K{9*t$5VAN^r-e=j%sgYhLT!U4-W#lJ z@Bf1qQ`|ITV%lG&vepw%OLv>fNc{&MMTF2x)Qx-V9B%GdlkK>@t}FAfSgD82YEpA zXQ{fDfn2V~P+y@BKd)eBZ1r{oYhAi9ZCWjP^a~yLokVg+`R%NtEEOkUH82kd6o(js zk-~+FnAg^^lPZHL`n1qF_FeXU#`zY9y3*D|np<7x_>il*ii9^3 z>2Zh6&4Xn-Yi-wE7`o=jp`ljU%nS*VQ2X5TBor4H9jJqQvGnuSkUNLDobK=QpIT?tZN@?xJNqZp;|gLbT3KJ8LNWU6e$>2) zilH{0+mHF~efVZ!tXNpmW3 zGpXqg z8uKe~=8S6yVJyd%^&f-9!+s-v-b;VJXlQVWH-wk{xjBRDalAi`Uk64bkB1QcBUoTJ zjInob6k}y2*+)4s&jGPBjvNR0K>#w8{5#%j$cYil&koS21TNBDn4KlAcKq1__M<*m zA4ZvkRl9E=nn}aH6=Zbi6r^4){2|Y?7HEbJZYI|da$O2$(ni8)vhAho>u+h__H;z+ zpGpv16t;seLF_Ai9YjOkOTu6Kr`Pi_)KON)Ale~}8@(SB{1}Z{Sgg7Axv{55kSV)f zre~7&3Tm#;o-`?UjU-K)QN3KHoUN%w_ZW|QGU3unq1Ec7HhQCRbKZFPqM1J2{D!j6 zLw&wdhpKKPM&5fVLiqv`qLNg#i|vC=cfwq$Q z{{=J5orWEIW+T9(p90p~FM|CQy)<%T4&{1g9cI9|i&?^0W*(?%3Q?DmxriAfEsRQl zXKO#mQ}NVAgMAXMm-wzE{Wzc^NI5WzUr$T8%BF1QslGM6l3RATfK2M&fC5^?9s`-5 zf3Qg(QFTXq*YC9(dw9L(TADr8G^yqLOH;v`#W?c?h@0}xhv&GD*ng(xaYhGWKkl(6 z09WJQD;Fko>u`CUy;$x^53qb^;!IA0;mj51rXnOeLkFVl;M+>)EFmT{GzI;I0xev; zcl^W0psLwV2cM$`GL|w5>bke+o(mY5wTF3f4vq)eaG^HB3l_3FdeoqjEzc8!2?{HB zGQy_3YZv4%?rd8kE6XI-^I0xB|E+a^m2f;jx20$J$24j^i3lkp=f~*j`Tvs1B^~01XEO>yJi!4sWIj3Unap835&o> zpYi52`;gTd`tS#W9VwbQ!iTLn4j>q0t$ur&aY}yQWJV5tJqXsS$|P|_M*2&eUkw`4 z4&!`AXiXua_^kJ<0X^RGjBVWI9KX(xqR};d;3N*;NOlUuG7biHuse(c-RW^J@tThg zv4@ZUY-TUo2^joTsxR%QwPD|p$;DOY_cI)OG&!`Owsx-;nhQBp<0BR9FW(v=fiQG* zfuye5dOfa5=hF4TF89y;{sf80s^x$i6w&9zqLYP%F8j1f&W|I zp`UPwQ{yNL(MxqG5jE$%f^dPOpU$!aC?uDP8H?gSVuLc;k<|aS`Os42{h7hxE*Kr` zZX^@Z>>1^vdrFjF7OAeczWI~?h)Z4kUYIUK4O1s!o>R1{vxh&zIu$PfTGxGs2yBH+ z6SgBmRbs73?M0YDORpzoUz*ho^3zE>zENlt`%GMi6T1|dB$Yqn8#yY5w^)4)xk>9P zYn!QSoim7C0shbMv%wEcAlT4(6G@<4f4K!tzXeQ<54}Ah?cDxv$f;TvLc8Rw_ML4t zHP4X)aT{l}XW>+LI&|2u5ok;CS9GF%Q zeuBI)>F@lDkMj6_k9em6LODgTZ?9e5?IG$8{RsY%k#Ih#*OlfIX+ezrA9{+^7BURx zX6b;aspcOcKV<6GN2|C95%?6OiNx}-Z2j7-v@4@Lv4eD1({n|Yy-nbJh-lk(Sit6_ zd5JFt+K2t4TrKKsQkuyDvOiI9~Y)EP8F z*~oS&D%v`ZJI}~3O3`tY-I;d5D&Kam;q7wrHe$uJMofe7M?mQ3{q|L+UTxTtN!9Udg4Je*m;>_iqK^xP!|;yU zp>4MVq*hrVNmkj~!|*`K_o>~Rct*B)QTZUZpX5GYg-qh+Ktw&NQJeMmHmU)N6a)om zwfxkaE#1;~RU4d_ukO|8-HF@|1hfr$Sd__>WO_}jMD;||e+pwJUtnf=d7WPjy9=Kc zIyD-&E6BZG4U-FI5BvsFV+llhWyiRK$?)vfGdszRRjA99m&VbP{Ji_zbNE}lkqhe3 zYoRf1gBRGFzs1oVH-T0!n@y|}%W`E^b<&jJ<7`O;ux)|()@pdYLSU-Y@ldz9EwlIU zOmT8`vG^EMEXyoZUH*a48p4xat+cL;UmNt&gBdak?c%u=H6-DbN$e`$VS?zxIi>mr z*=o#-I-VZYMSO#MA>7W+m%NRiNo_Fy`bSn14$9V7dcg#WnR`^sn7{rnGFs%LA(ANI zL7;x*4Y4_HGRr&tU^fX>UJkhFtfv#X>*m@34;5vA$cC%SjUN&KH8)4S*`gqRbX>}q zI@G*sF6lj3>-VJ4Y~3AEF)Q_e)5z!|(xkJsT2pkcHs~80vm&o(A6S1jzmpPxb^NtT zP$^0;VmsD^_%M<%r2sw;WJh~8J@GVd`cOstiXX2L$mL5IEHp8TJtNq03O-uY z+70V@F;^z;EdP`%eAg(we!=Y`XPy=pHTP47xpkhfvPv}b^T8fcP?dX8!77oH_j$ts z(eQ}U8Q;Rh&JN>nY^P581?Axn)MLF@gQHmmg7hBN!*6gm9=nQfZ0oIq!IcaODnaWq zuZyq&w+ofHr_ zB}jig;6n7}W!a#aus2-w3;BI;$+ZPe0sJnYU*c}1eawttq5}-QYUc7oh@7~XRp*&G z_gkTC|3nd08Kks;SHC)juSnS1HnW{9=|0SE3fblpnWxz5+mJAsQ04UY2*|sHZEH@TlLVGo1SIykw;iLVTY2-<|QO?>LUiBo;NWGPr$ca6Eb5NV7qn*8n+ieU zK$!}z&-BF%tk<8E_Y2QCh(iZ(*qg1f11+d`LqY6H4N;CQeRZZ7ExbUJ<(u}zPdP&* z-RbT)S5%kfqaKrPJyp)To%^Vd2POMskeF9{3DGTQE&y`zknaxkJF;h3&sZ_LXhjZC zC8i)rZ(+%&e-(z@JV)DTQpaREOPt_h~%^Awgd&QWZqt#KH7paKp5zx z_XKf^eQ*_yz%Ay`tj3Kyw_bXu-=VKKjTfhD;)v90E*g92(YpeByRRP@-*b{PYRaTW z?)w?E4yJ1QfClQ`g{3~34pFZ(!%oZyU#OhU*_24-l}C@@9LG6Vi!*f9H@%zJ&WMTv z5XX}-m6~zBCZA5u7vEjr1hASnr>EOklb5&|Dp7=VxOEXZ1`vjRj|x$uBQ5_bZLxfs z`db*?s{0c6&-CG|>Q0bVA?7Lm9>wxJ3GB-45nL!po$I#XnzYl4WNG&*BOTRQwr|z` ziZrfl`)qkq4U?3H%Irk!@?^@=)ZZl@Pn2VoxPvMM;5e1J<*6txZe{(X#;nmPC^Fhb z34-uNU)1JWDzgI?pwZ=!)y^r8qY?e>YhMHIIs6bY-SVps!-3fP3L4#VXAu_p7f<3! zc47Mqt!bl)L2=gArKDzbSWEKS%ssQy&^fDPMSq$`MYiYM>X+p@&l@eRMl}Ld{@PQ; z%_+>PCTjNws}{+_M@3SPup+cznK9##y^vlKalp-OT2ui%5yT1V&+!^}@<&Fq#_lWn zp&jJ4YKC_OJ%0Kq-z;Dx65!yv`4}Wzsf(Ul)Mg%AubNHjzJ{xNx-xZ9byhuC5l4Mh z9m#i9cUzN^NMtl^JyQMGFRQJCH9YxUA%FYi)=SWyKSX?$UQ+!Z_}GaN2pVsc#ArcL z**%zBUlWs(aNH`b7<9RyYV0n<=vRmkh&5TB)G7zZ?cII=gH~F91AvXRm+AdTgLeb zoulk1*M8CsT5mui>YpO{LSIVUfwWmtRN8vcU-K-ascY;asw^w%<8$Gh zcVIOAuZf~#{(UBJ{7Q^{M07ptYoQNqYq)T>$21|~8~lIs@%OWJ)-o<51lD0eYN#^( zOfb>nNv>hkYUG@01?-;~@s*u{M-L30I@F8M@Y#d;a`iTT+?H_HZzwz3W`7>1D!Dz23&`lnRKoP zb<4ujuQA0}1?c_eO?NynJp32F>~~imSir!{NA>JM#j}ZoJ01L()CnjdbTwws@ z?WA^~79kv?Zjs${?O zCdb#}pcG~F^PP&N-Z3)_%g;q1`p3idL&QhPt(kn=gtu1A|D)+l+>%V+zipe&WJ=|? z++`Z4oTk#u5EqsjOQ*7&#>yoX(~^=3l}r(V%F@iO#wmBGu{2X5b58|Lb14xm1$SIQ z#hrbBncs1|{{r04^IZ3Jea`cGn&U;@MdoY>W_Aap*E@=T=0aiuJ1}s~Au)4}%H1Y6 zgT9Fg5xS3CRG6v1);D!qg^ig4)|`P9;q${CB!JG~2$CxXLY#Lm5f5pyRIX-4yHSa8 zb|1(;Wayzxa;uZq3Jhdf>oCoju__=Q+`LSck1}t~zfCDZw#iQ#2+JqS#Lh@{rZUzu zVX6f8kB1%iO#Gt*F9w=P_BgmShFasIk8%NPH-YqZVGs_a$|P^pdxgd1bgBKZf6v=< zw<(WsY3#Hg;M;=_hUfEbV$TBunbpaqm)L{O5gRN}f1(vh-ae4`?TS?(45yu%&-~?c z`vb!Z&x(e&Ww~)EcKjGd?&+v2wVGX?<=6YwPa7f$CExwmd&A~9b-qz;b$1P%@V5w| z;T!9EbUe!^kNg#SNRTZp^XkXHU@ZD)dW8u}eArfj@NswLy&R=~=16W-A~EX1AB@}> zjb(;-^)@H+n#EYqihtnrM0rFWIDW7DEssc1CI9jnPr=Y|;dsvMbWBGJYZg<=ZrlE^ zI|8tlP`^smE_Ao7i=CIF6=F1T{Y7o#qjyxHG)A+`QL&FWX+-4d^-r#R4|BWeO*-c9 zOPuLyv}TEpmlQ@z9z4`c0F?<|>Q4x%K0oKJdAjjKyREmyiynu>n@;~eTX#&;9C=RHzt-Iamh!8)8v!k zs3JC&u=LezVWqO{L>*X}r%#WA^{=?hlmYQm`CjB_^%I?-3@7|y$M*P0CNGP`0Q0cY zlJCm$lkXEnIf2E5zb)qpKU{lxvEnLjOQQAKp;HR^)BM4=S%TN&?9VWt0*V7H@!lc%=VlHa}l5C`jocC~Aq9-CORy zvBXr6tv8_~E+JX#^(Uezf_)XrxVsI%AQOCS*Twy6Ch(U|@5_L8PG`RS>~tukW3$XwOasj74uMDI!wTzeY_3R@S3nw8x zCWk~AadAGj^a!6r+Xq+i^`Z>cGwJ`HTCDWohpAZivixVyH?1ZTdH@q9BU*8zuuRv% zME;HFv>`}Z3-iY(dsxRY7*`owhQOMxx=2v?;JCOZ*&>p#et;x6%E7JXacGo65pl zg-MHjxAELffK8ys*kqB66M6Wv21Q(=9>>1%P$@MY|2Lsgwg)aLjyDg1Wgey(stwK0 zBl8$aE%F6dRLg6+0Pxc>c7FogOhvfCCNq3R^ zzCUiRbfhqiyV|#vz3XOv<=8>w&&a#-)_`fl?WOf3?>wssNf{ZtuNh27b@kO&suZ7U zX1GSj+=wLl+P<>%tzi~KP#EOdW%UNI2bK9rul0S;aG70{>o_u4mA04SrARsUXy7J= zv~wlfr8`ML{#dTZ4Z=qmixNZ!x9-GOv<(2q{{nhu;MB>+U?zW}!+VMID<>{5f1E$4i!Jh#N->lxTe z`@cVPxFSm~q3_hTdvYTDzUCZ6MTM5Xh?cr8W9IsXMzLrmmRH$>&9Jhka7@OX^r6VJ z=rQ16MU1xqA$a+~E; zM3AuV9m;@sj^=5r`DL}2*FY|Q=z5MuQSmHymC6E8ugCk>{g@{$fQIpd@R2|4mi&wq zLDt8DQty!lzE=KaHX2R2f0gmClu`h|I)a?Yv3?`|R&@1t$GhmWI)t>q1ln(RSq48? zV7ZXpRmjk`h-Rztg7qx~30`-BKC30ZflbPC%;{ z5r&nhxiWxAPxFr86dZSTfQQ!f{lZb<((@do5uMCQKO0+C{k@n09KKZDW=P|Oo@L%x zD3X|~k>u-h@c4y@$s6m3+HcZAy-I{uhQtX=10YN!qTQ6T9w121@OpFq(T4oYzy5_I#-R(R?$pj4jy5g-M`NICuqbQ~H$+N=4pj5#qRAPSRnV)lvM6XSUY z(sC4cR3GY=%ny(A4WbTLbCt>0?r0Xd=2azNb_69Zjg=Ssvc0}%McK1b23w622P&l3 zHT%b)_{ZQ^pdymIeKf_vYAP?j&IKsyTHDav-Yw`L2DaRV@ShAF;B0tbzw})@e?fnE z6x-`iiMpe*)d{nk(rQ%bYp>PUuG24Jyq*R3nnU|IPs-{Q`H>MJzJ;y53C>|`Zp*%L z2rK{8RC|A!-&tw@69Biq-IeAVb{pfHO6&AyAHRj);g*DtxbET)k9MJ{CR^gC^&ECY zCnpu#)M-egl&2$gI9GrA3wMpXSOyEqp{PV57{f408-K3Z?Z4G9z+pzIZ{W0-1f8dTH0-ckv?l!#jsrHI>WxD7xS*d;bBLAzr)lV22AKhE`C9Ua0 ze^+?qAXs`jxwS*uT0||OYo2R#CjP?_Ny>V8pib&&e6-tg9}4}Yt^Nx5;c@)pnK+qj% zG|oDLF0(HgLB(Q#lsYc(=$SK|;T)!hTewywFMzM67#l2H7_U~n;rdA)3W&bNlprcM zxsU_`zO&PX_E_VZzAG+j?s;t*e`P4+b{LP)9v2&CTWKw{?aB1t4`g*j5@u?MZ}q#a z+n2@O4S!8&S7gcNBYCs0*w&4vRvdm|)pzKD zJfaW6jC4QM+|wftwhstiN;QgGy|2FeM%w7bWD#lFJ~ZM6!321<%afGFlSL78favk%$r$(^`n6jWla8l z`~ptsHtOIcH5-;+vUL)~^4{6YlJCSTmgEy21Chgg2`+?b%2IV3OOtcfe4&5%%j zlDlQcs@L`PA%z8P2kc_mW=^$(e8FW!UEFbBL8IPBYdV$zT0neFUq=Sm@8LonUM?;W z6{cm$HXK{dTfXY3z-g3Z4F>k3Y|MkdzU4;&W^a-?D{P^i!SDnBB0JiV64vP3#nX8# zN^1xWt`DDq2L$7o7K|PG6su2qJdwNf@ma!(x8{brh4mP)`%wqHAYwi*R`|})-Zq3~ zFh2S?<-A@%NO1TdU9|HiVc~wsfw^yBUEAD(4v(s2V|d(>jk2WcBZXlXAno3^4tl$H z-?L%8KCJ!xhWNmiXK2RtSKjvP-{JB6eTKymtP2@_1rxo-NjA2x?-|^L+=CA28Cm3W zA0#_EI~5cfM*duK9+jwE{b4)rlX3pT-Qs%bb#HprREOLxbC~5-Fs#SyzD`iSM`pf`r{LrPweX9GNcFOSlwh$R77;=ggH^Dg99GU9;eB05$TVojkO^(YcL( zIz!bS%iAqCyo|&27+@*;nV5kK39@jN8Bk|JJ4?zV8jSz>#3m``C}u!T>?a?e;Vljn zI4DckekK)GCgQkDwNHp)>gNwKiSn*%oCtf*MwUSGv*%gV(1^C&@#B`@s`qiQV$?S8kA3^fQw8!gL$OUpPPo7-XeLj`Mo zY*?1Kpwfzmrmk@;lzYePMlZJwg7+aAA4BGuygw{3vnvA`$F6bxK(-}uPW?8&)}Ttv z!MpM#UUh-4gh@jhNQn5nELhtf%+o~#h1F#OGKK@zb0*n;986*D^0D8p$LLv-PP_t^ z*>jg2pzOPBY8}G-_J9W}gW@U^C?6c^PT#fmx7MvPl`m|Um&9*N7AQe}k#WOuZA?`R zwDG=boO&Oiv*0Qi*HASgIu~q!QPR=CjsG=EqB5c@%bKn$MH(qkGmR$5z9Q5zfBLsp zuNNUSw97W!bj7}y*k=)fzq1qan%rak`k&-|Oa#kWbJB6UUP`7@w(;kTls>olAo+^b zz3D5&Nz+J-LMuDejT7e_gU*~J>?o#UR1qcL@x`>?i|ej%^LM3u3K&3?P*}WE2h}K- zRUDer6Y>h`bPhHEQ~Vw((P}zqQIjI)#}`xZto|s*CpEeNm~9^h{S^C4VomiTQSLe`{76**6Q^T)?w((=Z!l>ym2VpSMi#yfw7Ojdi- z>-C2Fai{SLL*jZIevZgZc?o6?VB>bUbvH6KvocXm5B;m5q*7(6vli<0OL(_pE*#jK z7n?1wgz>2?@{?&-oyD{xn1T<&eq>fobRYt|R2}QXk$qW!RhG`AW#^ZC9d2^C!_rKD zMbNz0>%%Ak>WlRs)G30+Bp1=4CzJ2s$eop}0oV*LzU~b+U2#Uo+6#`~(($o9u6uoT zHE-J3`tLtS{8s-v%VA`j_j9rJaH8HL8AARB@$S+vk?89^^N(j$R2G%~(_f4Cnp`$X z17_})Q|ha6^p)hdVmHZjj#mLT%%{4xd+|r$zT0h?Y-`cvdumEP{xxl!dh`G?V-gf5 z==+)VvAYHSdVg_P_2e}rY_(y1EXUTS!449|fW>I?iCB{J+_E2!knglUw&Dkteoqh? zS@HgH;PZ%oH8Xb*CWUP`Rk7x38)md{rgiVDjLRa1@db?@N!9|NDe1GOMzf-PbNNGE zb+|D4C5x!*7ni=WIjjG(6kG4MY}y`UFj(ziU%x8>w|b>VI@5CPo@rg18CYdwOF+N0 zQv5P2(BA$!f!KAPMo9+F`C$CupTwLme&ZU};-Oi{StDH4f8A*(aByal%$qF#AIEU* zbVe;MxL)xj04CA}yEnh|u0Z)*JqJod~VyeK6(h%cJdG(LEW{l8F=mXCh zW`W0})`x1Aq3Y(Rm0WIZMi;W&@r-0Lb78RTc~A*xq2vaz3$(BP{+o1pSwexznAI3v z0IsPn@wiQ*58_nC7c?IJFncMq)MsQ?Kc7d0!K0cNzSLLTfFC56sD*H0ukgukKIWTKEeG>j0HB&9<1C}6#igOK$m)i~zTmdnuv zr8$G_*20L6RWrroHbXd>Zo-6N2oFQG(O9LMf{t&|oUdm{A8oGqv z$J27HD{}unS`%A5+Vr~hlk{WMxwBT`zIhgG$fosem=Xl;a2^;nUOyJ678cpqbxaCc{c=FVyO*mu&T1pEHs zTDMXxOq2zlA%v4v(+14`1q+xT6=~P-+#2xWOwh|;ePSu*nQgIk(S%A`G@vrNpak(T zIDa|?QWz#Xa9DZYS(cGLoWKcx*6H6ZV4+DW>drGzdD740^zXx|q!*yg27+Y7Zf zR$dl~!ijSx4Lq`0=*^p-|K-%LqRP|9^`Z~Wcc|wZlI)rH4-fscmHsRf*!#s-GC_tE z@+&qQym0n}MBG2#Fzt#^bODM)j=&%4%;doZkQ8SjLNKY+H&U*eA$Iwgn;ssqrZp82 zL(&3EZ?fHH#eVEuNS1x|h83QVSKwDGfLcG(zvs-oj%gDSxt+KyzwQIVMZru=M9vNU zZ=IQuyi{i|Sgw%Bs9M+e?L^STfeQb8=F$q6Xq)A&E zeP#1r=v%*J`Fkkbn|?h*+1QWS$9tTpl}umO|Hu~v0Nq!JyAYPfvEp9nXY;1)119un z;sV2kLJbS?G8eCGgIUFqq5~B+vo#({aZVy`o$AQhwW9=oO?ohJg+do4#Rp$>}MRx->&@RyPCcH%jHVW5k zbFcJr2_5b6s^n}CxOjKS=VuOF&*g4#QAfX?9$8VWe8}O&b2FI(lHu_fRqnd-RmY#i zC01c|HpAR4L%uLx%Bi1+4?Y=*`RR{{$y)cLmGAVz^HrC@6Ae^O%ArWc^|(v&?y@k( zdVcXIu2CBTF+vnf!Wcy1B)i$DbgrsMd0Mx%wf(*AC~N!lsIODf{kx(6!WRh={9|E)vZYu21kulDVT5d>d&}=OohyH?-fHwe`&Fd+dHW22fl)2d*Y3X{@ob(C znILDe^BUA-ttM-UqcYY^A(U4~=%OXfbAod12wW$7OggH)EJ`$L4$(`Mw7Q(J+e>($ zO3f##sCHm{rGTvyyC=e%8)gAMY>znH1rDS2Lv%pfZOBgktUbEqK@(~43Pz> z;rsKO!5yj8+*+Q0>2qnPkrzewdWt%Yx~%LYhH#TH=K*O?c=0-EQt|JNk^RvR+2$-$ z2Gfrn7h1$l?BUIHXMn51e*yE2R$Sy!H9W@3$F=i(!Jn>cU%qyj4}bx;1<(Ycuf2V@ zL-C7oh|%H8d7sK#qQtWW*e^K=3{EN^S80nE!M@;EH^%hTB3!J?e2Y za3YqtAlM`W| zE7=@udD4t%(2U!Ao;*u%NLZP)+(z$$@_d{$pnssKFw9&X*?tOr=WU^_OS?8Q>BzTO zG+L<^k79$cZZ9f->bRIdE}(z=rI_YWfwilogD6MIyWRLc@>yr@eG!<2rjgvag9WDctW1YuYXa+CK&bP=c$7X1FN3VCI#JxIijb76f? zg;tSV%1qm{n`(1RduF8X_lfFi8`)bkP5jFxRcyri=R$b^TfALB>a~$iUbq!DC1wu$ zZejCZ+OPK_@vE18r)Nx(O{n;P=Q0|rd%t{)Y)RGTJVqA>D0M1yz6cfO>j7x95>mt1 zz2=bc_he+dpC67HOilvOs+zd6Hw5;W>^w%oF`FnqYs>o)k@nj7K-0Y&0d>;gKSPC2 zeNh1g9Lo2A-(GKx!@oJ#QMnHm_v1NIoe{8`hP0JR{}}srttkDYUuoW|N*?m^+CSrq zY4O5SHCfdG-4JnoUmlO-M5uT1>c;Jl1m&PcA#hO%!s$+9jp0VnVN z1q8F87uuQGhUS}3L21P@FT#2zb%4y9kv5>eZ#fip}q*8E_C@)L=V*39mK z6^mD|*-6)B9tV_M-S5gkr1baOcg>eqiEjaQx)rJODWE3dF4Fs4Z?~~wkI>bDeht^S z-9j#I4DEmGq=={t#M$>qq?H6}G4Zu&ICZQ`VMzIHy_kukawxlWd7$vYO&{cUNow!A z(poK51*jL5aCjb0qA~t=7lx~s96(oGDM$09S);iTJm}VTfKI&8=FJ%xY2uCcp6nb~ z>SveC80`Sg3P-y&jwvc`>?;N|4gpY-Ho08;+I0r4QHV&4JQyA-cVa4{Inu84t=Tx* zfJ$GKqFhg;Z5c?8(dt1oNq{X2ZaO`a;0`@AB%W^q#}P@cA5n?cqq+Vmda& zrO75L2U4lb zuYS}U4UI+>xNw^zS#8Op`V1kA7?LH@a{?)_$IoacKcUkC^=?kDMsUAc+e*F=8;$qH z^UiZ(>+n`d{^f}8O|Kl)QRn0bUz_cDMmLe~&C`yNPHPy>?>!*VC}yXO4dKg`tv>pT zuu%3z30Asr?vTti(W_~1EbmazT(vEES~#f6v{9$WG){N@XWl!i7${iK;Kna5$FJZB z0&1kcXP&NAD8Ub?lb@&cgS1N9Y+!5uB}#4|C2YIbl9pw>*Y0r;GjCTa7I9)Cs`c+h zbJAXL%=_t+@2fX5Z0lB=2Of=EsU8HQ3$E|Xt`6{0mvKXdK~8@F=|B;^T}2doA+|A97i9Ni}!RR4u`@$o{c1PN;vr zvQP_fHY(Jt>KFIyEVYy~wON75^1iPv|GW%BE#)Q{tNe`8^cHN`B0q2Kyc&MTDtwfWU_RF`<^{J@&b=bmXdjIVDOIQ1 zksKXJW!sGiz#lr03KFWyLU1rJuK`rNa_-Y4V|S~WmTq(+ZnO*f8mh~i%Dso;!YUu~ z@6)Gy{>%^!9jM1FHYV}1gVOAc$o^&JXKriuyFpmyaZDodr(Sj6Y2w6hHSEu8XG^TY zpdpxURlt9t!E!K9X0ckVT!(l9%J8@2EP+imM3_#*TW5aVZoxr^{E{uavR2ZhsXSnZ zwS*GWghFi(oin|~Vs*Mao=VSg!r8K4+mJw;4%IKB#l3?+9}Hw>*W;cTqO{^=Li_#; zZtzuMvnQlt>S`cnYK{eBms~5DdUJ^_DH9=~!DXfm8p;mC_@ni2!K{>OtAW;xP%<#C zR%%JKj>mi3DPe%dsn*;zH_K2Ex!&0o7uftc>AP`$lP-U*8r#|ct8SoG-V}atpAOJ& zv~X5wac;7IxTSO#Q|2J~STttji|z56HMW5U4gl399^@PxgK&u5h}!w^>xHcR?g*C- z0vEV$2X^=rYbqM}3&&&ueev>uWp!V4l}8P$=i?oU^twq#+XOHFEh3A=%k>TKH1%&| zI1o7!JYohRN|H44pvr*;fw`vY)ERu!KaN4##<-(H9%e}?|Iab0q;Evb%Kp2hX}`k2g{0n8m_PGWjUl*eB=M9OVp6L8nj zGfd zCp0rtk4_q}!74l7hz9r10j-)4)xj9yn#jT!)B-=+6Bk0AauB!3b?#k3lyxD!&e}3H z4+Rpl&YX{BcdGH$|KA0`%Yz+GzpPsPXpkQl#k5B)qBX#v=_VDgo(D6qmXnYmg192>g< zm6ld3A(enpJ<(26=2`OM?4W_Iy1^@`&;WEJ;Z#N%qcoWo7MhRk^$%Zr+3wqU}$V|YCWi9dQ5Usp^R z@=7b^+2xkIX$c1rLNlXLniCUN4?q|Asx0+5M}b?h&9vw$2W$xP2iR%hOY=f3Rmq1Z ztpU_6aOtWKZT-2|4_cn*zoA{cmfn%96YAa=FXiH&83BOh^FYtvW6-z1U7kkbr%8-ux(xJ&6C0aL zggm(q*aa(8RTdMbK*;9#NUFN|UCcybUwnVtPv9nqx9Z!2@08ti{3Em_^r9^&Ip#KN z)PJhwJtgH?hkwHNS_dZ(nBv@^886=VAXQxPqBLl#zhsMl9tZa620BZfXH-y6*i8ss zG)-%web)2N3Yv(pk|>QRDRrUbEyMWT!6plDXw`iMJPvPrJoMpZ6VPKXOkiF%?=q^nBt=Uyaj#ig8FNPs9N%MVhgmUip1;Ah8Fx4gujl5s ztlDUoqQ=`#$7uL;^_tr=GlyDXZn&V zHau-^;#rf(wf5iAOI0p#b^{plm0b7g4Nq!0*#8XO7|Z-dTrhrtWg`)8u;}uNY1Wlx z-u3m_wo-q_dE-O4kQZd%D+&C{YlkkKm9B9_e&(TDY-u?*MWB|Hge-Y@=boOAlr$gI zOmipFz>#<4@H&**ywF0JF+xtrNSf!q$ora)Z4bRjPD)ylG0Ry$B6S!|F8gtIa#=aB z^+kZ}8^7mbHq|=H2*_I$)J_+&n$(v&Xu@sacu2nVzy-f4u(W0MrN;j@l*RD2=55@4 zIxx9%*+fB?`)C$2(aLu8FN)I{a>Ev&rqcuVmu5plyud3Z!0wikpiPu#V#JwG*?ccI z)!-Sv`t1MYBSZz7=PpY0bQl$a#QH@dj9aETc�WPF!6`F8Ni>VxaIVy}A7mknh% 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%0DnPB__qt&@zHHbV+Gn{XiwUr-e*AjY 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%0D9J*-4z(U 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%0AlwJz1 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

    VS|@$2e`P55qvt0gpn^ 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 t$yVvc$d+~N z+hFYb7-P(g*?&*Hz2E=;^GQk1-1k|obIx_neLuanG&kBOa998Y0_`)tcKIdW(# zH;8W!@CRH*ITFMja^Lv!#eagh)+f2UT&yM9W^Az)Uf2rB!F`XgF^_8Zc^uF4lCs4v zuE4zr%RMBMOo`A%mX*&&x~>1+L^bmNm#q=KvCKxc41EFTb{^uc4u;uKA=GuHWK+P}e^hQn}>Thr-B!Bx(U;cR$Lj z0{;EHg|39!?L@iU6Y6dQz|ImvDHjJi&pwcG6$I4-AB>UF5Z_nWc7N6!p$7WBvRlf` z*IMTZ^Nh29{#tt|_F;fdEuPUsiZ2*6~bm2N;eA78q%R81oFTpsc zZU1dvz|WPyBBIT=u4IoBN-ou6Bta8^#&&k+Yr&|7T}CaNoz0d-Az0##;b7 zi>+^DGxQfdhJYnSL4a)Sn#e~_Q_p$Fx92G+8Ye1(K;b85C;PjehQDN6WvnX#|i2LIJ*nTF_cyRP#Mdw;#}ukoBKS+N}L z(5Fj+f1on<^=jQ#hw?(CkiL?-C+-IDRM5UGIpcAS{RNNu3oEDYdX@EE9on;obky7K zHN4%et#~2#UyC+e8$ZuJp}z7t`t(V)t}})oOL8RLR7$&a{i ziKh8}UX7E=;fNBi40q?P+Xm{mxngo{ZSL-VU+pJa_b5!jti6p>;gI&={qau zT}K{1PfI~xfirB!6)Pv8rJB*L)7HUgXAnpUcqkml*o-mWoK0BkTz?QrM znhYGV4}4u3batnNCBqFe01yIVo)5Z}-kcK~d(+l7O^*Ie{YChXALoMUD>*GT6Jk&c zcpC_0`Fmr?=5_(6JxQ^%_o(5U_PTx--1WU&LqkJbJvxL}rB#-2%-;t`-XuMAcTd1q zOj@xWjuA)ny#|~a6uh#Tnc1w0xl0^3AmS#N3lvUjzLD3Y^p(aRNEMuz$|$fK!xAz}v+| zSKougCeK0Aq9zD*5O{i2;i0JF4W)x$>g)B57rB$R+bT?L9|&-*>?B#}+oZP?I9Xyo z&iyN}Q>;^_=4%>hsq>|jw?tB)8Pi`!V`W62Q3%puFQ)k%ayzU4eGAg|uLBG?PN|x2 z=Hek!6F46MP{%>;4mHXcQA|v1YuMK3b?J`6P7+RQKL`Zm5747$zOzaj^$c&b(dd5s z6SM7owxDZ_AF$zYHtRvxhqJRP`T{V#4`7ntz6eL}Da!Pr(%cZnW@fPtLmFO*n`;5V z!8J1DE;=V~>>hnOU>03@STlO$8G`X zL5)?z0VC<7`{!OyM@_>2+XcZ}`9T=%5#XD;|q6xoiHIWYTfp1^z50_gv|fk z1cg*#Ce7B6b%(4MM_eFk#F5MTvN>VCScRPc_;66Is{wQ}#{%sB=#fa2??r0b(!V~K z*7iM=Z4{r(#l7dtYqZ0A>uTe$8XRS94Mn8uuY?xXZq&5+^GUb*PP+Z42K-Iw2c5to z&mZjyHqo?|A=c<>MTI+j!}n|35>m1lGZktAAX+n;;)E{x&v^~ivnux!>o{zNThWwH z67wmRGIk-a%=t;{lAhjlL1~XjRNtZXe*dMvFw(SJ#xCApeIs&Fj*w`!pu9+ACPv?7-Kd55AghswIV{^{Te)bSVln6EV7^ z7|oa$jSBYvQ-V91>a7#)h6o$B3lmEGQmbS{0`un>59`ZYcUR4P|tp4|%Rl#eB<$+CsYK9&vxU_tUw6^*g>2 zrIAD0|HGKfGb`UiXJ5y)mnkp;eD%$iquWuR`0Ks%uj_c9Xv^Q8=qF)==3*aE*2iYg zeB+JOhW%&oQ<1K|hlpeJv#*PORXgGo@OgQ89e`VPWJ~SZus-t3!5ifx4*lcfC-*x) z>8+gm&n}~9?i}ekrqCU_QSD}0)O`%P*nt+{5B?HXQ^->Ab!5*C*z$A*X9le1&Yo}O z^;iDihW^j0XU~{xEgUraj1QxyKKIDSUbtuxuZC6jgl^p0T~tqh#*{Ybv-`H#z zZY54rz{&S@ z$Ka`+Y4|Vd#x*O11|kuPr;Q8`{*v-R9W{3jvaDwQr_7xuk1ftm62`>e-+$wC=un5k ze01MP-yELiaWu;ZSHCsphIl<7ap)fv#(&Y1&s*Aj54tlG)!*CumVBo^kv~L5;!D_K zImN#ti180sIZ&KBzu(Aic0bRvVDdj7KWa4fm3VRK;+}nnfsm*s=o|kmzrCVwh9dj_ znEW`WPOG$#7l|&z={!IF^X<{Wtp;=Z(z6`@b0fSWNX8e@`7?tPec5#WG>F z@B?!mhv3YcX!!ecxILpV2L1t?Nt-=izmHwN^IrrPh;#`@oeiaUj@^$wb7Sfi~imGVSz3+Y;pkB>LILq(Zheg9iwU7hCR?VYSg8EtaF zk9dWWClyvx?<^58%cYDI7<7ZNrnQ7S=cMoEX`x5{$5+~s7k)|)^KX8g_TUV(+$*cB z?CdiF7xI_b+dy<|khy31D=F?CbZ)gN=EQ>L)S-RX)6uZ2wNBGl#O>$n_!{s)ngYs1 zxU}x7&o|9jKa7^5&+Ra?cV1g|=T<6>t7Y66+OAMGEhl>KH=uf2`AYL-?gvQW?`j!R zf-&1LORMu5^UaNuMW`vTCMd}fMzX<|rQ0^MuJ5STi~O4trFQ$Z zxK9K%?F&|3#O_AB{45MBmuwrazpGc@)Rs29J%&N9uCC61O>q!uJA3l`0!I7K=du*B z=N~gnt9WIDjs*JZnx%K&ewubic`}FfO`W0b2A!higjHLdTAs{LK~bkdXEj@Q?_+-C z{ZoRsFWc_f+w87Oo$v#SwTI}GlB!Jwe}&nPIZUU{>5a7~bEi)6jppoLhNnev^L8vD z!W811p}kQM#=o}7biroXq_^7o)QKS-ZjymDXN0NziZP9ZdLyW2uh7F{1f@7dIme$dc^)(kQAlHoBOI-&wtJiHkB@w}QGxZo zH}mtwrxr@mFISZ4I3EZE&`A(T@yedNwLh_YRiArROlkAqvDd=IeaU}og%+T`v&a>l z6_r!HrX6^q+NEx`^MlNM&M!%PT{KsVhF#G=+Dq42{hs|Z?{@yBXs=)w>!|}? zz{mfr0j!FT)tKT7U9Yc=apqWA_g7vFrpT^ zXSXSomxr)^smye(c)2IlrbY~qQ&eG|7wjzPqZU6M1^?M9C}2@9G0xpmNFR((x&MzH6!Il26LmI2z5N6!*1u-k9 z{I}kkkQ>Yku7<4)l9&uc=UTA+`7=X=bS=N1)6-55OKxif3J~H;0Lt*kxDZj=!-pk> zcS?>)2hS(C*6OO&oXWjPQ%voNn75EaWDYR-S{Z9d#)vP63>D$jyqNx)Vkh$Q<;zRa zg*DY$D5I-7e>Gy-%}4a!x2+5D*JM}CB_2_q#k~r}@9yn;s%Jg3yRtbSK0imMFF?hq zh}C~8g;A7Lk4szu@`R*50JVQ)K?~EUR`4&ABjV;d`17U3h*!%o38XhyOZ0p{zcbbk z^?ur7?J|Wv;Sp$FMIN>_#U;^fEwkYf-woM}5PH6Az33Nas|eU!8~C`B;g(SX z`1v8*^67~ODfZu#hjW}E{`E=Ia*lp&{!dge=@J*^<1Zv}L(=%KIv>P>tMRj(JY{5A=xF|?^{4zh7Ym4~fkb+x zqM69O)A#K=lD{oC=L|0$mgU_)O;iyH@BGt1@6Mzi#!KM0kg7@2uF$j>o}#abFhG9#l7G(3Mm z_xOQQgExwJb@JP3eq)Hp#`8ki=vU?vC8}-wC$j5rh^fxvaPR;Yjh@o7>nlq(Wcib2 zc;OrAhFSpgzd;x7EeQ?|1=gsixlYDASR<@2=FRE{zCj%rles#sXmMee&VrY=_AH42 zL9>}EyRfh!B7{8$dAX|q@=q7Xj7#dy0Ver3 zFA=WRJ3QM#D{PP;eFP-oa_fRIp818uNM=zh<$A9;lt{E`}e=>R7Il{=<@0V6FRYXJ85xCC)1~I zqZilePh34?K_sp~uagN6nU6Uz3M=2S$3t%kdbQ72>gV|;=P6Y1Ct=3uxTGG?O@eL$ z@$Te0|F!ptl!OEyKtz2%47$e$A2*Lx?Ng%UJk%1KKQ^cgmDI9MI6#2qFoN_(SY$@e z^MBy>gXpfQ-T?D@A^5T(up)i@Q946_(yyG`!=e5$0y9ha> zQpE8a+uBD@xp!cnh~4`3&&sP-&OajvM|eHk9(#>p_HJ_5DxOnMty4XCKfWkqMy<#W zSoAvZ6*;_qbn404+S=YyH_Qeq10fIsVd>r4>7d!ooT?9jF67@lW&O!l1OYt}pgC7t z3p<5`S-TandB`ifBF5Q-kdfL;M=3X5tUeO7{zgyF8^Pzvwm@g8N$OJbC+s}ugR0Tf zA%&BC0a}V@PZrk|Q+7>C#~VP|f>?uoR#lBpM#$}SR&rs6C<5KT+=5@-mT)PDNV;4t#|N#{&AxUjtqe0-*MT1x$@@DuCRrU_cLFT%(p^!@;9k_$Ad5C zEv1r-&!qWXeHcBR{N#j82C?3BYhlFEfu&K2fQeX|D08|sa46U4U%Luw+V_q#GJ#z2 zJ1tLJALED$6hAOYdFdRdbya-FWG8o3J29hYZ2o;!GzjsIM&mbJ} z(*l&WFa-Bw4Bhvyz1r>QKXlup;*Cl(`}Dbb`~TSy2HA?W3_o?ZOi3CDYHtXw=N6;x z)!+dl^T$<7oyG}?<1sIGiZ*bAj?S#5GDLD;9O$F`lbILdMPurph10+^S^#S4FLpi% z2cJ9WwD*t-AA?YdkeMk|y__@q_|eN>>3PAbkIky@(sFN+t4bY7cVU9fOH=S1dgphz zo;SP8fL#BO z+*H(LeXlUaf3JLneTV#7E5PKxQ(!sR$eRT>=fSh*zx@`DL20%}j@G zwR;RFDmB-O(~eJlbO!RxpL&P;wPhKu$5=1Dx0DfRzRSEnW`$`!TANF7eLBJah-`ar z1M{H8+B9kFf_?%R%`U?2e7mMMZZ6TDR#w6yR~x&RCtoiAcKIm9(?y%mQv%Flepj2v zt{&7K@lYnQt%-v5K@L=Pud-a}RbK*Jv<>G+Iv#3~v1RvD#k+^9-=#R8IKD7%MC zWN#Dnw;NEM^17=%N;S#dU>l42cOySmMQ`YzQgpcg)l`Ex=?I5G*($q6VEz2PgY5AW zZ!-}giUwE8E1K7kXjy@hsb6Qase1ndu+hfe7o(N+@`-vhvmP z)xImI!D0Uoc*nOsTD}USbvo{s@+rIVVZDO1=K9p}MM%!ddhqOpz{~ce_r)6$2&O;E z;tKs*^O7SvUy;lh<2tP9wWz}*VuO$wY=MWl8dz9hQq>g&eh6KL1w8#97I#R$;(?s; z+5&nEn0;kuYuZL02(ig-Peo=Rzp_#0AF0`_V`X0p(vj~?R_?q`(d5`fAR&&J3YO_o zco`ZYe$|N4MW3qR z!^I&!9dyCPFu4dozWjs8refR3Fi*^Px{|x7t(Xlr&k?s;@Bt1Fndi)1Uh!sP04YGxny{e^3SxKAzm^=xiQ>?a908s&i(@*({d@i;Ggn>H&{)Hx7c?~`ifSq2^ue`luK;Zd zV^->(BJOiO(tSI4a_?+#V;c0t`vcoC<{&@sf7$~5IN#WwtfdM>S-GQSFXrINHuJ07 zV>XK#^{hbS$pk$V=+plM(`Ix6ui??*krDA82!~0}Ti4zqpvN%cq|`@KjDH04KbU3D zpM(t|Zj$A{S%@Rj)qpZ;g=!YW5KN%0EC#(32_LZP#uGBZC!Vb+BILB|$bHkDE{ZQsFgCv4~hC5fM1?`bF8xk#9m zSA8nx14A>`sN{K_-CJwu;EO+-YlaU9m&FwETM`7T@D((y`X)c8H?bLSMDDd7R9SB4|`@#!_QZu1>73ga&GvXp z=rGHK*LaJD&L<?i zk2)Dpnw@uYXL7kt+}lBz?oQD6F&Y{ow3LfsPk+m03L2Yr6}JY$SvRj@Xxff}tR-mN zQFi)L+tY)ic*T4-O0%eiU}y5Fy*y+235Cbzw*Kldy?X5|ScJ3F2HB7Gs?69}LNvbv zzb4~0G-5kOpsqE+l<&3TpZy~WepYYXI%*$(<3^EwY|4-0V4osK0&Xyg+=-)>@-ho{ z4Y0o*H$Z?(e>q?G*XaleSw0%|oM-Q$=g&MJ%)j+V|3`#^Oy+wm--eO%9rvaARM?1^ zrUpz9!fHf46oyFGBa1b%$7e~m7V7whPf+xMzS~)IYS1{4jMG&!9LFJX(+I@kByYc8 zKB8#7q)pbtwd$ZtWP%Cve|!V?272M33z&i9>??Kc`KQlu2Hj@GwP-}IMbs0FzOlB0 zWmYtf4ZnFH)~@EN*=LuLPt%t6`LM%WqawhgbTu-1qnvMR9Id@{$h#|q-Cs>7*}^j` zYN#E;V08N$4-2n;(t2| zC3QEA%*LerTu(~CP0I-V%bDN(=tqy%;#l7HW9ZD0gfN+pWE`)7sP194GuozqNT4+8 zIJ~7+7L>L9dv2k5wBNQXsxdNhre>`uKYnNJ7kuC;TcbRMB`8LICYCZq*qpUyR%frT zB0e(P-L}-yW?y_f4cmc3no?}wng3K^)y0`VBNM6mE3eL#+V-D5G$i=p=Q{aU1&YK4 zy1ou<5l&1O z;Rd}M1fUHg+Qd;<}FoIutyz`x!rl zO(4yu<-)WS>z}8u9doecgfPP3oZKQimj4Y-V!)%hm@zPH@fs<0SL(11*ES95+AJt2 z2mo8@i5EX1tlv%Zqxj+bzOzaPjVL1_H(H^ z$oq8U3R~p9TM#5Q&SvZr}J1~&+Jl+$Zll={LKm$EqFUz>W zh*JX%t3UGj)~&JEQO~F2<2mCP#?JG1w-HRU*D^16Hju!k@qP4>bM-7JuZ3Tm#m2E0 zz}c2AHo&9vO?JG#wu)WVnS-v5F-&jl6!~wRc1V!_bcE5WQGcLf;z{Qjv*+;ZY4xO$ z14HZXW0JP3)i+#hzcp2EI=l*L%)fuvX!C2#fex#(k&f%AotLNT8EYD3#oQClko--2 z3Xbi1)!-)t&ZZ6h1L69P5gc_R)HUr@MF|U(<{TRs-zwt$w-hd3enbCNIlkzbZByE> zloJ#3=DS8{E#vl7+KaRzS>&yDl2MwJQEr{Ya4z}e%ewF8!Lcb%B-#7K2)|WCLU58Z z*p^C!2j_V(tT|=VL-3cU7+HRsbIgCwdevOq%uEz`|BJENUzyG{y}x0{^qdd-bcO+; z^z7=0o4dpLs@~qUU#+eGZtIz`nP%n@!E<}NOfkt_a>+ltx{h!x;)rM4kKSU>+&TB_ zXh|6j0l zf5gM)k6-Yf(9HMT{3Orx3oAsh2yUutR72c|HOI{3of=$xip21Z#r@oRFV#VwBZNwB zNp6n_k|v0RNe3mvRUw!VQ;r>P6MP>+#a-llV(}6lGLoppbzNI{Ap%X^12R5gjJ9Y! zzY1Sw6jn@)7ooPUoA946JhP~%rPsb^?bIc{qgNleTs#Jdi7~x7czCMZW5jAH)6X0m zgLWi++?mmTT%>EVoa|^Gr}nc?5`IocHuG#$sLB$qWQ#mRn9(TFg+V9_Iy;!P_#ud= z#w+B>X`19`sxI1KQT;-6w9plZCMzqikZN*;HX=LomBwo3y2@7 zf|a4GW6(pV3q;Fs|&S|r@ zVJM_GScIP0BL#t>#G0FoB@ZzCMx!zXZd3IgfJ*@5|xpeRUF z{m>9xaf&?(uYz?szRkSJ5@j&FfB}6)n`|gBN;G?NDbZr=>Q%m@@3;+qJ6T78cK5b~ z|I4k&{o21$lo=(iY_0lcFcnXdA1+y7FlFj1r6EfxA1YNw6d7}h<}RVjtlGQNYA8`v zf;Dtc|0kxz7-kLA$+HH*!8T>9w+eR$4IXu%ky9ZhE7?mW+ ziByLbs`5ZQd!*QtT$GlwMA7;18a;$g=QvCu{8fc0&3Ldc>&hx1LL5VQ3*rswy$FwX z2oY&4s>NTZ5^(~~?5rekrdvgFaP_0lg`KN*{ZU@k{72czn1fHxyD8c;{Q|z>2x+Cc zLFAYX-Do&$k9cWsyyqS;B7qU&I__5BTaniFd9D-|WZv=g1dbU8#dU7gRU)Qp4Om84 zA(7LU5PaLag4Oy?)!8>-huah0VgWvz-h72YUiKK`kdM6&7T zDuQwJVQcv4SMbIsT@*LresECS0&ASH<;y6^%<8#Tw+GRv=dG9(sguBNsyx`t@L>4SIUohjMKFS_)xRrs7Nbk(1>L2+i? zqg`VpQ5q>~y=!<3J%>2|j7Hg;u6>bQ8}^Z&pZkhKfKhAl6MmR-;DlZImAYX&7sN{s zfa6nv-vKET#Wo^6#P&xxv%i^sydTJ6E_H_&`op0f#rqls_&2b;!e>V3L&+Xjt>L$C zaHJGv4bJdhc$U$MHv-#CMOJZ~9rZWRmCDINWstIcd;6Wa_uOC*bslo|)0@aq+19q+lvt|_w#htd#v z8!CAS%FwHK6Mkn^*NchYbD48g=|$j-B;k_N77B&vwllO*>sL&pL#E5;Se+~8t2ebPrO36n(Wgn12x z=faM>oJ2%GD}IR_g^MjiMMkVHFUQjmNc5)D zF1aCYvQuC$XI({a0OP0EOg7XD&X5=$YB0#>U_EOK?P0WqfcXTYyE@nRD?JxJ3=+P? zS2EDc(UiSC$T^F&ab7|MW}d32ExLw3Q{z;J$GD!2-=Ho=1UQY+oCke$gXXwJnqkG! z>$inQEhsg%x&A0c;*L_d3pgTDYG@e>xO1A6dh4ca zy!*g6n+Us<`Db$6za8-4Px2plnaCpBUQ}_chn_L0@@N;yZfers3i{IR^Do^UBzAldEq~IdU~yj&>sLa zwEn?FiX!c9VqM`87%;OMZ&yTkqB)a<(E^mC`z5{15(sFw=Tj@KAIQUa#hUbc7J{$j z53YN|r<=L=%3c0~DMk&H=njF)AdDz)XBVO{(|RDfA$5=wAy>cwLpK{E zgSl3jpclk}PxIj%oXB4!2qz_}T)#QEJ&IsjzPj% zYXP(eDPuL{wXjwlowfKjkd-DQI>HjFAd?cMfk+CQU`nnU*?)>5nALvZuw%R;mtU~f z=ef~Txb2-kK>zN=d16EGu(rn zc3-SfQ&`ZU$6tgbS9%s(&@CP-zijDQT(UKuRx{DPMy|9ck;SPvUao75jB^j(m}O`W z3CwfkX|;0kslu1uT@aN6>g9{}d&bGuFNnQ{LbrWFvDiEyUssM(U#J#5*o0jYK_fKp zT~fbyO<}e?`L*bpvVId`O?gV_I{Z{Dfmw>n7EE3tP%&gyF@i{g8B!V54S~ieLDb%b zM`Jz^W)W-{)fPyUq?vX!`HR#$LYyC5xDPPt9g|g|%sDj&4vKY$K~16r5|?)LDCpPS zm{B+@mB8r^wBPjUc)?;l;J{cuG90#t7BFAWKd*U5>{p#}rgcA@1JIqz%An`MU(c$y zuglrmUYrQTg+U;TrFw3liB_AGb;;07>Qpaq#6iO z;sT2igJ8NF7pJWZI>D~upxAa0cV%|hF2-^-X970P_GOhoc|H`M>xHu_SKouW*sNRn z;o7PV8z&$J_n0Tzaf-B00N?ygx|$C)T`*ph*J>A-18$g>sbMsmtP1S-aRxup(Af*J zul9*g46svIohse?D|po(jT^aA(V1`>%AQE z$IqQv@p>m~`>=9=p*ISaeq+Gk`pzBPlM|i~y~VgpHWo2{ZTmj6iiF&5%rRPSz-u|Q zq5Wf$&WiUTC*m)(Fqan2o|#{$T_faj%!}AWEdu%rhZqr4#Ywm=I1L%X6_|X}2!duO zP{5n>;ZB?$9lT4aE2csmz0+cK1Z?r-Nn$yWhJV+9SKW#H#`W*#C9Q(jC@D7<{>ZEK1ZDXWq# z+mSTgP9TNO25-jFGx|mdgstrPVpPg;vc;-usyJ6Bz>+ks2A5WGBIx@%)2>0(qCrf|eC!v)k6<2O%qv{X(s@Ly_pr?K-rp96+5qTB(S5FdCb%P6kV5P}_#XJC?Zs9#V6Lok)4x0Lu1hz^S@ ztb+>Qfa`zQuQ=;WC)T^eH8|XOUMyEHTaP+1u-KF4#$vIojyRfZWy&dgCILkDPsLC* z-y+M!SN^=be8ZdEULnBco3uewh&7TZ=5!UcCp{pgY$jG`upeqa?r5LYrcG%$3F zZR=)O1EOu+?c(`y1Qp)Z*@lm5_D`Tk55qS!>rOM4qk`M`w{cU;;7^>}8-#f3z;Hx3 zT^m-5_goaS9!;CtR#1K`Zy3x4^O6!f`n>lyl|U)BMt0iJ;Eck&-LhD@oQDuPKfOeEyon_7oNUC3Vk2-TXBO_zPTcw?cs)Y* z-TbIrW!m4=)0$o{HzKdqDKH1npNf@%9Be$CIp^9CUP_eMp@%Ugg$9FW4P@;W?y0q& ze?=$)BZz@>Y&X1H`&dwG(7CKj<8O8xOV4%$5)s4hW&mm!jbSKGl0il#94h?%%~~6M zj_n*=H1LUq?C`_~4W!{b)!cf@@cjVCx|8DaC{gHtlEm02L65de6`ld!CXh|j_Rl+I zTl20bFK%tkIYQL&35NOrU^_=dT;>SJOqU)m6E+}Fh0?sEj1t}C2&Q0~aqtG531yf% zX1$h9Gx*U6Wsn+bBUuK#z(BT%@XLu0jX}s-%HLaHP_Y=`bWP_2ILiPS^Bivv0Vww$ z*nZ!dki%Wy(p8SJ^{#H|F>_-g@79f<)Kr~h-sMKD^o76{xVe}T4auZ&nVre~Hni8Q z2sfE+l7Y$w#sct-Y5sh6(oz|x;P zz(d}puH8Q(BDe(m!-f&OF1nJEjy>^QR-0Y0dR#`&s`GRwTxwfKuRlIQ2)1|;Duj=s z(w1Ivb}}IG?+JB4t(0(b4tC%`(VH?wq zi)$8MVub7n9aH=@v!mHjLolGROq%3)^UzT{nVZ#*cstIWI#h zZXPLq-#onvv*h$SmLaI%A~3RPC5PN9YV;DTdJA5E7{n{O%Jpl&T&FuuXG2r@Em|(v zEz##c^vJ4Vj+p*?&|KJEIP4?JIpdPC5ckm8(R zJE-f&wSw1c0E%e(u0XoWVrdVdn;1>sSK?{FtPU>j36XyL3254d1|270=b|zGvtcN9 zFFdjL09F*M>IPq+B+5lafninlu(1af-4)oo_XOptHQR^&E1*5P`t2i$sAhq=dd5bn z`-w3eST-oj%T+eH!Z6El*YjRMO<7{=u3V?jsKc&s&Ar-?Xml-X8%*)=<9u?U!H92J zAnMm>j;?SUB|e!@1auEbV6L~L2j&YG+fBU1SH+oOcUr$JW3$w^ljzY5un&h>v8VX` zdmNm@^6E*~`ZMH;0D@{>F5mMq(9!P!IM-~09PZkd3xIK-I{!0cxQ^TTkF}6E=d81K zM_N;=NYgG=3rr|S*Cf5obCEK){>4#e&B(FoHLi-2?F#XNKwmBxTN+zIo+{x)8QhUV z8Ec3#f2Pe82bgz2){4++oPEoy+-2Y=7tZ6ea`bhN%JZf>dOvC4Tzm3QBJMxRa|v~- zy)Dtx=LPXKFm3^cy&bMs+8)#;r}?%{3+{5ZI~}vYZ9{T@>8GF7K;msC&w099!f&^% z#dn)JPhZIl{0@Pse8$R9!Mp0to&;ASMudLbCV>*^kq__UgtHFr)JpNNd^t26gv?Ec z=$#hs^g$$YJA_X9Q&LAztpYCY0JmT!-S}J4&z^16BE#Fm#DeBTj?#j$o7O|kG(_$A4Colzk!MJ%`ZwkWvo^eo(I!BCN5%px8?G>Q4n zk-O)B=?tty*LerATGkLk9R_K8tkBIp49wF0X>Qr3Ro=B3n+f=!a|FH8iFLc1i%&D- zOONxpV8AVJ)s4-~xA@imR*NG#iZsu}rouC_VcR!!j|8xlw84u=37SbQP z0n>HX#hN$7pX=mR-AT)8i!@?RE~=G*SurkHUe*MsBCKy-Nu6)i8Q?%dDKO{Ys2%i) z6ri^DP?yMKDf^(q~%w1ar z(;fhu%C=d{ZPu-Ou7p@Kf*Z`H#-uxXqj-r?J;Ulx9OTLU?&P+w@KM(@k zE{f5CGEz%cGN>?Q2BmU;|{-F<5iu4J_KHJe4G zV0U#KPoU4`tBeM-U6Ruk8s=Dv8<0u_8D4+)C=xNh^@=8Bovap#wWx3juTS0r+?9{4 zAK$ODen$B%L_Rn)k?=QH-Vnyh*}R?KtsDcKW$fR0!J<^wU!nGC5XN-I!Wx=nO07l5 zqI&#z=HmsPGt2>_%fzGtY02|bJ*?7o;i&zEKS99K!TRq*&{0OEmuTUe&T8 zk71|B_{P8>tAyQ4#4Vcq8qMbR{k>3cxNIeY_m&J<=;zj6;n43KUq%?MG|6#mAD$~< zKGIJK68CQ9AGk`^5}j5})#CV)_ofZCvS_A4j_sEaQfrV#I#O~NR*;NEo5XtFwR+m$+fq~STgXn_Iy?fiE+j;k%uAN^rdRdSrS^ZkvCXWu!o_GJ(I zvYe)|f|uG748Hl+Ybz71l`!$dNZhs?RO+Ic?wsD9L|?|(xyKbFQXSc_&1mew(W?nM zt&g3YoGz0IxCd$#hILluaakP8DaJjEPO5q*&$)Sy`+(R@9(l z7wmY&j!&t9b~ZwAC&A0$1_vC?f=Y4suq%_1m0ZA;u%;>IqK0imf%y}=M9i3L%-^og zInhF1t5v{xDDQRv@rwA^EO7b5_te;m-eykYef^86O&tTQRj5S>jEAd7ma++D4>yQ% zOMg}duV=G2*drH%@(Vq$UT{OhQ%rha;;o5ewg4c+oPxK~{KFyrTmIpheFSu8T&u_M z0WY|=DA=CsmblQOP`Jl;_C$R+>oTN)uKM$te=~ko|30GyAD==E`LbZ4IkP)0v%+ME zeCNoBT(werxR>kSp2k9IoPb7%wf28C!4JNhp>Na@C{A&}xzt6U0NOeJC(le!Tu-iQ zew2KU`)vK=p!P$!HLIJxnuT`FT7mF?c?7i#@7j!Ho$`us`%%f-Z~S=lhh6&a?Jv6S z$OG@90*)y26hlcm>zp$K@#H1UX65Fa*LyQO86>-dx@r1aJ@HtJ?Iaujs8wG04fsRl zU3Tt_;}79qP$}+gmgZ*QVEiuF%E240*k-bFJ{1jg#oUJ8FjM@n>TqbtR(X3U^_ zwmH9!$4{Sg=|UlJU?FN1M$+VhJz^gh$DKCuk=#^psc|f18I3|gGzMSe;|Y}orr`^V z`)q#=i>{6)t3#TWhWe{7qF8`;sZ*0g?HKsh6 zv^XY;*tx9y79p>7tX=i*0CKch>)vt<$E)B>{resY60_ro_O|y3@1|aCEA4gbY>OXl zGEmCo(f<)&kFVUKIO`Fe`_4R`QRC1|Nv4pG2&_kM&SW5N(zQF&i&y$_xY(~PIe6N<(cc8@ z`OGuJ#VXO=O2R)}!(&*5(X6O{F%qZ!Ft_VdguYot3pK{*S!Bb35Qj`R!KLCx9>Gv`7i-qUE?$PC;}f9 z37Ue8u5u+J8sKQW9$4x7V@nmpbPun{@+#>15nu4TMH=_S=_0WI6V^IGmE*Gud~g~o zyv1reihgbTD0QK7D#W99el{Ba!8yKsmknY*MvnBi;*UNbor90+2t7R2;XxqW0_kMk zuO4;g39TMU4T$XjT(T$fz-tkmNptbg>}&iXNK5 zj=q(>bMNwIifdL^6$8)02wcLKOX^!iHju(vp` z4N8-CTE(`a%TMKt(IL&A$m?xKgjr(=s z3n^achEj#9=IOXO4CE)0G<}8t$fgEyyJ_h$Stnp!X=J6Y;yPN9$HlbJFDw?A^T>dn zzwI{N``$4RYADbOyjZL-;@}qx-6po(oRLWD=|Cgm!H;ip+lA?5W^0GJqG2B_+WB(= zi|jQ>s(w(_`LUQMfiFs}DxX(VE6V-SPcF)zFT&fMNe&o=g^qeUYtt@g;=A#;ndGN z&BFEWvk=!ayx_>TJf#N`(IHs$VgkXSj#SVAJv$Xvxz(i@exI?rW?NADX3>W=0$pYJ z{w|SDSNuLFjB4MdV$BkvRAH3k%k@;Be(*HS)z1k;z2MqS70n4)eB*-+#Y+uTGhWeK zwohdN7|NFbI;wnf>ls-p65p!iDPJSuwUhTWBG})XD8Zb81enq4fwCno?#v(0Hy+RaLUZlq~>g`%No{SDLYemly2%)dK9qD-9E+UmNeUcO)hLd>9 zz(C!>ZjZULCw?~<+W}E4PbHf@i(@iPGvStqItC>2CWqx?sYWtFW}3TEGrsuQn#!-N z`+MHeiW)hyj@Jyt9U*3(K`oCT9B#zAFSPE%ps?}}ig!6r=)BNcnoB#A10Dt$4U(#& zj)kqJ3sZBiP2OM=KgS04c`cUbv(v#AyQQzNw|0iG_gj7$ka4{ODnWBC!aNgE;q`Xx z%Sctub~|E~wi_susX5?9b4QZ+NeaKHbibCy%{lo^%=q?B5AyE0zg`xv`qClxwHHIl ztxt1g3OZ*B@$&#sV8UrgK+>I4F|H@I*>jly>`kOZ^FwPGfBd!kv#_1{jw=s4nUPO; z%HF&nYyJ`)+s2@sFSoiG=658YAaQkJd>OweMxfQK(7sE1(~52(Rp@XJXC?$cW=)p> zr<%BHqoA>vSbH*{?oLt$TsWz z-|wGW;<6t14iir4;CMmy(7z` zp$wOv-&xskvuWsArw05Hi-C*IdG7nwTNvycpg}e#wuD|>5}bW)O`H{jwj73|2>OpU z!TCN1Lk7Q94%|e@c1e-jKr7eMPuOsDZSHd{GD|}s=+G0loC1!6ybH8zz;&WOM(N+x z8N(GATM)u($GeI0}NebbfnUjR^cVA{{P=lDC^lThk4^TQ=`I(^y@z3~DnHsyG-YIPbDRUu4CwX*uPPP-+YlX(Eh5%9XB zR1y=%EO*3RCt!U2$U@}Y9&X&Rhn`bG0IM_J9GplR=)&G?#o2L9;e3~NHRKK}ZzPNl zkSs3$d=!oM^U#Y0dNL;nqwm4-EfJ*u*X3nRJ9_PofN83J zcU7E>-sFA*B3cQSo&+&7fP@sa?dqx|%rmP}cDf3~`tK0K`z!%LkUBspxIV%@h_oab zcCh1n?&RzLS{=$)TX%cKy#s;GJW{ek zJj~a>@?`ML)9LyO{^V5I2bQOg)Dyrp<A$&*4PfUSL=ZxvePAsIp z=HM=avVv=Sl0thZYT|H%Nfk}l=OvJYb5QcitLyZeTugpjV}I6F`4d_!Us#s_@A~NQ z@7{-6K}&oJ_~Qrt$~Wr9V_FwRl!G3|mC-6wwpQ7$;1bG}p-+dHo)n;s1M8uH50eif zRBO%ZzBms8EaUoY}tOd)qrx~?7f+qC~NF;olj>KVCa+YT4mpTdY(m+pL3%m zm44IPdf)c|=YKHVP>gL&Z;=a@$!!^UYrj%A`;CIkX$x|*V_%S8K$^G9 zKgUlvC;eFU%s(NE_q?5-lZ@0N-v)+>qyq0reHXtftu4mj)N}jX^L%Hi=_GfSc7HKA z1S-t{vFN4K7I73WXlwm&J#XcwWxi_k7p(os74*jVkDhs&Z##|rdxs5t`x%TsSy&cj zL$nq9FRAwy%1T;sj+?E0a?4Jc_G~d3tF()a#wvuUh#^nXw#&=Ys&eLU9uH{4wJBQI z@C-@JS8}iL(tC($&wm~Z+MA9Gx&Ior$volLvQ-e;;^(s+h>@xwu}|6$gaLTfFZpbk zgBEEuzjrW6jPc`nW)@v%OxPU`eVC=q83PXxLIvSsg+{*k-O!Q3paAn?UK&hVS@pZU zGc0aT&H8?GBKQRnDG-?S{5$^Wa%slR$0+7Rfj=wWB@u=SoI7NW+A(6F#O$O6>O zw~*Ma5f1I%kDXvT!5|Po9+rO!I(}IG{Za2Yi0{<=O;+~r$8U3?doqcdS;`dbwGjIL zDenHET9k}wx)0s34K^CCrOz~xSyozB=#QQI`_0)v=8U^#rFEd;|AVB?U)yDw)G-SM zflK$#wFTkcH58eeI0nW>I4okuBDGdOH7;brZ|rR9i9#0;4|Wn0DI zHB{y4Pe#yh&xr_n+rq?-^TkyV@}1{VbSmm(J{<^pY5G%R?(UJvNf4Ut>EO+8KZL6v z5%-*SGQ4wS-`-W_Q-9gs)~WKHy81=TTq9G1ejNV{$bFpe@Hrhr--j%XF<^b zd7a6mlAj?$t%y z-{_+3ZDi~ZjiQ|Kv#t|63qGs4u|^DmRrRX#`C+czGuF)1+aEQ{>Zwv>J@Q0Dqzy%^py zYMQ;zo6ROnwgu|U5AH#gueSm7u29Rlpp8rJ(icuW-U&X=upsuD*+(*NP#C1&hE6`f zIufYJ(o;@^OQvn43zNB2e`uI}I z;?i@b8$3~i!=k!|zQT0dOfIf1;jdu&XJ`z{V!aE(#EApReHG>+E;1fqT;xon01xyt#GbwjAGU z9OK0k?eTk!SzomOoVeP$Kts>v*tbIIuXE0Ve0}nml*_U3 zeep*3%iMoOxKDN84;MQH+CdTIR?_L~E`S)@_5zX}k{`Y)G9%?Cr*_b`%XEsE1*%l) zd~=;LH~Xdv}B9XDF&j>0H{JCYnAk4ZkgWtkNVH?=_~+ouLwKN@0`oTy=Bbi4RiqhNomGM|4cHM6Mof0h^Xh^zZ zc;2u4$uMRJv|6T|yXI6D`|mj|1g2G{p0jycwdX(i44{Y@wAq`CT%QcRrB>KATyFglt!)1yqwTv@sORXM zMiDcj%Sxq-gXoANm{vsK*1K#Ip?!624pRbJq8MsMc+x|f0891k;k2NSd!|7!tjHWcklH6BX+V~!%&lq(zd}1&$j`?dSJ!{n8{uMs% z?Ps`SE?BInGLqr&_c*?%XG7x>7t)5U9k9T;05}U?JZAW>@alX7vpWlhQNGy+|GBm| ziOC6ORTCM2{@gTV8^GbR(%ixRA^tQdb2tNOpecYm&gEgo&F3ha{5t{xRIv1jV9YY&F&r^jzkN9>eTmp%jGWOb^`GY?m{8(N-}Lj#2${ z@7&-x-XbHjI0l`hVY&^QXL$Oz;0=HBaoO2>V#)uCaXV$U($+6NuleHvL9p(N)&tpkEoBr2|fv=$^AWRTpNvM8m0S%b>$?~v*%~Y4!D7= zv9g+9=t!si&C}xoj(NSzy5b$3hNXWT$~X88RLWhPh*{(yhtu@P;nzA&C=3cQn?t|? zyF>iWy@TbD(r_kHMezN=E&o5Gxx&ws#0vBnxq9V*NEveJzox-X40R_^-_=b)vPW1bU6Bu%2xly3>()#*UO*^9*hOkzznfn44beBDJxGs5Z- z=wlj&@+J*{sa)6j)m#&Hj@jGWgMP10!idIK36Xa}`HY)dVRuYc_B*bx<>Axp4`DoQbv~paV z^T+yVnQMC(!(Qi)H%|hl#}Zq9e$jLm<$2~Dd9dpS3&{l37Fq1JkoGD;J}_XGWfdVy z^Y%te2oE-!i2f@WhGpp)0y@1TC3g!E`m<5ri#UzBK(QcxK4$4KJFXv9o7jp1$pPwb zIW5^aMKf?#0ue{ueYibwVyJ%n$=iErIW$YoUWny@RUiOq{zI1JQf2X_2gwoNoH`iS z#_Gy$(>EMvDTj@G0+rq5hol#;LuWUfHMCx`6Xfimo}L=niUyf?2W!z!DC0L-Xq`xU zJtq5#)4VoutN|_sV00UxP{0z-b)+|?=JB7;syywSERSSCUs-;v-I=t)Q5o~0p&&&2 zmzKBkrZy+LhGUDP_6{WWer3>wE9|7VTpK1Ii^>SOKjY$pT>S4h&WIMb%C>we&4R9a zcHoa4GguLYNRnKIv4Z+#vO@fZbjk)m^S zb0H-UsJsIoOtaK)*-%?*E5H(V$8y|RVGjuhU$0rhSZ zBN9K|0X>Z&kj`e-9LzQ?jrZ(+Q#~_)`1vsQS8}edYtr?@WJaHyKHP%YxeV_A(8*A% z^QB@EEY85N%dORob*r*Giq&dx7WM3#7!CO9$1=|z8g92%gEt*+7u$X~^e`O9e6FG~ zhf{#$K3P-94c}5$yX^>MRlD=@2ujV&SJW_Zf92L+p7wa?ZLVsx>CL-Ra}bI^;bbla zE-BFfSG%AMyBqI`X(R!;-gl8%6)@i_2)k`bOzE?jT>Y z6?BB)34Yud$JsC8fTNn(fcnNonKQ7;OUDRJ&#(S>^DbL*2<#}c1efq!qUpSrv;NR+ zRgSBJ`^IfVqw?V?cBtj_3$XQ064(&1yKF=*EV`A8c;4(0ANrS2V-hAP;rfy9UT!tyC$)F26 zzn=pj7(PG}7Tkq5=@h>28~%SKD}A-}0gj%+AMZaGXb^O`DiJ4esdE%Rk>1UBM@4?_ zTKc!eYwJbd7v+}6@!cv}n)|Ux-X151fUHJLUBymXd7I<+Jrv-|crFtM%mJm67$la4 z%^)WC{ovHY>Bk4s)0`)LS<@xk4|C(*iuaJk=IB1(3BKz^swt*w2MHELk^;;TWl_jT zAmV|lNLVYRakB*rw@@&W4Yhoc2IKheF+8p#(xi<$gj8N+&b%6bP~*2X#qX@yhgZq` z7<6y6_ztRjVu=Jtca#`_oKj)zpG*>ewo%;iU21-t$-2C$61p+HfWkvQDw$-X>l)Hn zw9fg;u%z_6A!w7|Y3uGoU=&~K2HP*$CvWk}?TWowGZe8c_TM8&4LSk42Bl>IjK-0w zPi?K>eL)z-zA&!u|JBQ+)BWPsFBTW#E(%eA4_Ksm9oqUqtZR-$?uv6cTn|m`BT0$5 z53iB;d)(FSlxPXx51|4A&j&yoyRdKdcz_z%C;-?I3z$Sy|0gBsH+zB9y75Pmkk=I? z=W$Ru3*UgrpP%x}w%7fGY2-V)8Kx;{qo?PlIm)rl%&rib%ZowQq@73MNU0p^|pw3Cr*N{L3sOXE&SdmL=`- z#(%BeQjx5rWoe=IKf6!dl>74jz1o>m6ff>BEo!|L$hyw&fT?f~=bPZzrv$6A28GZ9 zE)%XiTcEFi6uKZ{(`LF<*kNtX!%A6+f;0U&5Xc)=0?NkmnzVM1I!Kjyv6^VKDl$KZ z*f|~hki2>USCtB->xF8_$3iFkyOC-zFSrhK$G0AFXZp3b9x<_IF3Ywga$Od#9$kCy9QWf za^(52b}6OR6>kp5OQO7WUOc;C`BRo2mHg(U^!W!60eOdusdaL+)EgHso$=vz3fJDO zkHE{1dv#k%BW%GUBLVc;15!YCsHyb3GlQ*xzQXFy(~r;r3kB=(%g-oD|Ha@Ry?(Mu zC7G94B)^x4slE`E->#I2$$rO6M`Z#WA9)Fj04@1d-R}k#Lus1%mKf-*C#FdQY#W;BZ z9rjyjKsoz}0H0GEt5&JcRI|6prv*8jXEYMj?fl6WJ{6 zSUT0lpOxCBU4Bpyrdjy+o@*gr&Ao?>iV`mSK8<}NdBSwS)+ssX<@QB+W;8^$-{!4j z@AE>AQgbx4JjZ%cEs>9X`knB?S7%HvJb{Vy&Jot3y;TfwtPr?y0K>`&th`9kOOD|`bq2Q`xZ8FW-q4Ik zX&Xg-fYI@|hmbTlof1xDyWwWYN9pH%MjGWAca$$1`DLM?Uffz*Q^uzw#Gq|>gP7e{ zE30uUJYCKAvBjq6c5C3%>tsd5Nd3S%*Qx=gg;>(Kc#JgRiyAkm>5*SfM=XqTRSRMp z15$B&yiU1@YP#p=V3D^}@t1=)0E!}T`dgxy+LaWL0Q>j*3~$mqHfH^KmeXRVoIO9}ltNg#hNrn&T62JZh zs!m{l%6-lE_a%wek!$nloZ+AauF9{$-TucyC3Cc*=+lAO(58Kr+o%%dgaul?jv$5t z4((UG^5_TqBw)%2AXCa8O70dJRb^jyrA4rOvTdx!JK5w2%NcSP(NJ-?8DTr82; zwmV!(!N`AiLyu%Z?>9=Xr)j_l6oEr9md$9O4>sWm=y(45H{Xc7!UV`Qm^+sYl1__T zm*sx|HMlyNw~NS5$`K}5x_Qfzl7ic4zH4b}@Tw#{EM595^N033UPLpPEHHxeD$ntZ zk*rF|j1-RhAJTf&D$rp$L(l!vXob5?AW#Xk3EX)hClhjf-*-D}CkT<1UFC@xtAHkc zW}soJ-Xzd3EIDST(v>}?`+&)WE@fC$vN~WgHkXnuA*Jo!n9lTtbBc9}s;=y^mCjqa zMApBiZbWrgE88!2oFlx(8nQb*02xTDda|MX>?|7VoHPr3Qv2?gu>FTkfKqSC#u|pF zYlQ|qqR87eDqIqq>!gY6&K)j9Up$u9V;=}Xf^l5}`L zjyif8veB=!JkYXTGuoKIC=n&YkefOSjIs1HI9+1Y^Iu#IYh=`_=lDUE6+t?vBFeBR z_uu2l>stMg3U}iW2a|#%WRfng+Rd1!MPY#bG9Uq{vK+9vch*kvU)xVinmSF7mfhEQ z`uE)0cvVihLtUT4QhcL1e~cli4F7sf_#Vl@`+CZV)<}jnM691i5t!m+p)az7lqePB zbU?Z(x@V};ylciTainzs+}e9aU#y*QkW8TQ_vZ zCie}B7!n@_4QaAuo@&`_c7ON68yIXg>LAn_X{5@#$o<#9V2U-r)_A1s-2^5sZv+C+ zPDZREAm!4Cp* z4>nM+_bZ7fbb5gzQr6#n@!m(wg`zp#@jiWAbw8UIu|^K|E4!}-vQXI~@CHunCS zb2T0lQ#GHea|c?dxck!WIj(XM|GN7JR;SHn`h7Ovo|0Pv_5ggekv2Q*M0rcG+w9fa zP=|K>tRL*m{K?Dlr9Dk8azFFosv^mI8Fdr34YZwm{y;GO({W@VKHKu@z3cC^o;x6m zQ)-~av*C;b^YIa-jd|OeNgn}{W55R4=plpRq_gK0893|KuVNQrx`u@k4+qbChuHI=$_%=?0u^m9@ z4i@%%Sm-m=^a~gSfEM2hzO2JOnHd65$^Sq~z#aM!c>z?+#@E2{VkcjJtqo zf|Sf*!w#5k=rJpZ*m-zCQ)JFzB#a>lwCa=sK=If=j)_{E8l;^jiHle7CMIK z^i&%Mnb{V9LEvB~2(emBm)j03J_$F&Z(d_~?wDs8c4kq{ev6hdE}Xm&SSz#l-zoSgPt0gaYj75LDXC;j8)i&E?m-b0_9`hrj;xVDvz}WqQPAJB@rX#5Mr( z&a0leFXy_^rSwaU*XEGwR}B?sxXq@G!2`d~*&}FDGEr5BvpaoCIsE^ugdK=n+*@2dO+LWM}ay9?$G}?-(dGnGN8kdOob+^1nvQf-f2QI@v)w z%7R5@n)x;P{850$PwFQP0Lmq=ErGk#y{*7Dw#naR`x;9Zqh`9WGw`A14Kh0II^AMw z9T826^*Cqn1hV?gxu;Kg`ne>a!ziqMF{^$A2i*oXp^SV69hSf5V!p3&14A7#X4B3; zZ8Pc}-S*~PE2zx+3ZZ`b?V&xvu&}O=T=QS`+$&Y5_LQgK zOlw(mR`WBtym{v0*IoQ*Fa4@N;LMGKX*S&{H5Dj_Qky2}>-)#WX&P{k{q;3?cmhJ8~+ZNj#NnMl<3TsMbE7<^D%IeMTt1|I?J967yZ34Is z3DLym5u~D)JxZ7>n*-X+I3`!EmY4r!c_CK%6~pIi?&nz^h4+1u_55`{>|excDCYji zs2I4nv~xnRlP(b}P9kf_EhJ-Ctzq5!JoX;mJ)KriP1KOD9g#?)5eR`2Qxal5r-#lj zJo(f7>M=q3Z$MF&OQr~&yw#BN+=EZ27YK{XrDc`xfFU)*M}(dsP;){H^F*jnf?Ihy zwyKg$JX|baCzF;Fu^VYdQW*Pm R~4!*TbV7TU1Y*^PZjoX|*I8%?wBKUO-U&u!D z)#Rc+9oJK$fdP}ZwaNV0a{F(Lol_EK#~o{&>g*Q<#SjFC5am1!O?%6u zPUDON54=MN&k2EBZW<>JcN;w-J3OC)Zirag#HKY@OaY~9_4)Q>3Ed?_6X%*oUw=_A z`$0RepUA7bZGojb5Zcz##AaJb5u|{GR??(zJBF>G>PD2ff{o<<<+YJp4xubwQMAZw>>EQ~gMQQn<*_SdU7W9@8E?3lM~r>j-7mIlc(M21SUxpEQQP$k zpUwnJSh9NFEf4aiF$+F8Xqy-&`yZ=)*Csimbj;iUD ^kvr(tr!whOk}u+W$Q z&6t2xqDmdKoAPZnXD!zGF{P)C*7eL3=qlEukMeke$hCJzZYNjP3e#LBYg|k4lz?T; zIwh_-*j5N?)$El~mr`FPc0ElIP94ZWFbuG?)Qh@;VDhar>{ruNc?3b{$Wgt-g3b5_ zp}sF$R(>+)-M^RISJ>3|TQ*kkYiniYfW0qn^a`M69ly|%c78f2=Bhe7_pokOYczS2 zw5__l+C}O68;frAv3%=G%h#em#6dQ5Uhe(6^0+_!+{=2c8|)A7wBu1Q$IF|8H$GO` zhGzjidc5-nD_>$$AZ|dm3Dp2j@<`Tt*Jgy(i&b+DG<8NiW)OHE9TCwZ4hwk~G}P$r zEW?K=uHdS+_y=5BuwHtWKyNEN(+b$V|1EPfJ3{VTeZm*g8+9Ub>GfFAgQr_~F@rFh zYTlmqV@$#%<)j*%6qX{fi3(EfX`}vOs5Wh|-lb2vs%KrK@w8! zVxZKI$HI^j<-Iu`hG@kR{1kS1+`!I_&GUeaB#nS zkDFR-iA17ph0uWlX4Zr3R-i91A18FWE70RgD2#UasbcXJqY&lV`MPKjb_f=GxHUaa zZiVTvXMu7nHUXqX@`(DWWBtAucJ`wPpJ?lyD1n{Z==Kl!^3Gb{a4%UcY#@Q@~ZV01or!tv{Z zV_$inrz*xpbXecdh#|_1gkBoQm$dAn^I+kPQwS@1!yrAe*=X3tac~`=Qy3+70Uj

    7Rm(C;LZI*Cxj&Dfg*JbiFrd|PNM{U@li`SzZvNIGU`04q(jsU4AIWH1= zx=lUM$Cj#shCHvh;B;SBdxcX&6-I?p;qbMU5jJrlls9YREQ8lY?-iGz2&AJB@33UV za3CR)w+|3E$+A4G=yR2&=l?q-f9A7P1FOsLB6a}f*DoHCLj+(&5F*iGMIU*4cqc=m z$ChnJtm^vv+V$+^s~3nQWn{nN&b|h^t8NTJx8V1Y8Qezt&@Uap2!JXASbfGNQBbVF z>C_Ry^7rd2_j~ov?%-l^ z_brKGSs2xGhZDZYg4N96~OJAy7+fNKLm}s zg_Vp-&eJi_HjEUx`o!EvzPz&ZtsUcevbV`+BWX@;FO!{ zSP=ju)DfkRrJPsQD>BPDoozrb=WvQ0c;Q65cA&(hv51yNJJ2Zs0 z0QHlO=-&n@`^hZt$0D`>=k<$6aw7iMu~wDl_oR>dSVM`4yURAR2KTdjefR6_ z@JIeBxtl^$Pi~^QO!N}hiH+D8N*C6s@>oB_4rog&2fsiockiww$!XRn_|m#c5o(N` zT;qgYbfI1x0MJS3NQaFUrR)z}x9K#qW@|4v`<9tKD)blUC%n&|H!OdZtbRz@%jeZb zDXz+82C6@0Rj${d8^0Zwm{&^$mz1B-;k;SMXQ6q_l4x=#o(`BW7-Nht--@TJ7JXy1 zZjAhJyY(~3oPF>@Vf5~_s;#-g9Nrok83s>yvoGI)8!TCi|73s?M&$s7>@=kd$#I7b zv_V9b*~qJHR(Iv?0(ejqdGTkKx}Kh;Rdog@fAjlQs6!{YVM_zaXag2QH{rZP44-y2 zIl<^hi5AG9u^wi~wq8imp48xu0j%zylWQuyUDTi)TcvJukwkmQrYw-7HuiHQC z1@=6x=t&y103s0yJ0|~#L5KYzzxD;@2|z47Sa|*oV{vB?NOou);cTv9hoNnOknc4~ z3cMju66SH8Ow{gzQj^Rv8pT+6jn6>*m8C^yJus(g!S}V~`<+knIL;&3u(!d|LGJA? z9T5Ng0t9ateOCrdJd|8gQ(3GvjR@}^mQC;cb?UhuIXBR=$|xpZ*z?VUnvERVjXwwv zA+fHL(M1EOl?NyPKnypz*=qpv=NQ`WTdH!vuYFVJVY7?Wltpm_dgN^Rqc_K2S~&B7 zLN3oMF=Vn!83xLQ!(BE+2YJr_?NL`EP&%$Eom5y|2weR-x{xVr)rvsn&<~{7Ccfo| z0MWvNFY#{RU025vm49^)z>OKXipqd;t7qozJX3*b&abDIx(@v|en0i+TF*Tu^4q~B zJRz&-E2tk_@xlNM3KfDNfC8wAnUbb5uotAGK2Dtn8InpQ? zb0w)trZ5kNuBHXs$KH^5&Mu6B)5%q5kFefwZdEzTM5lQTm%w7C7wwQ@ltnj95dFRL zuxkmJ(MgTiiS84YRdOkd;nN54{k^w5nI}Y-cwPlVKp#_zM8BJZ_@EfUeJ;`741?!| zl4YFV9yBmcpY6asqvm*5!r&d+<7YF?^Ev5Pe8ZQYxJe`&BBxfb+R|q@w{qhM+RdJPRpY<~8@RUO<*C@7x`)h(2L+ zKyT7v{28@)N#wARWPUu0i@5()Y^^IUSv*#dSLoylxbS2<#RS2uK8G2R-~*E))1&VlYYO5b@|M@yTg}E$y(rzHo*zu_Pfj@!rK9*FO#iCYx*2bbRk+rv zHRm>O*K~X3?Dig6$5nzGFVgCCdmf3U{4p^j;`TZK4BR zc(`d;a;%om$b~W{J~0OCUghWLVE<*S8g`} zTl8EPB3Md1l4ZYKcsJfx#hjb+q9E*~RPs{_y2;iR#WAjOWhIG)Gb=E)#M7l8tZUYV ze0(sy4hy&^4)o=~C~QPGO!$cLv2A`?k~vpubz;j}7%0G1o%BXVzb>hI&z^JTqzwEnzp2*EGlLfsxAecp)_lKVZ|54CtDyOs}C>TtJeY|8w4hPaH)iEv&0!bu1 zlcVOyu{cH46I*s;L|t&DRuMlVjjT73*Cs-B`4b*Pa^_^9OKJ?}$bslfa3`w|xtP^ak@0JOsZN|QH=uk%8f?yFJak!<^voVD#syQThU%;`DA zu5hY#Ca(_NcXb*`#65Ix57`P!Gfi_?2Csa$&kJf024*2DJiKXfg7O~$g|^fs#-nvl zGN7Vy{ZH`o8RGv^!=Ws~oCy4(q(OZ0oqU7fK+55xrKJ{)?p9Qlklcdqf)?nNS4i)z zAH;mqr=j~t5=~5;x)(sr+o!4@5F0vSb>t&Yrp&;VPxe#*tn~>C-kSL4ybRcw7+TZh zBvanL_3Dpz3FuZUTVmIuSNA%ahVI&I?BmHj&7j5;Np>pF&M+doDj={ju4Hv+ehd%r zSFsW%$)pUzLHcu0qZ9)9jcmH2LQ0Dk!b-qkY7MID={MUPQ)F$>V zeNCQiQjJb4)jYi)OlS$D1yrh-mNP9CPhc}!QgB0W!prF4B0%Nb3Bx?AS_0%uqr2-s zz5)vTKcBDp#OO2hXK4s>F;4AwzXiphR|5tv*0>p5!PBim4XVzX`GX+#C||#VKi94^ zD)hK1x9!y8RX6~L3ir?Yx+XjqKqsvLhDA#B)V~l)uf?Al^y8nLBF_Zq48H07Ep*J8 zL$a|&V?V+rg6G)#`P-V|P93wSvjcymKme$zj`cM1A+x`K!Yr+9Kt0&xPs93#C24l% zS49h=?wJ)zi;9~Yl~x;VdG_01q#Jfv;^qh?x~c(N@IoqJkIf@9B!#Az(wiwt&O&N4 z%Q>z6AhriIHE_IFB_u`H-o_v;u{|-u)gO1okfr%u!W@ zxLKK2EQK`#s$T+I*iP2OTo;*Dl~)+26x#)-bdKNcX#GKm2(d3{54`T>xalzhEC_JFBFxoh|q=zL8{|`2f^)i@;{xbSwNAwC!l{{SKQaV68-o+7^oX&{5C3nRA@a))>(tZ zb4C41pQ~*bA}YkuuEhI11it+?&pat@e2a~0X@KHVoQU3kzqEk^dO?3OPKk#>6&MT( z-Ga4YIhAQVtdSk946NKjXYg~rNpvz+^!(@OW(t9f?D{>}bvetiVEl~wdqcBRRyE#G^Wg}4P6ofMuA zojy@^(WeaEWuHL*g-7wqtAsRMza19a@@D}PpzQol-+isS9O(O_^r zmc+FH*xbeHaW@Js3$mn{wMxDeYu>1G1=%=;zEWHL64^fcQm+$uju(Z$Bwa$i0H&W}{ZPA-PzT)|GG;3xG7;oIGc4C;-D}E|@Z6npj zg2p>b{N`4eFdv;ui7x6kI$Rh49lRl>?WVjRFd%+`JicX}Mz6-pU+ZbINXYPns|CJ! zCIeja$lS1tcrD^Opit{-I?**tn=|Hy(IxlY+0*YPgGHJ@I7)`tuCmF4=JUmPua4ZY zS*#G01Hayfh$6yh7r=r#<}_fNybKnB&wk$aN4W|49`)n{Tjt7C(IW-hQ@~B(pKek9 zU3dDO;+*fvFIBsA02CQ=2{2JFS#*ihK(&5^2rVxG!xim%5zqD(?!OC5+P|mA*vTf( zGH;Vog-Cj~8T*M&-93>;LzEUZV| zFF8)W>885>_Qg+Vy>UOUty6D0|K0M3ZsU&|$4sttmu>4U+qzK@z?D*0>qZOw+Sd57 zsK?`}0L7($Jk~h~l65?ulHbGO9W5KXkrWbeo^aMe9oj9u$JI-@`1zjtMJgtqURViM ze_6~8)|&C71GhXkRgmFo>_8g;5uZGN+q{eJS)m(SY!K#Fd!jnvH0JxMzHx7FtwgZk zu|YzQT9$+KC{8{VB^D+3g$AMRCLPhDL+fg4=+y3&G2^om+^oNWaJacL)jyL!hg>I@ zi*5HUwyq_>48P`AhF@ezT4m;{jDvG1K3Nd*JFcy@_d0}u7*0FG`{?WfORhdn`WANz zFKfeIAjK^r#MWuh`)SD2bSmO*k*Uwf9fOh(W15?S5O)Q8mJ*OF9M`4_tkM?McAtdnSBabJX)zuN^e?e$Oo_@ZOGLE2_Q)eGxTqi4@c?!KH}UL$K=sjSbY9C5#+a4mqI+b zVmR^Z7=h2IhX{BPQL%NXZH!4G&GJT;uM0>5qtzirJ z6geht@{43t8_WLr4?8Rm7D<9B{>ff6JtArI^GnK(J?hn)lu*N27!3n6T3Owg!ahIV zKIote;*M#1knyf%7Y3}}`n8<)AZB?zB8xg7q7wssImDEI>KQ&#^IaC=ZAj>(Hj$)q zS%01|jH~Lcx&WD>^48vVx0_m}<|V==zc0anyt9+^05qr!INb?rJdXD#SJ^NZM?VBD zkAhYiLvGm5*hMCB@uq(R=P)^j^^iM4DluiNW=r|-3y3r0uQsNvF6UgmRSy`#{1WqQ zsrLxxWK&3I3+t$EapzrnL2(3D4IXJuoON0TJ+wrW1b?vR#ex0pqBwwTz!M(g(Rh-P z)xRlZmce{dm00#$st-dd>vvtt>HZ%}SK`R@|NrN{EhM+0L#13LgpDLArJGzMM9H0d zwz+fPp)gnJkRvJgT$!6fO73Bfm~D=Y9ly8F_xBI*e((KyJ)h6V^Z9r_99pfykByS}s-TpfB^|Daj8ox{pDG>3<~`Imy(Szr(9B4iO? z>jTzbjIxiO<%?CNT1i{#zDrPN+*j?5t+%3`gyX{TyHwzGEM(rjnHDP_^3E=Z{Y!Qy z`oH=wz{lh9pa$!@U3y)bs_x|0%7|0rMCz$;P29*8;o$iV*bUH&Mg{T3HGk^EHl4Gr zIIDq|sg*eE#-Jxh@z|Fv%FIWia{W}CUEtK%e-Y2|_-USH7GS1XvzQhOsUT7V(Z1+c zUOd;wmAqqL9dC3GMy_nYx^^j>MnMNS5a za(ByCYU{JvAC6SKdvF0w$KMj3(ba70ZXJwawU`~EMpmk-wqZPhdR*lNVv%)l$Kzb2P3~LV)59E#8@C)P<5Yr3O~oH#-9z4Bp`F$3VD?`Nj3X zf#iW@?1)W~c28b9dN;+1l5L-`8>{rEydf^5W5-};Z+F8 zZ4&D>two~ZQ@;H?IKfq-FFCfbBY;$RMiXVD5eh~Ru@%xl?fsNgi5T31gjGQH04%Z6 zfZ${{8}wbCE9-k7XYm=f{Ht{6q@JQOC^35qT$J$d89i1uW^_2>8 zckSX-jy{pyfP)MjT16jKTIk z*|`zOibPg6@1z6N@A%mNVVmBELbKl`>{G)gl?(@n%*N=yCkD7cx*$YCKlZr_M9GuGrfN zY&i-r^++>M7kT9Em{urO2TjtTlYi1E_6$RvMHNdV8qwm9(R5HNuKm=dH%#^Z)U(co zH(QFBK6p5hgG`b}ARV)m5XcmRy?6AV(X(Ly#VlRpm)8R4@=@R|wt%;|;O9}`{?Y7h z5VM}}Uq60#CKMbvXy6^lMrPm4f6{!jdn0tXY*OV(pqy@c!@fnpDThF%8z{jnP;Q&w z9G#!cU`Ovne^uC-ab?Xs&$zL`?8(sL{&9p2sAL_Ff8w6?0X$o}qgCVb;8D8(s53#o za=+0sj?gca7S#Gs%TrRXMQg9w!C&HhDaQGb&z`$eFJf(Ik#d7CQ|caG5zm@_`?~i4 zZGQap^_6p?tL@+E+TBhEx1$2UolUWQO`MSa!_F(LNZ|3N{kte1)B3!Pc(;vDc-kY?7<<2 zXwQo{h(|ADt%5+SSmxeZ-@$~?{Zs8r=S0?%GwcWj?v88n2WV$?q6xUIWp>5bBIDS2 zX%_p#=dU9!ve>U6iwKeK!`ncLk29F0RXj;)+{$ry;j#~xr}1AEes{6e-&v9Uvrr_n zxRqc^J*{ViJkKYpD(_HiXeVgpG@u2Ly#qwD-g_KJADkbDvcFC@b{m@kvj3WwURT3D zRGzyG)6o<`NhE8;$Pv^Q#(z>z!dX`sVJUk&vUk{G2Pn_{6dr%c?gkg?*WDy|TA8Cs z;^Af7o_FZmYN)i5hM)mbt2u_TtC2 zfSo7n4Cze)UJg(o^<9E}u1v|aY07QoIr4jVC{UKGBa2ZBwjU{qJduxCI5Mi`A4ZK; zw!!+k?*+tol|V@}D|9JQ%6FPZxXuF4LwdGLTa)(TL7@{f|R-g+Bs3#_V4%FELB=~n?6vZ z_}jkDt|7qy(g2(QQ)hPAd8=Rf5jLZ3UL1*O(><9{f{HZ@`hu%7dv&s{R< z=AoI~-XxVU_a@szfm8#9jjQ*hZtO_hQvHEFv-cCFq6QrKwnNfeAx$00de_Qs=9ltz zaCnQzH&gY$C@%*2FilGG2Fw)UsY8n@nUw+?RRZ8?p$`JnDg7+=koPSS@_f(fi)8n3 z1T`g5!+X$M(<8$2GftH8&Zt0&*Uu*@3j5SIK+ zt!MUqw>(BMd+an5tCq03L(Bl};ejo#>p<7`_mRm1(AIVYG#ePU^Y4!);&)ZUkC{?M zP?A0Uk>Mdv)2Eb@zZbRRZDJv7;T#DosT^#EPbnERF{2M+nZ3Azv)r@PxzH<&#@%Jb zad=?9;KZ|YJ(9b10j2H(kJRarM}Tu8UoqhC;n#Ec+oW%T`K$AMN&DW%8>jh2V=EmH z>JQBLnmW0-%GWD#^jCMB>K~|Lo9-#1j;tV#Ox!YpCfwg@ez%W($#BnP{%v)UD$w-* zAWi=b^xc5CSjoK!i0SkKBjmHd2H*!7#*LO>Fv#B#+{^wp^V0~$p?v;Qu=^(IVna;| z<+$_@uAUMOO=<*Z2wPRC4^7?-kMm7=hXUww3%x3pnV-H+T+{3ls>lbT49tNII3Dv8 z#ue))bLUq9lBA5?r$3kUTNNH;8+~>X-rlO0ffDeSj3zzoD+#rvIA#czpS$H(L{d{nBZb0 zEmCr$vsIN4dUyq)iuv~&7cgI)4D~#ed4jvV7?uXc7s+#Q)wiIZUft7Po%yjCR@WcB zv0gWFGhIW~XZ?(Zq_xk%GcQjZ9^Xkb_W{g;dOm%d>RMan!M)Mg=kwn(>k4~1eDrD; z>gg~?-5RWcCHBo&)DL)C70PQFHEGA6FfFFPVj*BBs)=GER(^f5k+BAHn;?LwT9JUX zIawuJpjC#k%603m7l`VSB3TyH@3a&gKtGN6S~nmeShp+F8|D6_KkP|fi_$~ZXKya0 zSOZwr8-rCMcI~=qeQ5zs*~xZxBIw~wNkO?QNzyxBI9jVO}=7JL=l_}a$Uv9T3V0yrW zOT$?7E_k+s&gYvc5K_G>Ve*Ii<}xY^fP8Io%hcPbsZ74bHO3$SSVF|h5`Esd8+STX z9RhXmC+kWVeEd5L49Z-{3qAe*@FcU1y<~397@k{|p!Fa5@U&ajV4{>guJT4&B`Tvc zQXMI1)Vd~>verqv3>}B0vyA9iV%XoVbvoQ{z1IKc?}Jc2yh8@LNw!uwb^ucE#M<|! zhUfnYEd|(5fRoLd415S>^i{H$=6c2|$35+SKJ*!mr`q~KXupa{q#P5}4eOd>#ie5=0+8u$(7Bep7(YLOW}fdA>gf8#ku ziF>KHBJM=C6eFHj8w*c2#xvQr>Yu>FEf#oo?{-eIYDHTgdg2@aIf8<5Zv20d5G`-Z z*xRN|hi&t#?KN&tK(l$jI4NNN?}VaY6@c!Jk;e%=y6Ad}XRyc!oSs#a=ntD)31C%Pn4Xn|Go+{@A(Yjn-g7zB16OiQCX| z4^~L5p`5(x+jQiumMv&pQ0yYm zs7(Vm4bNJR4|NT;ShN8A|2ehxk&zT_Ez{VctIupFvH*$>(*N@acYr3;k3%mKaJ|! zIo}+B0yk6G`l$4g3;&&(012*u>0pBTpngB!p{3+G3S6D_S#UR6KGG+FZq1I3KvZA{ z{c=nT`+8Ts2%WDSH|J1k|Crb5P7Esyy!U^e|$?!!@j8Of-z8fXZ&5Q$f5`v=g&N*)c& zpzeL03b0|>5K;KGH}hW(7ZMGOKXRb;LWBE5oCm$`L;JW=-9}BXF&^oFQt%eTFkX^M zcFOKGIoJ;@@4`_OpyPC~JJ@qrO>25)Ml{kR*cgDV{u0YTYP$QiW zcUeMobcP)_TS*;qU!g1nAruvMbh~=arEpZBJ|GZ9x9Y39+3?qg;SUaBpfLaS(d>v; z>Nr5kRoVk2-^_q2>kL~V@Yv2(pu*-8!3Mz6xHG_zEbamMD;i$HW78hWpW9khZiT93T;QUw6_f}YvI1#t5V z2DRHQ=LbyQj#mJqLn7zq>lKuG*dmYB|IpXsBAe(y@C#xK0%RZN6(_yaNE&)e_C{5# zc+yoU`^;e~Gm>08=hsg7UVtfN7Gh#JPNt%O9(?Zs6ESz5r((eSQ^K)Uhl5VXs|tgS zi7UF&tg`%oSNed+-14QjQsE4rRKeJPS36FhJJ&_BUKFSoen2!f*wR@lmKb{3w{4Ma zAn`4&`@9Vr6J;5PJawew2Cr&=(eS-~GwSbo1VWknhnWrXaY#Hya6d zj$Qv&$$GoCf!*bNr*(<#ydZk;)U(@?b~CjE#QBi4nPT#N?hJgmCpE}oZv4Hy${nOL zeFed%(+wkaiY)>p;+N(c-)oI+-H$|uA9M_Wq1QUJ*;$_#wA_iPaS|&8CzIPjIfa zm=mMDQ(IAtv)?p-7pOqDo0XL#_iYo%xq-#@U^GdLVV3LIj-3u>JD_u?pAvq){#5%W zxcaeBUxv)HtjT?-MWDx!tQa#G4|if3R6RfiRUEM|!8QGYo%qw?9w#IB?~w`y9!Y+f z-V`;38WzIv{TlU%BV})jX8{vZjF9vG_*)XI0l2Q*GTjS+p^o3#|L(vS|J2UP7VQ2; zBT&?sY|GjGbsq^)b|@c7}y0 zzv>KCY}?e!uye&VPZ6*@E_aR>*~m>C!Hp(6qJ=agqa!Dm_7rtr?#XO?ZXV1%$AiZz zPQySC)0F4fo**8XXK;o+H)$&D($|iObU)oEG4BF8IflJTFy3(-RD1abw!k*LfNFtD zM}er94X1@}C9$fcfQYG>at+i1Xnnf3EPRw^y%JQXGQgME4m*cS30kUmz1K)-=9#lh z9TF4y9M#YrcUy%p<)aD_w#NP{y<70Fh*{9Bmfj~@ z>PY<)2r`{YCDADHyj23FH~N?>O09C=W>jI)I)@}c2lz`}Qu8C{ENdEYc3-RobV-G0 zxe6(VGC4*I#`|OKyg>;iu82p&Vl;2t&xa}qefZ_RYm~Wt@R=j6RFxjnTxa@oJjg)7 zY3S-l_EX9W7{5VX--G4T7^~F@BY!V=Rvf{)O!+DI?`Qzaaxl`8+Lwwcl$R zk_C&~|I)xQFu%KMiyb$0#<`A2AFj%8}A^o|= zOwcz0bBI2c|N6w(%-s%a?H1y?adkWV2sQ&yqAAg$hN42PHQN)T zJrfC^&elhfk$|15n*u%ssKO46PkIg!8^6}O4_$9O0F{D~wxOf78}&DKn5LntnWr?I z)Surq&Gz?n5RE*aKNMI;x=7Y+3#8^EGILL3l+Vx8xRjdiLF+eIst{3TR1XV5go8S$ zEU}w9MoFmV%0&?HbH@l?XWKiN&Zc>M{c|M=MdX_W%$H_Xxe9|pIc>sS8gT@IA1QvW zL?c0iQSFom3pXZ0-rBvIKGekZn|j*~-PSyQo*AhbxctlRBzW@skKd+4;p~%t!ik~xc_kq2_+@_$npPJf znD3H-*dk>tSnjniKbf8x8=?sBrf@LsQoA4S>`G*Y{3NdO%&?GkRgnn-|0St4bZ`}b zvp#PCQs&x`T31C+rUipi4Ad^7N{rTQN5a)P&cZ zE_edb%Nkj=weSAZJB1gu(4D5~F>$`<(3+Mn+w-&lwu6b|1NTnmGo@U|f4{4JSz)^- zcE49ih-a?Y$tnBFQv^zSfZL0b^5tac&0C9&>nnTNeU??}5zcgT)T~OX1unB+Erm5c zBrw37xymfd0wDn{mOg3s^6=xJwSj~4?s51SUwH^B2?Cq;lBCWsfN{_u2SWMvKQ}x* z)<8*m{z_2aQ5g4vzz`($-xB=g^VGEzvYDCjV#XoPx|lUg>o}KSCXr=eo}+HG1590e z4(--o&>@o12eZ59zUy-#?5X|UMf*N2Ya5L}tiJc}^)qHl%D*D>D#x>=7OKgiA1wJX z*ypp`@AtCRKp^#u3e$P27!@>Y`4UWEBoyonq;eDjnrmbrbw1)d3H;TAQzHRt_eDc3 zh5=P&))DUEtL*Q@LG^^D$#{@0l^Kbyr|qx4`oI`ugzzt6(a%gaiZj#OeWW@75Qw zbs<^}KkV#bu3g@@y`j?(QQTZ_({f-q;=Hd06w+0UU_NT!jwSh7u-!?R`F5F+LA`T< z`GH(??AC8QReS3B2wB1uC}`Bb_+R#GEC;%wMSXjp`zYzO3xY`$UIHxKFN-R^aE{UO zZfR-W&+zoDSC!_E%xShL#VX zGU>E*#X&zOMzrvhgDyxgI5;$quve5WnSQB)scPA)4>nVdr)Q1bEh15TcS* zQXO61FZAn|3Ds}e4zzbPp$GRlBK?*UFRpTLTxqXtGkWv>_+{E{##&sM_rF`Lzz!kp z(d>9xA>_e(q|&zj&M?1jkk{yFKaD{(9ahk+r&d2oJup&HI7R~>sd^cCJBm)FLnO(2 zf#>~Tr?xFy_hV1;Y^l9qa?SAa3i;U1wk9&vk<{euY{&xtaO&;FSgQu-IQnYhxrI38 zf>c4D$WW->*GLnPapPY40Cm^je4}Sw4@;a5b@^B4qjP(8q1|@+;Z@MDK7bH}AnOWf zIE?#bv!3UtFZz8$ooVAS%(sri+&WU7Hrhim&K?R`M~#2sZ>=4-R{`t^-$ZS&a?bqx z?+T1>;(XC_dES6ga<@ulQ!$AJl|aX^EjL9fMn3sN3w{ENk4G$BsLYGX2^(y?$gJ%Y zjat#=pf-Ek>L+88A=_#K9EL5#61pcKzkVRw1!TornjOj&9xD^)(zOI{Fd+e72VP0D zm@vwEy%Y;j^-9%HUBN_kDQ-NYLc2GxD6*nOznQ@E_sG>%%>z*RJD?kinB6g1i2|v%q zEQ9IAhR~LA6B;~>Q7aorCz1cv3@zmxqdSi8#kXAo33`MqhEB=$A^KnISq{Qjh(toW zRUqRvQPx=hp(dmv+{yq29gO8`zS2ur2_9CdQyp&m#>o)KBN=BIO1 z2PHMUiXl@I=nKpWK$>nnv2n?|Uus!tPrZ}#XXAhK9v@2-1*_`-is@S6YI0WX_tL4H zf6LiFqmFt*xs6QP#h2pYG0Yrvo+S{%Uh-`B$40lMeSmg6<7FrsKb|602rOxW&$lo9W*Zj?DEU+yvK3 zlQtF)gl}4pihq<%BPkoG48yW24OVA9 z26_Ray`j$e{=d@t)jMh#e;>GjfFeoqVgoQglsk4*pRqOK+pmG^Zm-0u4h<({oYe^E z*+6Q-Z$G83`MY#gu?wL%Eq+GMl1aHH?TX9noXOR;tY5R)s>|nN6*ZD1V37UF1Q3sG zP~oV7+4&8z&w}m;O}Ln*Yiv(hSb#3-yC7m3!~wJA9M(iPNEHfz7D@Z15}t_}IXjo3 z($gzGd$vHQDvw`)r+rJAeB08V2rheYRN;@^cn{m*C zI|Bwec(^vr4SI)DTuVzUim$3OHO$qmr4;fZLMn#x{ir8M`h0y=3IQqRSER;OW@Sph6 zMIJ%^vX+&*7?A>?9Ngn);2V|-cap|>Om~raCr4-}FC~5ltl6 z*dM-wWq}>Wxb9^~kxaL|jF9b=qkN**n+_ zO$E1-%GTgk`uBApNV523anwCYAjD=@ePd9}WM_jqAqkv}lDy~0lNe}% zFC$VEZN!UiV7l|QJ{c+N`_{637NE%guYR=$0Sw~5fMoKS1IfrP*7*M0E{$W15#6`? z!kEUnSnd;lb==ILXXWywW5L4|Z@Ev!k(#IbaISgMIPgirMu*6)J)sTwB_av|<9_icF|7ov7h>!+O z;F(pb%cECqDQpav$h;y8Gzp&@<_T#XMCDXVYVKO5f_<%$qu7PKGC8t9fG)&a(ru`D z)dH=ENtKNxjL9A&ul)U{mS7e|sRxp!q+qQ=j12y72fwBct2j_WdQFE_=qfXa)tdLW z3Qr+x97|oURJb3Ye~IQT_x|l+?cuu%?v(<23MITVGwmeY)}2F0p#q2utap6ZB9lc_ zwH$k2R&e@Y>msr0tNX}gou2FM6I0e#%s@gP>vp2tXEMw;fo=C5kywZ-ina!K+||C? z9ohJI(TqI&&V11?FnA$jtypjCWCF3SBiQP;@?Hu3P9goXbJWTnK8FumjHtMKp5j-t zK|P-i+5zM)0pZg(jjRdR0?y%stHY-d0Xw+VEHv$e|C{24P8AZWIv^8q^d_l9;@qjxP#l9sDv!II6RhQcB*yVLmT zckt5qi9QLoTf)6p?QYHP`6WXIkaD^=e?oNQR}mGkWH_E_H`0Qpvi?I#Q~u+@C9v~! zhL4^ZryKBDzJg{`yqdQk)VF9rg7C2CtV$!@R#E&B7WdIta(*rH_vghrvRA<;bxeiq zw+1`DgVHn5%O*!S4sY3q*yV-~5$`8~@n>iMyt6ls5ljOFmTWXT9&WHY->|4A2eCM3 zjb$~CQS*>GWa$sAANB!T7biVTxYOzn)i=6uqcB#qW)Ib?R6;hcL zth5;o+PH7HEfJ*p0Y*3nbjN_M%()joC<20_7f>w*Soltq zH)v@2OKb*RY&a4U9ZYpne-zw|c+mB-A0S86(% zCsu}iU@p*(_wEwKZ!=;s!EXiSF5c6;RO24nSZzQ~vEfwz&5o90#nv&AJ-kTK7FNrC zY0UQQ!#vr*K7t;_TS()dE*7zi$zD)2+nbesI(xc~xk66M)$ZJ31%!q%MYk@ z9>W@m7197Gu`8^UQ!JlE7DQ~#1sS_Y( zq9QeScO1FYLV$f^DxurbL9a-#;q7nNuheyAxlp>Wg*JgJog9S7`%_l)4H2U?_Ya>! zaH*M=>}ABUcYQxGr7j}eT~Xw@LiS+`j|CSBdgRRVY+2IG5J7{Np`#KfisUvKW;&0D zKLMEJhs5V5$Ag?GE&1HNqB9x3Rz%3g=NTzNdf91 zUx{GuP6lQw|47+FlfIWriu%=PjGemoToZ6ay=9I2NC2c4j z-xl^0C{%nRx(FROxP1+Ziz0px6%;ozg_>Hqt%g2W0KKqJN-+tfYprDP>>M1XZXAc9 zG28Q-L4eGf^N_&~uOEo|)(liM0nSB_qjU+9_L`(=ivK-(Gmw(rTUYE!$6% z8P>$g`BBo5^$Tb-S_$7o-HQu=MoZE!9aMs&a zkIHOY$R$30EgAr&Do}9m4avW$l_$T};wSbQJ%pZQ-~4+JrKkD-6#bB$e^UQGXZ)<0 zqeoM~(ip@K`=oUjQaBG07Dqo!vrgj}blHNn*3DnGY)Xq&RL1qv8_)T{_YT%7v6 zgdKyogd9cVKuug(88x|I2K3H4C*xrbg4Cv=1bZ10vTbV+m} z_|znf*SfFuku(Nv{lAA)Bg*96|2~DkypA5-F=b>W41pI_$`l%>x&ge%aC~Gj{}I`3 znlHAMa`s+2H$Q#!IupA9tC;GdB!r?)y8pNFj!fSgEFQE^Y8s6CS{9mHDs z&1f&|57jBkf))f?V1=sAbkQ?W%l>bOU~^Ou@-nm=?QNlScjyqKk5(0&+8>L!zeh9+ z#vtg`vTLVpgHtjRy}yQb?U!3s`bGl1m~j@v7WLLS0>+{Z`L^>ZkM=R6Hy$-yn>t+A z>J%()>+uURA#LVhEc$DOzz0;kg)pFFZ*?mfY46aKyIP(g93&@&ItghOtHR~`EVfd^ zB11*BTlo=Oi-2J7_V~~r>QPVRRKAKmU|P@~C*6L?H|iiac6&ALvZL$O)t9|*M$^i! z1XTM}NYq*M+d;SHIgDR!whedcicE*HLq83H`EtT}3gXmNU-`pvxWqM0T5s%)%qZ_j zU&eij1>=e$X?>PUk0tTG9Gps%@oG7B(6Bw&`__%lU?qgA&e&-rfDYOcc$C5TjAHIA zAYY$~pG=vRfFsx%JB>b}3XYv zj4)0 zDigsA`w_dBN8uP(K1j2?x8D`-f1Bmc>A-X?FK_t*_K2!&&HxHB{>{4}EVO`x15$kA0gF?#>VUF~6#DqQ;2h?>Ba3mppUpf|Tk<-Gse0 zCHCH*q4TR+fhBCJJM$cUX=51d{xzEu zeGu6}kvav>y~f5KjMQy-Cw~adT>J%#8yOSS+65ENr&m2zjV zX@5lp4lSRu;RM#NjQ#PfvI?xNX4^r-Wu)xJ-HtCmjLovtKk7ZsHQ^AqTkU(g`|SiI zeLZRyt?^^*nJi2if3Ky$m{!$$bBg|ln#-)pVJGco!f!#r&4!GJ+|JDwe$+FOq2(e~-u-3an29?|nQY3+w7C z;Q`qv*mGCqvhi)-LV_MS;I9aTqTY#ft)|q!++<`>a_QdYoAkpbQQ8w(Z5uOK>@xsQ zrL$dZ+(FB*47Iz!FHr!3Far%xSuty@vD%yIY>x-?P67K>wI_o4kcCeSCdj@aRgqxW zPE}uimzs4%%J?H8$CJ0qY$a=2+-9}*59uqeUNeUbYJnU`%F_tw-OGGT9v<-;Z2I zSjAhp;2(tv5xlN$@$8KZpd&Kz9f1(WU^n%CnJ**Dz6-p4nbxaFtjr0z9eh3eOxl67 zBa@tsy?TPGJig&SbQ=<=B>0k}H4%l?IfnyU2ORpeg$&=V;iAu-jBatO0xspGZei^| zafE5MRLP3g{KqDzA&Y(hM`V(~VgEdj3#&Ft`E(K!ERjl5UbUc(E#l{I{v%5FvCTGv zUaD@Z=1q(%PXO$;@eE=ViS=sM%mxZXMefK!0eR_t$k$L}y=g_=JpT>$cfp`hw-Kj) zzNjllL-<>pc)Q*z<%0z#C&i&g&{YeG*^V_IdPu?I>652dEf(%tbQ?alRU3vSS#(+0 zGVX4>2D9(&tgB=wTf77|XRn@+Brx+6ukNxR6tQPP=H3k5bT)eg$>cz(<;&73W{H5M zvCp=S=b^P=vabJJWvXn|J!D81b1N$Jm=m;j9&b^3Gxc6I8VZjP@30t$1juY1w~zpx z7b!&bP$~id^l(u7eFdqz^d8O)`eCwN&XQ{;5kvAM5Gq=AKFoQXo7>MbOt)JP7E9>Y z_9#(^3Ch1c{tDD9L;ysSx!_Jy;!Fbz$S)LJMiH|lcPFl}iWP)7p8R;8+tg$3kmr|<-Fu$8&F?at5zE^>G z_ps)14`5bwIgnM$>2P=U>lCMAh}+~(&LC%dWejP`Q(b2pp^NEt*jwYk%iak|mF`2F zxh$-Yy^Plnrl)-u8T}-R>2vMjF0q&T>)H6pHgL*+dpARHGtK>VJxfiE1fU|I(!jGC zTaq6O1MnV>I*X+*s*O=`IeK2+(HgePp5hI;(R!nu`LH_exkyC0nM~a7C0d@^?8cG8 zdrr30D-$FgOQS^)+p13j9nD8Iyj#ss7c;PTD zYu=xLtENHxCLEqT)I_E2Lbg&nH|q{V(}uP!0j?Ra8~}E(Z7D)1ChVFGrNsKpM{tZ_ zDE9-DuodTeV@*V^T9@PW6FX~R^f|ERboA-R5n9q_K)v801B_>KNHaa#7Iha+#%2Xz zhMfoh{ZS7#S`Yi}furuTLyzkH`833HG?U_}WKV1yt zDmoFmrk(og4W$FMX?elT)}K6rcci4Mn3xGNbSyX;5>XB=lN=KyTH$qxJddo z2y8XFdurPa8H9eS`QT|#>rM>&;F&)`n}jgBp|yRgs6JL85$iSLX;SRkw0d;ttYOe* zn%72^{-*y$Lz=Cvm~Xr~>l#xljOXX+9->YD2zWVGZCUTd`S3TMQEbzybHS4rMu()u zbd>ZhArU!3)|`gtIyrxPRrSJVQ;mhI&y|odC*PE(_ik>RLl9Cg-=0XL3SQ4HB=rf% zg;O0%8S8r6OLuAOr!|WTP{TucLhHJ1_4fU zz@ia1WogKwWYsNGEp&+9bQrVtc{Rw`#AwG616^lw_I9!xg2MFAd3Zdfobb94Qnw%@Sliq#5B(oO-cU6>HEBXn_Imz!3|GeY{!Y85g>J z%Kpn-$KE229u&YZHkuY$mO47RN)l%%vCOdh&u%;72;btZ@uoSJ1F#i$mp>@{?)BTq zQmZ4RfzQR)_5O-m+B!P1oAT5{T*O8u3Z+2S%d8m3{cRs= zY~4co^=Cl$>ziBqwVd$E(wo*;*W z3nA-cdw7%|1Fy}FMbRZYI>Zn>NLW_AXCU*HL`3z0p4%S%Db1nsoQ9*C@XOSG9M zSpU_jQ>x_e_rCpuKK>){;>89uH8E3x^*+#UJ_QXg9ony?srA9#+RmY-^%sSOm$^KktGd;1&@9_%t<}mW>a-^tGg3j4l z6S~g%mbYGoU}p3iDRRf_6mN~O<7~a!6_v|1k{F0|DnQ<&~J2Jv8k{9bpkPJD-Z@htZ{`QCmUGkBa4flvAB0r&uAv)H1p_5 zT21p-{dawh;taawj}(rrGy<5mMk7XIx{no$QMBX|&wj$^XsO8DCVlb>>jN;Dn-i#} z!%A|=>C+Le&0ACGSbpH9J#`EXkd&Edo$J1}Np#1_W(YMy7BBs+)jT`VU8LM{dQjPV zOX15${+SiAnHLt&b47&AnXO=Eqwv}568gP8d3&-9SxiQc#rM$Qd3R~5mQfADuZ7RxirT;`qPpNY4q_JZ0H;r-ep0n zEJ-;p{2hk%=ys2S)9Hkra#w>sdB(Eo-&V(U^@4-bh(eu^VgsVQ0E2Z$(xN)N0~koT zWAqjdS@z$Fo>pP5O=>@=-JRjD54c%UqN#G2@O;K-ekA@n`25OIFM+XLAGv5-}9VcDRi)Q}z z5W7IE2{(Rr{qsn&-pwFOIosTL`LX0_S1bMdUSUf}8NbfdHE8ef?x!vbw^N84nEK^c zsB>qrDip!7j`|F8fS7sSO<|g5wf37~Iyy-T!|!tZf=F`22h-&Yriyi@-^pXmXV|`UqmqwQ zwvP5a3t^{P_STicpW90*g^!$ zDoI!h;cbkPgH=@d3)|xJmtXF}^q!&QyZk&_HywCk6Tt4%zi}*6E7Rn#T9I;Y(f!k4 z3{LLyC(!A>WMeSW3*nP&Z0T8K3`&Z>$iC>uyY*_Vev~X`IP7Qa3u#j21m%1}eM>bVH{N6``_wrv{iJ~Jtl8g@#_?Wr# zmT$qY{?xxCpjN3`^o*ehdNN@~t8)g~#{SnaR_>YL?C>URDdPDrAiH;+++x)@*1Q&y z6^1S==m8wofqP>%3z0oga&h`6JVhtVo%L7T4)q1tb?be__Fw9geZ@8l*b;pkZ`rB6 zpnQ_)$tn50wyk;A5q|N)=qs>AK=wmcrqSfLd-^U#b0LTuSRU3-H=~etk|K?ru@w}+ zJazpOeN-n~xy9S|-91n*>~_D7llg&zx(Be(_*nxz9z~enddw+Vb{PGZj@5Y<0ECu5 zuK6yeJjD0d#M15DdPii1s;IF^Ui`NaCmqYL#83D@2FZQdotZ`F4fV5rO7eDj*?W=} zD67E|L^%l~A0T69br&{2@xzA$^M2BQRF&`_4HqV&=J zotPtL25io=uy?~L`b<~Cb`2Q1qIZ(1H`c=|@BUMJO724^Y~^}c27Zzy6g4EA97epaFDq5lH1(6^(hNz5wYPuvuj--C=Dk>s z;DTpNGYma&`9wHTxf`5!RqT@pgE>_jR2{Z;6jBYWx#PHnZ_{AKChxU5TI))!WM#X= zp0#=dh2>w<_C6Q0lY4~L5C7ip{wXp}&__Mw`#$C)DV(KdPT0C7r)8PzpJ`=L6JOI6 z=&t5-o^=V?eN{lGV{>@l-fD4#ayX3qf>uI@o@W_s%AM$tplHwMy>B;YO=%?%nZT51 zx-pmQzpDn~$B?HxY!bF@%27v=H4gB@pR-Q+=WdU%Zl;(aZotd2^ZRTBm!nb|D8aJI zdCZoJ9Q%U-9(V*R1aWQ){0Q;-^pti)7e< zgCEVrmFBH`%vJ$C<=WUx3^5W=r?EYBK^7%Yq~>qZ{d|M zSvt^kly;3oYFVO-L6OyeGk(1zW+(2bH z%=I{_iWV;<+d@n7`hBV_FJk!A`!hv2Y`5je7)hi8Y@*}k(i0nVj>Z2gjQ*g@;)tD4 z1F8Z(^yK<5w+Q*&3Lf!Od-M3hI32H3m`F5^s&izLr`Z*4I9(2zbr3ed`Jjv0PBDfD zMdkUEWno4|Z`-C!p~XpS7+x$UmIkV%VC(MbOH3Uqy=NzyG|qtTKWqd>GQbzPDQPOK#s5Eiqv)vr zi(62_KB#CN5I25rnQiiK`IBk1?Mc_-veg${yM;kDpoDkHnE4FTq{{XMvzZlM=FtVW zqRsqOn}l1)AEF|3l|8xO&b-3k=9(;VI*tu-ml_W(U%rX4XFCpi^4U&_A~UitZ^I^f zaWq&G^**&;+ghnQKTGj%P&r_eEOEKJi-$a;pRot@;L3 zk+lc_fPGeYUMUYAno$!x|32cEBG0E1 zau1i@{Y~C4n_@49{EmfzXr|&0IAtm^4=W)Ae60H$ zn5IgaM*~@N^>O?C;#-72K4~H9Vnqj-$66cB6EW{BxMsRGBOLD)(qfF;zW!WxesAC& z`jWGeR*Nx&o?H;pJ>-q3VCBGI5?q^?qhdC`wi01KclX=ycm9v0D{*A<|KoGZEoZJJ zl_EqW=Nw6;91-OjsgxtRa&J0N2o*V!IT8`0+_5<$WUi>l+#|`bH_*O=LZX`Uo8o1l%RU)zuUhcpQPl*3uOOUzG9Cc#*%cDrCZ z?2p^M^~xUw9n2{^9g|P@EolW@)ru^!p1iwOZ|HIzzWXGia0Ke6F+d(~Z#@V9_7ODUVkhsPQJGG1QJuHt z(#`P^ew}2#G~96ZmrZf(US#WYkg05dXWT1??p0GWSHkq7^1}$_Ti)iHW`tI zoR^V89K)^d6p0z_mh%D8?4sBdHscTu*+N>Q$>5P9{RdxMN&i~GM`BvhK;St57rT5a z5PWj$IJ(pyzu4sJkd1KC=}}j#^gDG0d%ZJe%tT&m;|m74yF zuJZ{^=GS`-Ud7DR{-hcFD2Ne{v$ujT4f)dzko5F~XtwGbb;;S#@8*uYc_tfv1bR<|l@So{t24 z%2Ap;v(X&k9=L9MpWg!+dl|YGBn9;UJSZrgyn|v2GBTl|Z?o_W3cQ;bJxmSFWZqX> z?c;NYu{W6L=Hc^@{`3JFt&J1*Xc;~jsu`_q@>SAyG`Jx9_93Z)-R-g|Di(-)17@8H z%OYgI4$K>W4B(A-=B_z#1*3a&31Hhp2og}BNyD@CjN5hC>!(v5-M^$>PR6X&M3eCn*hEu0Iuk29q2)@-TrtAsnUanHlJq13N_yRo;m1B{ zZAI5cN_YaaH)W8fBvm0TZrUppd))c@t*iiUGj4pEHTVB-S9=}^izuks)ZPHh@~H|y zhHgLnd8SY|gw??)vm9z>KRXmP8Z#&sGM{qZ;2|D`$%GTSLfuZBKcl$4wxmVD%Rrlw zfocnc`1K4u*(gZ}0jTK`ZOm?4zjfNHNj()Qg!}tLL#ogU@=++IpW$6Z`vjgXSbzFU z8_x1`K9FA;jg3$|kptQGfffy4zH!Y~z2AsreT)4J*CO0)w`ycJ4;sGV&TRg-`LJ=5zCi%YW$Kv$jw_|5 zOZ|7C6xx{B+lHqJI&Z7tCq&AoICKuZTvIjP3iyz+Gj=w3nj8LpGxuxuwnH2Y&YzA) zX6nh@HREwXV(-wU;v#3q_6Rz+Mn!aiRjT0DXiCz{{QY zr_p_n5bU$PyiiZ*?ByUt=!w*>d>{-p_1~_1@_yl*zKQe{qyS7l-rS(>Je-@^lpG0% zG$B5xJtwE7lOvwtgg=5$hY0sG(PP(19@-m9_K_50#c z7Rf=JSgkm@luV`{I}$L`=L7>qh;5UaSSI<}ntvbubh@axkv^#QHYLEEUaV_y79Iz* zeFG|Mq9!wBy1@PV*S+Q)=S5PI^2e#P$sdcPQ>kyGE{W6~Iz<$?_-;%loS&IOC^v0t{ho^ONcPGmN(wuJ_{T)Y=3aH2QNS0LwsUi=mDF(!?|k^s=ex47x1JD za0)8=y)qW+^9PE@74Ts`M{PecErD!D1*uo>~K zZ&H#|Jcp(3(1y&icb8QD(2O5McMJG%&-16d+*ikd!uw$L-qb@rrM`TBfY0VR2L|2N z)pEl~%GDy#^iHyI1pA)JmITIsYk*lP3DFJ`DPE3W#`3Xj>wZdrS3$&z-)c&z6k^r& zeI+qkI6gqVoiBR{=nY(d{$=(3o9N22XMqahqb%@a@o$}8c*R-h2kG8ON6;*%0;zw1 z*8p(?(sf_e{O?sghg#^t^gZ$G!Jb5QX1)uo*I$bP8C456AJ!mm?Hy)P%hLS~FqS-k zNH{GA{tVTyCowrVcy&h`+t_+9wmRLTY54S1z$y8YvFgU3iGKAEaCI*_30apqjjDav zS(@$X=N8l!?oJy)01(Lie`{j?tl3$_fB1tM4ybg+CWRaf+JVhW@h271pfyE@+c^_cnUy~_9k zwG3zALNeovc5hR=PQW1K8plX*gpM$M#;kezt3T8r@_BO zrwYBm&>A{bt^VnrXgh9!ae+sB>EYxOL*|?)^QN1FyWL`yBYP<6!2WTb18K`jRVEHiX}ge(YLO^pNHrPY#VUZ(}eZ z$F5vUhY!Fk_rimXO^~UOgFyYZ@+l(x0-OKPpyw>L$d0Y2Wr0X+`NkhF;sEjF-ZCLZ zrT>cwJncKAa1KIyifojF{6kPq@`^()2~zmu>-cB0A5me_=O}eGzb-gk8)a)%&Zf*q z?RZ8wEV@W`zf%6&Gmo%uDZ2E>WLIH3AMEO(Pb71|o}DSPx!yU0U;ER4)>)i_)C~hA|5n&|F}LaNlPDXl<|l3A zsusZWt1DL55WSu>y{pU~V)Yq6*A@X-+8TznLz7D6-a>cnSz#~@v;jK05Pr1>HOYs~ z1D2km*C68qIJ)4J?&K-XFOY7frdn31I$Hq|cu)S{;Lx#U1R3TRYphkdlA4j4jL;QH ze=P+`MY>&q*nfmwad^$M9j4S)-Y06tZVbAt{ypq$CQ1F)Gkawinz&tGe3@Ojl0u;w zEoJLWF04h17TcO%Ovk!BVGPh!v~tSAJRKQCk+c_JP4O77011ek+CuK_fP?;V$Uq4J zNa?(7B!5f+@zcAOjAZs^Z`>%4{giGP^VK5iF`G6}UA1NWYdh8VP`chmpsa|>CR525*z+#4fW8QyZd#Crj5&+5k zQQRx~AA-b@Wd|6&huoYD+D`0csHBeZubulYSDkPTTc-lKSmX{HTj-|x$ic@5&F_vc zrnj&4dh0g$E+YlDue$loz_OtfEgHHTU5a{lH4_W%p*D`KkLN$7M=&y%KBAbfzd1iocRS+iZXTr665tQTFoooy(Vp zEJx&_x&5rpH%c7L$+=gUt-tC0Ov}4H!J9tpLU(^~yPHw9iTPRSiRmofDBQAUZN1n_ z!qb_wokLG4UYC7m-ctQvD;~Vp#EyocppSfA z_Q-VcFo3W>wY{kJRG?jQ~?lHSfaL|b$2{$;du&4Ak^hFPe~F#b57w++-L|RwdC`^vf0$0GAYFCH zc>##fwaau|G^1YF4Pgt>)oaFA`)~^F(XrXsOV|P+x#3wwg3k$t%-txC_NM2&H6S7* zj$nL774aWT<~UmN9}_Pm^OKEhT#W>k=0VtIW(Z{$S#mN~UaVF2pp>nLko|UkGvAfrjZ28~J0*qwVkiMNhJ-l-hv_$glwLR_-+V6GtZ~ue~$X%GwAHUJ#rXoXV68 zI-sLJnv9HgpEJ5V{Ltc>+jmd(%^g|>d;vV!=K{B!%`{k_=Fuq|``C_{=!NW6|7QP1 z8A>mrG58H)JxbgRUl9HKE*R3gb}I`h$MIG0zjs=SV|yNn0fr(lW7W+JKYd6fN4paV zH?cdC{Nsfg32)SGJ+A|kCcvkb4JV5GajpYcZHO+H*%}?F*b;l|*Y#Mo*$a*Q zv$me@;&DrSg=J?#x#8eb;CQYhto+Uw>zib*w@e==Ck?8EauvS zXIEz;41hS&z_uPx@%G@73#FGIaw5-$^;qk}F4OEMg7OAmrwXHgtETmG>QqVnhdQMe zae#WgUmOS1Cfj!F9}Iv1Xnu|KrAT05VhTvWngp~+^q)zSngZy}#pE{UpUp>18^#jt zazn`3YiIA?x)z}~oLpM~6L%;Smb}3(6Nk3b2|=&=>=?^#3hE>~Mq2jJ-EpW0eVPzZ zSQGi}%Xuph+(j9GgF%B7d$!zVMKae zS8FGTnv54v@(F{(&hh^-7l$CD=i@a_0M&hdY%M73(K zQC*BNSN^ImYN?enomVcPMC7;>1FS^jw&3Y-JGe3I#K~WzyiB9X=$e;E06)uwKA_F* zCcCtD@(REOakvMdGDZmE_%^%gSgdX&akMJ0tXvLfNX9^Bv!5{S~{U1_*RbG6m^7Ci`cE?KsZMwDOs7 z9@OTJWsIR3TrlLUO_qxugx3%9{}C!+<1a5H?I-6J9E={MY$5S^@*F=M&OHg8+$HKq zN9Xnq@f(hc3DgU5`Bpc7b=j`Buw$z;$S#YuHf%-4aQeZ;^qNB;g4Z3~uJDW=8asR0 z7M873Gt{#7d$kN@3WhzKtyq}1`{Lf{6Cd5?F5l!F&ICZ)qR-o{QT!M$zw&y|gFm;j z>*8!06rTe_GsQ=vF%jQVdP%H^F`jkZxKZyX54q55gbbu&k$pNbKCWSfG}mNBXSwSk z7q5OtrkO6o9<3M%(Y1jAiGTN@3{{kZqf1b=5Ff=2ou>aw5v{cTKXcd-4bb(|K|m`< z!0#R_+1?#_wst9X{v+dO%AxPJ=?POSkz&kH);KNC)cD&hbPW{d7`$D%4Z(ykU52e) zIno5_rb}Pk=Ncrl!^xP_!qu z9H2_zzbVmq4ltGF*y@F`M`mijpFZt>T{mZvBS(=}GA;{M)e1yoi@l(kp^BPtwV!TD zc81dt<#RB%ZW;UaIs6--#X!|A5Bb~<9U~h14SBT#rVyH65p(;{qmH$sBzCVW1TCn? z!OO3m7qd1@THTYx9&Z&s1Fa2Wk6HBf9l3pal9%_NQgkDZ7^el#A5lp@8Re3r^BP_> zXmmay4@(a&8Xi4!a8<(E-9MTX-skq5St=H%)*G(_q&aE>7!C4zYdOWtao+AZ21G59 z3FZ*o-Iv4zi5(Czsy_aP;8YNnb&b0wob$iOSNjyChTm)2E(PKVw%thtLVSHE9yv@? z^^DfDErvc^I-aoxm0CP5CrQ%}CF@}d;Ylccv&D+_lsNUIcItch=|g6k-;qJFqY=j_ zIOm7IXYHHp5Ut6;b|VdF=S=(;g8Mf3>wyL^tmr3XuA&l5OokS0JyD%@)8(v_j9+Gn z_5*Z?f%arcVCbw_+9Fm7iof4JIk%ElIT3`wX4o1cFHLdba%@H;3eMw~X4{@H zAv-MRuc~nb@r37kKr6DD7@>#s5M{4!6*lljQunTu*RMh5?Yl45>|)UW=92ci=(I%{nP*-~IgsrBL}kUB=~wv(!JXSQps& z0h}VQ{AK1LO^|t}jTsMNu==Sr6# zhuRTm$>0S>Phb%P>u01NffA! z7wAV`Igz%p8}i!xE&i?G+IwG@W2>?Biwu#~;MTf)5l=)ob8_L8scTIMyuY_5*>9e;Ovh?&P?H+^Qh1 z$13T@qaub|W^W*M7O#qS?PIOd-Z&?R_FlJw&%VR@)P6)i0(AyS4#?OGs6^4@NUN+^ z6^|h#lueYsUJs+MQ{UpZ~0_Nm40Et1%22&{PyowHW;?#0nTXO1LE)B z%h}8R}1c;s3i8|1M0XX{x{LJ?7M{-ch#nyX`FZUw7&bVz_l?sf{wXpS* zYgeS79Lk?*v|55kQR|s`m{~l0@d{!POUcNu%mU;XgH<-wS|%Iz89YEZjY(6E8{rgd z3slBNBTgclfNlpp2`115AO*Y^Pc?`gBs>Yth#t*xTkCCJGB6(M2vy;i`_++-=o=g5 zcD)1K{Gjsp0O54nZ-QICDjFTxM^az(oBg`q`y=U)Z*D!89H*hovmJ@O1EmMn^s!av z)LKN9ec>2P;*r1p+9Ce z(+Sk{d|*G2X<(8VbQi`Z0&HUK?L1*I&;7;k;X%UlaRFpJxUSh1z>w@)w@>2|d1?bM zfAsJ=q2wEUjEHsHm3?iCwrELTQ#s+$_#qV3ZWW6aZol_uf2mf&zQ)XB55r_a^pN2o z_qoN+Btjdb*iTUwL9xfyvnIo5nGT*;`D<*xKHg%su3irM^!CB;3-z4a;y{78r(U!! z{mslo%;lZ>wYIlszE&}+{zjkTbv!P9j0$6W>~Q=CajNZb9VitKR=i(crmuRmbDc#QWkn%YH%%#ZA9a~HB6F)wG4A%wSqdmD|79GzJlr=$8E3qnvS z`j#pnh6+0d=J0}wCNMfB(1Pwe6RYs?7%tnaqG{obFrUZQkX+5gul8r$CnG&Zagp>; z=L@Y*Y?ym`x|l-PA=Y9nIVcUiR2JMp5GCJQ-iH-rAasg#yIDTsN)_QJ%$8~kYlNBxcK{RnHWJ|7F1!{awZu{kH z7v)F?5(j0WgLspzo;9TDQGWhi-#D07bEzUeWf39v&u zu7oX%^I}7aZ~5G1U+IfOsxwmKSDxet!`^>qf~7wV9G$OFv~E?zQ^&DGklQ)ypwn1F1FuG`bP zY)$DmquH#WbC5Hl3mc?KPj;T)Qt-XXw#pFAa0CpHo^fakzd!A)?zMdO^QDd4A2@@? zy!YG==;GfC==8O#60ApE)5Ni;t|v)B*fNYr&C@qQOD#|IE|(tc zU%2LIwaEro9s^eQB)M$chEf)#Hsbrv>yZ`_o{NkX6f{+V;E~zep%nOQR9J>)1rhZE z>5%#|gkI~O{ZymjXXn2jQ2B$?vZ)BW-}k$Oi6JlYIJ^?0fG^cX1_AIuuE+o2!Zg;h z{;6iSVo@0EovLGOD9!ai7B9!6LfosPE?&>H(rlQR?dG53+XDpVvwQmxLzyd~bBhK~ zE-Vrm^De{a^2US0&?UMA1vhL*&54qUzcuxF$aKmXTxe+>*=p=dJX_kYB!Tgip}^?p zYE~B{`45YveY1~4WXe2;OcM+yGD{EK8Yi>ZQw)N9ArkFEYp&9Z{1Eo=z!*pTi?jD2 zPdB{a3X-{48?Kb8cd`MkqbEQp_+xOfT&r`dUS`(avHo`=DAjQg#c&xAjqh~tKx#3{ zAJ~B*2jjFBJn~4~eDYd9UDaeLJQq2H{MEvRgN5`?& zq(eGxDnkAU-2X@}%z8QA2nFh-o!$}f)EG7EQoQcx(&T&$vHeVOmmOI!0FiBUIRJCqKWT~^=P z8*hh#L$aByYIsRyNoi(-(Ykqq|yinVlwKMS< zOup=$xE6X@UUvEc_?dzQgAq^7K8c`zM>xKa3G|A^fM4~!&55N<9ksf0Q=Iz;5&H1H zgXr^%{dj+jOBHBzPvMx7$ReOVYf>VT-c+_BSF~Yj6#oDl;MZOn2E=ORJ|)@I_6#0*YOJ z*p_?TI%iD8+tc3+hJ^SP5-We>5|=DDJ)dc z<(nO2QBScI`RY6JNhTm^x8O25$)wPWn6U__wxmUNhne!|lg~Yt%Ad!m3t%6?z*(Cm zj!wfOu4!9Iv=t;ZY6ZE)4muzN@tta)T`5WX#1cId?>#x<`c~~tOR^#SJ@ax@TX=OF z;A{^4y3ZW$TiLw&T`3myAMX6+@3ofUglP~Qr_ZL)!7rBQM2_HAAO0F& zyVDEwtvzi{FECnpw>koA#1;i@h`2LM*|yt{cT%NWivlQRqa5)raA-V|y7kgln#X=Y z_-gM(LL&phWxwKw3KS`T!w+8pkUuPcO~K#%eiV@jR26I-5EpKb$d;G59;WRK0JL7% zJqbm~@?bCyO8`Ev)I4^76NWIDtWP#(0~+Imeq*2w7{N) zQz0du2yJ9{aCsqd?-Axf>Jt_b6o)i`6!hX(X+hwzRj1fq)W!lW?+I&X;Guu zd-HqFJ7^hlS=c!9NIQR;00W{86!KxzHc@Br!0T;~3kyupYgca1&LgO5aQ4#e+StKG zA<^3+dB3{!fA@LPieDg=CHUJ@O1R|Hw>=T`rQKIh;O9A| zU1*{_$7uL{>Ezj9);saHUoD#p3~h@-zGz=@K=>A3G(~?~+v!mSt|3uGL$4! zn*D8aRN+cQ?j9dqwU>(jOF9zAMw43J<{-UuuKZ~_3@{NDMW{8o2N%xUA$pBwwCku} znX^I#QemB7VN41{)_MuH%|c$j!-{w*EcD=au(i=#x5W2{S{FP>N(mes_Gdr$hGC*T zuyIPbVXbh5+4?X<6c2`LtIYMVJ^AKO8Hy_U0$Y#OpT={8eU|%iy9_H;3Y>^G1?qL4 z(U(|by@SFK_6w->74G|BURNy`-9O6^zED*8DIN99IJ0Yuw!;8isrz3)B=Ox#yNzp=42AKeEAg+;<`b^bbdkR`PV#m#z3}sV{>3<$XBfWmp$7 zu*1k<@xC9=KA!IZr1sxwT%E7deAU~0VVe4KKga`oCHYt`H-v#PxXs(lSS_h4b7C$xeNUbcf*rkI_E!~O6C?B zEawqL9PiF_4KeP)TxxPsWk`vj+{5{HDz=K)fXYzyYA8eCmrsIncF>@vKd${+z;aEoX zA~qGBZYllU*QH$<9?e#$pp|KRg{W{|**77`!+`_^imv=0psv>OU0ncR?R&*K{@1DR zd&uQnUrhM?QBfgN@|U}mi1Cw%zo>x-Tyanbnj^G0B@ z>bqyne?eKlAU#};n}A!*D?pAC+*kJO{+ZmK<9|?aoEAAqQ&>hW72S>HsA-ys!N=|X zb-@~HUL(ml(SG!jGon#@4X5+3C+<`QJEB zSEs3Bn9Is>LtmERLkSDdK(%Z7L7I#dV2&a%wE22P3$~x>E`h3)XYlGwQ7L= z3y0VhfsG%6d_6z(Px|?JX1Afy!|yh(hNI(x@;V>^ZXYTQFS=~5wdP!9hs{iBNy|G< z+#?@mtVHBt`n9vMQWn4(NNpT-=ah{cf$XY37 z=|fi?%}6MUz}JTrWIm;^jJB-=tyEoE9{cS);SS4-JZ0~u7p`eX;DaHZd$2!+ju3^j ze-hP*NY-%wmB&kfJ5|^3s$_j8jv^3=xh0Sa#)NVAv6g%Nh2B~st4qyac}SYlIz)ei z?UK7xBD?0Y5&!r7Yj$^83ye|=!jY%-m)}b`)g5s1COdXc5?%&17gUBmRHbpO_*>A= zwb&*~NAE7^4_CCK3(@EgdyKUhZsYK_e&@uYAP}3yI?^p|+jx8{O~6W5sH`67v7I|`7SnAb zm(dz#lvq#KPCU~5v7UCM(6@A}Sm6Tmi7E)X3s)g4RxO9pwC?uf7UB84yx`%md6UhTS!Zh->&Wj?qD15(mpEE1O?koxKh~Ai8PHg4v4a50dhDDH_rbdSW z&H6)&;r?np`Z+U%yvNkq_djmB?kgZnND8@g;3BMC)7Zi~zBp=?T=xNXs7G23Z4J(z zUo($ZC_o;e^QqzicO!^?+Wc+{zN*LO2-`O7(N;nFw^EX|%J z9%t9W(?u2LL)pjBH2KgDQSCo}#lt?~4)6MSxARw4H{`W#muhXy(LLyyhwBrH7fLR^ zDC>0^nUwln2F*_#Q(5teu8#Dua*0GJMi~6yxpS2r=SErg>v|sDP_Kh2Mg_TJog^KH z9HQXXL-9igQT{K;uQqOn5ZkD~v(wLXp7_9o@>v4S4dyQ_{FEoPY7vHuhWyVaz7M51 zaVtXm3q;qRLCr@96lEHy;Js&GoDuc{c>!hYbx%Qnf+jgm3ovqHO0Y2XMHxK$WBqLG zvoPT>#=L?N`s38>nc1a^j4PCNU)YoqC8t-YpI6L_H7q{cA^#wQD_|5`7dW$A1gMCHr+1koDZ>M+>L)(E zvNbi(;Fi5hf9?Hw2!FAHncY>;Y?n~s8-zSIq^@*@;kaJusc@oV*-g(ixP2AvQzWoE z5(<~dEk*^7dPWs&m|l#2a-qP#9_RGPTZ(s_cX*vm;grDxX@*FEOhaBKr(2GWQwsuosdFCHVA{$uwO!Sv+ zQ%~})B(urQnx<0~&b;(%zuEvblSB8k+O|iZfp*Im0BnG8oT(VRfIsBxK_sLyEJh~! zBdor-CE#`@i}W#lQ8F}R{iPVS#Kcl2VwmaAXt7eg>&L!OsfDBYQ6Yhe?ibVa~XQ$cDQ7*n5u0WB~cFl=d67eFGLG zEEE+}efbId^Iur!X39uN^F4^#+p4uNxpdQ$e+e`{1y4b-gD|55cj!CJ+Lzc2 z?9XKFp5C}H*@y3Ep_3oF^k;psfRi$e4A%w}3F$joXD7?YXS;}6u!8DOLX-uBUKR$1)Kx>pYFeb`iVoh^dMm#5e`4u3mK+QX2`D_yO9b`Oqu3oNIqYw z`w3a`vv=D3#8oZFKYaW#rd7tvpU|w2P>ee#o`L*z;31{V1Ta|%WrwerJ{JJ<0zUeK z=IcTlp+4Nt{MorbLH0te@@CUtb>sh8YK$J0<|#r1{y>!ctUg?%Q;U6#ZI{l=Ll`k< zEsy;<`*Z`$?Y;r(x+&dFt6$|e81naO-UPLL+ia%J?3>vXt&$~*QfD8YY-xAl$9@Li zmB|TD-?z`Ug`nx60(E^PJv!6mkgVUtK5S3SsN3~UQh&|edyXnETGNWFz{~OPubXqG z^lASRXTVqfK0NYXkPt5P(&QIVc(DOC-ql-0%-n6n2Pc8z^S}qa8;w&{-ts6?bCJ$& z{>k~hTcD)%v}$Vog!|a(Xj)ew7{#9d)YMhWn6j2=`dd`Acb)$@S0X~je z6wZA{r&U5aHB_?w&U6-FKSI*$Acwsa=C=janp43^icF}(wy8`r8-q@6&U9N1CcP{o zyEfC2>0kwD* zn<^1lUoG4Z!`r2PwV&adu}0Wi-TM2?F1FZY^@81}GT6d_-PDseI*KA=4b*My)B5jn zeCI!@(T`m>D;m3A=O7qX`TV+zdvJmWxKm1)ETDz)lbh5b^ZHuYLrc8nJU4qM-RrE~9srOd9&L|FX%=PuoN9C9;smjz|E-_~+u`l?z% zG@kMIHHwOmf`?}w0`esk8*GW6gZx1@H~3y{h3TZ+ zb34PYp?jmMKy!?+VHTzAi}osAee2zloug)4m8nV{Pi&_|!O8NY0Uh3?Zm*m+Z};br zk^{3WAo%A!({~L>2k~aGi`9ca$gWG~n3T)v&X0uEFmti+_!jgnf^qRCF0|B4N|ze0vRnR=SK`T0mtA1WN`KSIs0@w zM#VvIJz(+jAgT$;C`nYhbxf++BEGEPf$Mf_kEnSxa9tRYAP;Ghb&=kqyfA8%+^_MR z1IyY7+5m+dwf~;kT>dDf4z7ydf#kR6x?Xl&k!tzIdA8hm9d}u6QpMuf&VVO7BxWSV zQ?gAIAzGPSQA+5aziXfO{s#A*cHg|@FWb8*zH%i=4*sB4L86~+;EM_*u&Bs|UtdGt zKJz?|M#evSWJdHPC|=s1d*fn9D2i&lm+d7MX;@g!TSlHwO{Y&;Q{sYTcTV@6NDZB zQESnoxOBkK*H2!0I>ecAVc3qOsJ8WayAmW3aC^l5h+lhP&cXP;z$Ph5qpm?O33-1y z_&+c#es))EV}~YotEms(b6N;ga&EnZnHtBV8?$6rf+K^k4F-MLnQ+P_+v`?c+QYY4 zxepDP^?FokKytnf@BWqH-L({Z@ zF?9e7HVF2Pvm~~yz(}^L#Rstr83U?HngB!0`2F)Z*>^esgzrG(>#X{Zy; z?hHF}ZvYPXNqiz?x>9pKn|&C%ob}_aqg^L|2xXy^64)Y2zhS$x2AUdTqE7M$)Qy}A zAAz`DCkKxB3*;9$IUxH`chvx?krVUH(&sdu>;eD*737NwO=B@!u;kFyxgoX3Dj@ga zJSLGw$;!@1#zLt+DrZkRG(;)ypS_BXEo~!TMJFHEc8qGM9EF_yJ8&^upq-G6dkTYL zp#EtA^q1L}K5~I$7L+RSINd&xqj<+-#Af55l!v&Ut6~wBx}8x<%Di1=&3mjiu`H0sAi4O{wjA?T+B-^J`m70t}x9ib9I4 zIAYa45&>U@63DrQ3MQWo`;>+92b-kB3q?sven|*6WqZZqSI?nJa?=6ZDk;W?q~+|c z3Cdl8yu^j9=#7g9cz}=TCogY5)LJa_bu3T;zos1}efoo-*#NwME`xjdc|z$SMYBQF zGCW3OR__53DJRV!r6Z?j$PyM;+doJjMJ+WY6EMoni<`?TC zeejFADJe2qbY3C+?eUH*6d88H{eC`^WpL==lA2%2o=@!LoD%8-n*$RPi= z_u&Gse(q`&PGlk#8MyN~jQc&3%m0!Mwk~ny7!QCC>;Aycd8hFVWE{PD0 zS3x)6b=!xBDaR+Z4EYvAj~8$D3shYiqbz{j>9EWidOh!8r1}u_j^t{}$>^kSt~*&{ zs?oD+hL9|#KmAvQU8(jRF!XKo{)zzE$TNvj!ZFLE75Pw@pDlL?WDj0uw=QBHp5*`) zqqH2LKf@LHqg25Y1h!ZS!#njaaYI`1|5R2P&jD$q&_|Ggk zEHf6SujFR*PN$6s2lSR_$iJ2mmm5>&Po?<_y@$;96k)oOOt+O?m-hlNV#YSTYAT2dxkJ&y`y5E9##$wi#8L`~CC7cF^Q@$66tnTYctK?CbvP4>a$kJo0o1CK(oA#D?^U-W85gy!t z1fIIX{1Ks0eyrMM5k5TGyw}q3a2d(iQeA*O%79Z{WT@!Z$i(qH+QI3APQTbQpDf~a zA(VuAIQis>G+ki)D>g;Up9;CA?xBBM7+wKgc-1;5K*-l9^bSx5DRcqsQckY%Lp-+ZWCkmp zp6ts?Slb3g5F%JLtmjsRu$Lai+OuCEqV{sIb<3yoMaN9b?~;CJZSUxT`o`X?J3==N zIhV1C7^zf<$VCKsseWkQ)#xDY4><6*U7(%L7yEDk;(U(?bj%&Wln8U*6JXWATYIEy zx#G8!p4K?kHjPJyhxcKeCvHB8?~_6o!{?;J%9oLTG!{LHDN>iLo;T0_vB=?9`jBcj z0NbE>Xn-Lq?6)SK801oowPcQ4%V5b0=nP)iYFbE7-)I=EnN%ZAk~xoUN+l!P$V6nX z=Q5oC_<10FCd%P-^d5jwJ>ABlO>MV%2Okbk+qAp9#NX>kFFzQJH<7?#!>+%L7mb|- zr9p>hsGjHIo2ou2YIBu9B9Ui~>fQJuS-Z#%LQ>?7*0;J;lU?4=u)~Ijdvpyz4V&iR z=o*~Pf?xMP(qrf7<$f;u*XT<8_5rG#tkFi@E0a9~+z2k*K*~(MIV^hM;wu1k3ZfuY z@e&~DW4u3*VKGAr7$6eYgccs}l+enW5Q*vsul>6&rGd(&Mh{Hf$kg~GI2_m;D_yZ%pNYB-J@_PCyfX+F%tWi6qpds7H{9li zEPrw&B7zq=tQvu(;Yr7N6c|MNGTFZUMEf-L$s3~S{gxUt@S)Se+Q(v^hr9)11-UCh zVrE=BAUuP@JgMulHYsYbt?wCE8NI9~O*B^UHj00CKw|f!uiz{1JAJUB&%I)lqKk;} z_?$;VSCbD`e_6E~8y+9w6dyupY5QAXDZo|WxN=`H zglc~)2BI{@y6Se-)@&c+iXek6Plc}?q~y6q({^}iZv3k=Mxq9C-!7LKJ+&pu4T|mr zhW}D4@!#MwgV?=yCQnUHfk%C@@1IH@aPQr0qi+(%vfHrSi_12$u$ZwMa`RQ&wkt$eIhm-bt43=!rw%wLhhEy zA4TvPrL6yTi~p4-TN5*!b`aRd`JouoTg@2uZ)?bVV3?BFylZX)K4|W>$=x}Q*?Lhe zmAg}aKkB9_DD^B&zHakHPevOJ9aFa@BG5PT@K;TCgn6KpZY`zNwV;Dee-SxK+fYFX z$yGQp&Z@3<=)!=^)=P+*1w%G(cHOF1_Mppz?m6&H6UF}i?ZMuyQ-{OU>^m3v;)|k= z(&OaJUyxJ{+`w$xVV(!lNoe#AuJ^Ig>1ss_y0S&zPH#RFGIEA}Wii@F?0_Lkyz$EQ zBgeH#m`;=*J@*pLME|1px=buwiynot?=#O&uJQAisEGt6Pl~#3Va%3l<9yVE8)`?_ z@XIXuY+bK|9LFEGradTFz32uQHu3D!SinATN>Q`ou_KIbS?ArjpU3;XdAL{^kBN#c*lD+%b0C* z8g7TZ2MRntS0ktK*65mhm!|LY?K+k|KWb7Z7Jh{cc+by)TpSE99*%p}H|P4~_ab8; zasxJr$-3C%VNt%-f4XDEoG2WY2et;TBZ!1y%sV*Y^CVU_jdV$sCMXyt^3#GHNX`Bx zhK+xp>|=|nL=_3$a)e9PFr#7M$d6?gPgmES+Oh2VjU2RllR9IN;ZvHx%g zawh9Z%a=b;H!cXk7xLE<(SKfCahm*WMFQphQ&^FnmL{)2&`rp_sn=k+~VpFPK9;NtK zEZf`fHGi9?(|_^QW;w`RK01>|w7g1X_}T$#j^ z27DUn$r`io&9@XZOz`<}^oxz@Qx=e`Q~~V}q0NHj(ot^pgOIca8EW9q75(-VCadAj zE8oB1=7Y~a{JSaVQY)Z*c(s6HP!&W2{!PBOF`NkMI){JW;ei|@NJ}6jGo1gswR9ue z9`yv-h6MIH!POt&!a*~!&(@=ty7%FzD!+w{I7JBS_E0#A>C8NpxZ0eEZbh~CW?tZ- zFD36+S+Ip}M3@_r-u@eHsRws~|K4-~tI4Ng{UWf5myUM$oH9xX+icsPGk>`y<8ylF z!*JL`=|c^cF;~(Kh@@}5*$qi6+YyDg`CiYvu%!Di$tuQZT;h|#hVU5@Rv5i<1@E*Y ze6l+>_s*LE64`OEsu8%jmPUQpM&x0s16rhb+JGaXA1x)L3)GdDKnI7z=g2+>wcqFu)3*s1{p zog*_RAENnw%HJKzuQ>Z+WpzHts-?tZpS>^H#lA^b!EmbB3JM08P3Uw6mCS`kqEl!!P8V>oB*AU zxu`~Pl|$z5N3Cm^AmAN}L_`FzO|5T#)y<4&3*(hq# zy$|2=x%6akEvc6LS#yt4@>HO%S)nr9K_Br}Q*aDgttwS3hibJs7k}gi+W})U5$wsE zOuz|CD3olQ+}dQ{!=}gmHX^-EE}Ucz12;Y0!P?4@jAks8JeaeK(OuFK7j|^ER{>b$ zRwSjm*8-Nl=0DT7nLLKH30o&w4M5v&c!XV)z^?VIztVSZV)%vMz0-arX7f*2yvi7; zY57mTEf9;KKz7Jo&pAByfdJKWun5ui-3rQ2KV&U>7u8SEvwjJrnsr|3e&?~M-#gJG z*@V>V!dJ1ey6rnc<*A>nWhskW@(6z#+=wpwYWLEj<)y0I61n&KKUY_qf<=c4w!@nNKsQX_5modn^`SJB$_fA&2 zrnHOy>pRh3ZKC=wU%vXgCiZtjmsK5a26{t_;o8HIZIjfWy$lVh%NdX)Kbv6b8m)P; z*VDMAw^j_7^gA$`sU8i?XYaX*wkINbgvZ60fFBM#UpfZe*TyoOM1ZjQ9s6w))0dIy!7F3Wp?nb?~W9dA^TRZ&LmOr#RiHm zY&X9<K-af6~%%duYoWhr=-Pob|48&OnIoKfXa#)HE zA*5G-j6b3ZP8c>D^SdSv0VyW{IPP=TgV-&G(JInBB3Jm)uU)TA(;Zy)Ih-y@8oqeW zre7q@sVT7Bv26ONeRW{A&5bBX_-;Tlb_6*eACo}E6(+O!+DlyyYu)K1eg{Z%&o~Vq zC43@RDO!4jZ9jaA8kMi#(|N|`*6_eJ=MTpm)q`v@gNzO-3wQk6b$E-1G^HGSl~PGa z&k5*1xAw;sTnU<};lBI%d>ZZ=*Kvl`KD^iJmBf3Zb0&-2q&iOCYPRFO| zOax8YAP3ut2~J1(6UwIOMfV?*8oJ3myeAd4S|f>(@DAg&!xR1kgB@-R%5(H`HFsnue~>q` z(SNfcyzS4w;94v6x{2vWLhKv30FwhkU-wWi+E2((F#KE)mGXMaWk*PcV?dnU*^@lf zOH$*z6$geZMZgQ?yzD*Ky+KiTKvVk5zP!=f8??Fo!vg=sUWq71^s#F1tebimAF-&k zfSBVV=2LwCi)z0a^81GN6ij7W95aW!6=OUvumL~1OTUr0>CWc~{_^jfuwE-70plGu zi@c85!ywO$s-&Ec*$spdgjiPQ4%EcmRh+9cBPAX$Z*4DUysn*<{O8H?Zj zp0P^P^8>Svn3*ESOv49vFFik}fJfLmZ~_-;g=emB)l5!X!9Ic#@>aRuyXZ|e|oS*iV#o47y_B3biDw|z!ozZ?(92@SR`YMH6tL*B%|FHErNlP z>8}KK21{W{*d-rNa9NuSzVhKz1nxn9%_(P6HS?fS;rE&|L16!GIqYG`QTVvNm-HdX z@11u6?M~slzweBGUrxkBTxiz%e|r6C-L*sJ((g(HG0fkT4dy@>GD+Yjt0XsWMC__| z+B=DP1g>{+P4eF z3P<(`%l@*aWb1HFr+TrVuL3hzlkWsh|3a#1C%9JBL#exGV>pU-_h@*GXw8U5+%mh+{#`V*5`1@qUx7UvjR&s@KDFR4-W6s1 zgTJmqXxYRW-eM*p^VnBDaNKo8W2O336P>zuk4tR3l=+*aqzcE8#Ofm8QZ(oSx;0li z;?l=~mXr6`&v1#J;9O*(`@yn%JHh?EaUdqqX|M^wTDMyx4Zuo96F&efhW&;RE^8x4 z{242L05Ot%iU(rRF+ESA(jN~gTm_vxh3<_YeZWk)KUF!J4IaNKyXS5SpAbeppw^uIYQur8GN3!zZ z@DNvZxHw11YA4^g+((~xQlXSvVnzyj+?^XR(fp@IX%-dNUOVkYh?#T>NcosRCwom0 zN(D*!qtw@^#(eXd=}A|8p74<8RKMTs;E^pmW-qZ*=S&~Q7ekg0k)6Hr)MW^f#uw29 zQPl;QnuIWN(>JvOGDLwV&bPeg4jo*MOaUc?h6g$Ve=G71hhiER&oW@=*@`V1D{Omm z@mhi~eWBe!13jFpyETExKLnY1nQoVV^~CzoskTcF&E ztq5OTh(-U+aGQb8u-BaACaShcmbhNLyA9IXA&!K2i@Fx|q5m-C<1m%^FA=&01}Otn z?MfZ%(mS&_?#ty-QGoBUTZ1kut^328WB84`NRJ&Xo-W(Ut<%IVeMn89OB`K~fA}E1wS^ z?*5W@2ip)_X1@h;b^2R?{h7dFQoL&OD#9oHstn|-Qt-S9-!a%xm4gb$@DU7sR9Sj0 z7jk3!S*{PFN~#x|*e>u~BsOj@mMHmvx(f6OI2j5Xmp}A36pmiLRd;=nZ}9}5DI(ez zbtLrcm4X*?H_(+?uFD)$5(i%)2n)2o!*#fF8Y%UOfq^Qz(jPh8>Oa2LzVhv84>JQl z;&@*?Sxq6G&v@b;Zx`;$#>omT)(IfDz*0dLaD2i4KC&;-9RGKwD4Euyqg?%VUxjS? zVmT!em%zLF&Ic*Akt^0Qt9Nz!dEnKdSgB{93@I;**f&*5#8Tl#?!2c_@={RZ^81Y% zmeknoml#dAzqjmiN|Jy6vl8M*9|`ifgxCcC)jjdys_VIAaO8fM^?1+-OY=xc$+UWV z3#0W{#wd{Kg}b(z6KhHoo{OSp<1tq#rJ>`kt0BRFgsSL0hRPs{kNGf*Sz;^|@wM9a zfQT%x*qndnBG+Rl-19%#*!{LF=p*nvXd%O?v+|;@Y`cZfK5nKaX6)Bx34|L7Y+Dtc z;R}|Z3mU)v^Jmdf{vg}JBT5>-^k2=oH8*XD3Aedo10;jiDWTcFA)4WeU8O={hcRno zYz07N|ML`k%Je$9YR&G@p;DGOmXM4KWJWVfs;3ZJa8~x=yx}qXcHAz7;sn#jXk&J^ z>2H(kmLWUCv!Wb5>-HLXwjam-BjF7R$lzw&kp6(!U54Y&TODHs(j4Ll$Dj|EY2;{_ zrU~=1sY()J1#^V2ziAVGhFN73TYhG1QZ)bIQE|yu;00np>=FtDneQY(0|=_pVGj2MUThe2ty`vw&wWk}_UD znSFj^0Q;Vqb0Vx0XM1S$v81wnbPqPU_pF?kv8_LbpUz>}f9ahu+8m3>WK@f5c=E zkeG}T7tv&SbVCx8-Tx3_&8yN%rWX`@wgqZ8|M)LjaNZDy6fcRr&=~~PV(h=0(vA;k zhOU7F2kzHGKMp+8;t)9&Fz@YF@e;PNfos0gcNLwR7G#Q!vKviUjGc!5F&y2hQX)H1 zCn?iTcQ&@59|0i>2PZUl8is>#wLH7;cDyL`Zt#@h(fO{F8y4Vmappa&8F@&Z4PRt~ z8%TiB7T6B^%oluTg67_2l~nY8|AF#O(;b9Ef|Aoi$#p76hAkI}lzGCSVYVy#n%D8$ z@C3-e{3ZQClFpF#u{KERnGO_A?e#WL6UCDVv6*Y9#=s~V7xx+8Em>)-6mN(c!@C6Shts|dPE%mjc={WJ`F`Oygkmrr_>(|e z*1>#eXUr1hGSXOzkXI~N;?;%vtX%xxx?zg`->k&y)FC; zW@xwOzSF$46gv$2P_BbFh0h2+VQV3!+w-kD<1(&MWDSKbOFF6{Gf}2!eMbq@EON2r z*Gp{X2gGG8lJw5vR7Hu}|nQ#jzn+y1eIsH(hh2At% zK5?Y(+VQ}jH9*Uvt{IklNi0=@0>{F2*~e&l+Tmm8ri7Xv#`c&$_$ox@psCEoq%!ex zZ^sWWxXmL*#8?E3o>7|nE|UvQ5KZ*L6Neg$qBYBQDUP$C`UAL{W0J9e8DRt5D z-Mh}#C-+C&yxk+0KMh!94rHsG zgo`ggM>yb8sP&r!)GX@tom3-1b2lLfHnOguPrR-3<*8!Fby=y63lUUQ^L2(+bG_7? zl%q%>KR&_85b|pXQyEiUwRXZjEtj9ly%=boufgI6@>zdwjXnyrf-on?b6SP;lb%Wh ztmb)wLILRc-Cwvuu?-+?I(01hg ziLf^0;cvswq9vQyABYs%d-t5su{il05BHENe&L9d_KLrS@SfkklPvyhFCNWEi!rbx z0%weYjehOB>_blfGt8MzaPNS&LsiqHpup0j~Oei>7&^$Ez!;Y7q9UfsPi^57v# zOG+pT#btt}^=gf>`P-|G$+GW(v0F;haJXtB|IBmnJgCwJF83K|SvW(i&D1ztoP*}F z%(zvXmfi)0-(d@8PD#&dj)hwqq&90_8Lw-$9j&pL&z~HrkZkd`nb!geg-5n8`M`t2 z{_243m&H^3!U;AD7*B8bOkVDPS50}&%Y{EvJ}YT-7zchEn4Fuh!k1$A1sG6PY^Yo8 zi^Jh?QTy#0U0}Nba~<47!BBiLxu>|BkBxscWA9(rnN3w~-uae#SZqswL@28y(E~L$ zjM+=-T#-T;y>J&bND7i%)D8a$pHZvEaU<)PiYryPn##KUmX`7sOaGyxK0kUL)s}}E zTZdlwT@ntVsU~?v8Arzke!y5ENh!ra>8gLXuit_tml!Ip`_@a>UGEoOu`}U@i(FV7 zKW!O?|7*Q~?pNHvwfI!O>!HQi?KSM#pUfgzT`*{H0*PE3VIPO4m$BX?t35Ti<$h=- z`hw%_WgThB#j*NqLNX=6VmCCO7(>La+2I?gu&-%byL)wpT+&SAa)cTUt&8jutb_`^ zZs#UdDaq9-Q68|wYk|MWAp!W*w|q?_vWN%sfq~V}-w+>A4YEIb>UhIREm*|dr!DqM z7s@(-v*VPt#Xzx0L~z@N)0#Xeg?fJ8@3!eJjGwjN&g8=Ubh?Wh?;a%DN}Je5U9k}# zp&W=j8=DDSxe=v{tOIr{Tas+;E@JMIr6{&Q2Cj+$PLzlsdMA00NR_fDSvp9zA#WmX#cX^10Bo%fQEwBy|`qQfm8ad10(LhEi(LAf6A;MQ>z(x zQcP|ZGs_J^^QQN2zODk77)Fuc+?3EUy4T%E+{s^}78cLsb`t~iet>Q+x}`u}IO30< zq+X!$&>^sS- zJDWkiDaYw^@UKVU-jel=>aK|2UJzoaK}hnsA$`mKP*9SWU-4Fa2aL89=5T+JvW6d~ zeSW&d)f6D{VzM`(dvXp!+>5oO@=zyPRS(vmFj{aMXTx^YpLOnSuw|f8sQb#@T4RA# zXGW1Lh{gC3zG$nZ|FS`53}gIF^gy0#kxqr^I1JE%TjKE~g%rMC@? z>yc4Sxz{4&ksm}O4oau_-u3ykxvyz-OmD2J=fsZ_E@P`#(7x>JUs!S5KbN$Ul27Q2 z*qu2pp|dq7Yz&MWex&RI3|4-)2b=*lxKP=%V(&%2{sQ$Em|C5u^nuc6XD@1y`|;Bp zzjoSKk3(d14FRo>nScGja6f*ylJLZ%oWvfQwPmDVKSUP)~`G%0cT>U zRtX3RxCJH~g-PDB+mi8HiXlBX#P<8CmZ*1Ur1q3Ppe%EB61a7M>r>DDGTLS={x7wE zA@;eHK(pJl*0OXYGji~=`%rQ}dYg1Aw@Vm(FWA{gPkRt@)Ou+Y_ z28tN)o4uog`A!jC1!0l|50sKs_KpeGLizA!lvJXb{{*9PbP6f5ca4k z%MKx9LQq#|BFA z!Bp0Fh6NcU!~R-=PhBKjtVH0tlgC!b_mJ+4?BqWBF+4mHT(b0HMN^|t32?y|?Qxlg z?c=XHvdYS9`n}E19dcP$Xac~pI{ZCoGup8qT6pehF1b8OfYV2li+iD{)`V=_il=af5neB%K+Id`gR2;qfT|1O)zhZ3ABpX>5KX0XAAOrW`-_~r zwt@M6NP0*1ZGC0=&fr@A(O}B~N$coyQ`GUwzm;n&fNDPFwNZhe$vYF<^69%!TIunMCZQzwb|YmY%eI~J z;9Mey;aU2}e7uz-n`l%y0N&mvlbe}1g9Kb~E{tAS#Cm){bwK5Tqak?LX?nC#7 z+@nt}^SBrjj!1#x$erfpDQ}hX7iJeiT$5%|V-*-THa|_Hn5k?cC$?lbwd*_p>y<@1 zg7OaCq|Z;J_+qzu_u9s;m5*;XZIW7f#gd*j7G*f{ggL<$(>r?mloZXbsc8gIpD}H~ zbop5}st{GMectR96lko(9RHa@W`*%3Py8noVbTb!WJftIga1b<;2rOSd(MZa*he~z z$`==o7X5g3fy;=vwr8Jw-@W8ugJ7okGnG_iwxk~dJcSiSa9ih)?>I&W9E-@0P> zws}jVFsB~H(j^hEK6ayCg1(_H-~lJ#wl9U=xmvjT zXn{G7d$rvMZZw9U+=sP6Bwp(98J|v$+2Q^|%G3d2P|n-Kf%YNLk(-s44o}~pcz>S8 z2VbV~0-{j}r*Vl9WIIzp56@dEo9?Jy(N15_`ksYh8hv?@a_wN>2Ii-;^-4})QU)s! z76B@|-z5t3tKN-VL|$I9x{SQgg%|F%ih1~(mb?&uIarI%BUp!Ms6S&hi*G0TZ#uX?qzz9J*D9*`MS+yzm}ckhTJ|`<-YAl zOQZ^|UpQKL0T|EY3J0!ljp(x!+xup)L3vdvC%!hIgARBx`7@FAcO&=ctFNH>(0Mf2 z^=CoBxaHxQdJ}+NSrLxDvwfDP2Q671vvUwqW1xx-8t*4elr#K#f5@Y=l5`he8MQgQ z@t+>ruH3*C^dJUdvv#F(bwdY^^fUynN1*w4CFy@|w10Z!G%#_EJ)ogv+4${0*MDVt zZ4eg&QwnC|)n?)#F??N-$y|TDm8D(RCN*gJ(UxwopR|;-TC7rHvLW4LUGEXxK;*RJwvD>w4;h9{=usl3 zmtWqqc`-P*Dmd9jh~H^XSW{Pc1Gbtw{Aa*B(0~Li{poRV&#qs$m0m1~HQK{=uBZk= zB(f%dfMbtE*ObkR64W~UX%0H?H;jJSdTNGe%bSh!SHUHBiioFf3U7F8od__m{t^3J z?orJTxf~hOP;?5d6qL2J3@j#^l$HVHdsEsbTXk_fgeP-q&#tf=?dU^+FMduZ~ zM#=f{K$GFb%W!TNuup9v1Bu@RErykq*D}FYcQ{rLj>Ojr^nC=MF44k99#0-xy7h>i zx|`87!l*ain?C|-E$-VB<4=zN@MJndQXJL3D(tAsuDXtji=ww@lT;N2a;tU#PQ-}S z65{N;Gk!iwP7Qy6-|AtFR=aTS>P^&qpqd~qo;WGHa+z`O(mjj2UKxj8=DhI!yiIpT zqZ%jC_a7EMa%p`3)1OLV64}G0diG%or)7Ko>Y5H15;h0I$l*lILypuneo z2}hJ^zS+DT8bb(UOoPT`znL{+3)F0aW>~{8#wZkty ztv0+MpZxJ|W5T{ggQv}@tI`FAhORaNrpJt2Pxrzi2$RhV|L~mKxBZz&~3pgLK~f22KBmqcAC;Yc3)1sbR+qV!x`GiPi|HxKRh5=B7HTH!StkYC!`l` zOOy8;hB)1Y8+gn=D>lbA){V>*VBeFcCnb*@0hEcRjOlMDb$i@bv~V%7u>0a1jrw+6 z_P$`WJ$$MwMaO%LD>wT7gM}C|si|4ijb%dEihlPHH`1JCwXJ&gem8*wYdo*<_f@6L zrtqiex$YukGGS&*$X4%k_{uc#YV20kVo4z}@1_Dhj=F@McDC%ur&}dJgMCUVlIRxe z8<+{?3P$(#AuNQNJKTPZDL(|%0D4v&f*~A$3WezSR?HvqeNU#CjR5F&)0UgYPWPtg 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 literal 0 HcmV?d00001 From 8232372de09d4df88ddb7d3fabdf6fb5ecc8c36e Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 22 Feb 2026 23:05:29 +0530 Subject: [PATCH 17/64] updated version number --- .../SelfControl/SelfControl/Info.plist | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Info.plist b/Self-Control-Extension/SelfControl/SelfControl/Info.plist index c7670546..1a3cd53a 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Info.plist +++ b/Self-Control-Extension/SelfControl/SelfControl/Info.plist @@ -39,5 +39,21 @@ org.eyebeam.selfcontrold anchor apple generic and identifier "org.eyebeam.selfcontrold" + UTExportedTypeDeclarations + + + UTTypeIcons + + UTTypeIdentifier + scbulist + UTTypeTagSpecification + + public.filename-extension + + scbulist + + + + From b9212a07a6ea8ae16031026be0082b4bf84604e1 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 22 Feb 2026 23:05:51 +0530 Subject: [PATCH 18/64] added onboarding steps and import export manager --- .../ImportExportManager.swift | 104 ++++++++++++++++++ .../Main/Onboarding/AppOnboardingView.swift | 31 ++++++ .../Main/Onboarding/ClickableLinkButton.swift | 31 ++++++ .../OnBoardingInstallChromeExt.swift | 59 ++++++++++ .../OnBoardingInstallNetworkExt.swift | 52 +++++++++ .../OnBoardingInstallSafariExt.swift | 74 +++++++++++++ .../Main/Onboarding/OnboardingStepView.swift | 53 +++++++++ 7 files changed, 404 insertions(+) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/ImportExportManager/ImportExportManager.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/AppOnboardingView.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/ClickableLinkButton.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallChromeExt.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallNetworkExt.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallSafariExt.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnboardingStepView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/ImportExportManager/ImportExportManager.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/ImportExportManager/ImportExportManager.swift new file mode 100644 index 00000000..b6347178 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/ImportExportManager/ImportExportManager.swift @@ -0,0 +1,104 @@ +// +// ImportExportManager.swift +// SelfControl +// +// Created by Satendra Singh on 21/02/26. +// + +import Foundation +import AppKit +import UniformTypeIdentifiers + +// Define your app-specific UTType. +// Make sure this matches the exported UTI you declare in your Info.plist. + +extension UTType { + static let blockedURLList = UTType(exportedAs: "scbulist") +} + +struct ImportExportManager { + + @MainActor + static func exportBlockedUrls(blockedURLs: [BlockedURL]) { + // Require a single, concrete format. Here we use JSON for the payload, + // but the on-disk file type is your custom UTType. + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + + let data: Data + do { + data = try encoder.encode(blockedURLs) + } catch { + NSLog("Failed to encode blocked URLs: \(error.localizedDescription)") + return + } + + let panel = NSSavePanel() + panel.title = "Export Blocked URLs" + panel.nameFieldStringValue = "BlockedURLs.scbulist" // Use your app’s extension + if #available(macOS 11.0, *) { + panel.allowedContentTypes = [.blockedURLList] + } else { + // Fallback for older macOS: still restrict by extension + panel.allowedFileTypes = ["scbulist"] + } + panel.isExtensionHidden = false + panel.canCreateDirectories = true + + let response = panel.runModal() + guard response == .OK, let url = panel.url else { return } + + do { + try data.write(to: url, options: .atomic) + } catch { + NSLog("Failed to write blocked URLs to file: \(error.localizedDescription)") + } + } + + @MainActor + static func importBlockedUrls(presentingWindow: NSWindow? = nil) async throws -> [BlockedURL] { + let panel = NSOpenPanel() + panel.title = "Import Blocked URLs" + panel.allowsMultipleSelection = false + panel.canChooseDirectories = false + panel.canChooseFiles = true + panel.resolvesAliases = true + panel.canCreateDirectories = false + if #available(macOS 11.0, *) { + panel.allowedContentTypes = [.blockedURLList] + } else { + // Fallback for older macOS: restrict by your app’s extension + panel.allowedFileTypes = ["scbulist"] + } + + let response: NSApplication.ModalResponse + if let window = presentingWindow { + response = await withCheckedContinuation { continuation in + panel.beginSheetModal(for: window) { result in + continuation.resume(returning: result) + } + } + } else { + response = panel.runModal() + } + + guard response == .OK, let url = panel.url else { + // User cancelled + throw CocoaError(.userCancelled) + } + + // Optional extra validation of the picked file’s type +// if #available(macOS 11.0, *), +// let type = try? url.resourceValues(forKeys: [.contentTypeKey]).contentType, +// type != .blockedURLList { +// throw NSError(domain: "ImportBlockedURLs", code: 1, userInfo: [ +// NSLocalizedDescriptionKey: "The selected file is not a valid Blocked URL List." +// ]) +// } + + let data = try Data(contentsOf: url) + let decoder = JSONDecoder() + let blocked = try decoder.decode([BlockedURL].self, from: data) + return blocked + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/AppOnboardingView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/AppOnboardingView.swift new file mode 100644 index 00000000..cf0d4160 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/AppOnboardingView.swift @@ -0,0 +1,31 @@ +// +// AppOnboardingView.swift +// SelfControl +// +// Created by Satendra Singh on 01/02/26. +// + +import SwiftUI +enum AppOnboardingViewState { + case installNetworkExtension + case installSafariExtension + case installChromeExtension +} + +struct AppOnboardingView: View { + @State var state: AppOnboardingViewState = .installNetworkExtension + var body: some View { + switch state { + case .installNetworkExtension: + OnBoardingInstallNetworkExt() + case .installSafariExtension: + OnBoardingInstallSafariExt() + case .installChromeExtension: + OnBoardingInstallChromeExt() + } + } +} + +#Preview { + AppOnboardingView() +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/ClickableLinkButton.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/ClickableLinkButton.swift new file mode 100644 index 00000000..54268fea --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/ClickableLinkButton.swift @@ -0,0 +1,31 @@ +// +// ClickableLinkButton.swift +// SelfControl +// +// Created by Satendra Singh on 04/02/26. +// + +import SwiftUI + +struct ClickableLinkButton: View { + let message: String + var onTap: () -> Void + var body: some View { + Text("[\(message)](https://localhost)") + .environment(\.openURL, OpenURLAction { url in + handleURL(url) + return .handled + }) + } + func handleURL(_ url: URL) { + print("Handled URL: \(url)") + onTap() + // Customize the action for the URL here + } +} + +#Preview { + ClickableLinkButton(message: "Skip and accept subpar blocking", onTap: { + + }) +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallChromeExt.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallChromeExt.swift new file mode 100644 index 00000000..193cf702 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallChromeExt.swift @@ -0,0 +1,59 @@ +// +// OnBoardingInstallChromeExt.swift +// SelfControl +// +// Created by Satendra Singh on 08/02/26. +// + + +import SwiftUI + +struct OnBoardingInstallChromeExt: View { + var body: some View { + VStack { + Text("Welcome to SelfControl!") + .font(.largeTitle) + + Spacer() + + Text("SelfControl helps you focus by blocking your own access to distracting websites.") + .font(.title2) + + Spacer() + + Text("To get started, we'll need t o prepare your computer so our blocks work properly.") + .font(.title2) + + Spacer() + OnboardingStepView(step: 2) + + Text("Install the SelfControl Chrome Extension so we can provide better blocking in Chrome.. We never store, share, or analyze your data - this is used or blocking.") + .font(.title2) + + Spacer() + + Button("Install Chrome Extension") { + // +// if let url = URL(string: "chrome://extensions") { +// NSWorkspace.shared.open(url) +// } + let task = Process() + task.launchPath = "/usr/bin/open" + task.arguments = ["-a", "Google Chrome", "chrome://extensions"] + task.launch() + } + + Spacer() + ClickableLinkButton(message: "Skip and accept subpar blocking", onTap: { + print("Handle callback") + }) + Spacer() +// .tint(.blue) + } + .padding(20) + } +} + +#Preview { + OnBoardingInstallChromeExt() +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallNetworkExt.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallNetworkExt.swift new file mode 100644 index 00000000..7f182444 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallNetworkExt.swift @@ -0,0 +1,52 @@ +// +// OnBoardingView1.swift +// SelfControl +// +// Created by Satendra Singh on 01/02/26. +// + +import SwiftUI + +struct OnBoardingInstallNetworkExt: View { + @EnvironmentObject var viewModel: FilterViewModel + + var body: some View { + VStack { + Text("Welcome to SelfControl!") + .font(.largeTitle) + + Spacer() + + Text("SelfControl helps you focus by blocking your own access to distracting websites.") + .font(.title2) + + Spacer() + + Text("To get started, we'll need t o prepare your computer so our blocks work properly.") + .font(.title2) + + Spacer() + OnboardingStepView(step: 1) + + Text("Install the SelfControl Network Extension so we can block network traffic in any application in your computer. We never store, share, or analyze your data - this is used or blocking only.") + .font(.title2) + + Spacer() + + Button("Install Network Extension") { + viewModel.activateExtension() + } + + Spacer() + ClickableLinkButton(message: "Skip and accept subpar blocking", onTap: { + print("Handle callback") + viewModel.isNetworkExtensionSkipped.toggle() + }) + } + .padding(20) + } +} + +#Preview { + OnBoardingInstallNetworkExt() +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallSafariExt.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallSafariExt.swift new file mode 100644 index 00000000..ce19cfc8 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallSafariExt.swift @@ -0,0 +1,74 @@ +// +// OnBoardingView2.swift +// SelfControl +// +// Created by Satendra Singh on 04/02/26. +// + +import SwiftUI +import SafariServices + +struct OnBoardingInstallSafariExt: View { + var body: some View { + VStack { + Text("Welcome to SelfControl!") + .font(.largeTitle) + + Spacer() + + Text("SelfControl helps you focus by blocking your own access to distracting websites.") + .font(.title2) + + Spacer() + + Text("To get started, we'll need t o prepare your computer so our blocks work properly.") + .font(.title2) + + Spacer() + OnboardingStepView(step: 3) + + Text("Install the SelfControl Saari Extension so we can provide better blocking in Safari. We never store, share,o r analyze your data - this i s used o r blocking only.") + .font(.title2) + + Spacer() + + Button("Install Safari Extension") { + // + enableExtension() + } + + Spacer() + ClickableLinkButton(message: "Skip and accept subpar blocking", onTap: { + print("Handle callback") + }) + Spacer() +// .tint(.blue) + } + .padding(20) + } + + func enableExtension() { + SFSafariApplication.showPreferencesForExtension(withIdentifier: SafariExtensionConstants.identifier) { error in + if let error = error { +// NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.Safari-Settings.extension")!) + NSLog("Error opening Safari preferences: \(error.localizedDescription)") + NSWorkspace.shared.openApplication( + at: URL(fileURLWithPath: "/Applications/Safari.app"), + configuration: NSWorkspace.OpenConfiguration(), + completionHandler: nil + ) + } + } + +// SFSafariApplication.showPreferencesForExtension(withIdentifier: "com.application.SelfControl.corebits.SelfControl-Safari-Extension") { (error) in +// if let error = error { +// NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.Safari-Settings.extension")!) +// NSLog("Error opening Safari preferences: \(error.localizedDescription)") +// } +// } + } +} + +#Preview { + OnBoardingInstallSafariExt() +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnboardingStepView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnboardingStepView.swift new file mode 100644 index 00000000..7a2ba000 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnboardingStepView.swift @@ -0,0 +1,53 @@ +// +// OnboardingStepView.swift +// SelfControl +// +// Created by Satendra Singh on 01/02/26. +// + +import SwiftUI + +struct OnboardingStepView: View { + let step: Int + let totakSteps: Int = 3 + var body: some View { + HStack { + Spacer() + + Text("Step \(step) of \(totakSteps)") + .font(.title2) + + Spacer() + + PartialProgressBar(progress: Double(step) / Double(totakSteps)) + Spacer() + + } + .padding() + } +} + +struct PartialProgressBar: View { + var progress: CGFloat // value between 0.0 and 1.0 + var height: CGFloat = 12 + + var body: some View { + GeometryReader { geometry in + ZStack(alignment: .leading) { + // Background + Capsule() + .fill(Color.gray.opacity(0.3)) + + // Filled portion + Capsule() + .fill(Color.blue) + .frame(width: geometry.size.width * progress) + } + } + .frame(height: height) + } +} + +#Preview { + OnboardingStepView(step: 1) +} From 75e44e177058d2afe9823f6e3188b57cd27dd64c Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 22 Feb 2026 23:12:09 +0530 Subject: [PATCH 19/64] updated main app view --- .../SelfControl/SelfControlApp.swift | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift index 30eec309..fb954f0a 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift @@ -15,10 +15,29 @@ struct SelfControlApp: App { var body: some Scene { WindowGroup { - if viewModel.status == .running { - newView - } else { - oldView + switch viewModel.viewState { + case .filter: + NewContentView() + .environmentObject(viewModel) // Inject the object into the environment + .background(WindowTitleBarHider()) + case .installNetworkExtension: + OnBoardingInstallNetworkExt() + .environmentObject(viewModel) // Inject the object into the environment + .frame(width: 520, height: 420) + + case .installSafariExtension: + OnBoardingInstallSafariExt() + .environmentObject(viewModel) // Inject the object into the environment + .frame(width: 520, height: 420) + + case .installChromeExtension: + OnBoardingInstallChromeExt() + .environmentObject(viewModel) // Inject the object into the environment + .frame(width: 520, height: 420) + + + default: + EmptyView() } } .commands { @@ -92,14 +111,8 @@ struct SelfControlApp: App { viewModel.stopFilter() } }) - } + } - private var newView: some View { - NewContentView() - .environmentObject(viewModel) // Inject the object into the environment - .background(WindowTitleBarHider()) - } - func resolveIPToHostname(ipAddress: String) -> String? { let hostRef = CFHostCreateWithName(nil, ipAddress as CFString).takeRetainedValue() @@ -154,5 +167,17 @@ struct SelfControlApp: App { guard result == 0 else { return nil } return String(cString: hostname) } +} +extension AppOnboardingViewState { + init(state: SelfControlViewState) { + switch state { + case .installChromeExtension: + self = .installChromeExtension + case .installSafariExtension: + self = .installSafariExtension + default : + self = .installNetworkExtension + } + } } From 46cd683d788652df5e04d75e7b5d016d6eabf85d Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 22 Feb 2026 23:35:09 +0530 Subject: [PATCH 20/64] integrated extension state handler to update the ui --- .../SelfControl.xcodeproj/project.pbxproj | 12 ++++++------ .../ChromeExtensionRequestListner.swift | 7 ++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index ef51f53a..4168784f 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -591,7 +591,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 26210; DEVELOPMENT_ASSET_PATHS = "\"SelfControl/Preview Content\""; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; @@ -627,7 +627,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 26210; DEVELOPMENT_ASSET_PATHS = "\"SelfControl/Preview Content\""; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; @@ -660,7 +660,7 @@ CODE_SIGN_ENTITLEMENTS = SelfControlExtension/SelfControlExtension.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 26210; DEVELOPMENT_TEAM = X6FQ433AWK; "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; ENABLE_APP_SANDBOX = NO; @@ -694,7 +694,7 @@ CODE_SIGN_ENTITLEMENTS = SelfControlExtension/SelfControlExtension.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 26210; DEVELOPMENT_TEAM = X6FQ433AWK; "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; ENABLE_APP_SANDBOX = NO; @@ -784,7 +784,7 @@ CODE_SIGN_ENTITLEMENTS = "SelfControl Rules Extension/SelfControl Rules Extension.entitlements"; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 26210; DEVELOPMENT_TEAM = X6FQ433AWK; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; @@ -833,7 +833,7 @@ CODE_SIGN_ENTITLEMENTS = "SelfControl Rules Extension/SelfControl Rules Extension.entitlements"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 26210; DEVELOPMENT_TEAM = X6FQ433AWK; "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; ENABLE_APP_SANDBOX = YES; diff --git a/Self-Control-Extension/SelfControl/SelfControl/ChromeExtensionSupport/ChromeExtensionRequestListner.swift b/Self-Control-Extension/SelfControl/SelfControl/ChromeExtensionSupport/ChromeExtensionRequestListner.swift index c848d747..55c4b0d0 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/ChromeExtensionSupport/ChromeExtensionRequestListner.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/ChromeExtensionSupport/ChromeExtensionRequestListner.swift @@ -19,7 +19,8 @@ final class ChromeExtensionRequestListner: NSObject, ObservableObject { var listener: NWListener? var blockeddomainFetcher: (() -> [String])? var isBlockingEnabled: Bool = false - + var onExtensionStateChange: (() -> Void)? + func startListening() { os_log("[SC] 🔍] PlistListner startListening") @@ -124,6 +125,10 @@ final class ChromeExtensionRequestListner: NSObject, ObservableObject { NetworkExtensionState.shared.isChromeExtensionEnabled = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.chrome.rawValue, state: true) NetworkExtensionState.shared.printAll() } + if isChromeStatusSetInExtension == false { + isChromeStatusSetInExtension = true + self.onExtensionStateChange?() + } } } From 5dd76f28c578c0c0fc48e4952533848cae752ac3 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 22 Feb 2026 23:35:35 +0530 Subject: [PATCH 21/64] updated view to test onboarding view --- .../SelfControl/SelfControl/Main/ContentView.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift index 40cc7b8e..f6a1512a 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift @@ -83,8 +83,8 @@ struct ContentView: View { } Spacer() } - SafariExtensionWebView() - .frame(minHeight: 100) +// SafariExtensionWebView() +// .frame(minHeight: 100) HStack { TextField("Enter Url to test block", text: $newDomain) @@ -93,6 +93,7 @@ struct ContentView: View { } } Spacer() + AppOnboardingView() } .padding() .frame(minWidth: 150, minHeight: 150) From 7654a76af5c844e4af392570f8a9ebbb94dca76e Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 22 Feb 2026 23:36:16 +0530 Subject: [PATCH 22/64] added state handing with rew ui and udpated actions --- .../SelfControl/Main/FilterViewModel.swift | 165 +++++++++++++----- 1 file changed, 121 insertions(+), 44 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index 33bb05e6..d55c7ad1 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -13,8 +13,41 @@ import Cocoa import Combine import SafariServices +enum SelfControlViewState: Equatable { + + static func == (lhs: SelfControlViewState, rhs: SelfControlViewState) -> Bool { + switch (lhs, rhs) { + case (.installNetworkExtension, .installNetworkExtension), + (.installSafariExtension, .installSafariExtension), + (.installChromeExtension, .installChromeExtension), + (.error, .error), + (.filter, .filter): + return true + default: + return false + } + } + + + case installNetworkExtension + case installSafariExtension + case installChromeExtension + case error(Error) + case filter +} + final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionRequestDelegate, ExtensionToApp { @Published var status: Status = .stopped + @Published var viewState: SelfControlViewState = .installNetworkExtension + @Published var isNetworkExtensionSkipped: Bool = false { + didSet { + self.viewState = .filter + } + } + + private var isSafariExtensionInstalled: Bool = ProxyPreferences.isSafariExtensionInstalled + private var isChromeExtensionInstalled: Bool = ProxyPreferences.isChromeExtensionInstalled + @State private var domains = ProxyPreferences.getBlockedDomains() private let chromeService = ChromeExtensionRequestListner() @State var blockedURLs: [BlockedURL] = [] @@ -24,9 +57,6 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques private var cancellables = Set() @Published var isActiveBlocking: Bool = false lazy var selfControlDaemon = SSCDaemonHelper() - - // Safari extension identifier used to query state - private let safariExtensionIdentifier = "com.application.SelfControl.corebits.SelfControl-Safari-Extension" // Timer to manage delayed actions based on `delay` (in minutes) private var blockTimer: Timer? @@ -65,15 +95,30 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques override init() { super.init() + ProxyPreferences.reset() //TODO: remove onInit() - SafariExtensionManager.shared.onChange = { + SafariExtensionManager.shared.onExtensionStateChange = { print("SafariExtensionManager.shared.onChange++") + self.isSafariExtensionInstalled = true + ProxyPreferences.setSafariExtensionInstalled() + Task { @MainActor in + self.updateSafariExtensionViewStatus() + } + } + self.chromeService.onExtensionStateChange = { + print("Chrome.shared.onChange++") + self.isChromeExtensionInstalled = true + ProxyPreferences.setChromeExtensionInstalled() + Task { @MainActor in + self.updateChromeExtensionViewStatus() + } } SafariExtensionManager.shared.resetExtensionState() self.extensionIdentifier = extensionBundle.bundleIdentifier self.chromeService.blockeddomainFetcher = { return ProxyPreferences.getBlockedDomains() } + self.chromeService.startListening() // Print status whenever it changes @@ -104,6 +149,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques Self.loadFilterConfiguration { success in guard success else { self.status = .stopped + self.viewState = .installNetworkExtension self.refreshExtensionState() return } @@ -128,10 +174,58 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques if isNEEnabled == true { //Reset NetworkExtensionState.shared.isSafariExtensionEnabled = false NetworkExtensionState.shared.isChromeExtensionEnabled = false + updateNetworkExtensionViewStatus() } } // We’ll query Safari extension state asynchronously for accuracy. } + @MainActor func updateNetworkExtensionViewStatus() { + if NetworkExtensionState.shared.isEnabled == true { + withAnimation(.easeInOut(duration: 3)) { + self.viewState = .installChromeExtension + updateChromeExtensionViewStatus() + } + } + } + + @MainActor func updateSafariExtensionViewStatus() { + if .installNetworkExtension == viewState { + print(".installNetworkExtension == viewState in updateSafariExtensionViewStatus") + return + } + + if isSafariExtensionInstalled == true { + withAnimation(.easeInOut(duration: 3)) { + print(" isSafariExtensionInstalled == true true in Safari") + self.viewState = .filter + } + + } else { + withAnimation(.easeInOut(duration: 3)) { + print("viewState = .installChromeExtension true in Safari") + self.viewState = .installSafariExtension + } + } + } + + @MainActor func updateChromeExtensionViewStatus() { + if .installNetworkExtension == viewState { + print(".installNetworkExtension == viewState in Chrome") + return + } + + if isChromeExtensionInstalled == true { + print("if isChromeExtensionInstalled == true in Chrome") + + updateSafariExtensionViewStatus() + } else { + withAnimation(.easeInOut(duration: 3)) { + print("viewState = .installChromeExtension true in Chrome") + self.viewState = .installChromeExtension + } + } + } + // MARK: - UI and Filter Management func setBlockedUrls(urls: [String]) { @@ -153,13 +247,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func setIPAddressesToBlock(addresses: [String]) { IPCConnection.shared.enableIPAddressesBlocking(addresses) } - - private func refreshBlockedIPs() { - self.chromeService.blockeddomainFetcher = { - return ProxyPreferences.getBlockedDomains() - } - } - + func updateStatus() { if NEFilterManager.shared().isEnabled { registerWithProvider() @@ -209,6 +297,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques let providerConfiguration = NEFilterProviderConfiguration() providerConfiguration.filterSockets = true providerConfiguration.filterPackets = false +// providerConfiguration.filterBrowsers = true filterManager.providerConfiguration = providerConfiguration if let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String { filterManager.localizedDescription = appName @@ -222,8 +311,6 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques self.status = .stopped self.refreshExtensionState() return - } else { -// self.enableDNSProxy() } self.registerWithProvider() } @@ -231,30 +318,6 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } } - func enableDNSProxy() { - let manager = NEDNSProxyManager.shared() - - manager.loadFromPreferences { error in - guard error == nil else { return } - - let proto = NEDNSProxyProviderProtocol() - proto.providerBundleIdentifier = "com.application.SelfControl.corebits.network" - proto.serverAddress = "127.0.0.1" // placeholder -// proto.filterSockets = true - manager.localizedDescription = "DNS Logger" - manager.providerProtocol = proto - manager.isEnabled = true - - manager.saveToPreferences { saveError in - if let saveError = saveError { - print("Failed to save: \(saveError)") - } else { - print("DNS proxy saved.") - } - } - } - } - func registerWithProvider() { // Assuming an IPCConnection singleton similar to the AppKit sample IPCConnection.shared.register(withExtension: extensionBundle, delegate: self) { success in @@ -440,6 +503,15 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques return true } + func extendBlockTimer(by minutes: Int) { + guard minutes != 0, let timer = blockTimer else { return } + let delta = TimeInterval(minutes * 60) + timer.fireDate = timer.fireDate.addingTimeInterval(delta) + // Optionally also track a blockEndDate if you show a countdown + timerFireDate = timer.fireDate + os_log("[SC] 🔍] Timer fire date updated to %{public}@", timerFireDate?.description ?? "Empty time") + } + func cancelTimer() { if let fireDate = timerFireDate { os_log("[SC] 🔍] Cancelling timer scheduled for %{public}@", fireDate as NSDate) @@ -459,16 +531,21 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } func updateBlockList(newBlockedDomains: [BlockedURL], time: Double ) { - + self.delay = time let urls = newBlockedDomains.compactMap(\.urls) let flattened: [String] = urls.flatMap { $0 } - ProxyPreferences.setBlockedDomains(flattened) - setBlockedUrls(urls: flattened) - self.delay = time - if startTimerWithSelectedDelay() == false { - return + + if status == .stopped { + installLegacyLaunched(futureDuration: Date.now.addingTimeInterval(delay*60)) + } else { + if startTimerWithSelectedDelay() == false { + return + } + + setBlockedUrls(urls: flattened) + if startTimerWithSelectedDelay() == false { return } + activateNetworkBlocking() } - activateNetworkBlocking() } } From 0bbb4f83a7976541c1c4e9cc076a0ef2e2d37c49 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 23 Feb 2026 22:16:43 +0530 Subject: [PATCH 23/64] added new keys in preferences to save ext state --- .../Preferences/ProxyPreferences.swift | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift index 692462f0..6f6c9822 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift @@ -13,6 +13,9 @@ struct ProxyPreferences { private static let blockedDomainsKey = "BlockedDomains" private static let isSafariExtensionKey: String = "isSafariExtensionKey" private static let isChromeExtensionKey: String = "isChromeExtensionKey" + private static let isSafariExtInstallKey: String = "isSafariExtInstallKey" + private static let isChromeExtInstallKey: String = "isChromeExtInstallKey" + private static let defaults = UserDefaults.standard static func getBlockedDomains() -> [String] { return defaults.stringArray(forKey: blockedDomainsKey) ?? [] @@ -39,4 +42,27 @@ struct ProxyPreferences { static func chromeExtensionState() { defaults.value(forKey: isChromeExtensionKey) } + + static var isSafariExtensionInstalled: Bool { + return UserDefaults.standard.bool(forKey: isSafariExtInstallKey) + } + + static func setSafariExtensionInstalled() { + UserDefaults.standard.set(true, forKey: isSafariExtInstallKey) + } + + static var isChromeExtensionInstalled: Bool { + return UserDefaults.standard.bool(forKey: isChromeExtInstallKey) + } + + static func setChromeExtensionInstalled() { + UserDefaults.standard.set(true, forKey: isChromeExtInstallKey) + } + + static func reset() { + UserDefaults.standard.removeObject(forKey: isChromeExtInstallKey) + UserDefaults.standard.removeObject(forKey: isSafariExtInstallKey) + UserDefaults.standard.removeObject(forKey: isSafariExtensionKey) + UserDefaults.standard.removeObject(forKey: isChromeExtensionKey) + } } From 0a04b1c5527c7d3713e0930dca3ad9b2bc0ea114 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 23 Feb 2026 22:17:02 +0530 Subject: [PATCH 24/64] added callbacks in safari extension --- .../SafariExtensionManager.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/SafariExtensionManager.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/SafariExtensionManager.swift index a35061c3..d8b159b6 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/SafariExtensionManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/SafariExtensionManager.swift @@ -10,10 +10,14 @@ import os.log final class SafariExtensionManager: ObservableObject { static let shared = SafariExtensionManager() - var onChange: (() -> Void)? + var onExtensionStateChange: (() -> Void)? private var isReady: Bool = false private var isEnabled = false - var lastUpdateReceivedTime: Date = .distantPast + var lastUpdateReceivedTime: Date? { + didSet { + onExtensionStateChange?() + } + } private init() { NotificationCenter.default.addObserver( @@ -24,10 +28,12 @@ final class SafariExtensionManager: ObservableObject { let shared = UserDefaults(suiteName: SafariConst.appGroup) print("Received from extension:", shared?.string(forKey: "ready") ?? "") if let value = shared?.bool(forKey: "ready") { - print("Ready now:") - if self.isReady == false { - self.onChange?() - self.isReady = true + if value == true { + print("Ready now:") + if self.isReady == false { + self.onExtensionStateChange?() + self.isReady = true + } } } } @@ -35,6 +41,7 @@ final class SafariExtensionManager: ObservableObject { } var isExtensionReady: Bool { + guard let lastUpdateReceivedTime = lastUpdateReceivedTime else { return false } // Consider the extension "ready" if we received an update within the last 35 seconds return Date().timeIntervalSince(lastUpdateReceivedTime) <= 35 // return UserDefaults(suiteName: appGroup)?.bool(forKey: "ready") == true From 7136e2aff433d82e72721f6d5ce84510d186d4e6 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 23 Feb 2026 22:17:22 +0530 Subject: [PATCH 25/64] added icon and gesture in the ui --- .../SelfControl/SelfControlUI/NewContentView.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift index b031afa9..09473d01 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift @@ -176,6 +176,7 @@ struct NewContentView: View { extensionMinutes: $extensionMinutes, onConfirm: { minutes in extendTimer(by: minutes) + viewModel.extendBlockTimer(by: Int(minutes)) withAnimation(DesignSystem.animationNormal) { showingExtendTimer = false } @@ -256,7 +257,7 @@ struct NewContentView: View { Spacer() // Logo - if let nsImage = NSImage(named: "AppIcon") { + if let nsImage = NSImage(named: "icon") { Image(nsImage: nsImage) .resizable() .aspectRatio(contentMode: .fit) @@ -300,6 +301,9 @@ struct NewContentView: View { Text("\(Strings.MainScreen.blocking) \(blockingCountMessage)") .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) .foregroundColor(DesignSystem.textSecondary) + .onTapGesture { + currentScreen = .editList + } } } else { if activeBlockedCount == 0 { From 24bd6417f13ba7366c8f51a1543ff386f197c8ed Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 23 Feb 2026 22:18:04 +0530 Subject: [PATCH 26/64] changed message of block list --- .../SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift index edd5c7b4..b1b2b769 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift @@ -302,7 +302,7 @@ struct Strings { // MARK: - App Menu struct AppMenu { static let aboutSelfControl = "About SelfControl" - static let editSiteList = "Edit Site List" + static let editSiteList = "Edit Block List" static let editBlockSchedule = "Edit Block Schedule" static let moreSettings = "More Settings" static let donate = "Donate" From ca88f67dda50fcc5c5ee8031b08f054c42c2fd3a Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 23 Feb 2026 22:18:35 +0530 Subject: [PATCH 27/64] integrated import and export functionality --- .../Views/BlocklistEditorView.swift | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift index 7341e0fa..1da485ec 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift @@ -138,7 +138,7 @@ struct BlocklistEditorView: View { // Import button Button(action: { - // TODO: Import functionality + importurls() }) { HStack(spacing: 4) { Image(systemName: "square.and.arrow.down") @@ -165,7 +165,7 @@ struct BlocklistEditorView: View { // Export button Button(action: { - // TODO: Export functionality + exporturls() }) { HStack(spacing: 4) { Image(systemName: "square.and.arrow.up") @@ -1350,3 +1350,37 @@ struct BlocklistEditorView: View { } } +extension BlocklistEditorView { + + func importurls() { + + if let window = NSApplication.shared.keyWindow { + Task { @MainActor in + do { + let imported = try await ImportExportManager.importBlockedUrls(presentingWindow: window) + self.blockedURLs = imported + // Merge or replace your in-memory list here + } catch { + // Handle cancel or decoding errors + NSLog("Import failed: \(error.localizedDescription)") + } + } + } else { + // Modal (no sheet) + Task { @MainActor in + do { + let imported = try await ImportExportManager.importBlockedUrls() + // Merge or replace your in-memory list here + self.blockedURLs = imported + } catch { + // Handle cancel or decoding errors + NSLog("Import failed: \(error.localizedDescription)") + } + } + } + } + + func exporturls() { + ImportExportManager.exportBlockedUrls(blockedURLs: self.blockedURLs) + } +} From b0e05fdd9e97249ef757de816bdbd6a521019ed9 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 1 Mar 2026 22:36:40 +0530 Subject: [PATCH 28/64] Implemented event scheduler and runner --- .../EventScheduler/EventScheduler.swift | 274 ++++++++++++++ .../EventScheduler/EventScheduler.swift | 342 ++++++++++++++++++ .../EventScheduler/EventSchedulerRunner.swift | 195 ++++++++++ .../EventScheduler/EventSchedulerStore.swift | 97 +++++ .../Main/FilterViewModel+EventScheduler.swift | 49 +++ 5 files changed, 957 insertions(+) create mode 100644 Self-Control-Extension/SelfControl/SelfControl.xcodeproj/EventScheduler/EventScheduler.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventScheduler.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerRunner.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerStore.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+EventScheduler.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/EventScheduler/EventScheduler.swift b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/EventScheduler/EventScheduler.swift new file mode 100644 index 00000000..debe22ad --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/EventScheduler/EventScheduler.swift @@ -0,0 +1,274 @@ +// +// EventScheduler.swift +// +// A simple, concurrency-safe scheduler for recurring events by weekday and time. +// It validates time ranges and prevents overlapping events on the same day. +// + +import Foundation + +public enum SchedulerError: Error, LocalizedError, Equatable { + case invalidTimeRange + case conflict(conflictingEvents: [Event]) + case eventNotFound + + public var errorDescription: String? { + switch self { + case .invalidTimeRange: + return "The start time must be earlier than the end time." + case .conflict(let conflicts): + let names = conflicts.map { $0.title }.joined(separator: ", ") + return conflicts.isEmpty + ? "The event conflicts with an existing event." + : "The event conflicts with the following events: \(names)" + case .eventNotFound: + return "The event could not be found." + } + } +} + +/// Weekday aligned with the user's Calendar (1 = Sunday in many locales). +/// We normalize to a fixed enumeration order (Monday...Sunday) for consistency. +public enum Weekday: Int, CaseIterable, Codable, Hashable, Sendable { + case monday = 2 + case tuesday = 3 + case wednesday = 4 + case thursday = 5 + case friday = 6 + case saturday = 7 + case sunday = 1 + + public var displayName: String { + switch self { + case .monday: return "Monday" + case .tuesday: return "Tuesday" + case .wednesday: return "Wednesday" + case .thursday: return "Thursday" + case .friday: return "Friday" + case .saturday: return "Saturday" + case .sunday: return "Sunday" + } + } + + /// Returns the Weekday for a given Date using the provided Calendar. + public static func from(date: Date, calendar: Calendar = .current) -> Weekday { + let weekdayNumber = calendar.component(.weekday, from: date) + return Weekday(rawValue: weekdayNumber) ?? .monday + } +} + +/// Represents a wall-clock time without a specific date or time zone. +/// Valid range: hour 0...23, minute 0...59 +public struct TimeOfDay: Codable, Hashable, Sendable, Comparable { + public let hour: Int + public let minute: Int + + public init(hour: Int, minute: Int) { + precondition((0...23).contains(hour), "Hour must be 0...23") + precondition((0...59).contains(minute), "Minute must be 0...59") + self.hour = hour + self.minute = minute + } + + public var minutesSinceMidnight: Int { + hour * 60 + minute + } + + public static func < (lhs: TimeOfDay, rhs: TimeOfDay) -> Bool { + lhs.minutesSinceMidnight < rhs.minutesSinceMidnight + } + + public func formatted(locale: Locale = .current) -> String { + var comps = DateComponents() + comps.hour = hour + comps.minute = minute + let cal = Calendar(identifier: .gregorian) + let date = cal.date(from: comps) ?? Date() + let formatter = DateFormatter() + formatter.locale = locale + formatter.timeStyle = .short + formatter.dateStyle = .none + return formatter.string(from: date) + } +} + +/// A recurring event that happens on one or more weekdays at a given time range. +public struct Event: Identifiable, Codable, Hashable, Sendable { + public let id: UUID + public var title: String + public var days: Set + public var startTime: TimeOfDay + public var endTime: TimeOfDay + public var userInfo: [String: String]? // Optional metadata + + public init( + id: UUID = UUID(), + title: String, + days: Set, + startTime: TimeOfDay, + endTime: TimeOfDay, + userInfo: [String: String]? = nil + ) throws { + guard startTime < endTime else { + throw SchedulerError.invalidTimeRange + } + self.id = id + self.title = title + self.days = days + self.startTime = startTime + self.endTime = endTime + self.userInfo = userInfo + } + + /// Returns true if this event occurs on the given weekday. + public func occurs(on day: Weekday) -> Bool { + days.contains(day) + } + + /// Overlap check with another event on a specific weekday. + /// Adjacent ranges (end == start) are not considered overlapping. + public func overlaps(with other: Event, on day: Weekday) -> Bool { + guard self.occurs(on: day), other.occurs(on: day) else { return false } + let aStart = self.startTime.minutesSinceMidnight + let aEnd = self.endTime.minutesSinceMidnight + let bStart = other.startTime.minutesSinceMidnight + let bEnd = other.endTime.minutesSinceMidnight + return aStart < bEnd && aEnd > bStart + } +} + +/// A concurrency-safe scheduler to add, update, and query recurring events by weekday and time. +public actor EventScheduler { + public init(calendar: Calendar = .current) { + self.calendar = calendar + } + + private let calendar: Calendar + private var eventsByID: [UUID: Event] = [:] + + // MARK: - CRUD + + /// Schedules a new event. Throws if the time range is invalid or conflicts with existing events. + @discardableResult + public func schedule( + title: String, + days: Set, + startTime: TimeOfDay, + endTime: TimeOfDay, + userInfo: [String: String]? = nil + ) throws -> Event { + let newEvent = try Event(title: title, days: days, startTime: startTime, endTime: endTime, userInfo: userInfo) + try assertNoConflicts(with: newEvent, excludingID: nil) + eventsByID[newEvent.id] = newEvent + return newEvent + } + + /// Updates an existing event. Throws if not found or if the updated event conflicts. + public func update( + id: UUID, + title: String? = nil, + days: Set? = nil, + startTime: TimeOfDay? = nil, + endTime: TimeOfDay? = nil, + userInfo: [String: String]? = nil + ) throws -> Event { + guard var existing = eventsByID[id] else { + throw SchedulerError.eventNotFound + } + + let newTitle = title ?? existing.title + let newDays = days ?? existing.days + let newStart = startTime ?? existing.startTime + let newEnd = endTime ?? existing.endTime + let newUserInfo = userInfo ?? existing.userInfo + + let updated = try Event(id: id, title: newTitle, days: newDays, startTime: newStart, endTime: newEnd, userInfo: newUserInfo) + try assertNoConflicts(with: updated, excludingID: id) + eventsByID[id] = updated + return updated + } + + /// Removes an event by ID. Returns the removed event or throws if not found. + @discardableResult + public func remove(id: UUID) throws -> Event { + guard let removed = eventsByID.removeValue(forKey: id) else { + throw SchedulerError.eventNotFound + } + return removed + } + + /// Returns all scheduled events sorted by title. + public func allEvents() -> [Event] { + eventsByID.values.sorted { $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedAscending } + } + + /// Returns events that occur on the specified weekday, sorted by start time. + public func events(on day: Weekday) -> [Event] { + eventsByID.values + .filter { $0.occurs(on: day) } + .sorted { lhs, rhs in + if lhs.startTime == rhs.startTime { + return lhs.title.localizedCaseInsensitiveCompare(rhs.title) == .orderedAscending + } + return lhs.startTime < rhs.startTime + } + } + + // MARK: - Conflict Checking + + /// Returns any events that would conflict with the provided event. + public func conflicts(for event: Event, excludingID: UUID? = nil) -> [Event] { + eventsByID.values + .filter { existing in + if let exclude = excludingID, existing.id == exclude { return false } + // Only check days that overlap + let sharedDays = existing.days.intersection(event.days) + guard !sharedDays.isEmpty else { return false } + return sharedDays.contains { event.overlaps(with: existing, on: $0) } + } + .sorted { $0.startTime < $1.startTime } + } + + private func assertNoConflicts(with event: Event, excludingID: UUID?) throws { + let conflicts = conflicts(for: event, excludingID: excludingID) + if !conflicts.isEmpty { + throw SchedulerError.conflict(conflictingEvents: conflicts) + } + } + + // MARK: - Query Helpers + + /// Returns the next DateInterval this event will occur after the given date, if any. + /// This uses the scheduler's calendar to resolve the next occurrence in the current week or later. + public func nextOccurrence(of eventID: UUID, after date: Date = Date()) -> DateInterval? { + guard let event = eventsByID[eventID] else { return nil } + return nextOccurrence(of: event, after: date) + } + + /// Returns the next DateInterval an event will occur after the given date, if any. + public func nextOccurrence(of event: Event, after date: Date = Date()) -> DateInterval? { + // Search up to 8 weeks ahead to be safe for unusual calendars + for dayOffset in 0..<(7 * 8) { + guard let candidateDay = calendar.date(byAdding: .day, value: dayOffset, to: date) else { continue } + let weekday = Weekday.from(date: candidateDay, calendar: calendar) + guard event.occurs(on: weekday) else { continue } + + var comps = calendar.dateComponents([.year, .month, .day], from: candidateDay) + comps.hour = event.startTime.hour + comps.minute = event.startTime.minute + comps.second = 0 + let start = calendar.date(from: comps) + + comps.hour = event.endTime.hour + comps.minute = event.endTime.minute + let end = calendar.date(from: comps) + + if let start, let end, end > date { + // If the start is in the past but end is in the future, return the remaining portion. + let actualStart = max(start, date) + return DateInterval(start: actualStart, end: end) + } + } + return nil + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventScheduler.swift b/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventScheduler.swift new file mode 100644 index 00000000..c16efccd --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventScheduler.swift @@ -0,0 +1,342 @@ +// +// EventScheduler.swift +// SelfControl +// +// Created by Satendra Singh on 23/02/26. +// + +import Foundation + + +// A simple, concurrency-safe scheduler for recurring events by SCSCWeekday and time. +// It validates time ranges and prevents overlapping events on the same day. + +public enum SchedulerError: Error, LocalizedError, Equatable { + case invalidTimeRange + case conflict(conflictingEvents: [Event]) + case eventNotFound + + public var errorDescription: String? { + switch self { + case .invalidTimeRange: + return "The start time must be earlier than the end time." + case .conflict(let conflicts): + let names = conflicts.map { $0.title }.joined(separator: ", ") + return conflicts.isEmpty + ? "The event conflicts with an existing event." + : "The event conflicts with the following events: \(names)" + case .eventNotFound: + return "The event could not be found." + } + } +} + +/// SCWeekday aligned with the user's Calendar (1 = Sunday in many locales). +/// We normalize to a fixed enumeration order (Monday...Sunday) for consistency. +public enum SCWeekday: Int, CaseIterable, Codable, Hashable, Sendable { + case monday = 2 + case tuesday = 3 + case wednesday = 4 + case thursday = 5 + case friday = 6 + case saturday = 7 + case sunday = 1 + +// public var displayName: String { +// switch self { +// case .monday: return "Monday" +// case .tuesday: return "Tuesday" +// case .wednesday: return "Wednesday" +// case .thursday: return "Thursday" +// case .friday: return "Friday" +// case .saturday: return "Saturday" +// case .sunday: return "Sunday" +// } +// } + + var letter: String { + switch self { + case .monday: return "M" + case .tuesday: return "T" + case .wednesday: return "W" + case .thursday: return "T" + case .friday: return "F" + case .saturday: return "S" + case .sunday: return "S" + } + } + + var fullName: String { + switch self { + case .monday: return "Monday" + case .tuesday: return "Tuesday" + case .wednesday: return "Wednesday" + case .thursday: return "Thursday" + case .friday: return "Friday" + case .saturday: return "Saturday" + case .sunday: return "Sunday" + } + } + + /// Returns the SCWeekday for a given Date using the provided Calendar. + public static func from(date: Date, calendar: Calendar = .current) -> SCWeekday { + let SCWeekdayNumber = calendar.component(.weekday, from: date) + return SCWeekday(rawValue: SCWeekdayNumber) ?? .monday + } +} + +/// Represents a wall-clock time without a specific date or time zone. +/// Valid range: hour 0...23, minute 0...59 +public struct TimeOfDay: Codable, Hashable, Sendable, Comparable { + public let hour: Int + public let minute: Int + + public init(hour: Int, minute: Int) { + precondition((0...23).contains(hour), "Hour must be 0...23") + precondition((0...59).contains(minute), "Minute must be 0...59") + self.hour = hour + self.minute = minute + } + + public var minutesSinceMidnight: Int { + hour * 60 + minute + } + + public static func < (lhs: TimeOfDay, rhs: TimeOfDay) -> Bool { + lhs.minutesSinceMidnight < rhs.minutesSinceMidnight + } + + + /// Total minutes since midnight (00:00). + var totalMinutesSinceMidnight: Int { + hour * 60 + minute + } + + /// Returns the number of minutes from `other` to `self`. + /// - Parameters: + /// - other: The starting time of day. + /// - wrapAroundMidnight: If true, negative differences will be wrapped + /// by adding 24 hours (1440 minutes). This is useful when you want the + /// forward difference within the same 24-hour cycle. + /// - Returns: The minute difference as an `Int`. + /// + /// Examples: + /// - 10:30.minutes(from: 09:15) == 75 + /// - 01:00.minutes(from: 23:30, + /// wrapAroundMidnight: true) == 90 + /// - 01:00.minutes(from: 23:30, wrapAroundMidnight: false) == -1410 + func minutes(from other: TimeOfDay, wrapAroundMidnight: Bool = false) -> Int { + let diff = self.totalMinutesSinceMidnight - other.totalMinutesSinceMidnight + guard wrapAroundMidnight, diff < 0 else { return diff } + return diff + 24 * 60 + } + + /// Convenience: minutes until another time (alias with reversed arguments). + /// Positive when `other` is later than `self` on the same day. + func minutes(until other: TimeOfDay, wrapAroundMidnight: Bool = false) -> Int { + other.minutes(from: self, wrapAroundMidnight: wrapAroundMidnight) + } + + public func formatted(locale: Locale = .current) -> String { + var comps = DateComponents() + comps.hour = hour + comps.minute = minute + let cal = Calendar(identifier: .gregorian) + let date = cal.date(from: comps) ?? Date() + let formatter = DateFormatter() + formatter.locale = locale + formatter.timeStyle = .short + formatter.dateStyle = .none + return formatter.string(from: date) + } +} + +/// A recurring event that happens on one or more SCWeekdays at a given time range. +public struct Event: Identifiable, Codable, Hashable, Sendable { + public let id: UUID + public var title: String = "" + public var days: Set + public var startTime: TimeOfDay + public var endTime: TimeOfDay + public var userInfo: [String: String]? // Optional metadata + + public init( + id: UUID = UUID(), + title: String = "", + days: Set, + startTime: TimeOfDay, + endTime: TimeOfDay, + userInfo: [String: String]? = nil + ) throws { + guard startTime < endTime else { + throw SchedulerError.invalidTimeRange + } + self.id = id + self.title = title + self.days = days + self.startTime = startTime + self.endTime = endTime + self.userInfo = userInfo + } + + /// Returns true if this event occurs on the given SCWeekday. + public func occurs(on day: SCWeekday) -> Bool { + days.contains(day) + } + + /// Overlap check with another event on a specific SCWeekday. + /// Adjacent ranges (end == start) are not considered overlapping. + public func overlaps(with other: Event, on day: SCWeekday) -> Bool { + guard self.occurs(on: day), other.occurs(on: day) else { return false } + let aStart = self.startTime.minutesSinceMidnight + let aEnd = self.endTime.minutesSinceMidnight + let bStart = other.startTime.minutesSinceMidnight + let bEnd = other.endTime.minutesSinceMidnight + return aStart < bEnd && aEnd > bStart + } +} + +/// A concurrency-safe scheduler to add, update, and query recurring events by SCWeekday and time. +public actor EventScheduler { + public init(calendar: Calendar = .current) { + self.calendar = calendar + } + + public let calendar: Calendar + private var eventsByID: [UUID: Event] = [:] + + // MARK: - CRUD + + /// Schedules a new event. Throws if the time range is invalid or conflicts with existing events. + @discardableResult + public func schedule( + title: String = "", + days: Set, + startTime: TimeOfDay, + endTime: TimeOfDay, + userInfo: [String: String]? = nil + ) throws -> Event { + let newEvent = try Event(title: title, days: days, startTime: startTime, endTime: endTime, userInfo: userInfo) + try assertNoConflicts(with: newEvent, excludingID: nil) + eventsByID[newEvent.id] = newEvent + return newEvent + } + + @discardableResult + public func schedule( + newEvent: Event + ) throws -> Event { + try assertNoConflicts(with: newEvent, excludingID: nil) + eventsByID[newEvent.id] = newEvent + return newEvent + } + + /// Updates an existing event. Throws if not found or if the updated event conflicts. + public func update( + id: UUID, + title: String? = nil, + days: Set? = nil, + startTime: TimeOfDay? = nil, + endTime: TimeOfDay? = nil, + userInfo: [String: String]? = nil + ) throws -> Event { + guard var existing = eventsByID[id] else { + throw SchedulerError.eventNotFound + } + + let newTitle = title ?? existing.title + let newDays = days ?? existing.days + let newStart = startTime ?? existing.startTime + let newEnd = endTime ?? existing.endTime + let newUserInfo = userInfo ?? existing.userInfo + + let updated = try Event(id: id, title: newTitle, days: newDays, startTime: newStart, endTime: newEnd, userInfo: newUserInfo) + try assertNoConflicts(with: updated, excludingID: id) + eventsByID[id] = updated + return updated + } + + /// Removes an event by ID. Returns the removed event or throws if not found. + @discardableResult + public func remove(id: UUID) throws -> Event { + guard let removed = eventsByID.removeValue(forKey: id) else { + throw SchedulerError.eventNotFound + } + return removed + } + + /// Returns all scheduled events sorted by title. + public func allEvents() -> [Event] { + eventsByID.values.sorted { $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedAscending } + } + + /// Returns events that occur on the specified SCWeekday, sorted by start time. + public func events(on day: SCWeekday) -> [Event] { + eventsByID.values + .filter { $0.occurs(on: day) } + .sorted { lhs, rhs in + if lhs.startTime == rhs.startTime { + return lhs.title.localizedCaseInsensitiveCompare(rhs.title) == .orderedAscending + } + return lhs.startTime < rhs.startTime + } + } + + // MARK: - Conflict Checking + + /// Returns any events that would conflict with the provided event. + public func conflicts(for event: Event, excludingID: UUID? = nil) -> [Event] { + eventsByID.values + .filter { existing in + if let exclude = excludingID, existing.id == exclude { return false } + // Only check days that overlap + let sharedDays = existing.days.intersection(event.days) + guard !sharedDays.isEmpty else { return false } + return sharedDays.contains { event.overlaps(with: existing, on: $0) } + } + .sorted { $0.startTime < $1.startTime } + } + + private func assertNoConflicts(with event: Event, excludingID: UUID?) throws { + let conflicts = conflicts(for: event, excludingID: excludingID) + if !conflicts.isEmpty { + throw SchedulerError.conflict(conflictingEvents: conflicts) + } + } + + // MARK: - Query Helpers + + /// Returns the next DateInterval this event will occur after the given date, if any. + /// This uses the scheduler's calendar to resolve the next occurrence in the current week or later. + public func nextOccurrence(of eventID: UUID, after date: Date = Date()) -> DateInterval? { + guard let event = eventsByID[eventID] else { return nil } + return nextOccurrence(of: event, after: date) + } + + /// Returns the next DateInterval an event will occur after the given date, if any. + public func nextOccurrence(of event: Event, after date: Date = Date()) -> DateInterval? { + // Search up to 8 weeks ahead to be safe for unusual calendars + for dayOffset in 0..<(7 * 8) { + guard let candidateDay = calendar.date(byAdding: .day, value: dayOffset, to: date) else { continue } + let SCWeekday = SCWeekday.from(date: candidateDay, calendar: calendar) + guard event.occurs(on: SCWeekday) else { continue } + + var comps = calendar.dateComponents([.year, .month, .day], from: candidateDay) + comps.hour = event.startTime.hour + comps.minute = event.startTime.minute + comps.second = 0 + let start = calendar.date(from: comps) + + comps.hour = event.endTime.hour + comps.minute = event.endTime.minute + let end = calendar.date(from: comps) + + if let start, let end, end > date { + // If the start is in the past but end is in the future, return the remaining portion. + let actualStart = max(start, date) + return DateInterval(start: actualStart, end: end) + } + } + return nil + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerRunner.swift b/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerRunner.swift new file mode 100644 index 00000000..b10998bd --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerRunner.swift @@ -0,0 +1,195 @@ +// +// EventSchedulerRunner.swift +// SelfControl +// +// Created by Satendra Singh on 23/02/26. +// + +import Foundation + +// A firing engine that uses EventScheduler as storage and fires events at their scheduled times. +// This actor observes the scheduler and invokes registered handlers when events start. +// +public actor EventSchedulerRunner { + public typealias EventHandler = @Sendable (Event) async -> Void + + public init(scheduler: EventScheduler, fireTolerance: TimeInterval = 1.0) { + self.scheduler = scheduler + self.fireTolerance = fireTolerance + } + + private let scheduler: EventScheduler + private let fireTolerance: TimeInterval + + private var timer: Timer? + private var perEventHandlers: [UUID: EventHandler] = [:] + private var globalHandlers: [UUID: EventHandler] = [:] // keyed by token + private var scheduledEvent: Event? + + deinit { + timer?.invalidate() + timer = nil + } + + // MARK: - Control + + public func start() async { + guard timer == nil else { return } + await scheduleNextTimer() + } + + public func stop() { + timer?.invalidate() + timer = nil + scheduledEvent = nil + } + + /// Restart timer to quickly reflect changes (handlers or events). + private func rescheduleLoop() async { + await scheduleNextTimer() + } + + // MARK: - Handlers + + public func setHandler(for eventID: UUID, handler: EventHandler?) async { + if let handler { + perEventHandlers[eventID] = handler + } else { + perEventHandlers.removeValue(forKey: eventID) + } + await rescheduleLoop() + } + + @discardableResult + public func addGlobalHandler(_ handler: @escaping EventHandler) async -> UUID { + let token = UUID() + globalHandlers[token] = handler + // await rescheduleLoop() + return token + } + + public func removeGlobalHandler(token: UUID) async { + globalHandlers.removeValue(forKey: token) + await rescheduleLoop() + } + + // MARK: - Timer scheduling + + private func scheduleNextTimer() async { + // Cancel existing timer, if any + timer?.invalidate() + timer = nil + + let now = Date() + let calendar = await scheduler.calendar + let events = await scheduler.allEvents() + + // Compute earliest next start among all events + let next = earliestNextStart(after: now, events: events, calendar: calendar) + + // If there are no upcoming events, poll again in a short while + let interval: TimeInterval + if let nextDate = next.0 { + interval = max(0.0, nextDate.timeIntervalSinceNow) + } else { + interval = 0.3 + } + + // Create a one-shot timer to fire at the next event (or retry interval) + let newTimer = Timer(timeInterval: interval, repeats: false) { [weak self] _ in + guard let self else { return } + // Hop back to the actor to handle firing and rescheduling + Task { + await self.timerDidFire() + } + } + scheduledEvent = next.1 + // Ensure the timer fires during UI interactions as well + newTimer.tolerance = min(fireTolerance, max(0.0, interval * 0.90)) + RunLoop.main.add(newTimer, forMode: .common) + self.timer = newTimer + } + + private func timerDidFire() async { + let fireDate = Date() + let calendar = await scheduler.calendar + let events = await scheduler.allEvents() + + await fireEventsStarting(around: fireDate, events: events, calendar: calendar, tolerance: fireTolerance) + + // Schedule the next timer based on the latest data + await scheduleNextTimer() + } + + // MARK: - Event computations + + private func earliestNextStart(after date: Date, events: [Event], calendar: Calendar) -> (Date?, Event?) { + var best: Date? + var bestEvent: Event? + for event in events { + if let start = nextStart(of: event, after: date, calendar: calendar) { + if let b = best { + if start < b { + best = start + bestEvent = event + } + } else { + best = start + bestEvent = event + } + } + } + return (best, bestEvent) + } + + private func nextStart(of event: Event, after date: Date, calendar: Calendar) -> Date? { + // Search up to 8 weeks ahead + for dayOffset in 0..<(7 * 8) { + guard let candidateDay = calendar.date(byAdding: .day, value: dayOffset, to: date) else { continue } + let weekday = SCWeekday.from(date: candidateDay, calendar: calendar) + guard event.occurs(on: weekday) else { continue } + + var comps = calendar.dateComponents([.year, .month, .day], from: candidateDay) + comps.hour = event.startTime.hour + comps.minute = event.startTime.minute + comps.second = 0 + if let start = calendar.date(from: comps), start > date { + return start + } + } + return nil + } + + private func fireEventsStarting(around date: Date, events: [Event], calendar: Calendar, tolerance: TimeInterval) async { + // Determine which events start at this time within tolerance +// let starting = events.compactMap { event -> Event? in +// let weekday = SCWeekday.from(date: date, calendar: calendar) +// guard event.occurs(on: weekday) else { return nil } +// var comps = calendar.dateComponents([.year, .month, .day], from: date) +// comps.hour = event.startTime.hour +// comps.minute = event.startTime.minute +// comps.second = 0 +// guard let start = calendar.date(from: comps) else { return nil } +// return abs(start.timeIntervalSince(date)) <= tolerance ? event : nil +// } + + guard let firedEvent = scheduledEvent else { return } + + // Capture handlers to call outside the actor's critical path + let global = globalHandlers.values +// for event in starting { + var handlers: [EventHandler] = [] + if let h = perEventHandlers[firedEvent.id] { + handlers.append(h) + } + handlers.append(contentsOf: global) +// if handlers.isEmpty { continue } + + for handler in handlers { + Task.detached(priority: .userInitiated) { + await handler(firedEvent) + } + } +// } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerStore.swift b/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerStore.swift new file mode 100644 index 00000000..3d721012 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerStore.swift @@ -0,0 +1,97 @@ +// +// EventSchedulerStore.swift +// SelfControl +// +// Created by Satendra Singh on 25/02/26. +// + +struct EventSchedulerStore { + // Load schedules from UserDefaults + static func loadSchedules() -> [Schedule] { + if let data = UserDefaults.standard.data(forKey: "schedules"), + let decoded = try? JSONDecoder().decode([Schedule].self, from: data) { + return decoded + } else { + // Initialize with one empty schedule if no saved data + return [Schedule(timeSlots: [], enabledDays: [])] + } + } + + // Save schedules to UserDefaults + static func saveSchedules(schedules: [Schedule]) { + if let encoded = try? JSONEncoder().encode(schedules) { + UserDefaults.standard.set(encoded, forKey: "schedules") + } + } +} + +// MARK: - Schedule +struct Schedule: Identifiable, Codable, Equatable { + let id: UUID + var timeSlots: [TimeSlot] + var enabledDays: Set + var isExpanded: Bool + var isEnabled: Bool + + init(id: UUID = UUID(), timeSlots: [TimeSlot] = [], enabledDays: Set = [], isExpanded: Bool = false, isEnabled: Bool = false) { + self.id = id + self.timeSlots = timeSlots + self.enabledDays = enabledDays + self.isExpanded = isExpanded + self.isEnabled = isEnabled + } + + var summaryString: String { + timeSlots.map { $0.displayString }.joined(separator: " ") + } + + var enabledDaysString: String { + SCWeekday.allCases.compactMap { enabledDays.contains($0) ? $0.letter : nil }.joined() + } +} + +// MARK: - Schedule Models +struct TimeSlot: Identifiable, Codable, Equatable { + let id: UUID + var startHour: Int + var startMinute: Int + var endHour: Int + var endMinute: Int + + init(id: UUID = UUID(), startHour: Int, startMinute: Int, endHour: Int, endMinute: Int) { + self.id = id + self.startHour = startHour + self.startMinute = startMinute + self.endHour = endHour + self.endMinute = endMinute + } + + // Helper to format hour in 12-hour format + private func formatHour12(_ hour24: Int) -> (hour: Int, isPM: Bool) { + if hour24 == 0 { + return (12, false) + } else if hour24 < 12 { + return (hour24, false) + } else if hour24 == 12 { + return (12, true) + } else { + return (hour24 - 12, true) + } + } + + var startTimeString: String { + let (hour12, isPM) = formatHour12(startHour) + let amPm = isPM ? "PM" : "AM" + return String(format: "%d:%02d %@", hour12, startMinute, amPm) + } + + var endTimeString: String { + let (hour12, isPM) = formatHour12(endHour) + let amPm = isPM ? "PM" : "AM" + return String(format: "%d:%02d %@", hour12, endMinute, amPm) + } + + var displayString: String { + "\(startTimeString)-\(endTimeString)" + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+EventScheduler.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+EventScheduler.swift new file mode 100644 index 00000000..fee6987b --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+EventScheduler.swift @@ -0,0 +1,49 @@ +// +// FilterViewModel+EventScheduler.swift +// SelfControl +// +// Created by Satendra Singh on 25/02/26. +// + +extension FilterViewModel { + + func startEventScheduler() { + Task { + await self.eventRunner?.stop() + let scheduler = EventScheduler() + let events = EventSchedulerStore.loadSchedules() + var eventObjs: [Event] = [] + for event in events { + eventObjs.append(contentsOf: event.scheduledEvents) + } + for item in eventObjs { + _ = try? await scheduler.schedule(newEvent: item) + } + eventRunner = EventSchedulerRunner(scheduler: scheduler) + + await eventRunner?.addGlobalHandler { event in + await self.eventRunnerHandler?(event) + } + await eventRunner?.start() + } +// eventRunner = EventRunner(schedules: self.storedValue.schedules.scheduledEvents) + } + + func stopEventScheduler() { + Task { + await self.eventRunner?.stop() + } + } +} + +extension Schedule { + var scheduledEvents: [Event] { + var items: [Event] = [] + for slot in self.timeSlots { + if let event = try? Event(days: enabledDays, startTime: TimeOfDay(hour: slot.startHour, minute: slot.startMinute), endTime: TimeOfDay(hour: slot.endHour, minute: slot.endMinute)) { + items.append(event) + } + } + return items + } +} From 0e5d23718e3e17347a490c2a285dda51710085bd Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 1 Mar 2026 22:47:31 +0530 Subject: [PATCH 29/64] updated view and model to handle schedule time --- .../SelfControl/Main/FilterViewModel.swift | 18 ++- .../SelfControlUI/Models/AppModels.swift | 105 ------------------ .../SelfControlUI/NewContentView.swift | 13 ++- .../Views/BlockScheduleView.swift | 25 ++--- 4 files changed, 38 insertions(+), 123 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index d55c7ad1..911c73d9 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -37,7 +37,13 @@ enum SelfControlViewState: Equatable { } final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionRequestDelegate, ExtensionToApp { - @Published var status: Status = .stopped + @Published var status: Status = .stopped { + didSet { + if self.status == .running { + updateScheduledEvents() + } + } + } @Published var viewState: SelfControlViewState = .installNetworkExtension @Published var isNetworkExtensionSkipped: Bool = false { didSet { @@ -47,7 +53,8 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques private var isSafariExtensionInstalled: Bool = ProxyPreferences.isSafariExtensionInstalled private var isChromeExtensionInstalled: Bool = ProxyPreferences.isChromeExtensionInstalled - + var eventRunner: EventSchedulerRunner? = nil + var eventRunnerHandler: EventSchedulerRunner.EventHandler? @State private var domains = ProxyPreferences.getBlockedDomains() private let chromeService = ChromeExtensionRequestListner() @State var blockedURLs: [BlockedURL] = [] @@ -134,6 +141,9 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques Task { @MainActor in self.blockerStorage = BlockedURLStore() } + self.eventRunnerHandler = { event in + + } } deinit { @@ -548,4 +558,8 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques activateNetworkBlocking() } } + + func updateScheduledEvents() { + startEventScheduler() + } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift index 6fe4f82e..feeb70a3 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift @@ -98,111 +98,6 @@ enum IntensityLevel: String, Codable, CaseIterable { } } -// MARK: - Schedule Models -struct TimeSlot: Identifiable, Codable, Equatable { - let id: UUID - var startHour: Int - var startMinute: Int - var endHour: Int - var endMinute: Int - - init(id: UUID = UUID(), startHour: Int, startMinute: Int, endHour: Int, endMinute: Int) { - self.id = id - self.startHour = startHour - self.startMinute = startMinute - self.endHour = endHour - self.endMinute = endMinute - } - - // Helper to format hour in 12-hour format - private func formatHour12(_ hour24: Int) -> (hour: Int, isPM: Bool) { - if hour24 == 0 { - return (12, false) - } else if hour24 < 12 { - return (hour24, false) - } else if hour24 == 12 { - return (12, true) - } else { - return (hour24 - 12, true) - } - } - - var startTimeString: String { - let (hour12, isPM) = formatHour12(startHour) - let amPm = isPM ? "PM" : "AM" - return String(format: "%d:%02d %@", hour12, startMinute, amPm) - } - - var endTimeString: String { - let (hour12, isPM) = formatHour12(endHour) - let amPm = isPM ? "PM" : "AM" - return String(format: "%d:%02d %@", hour12, endMinute, amPm) - } - - var displayString: String { - "\(startTimeString)-\(endTimeString)" - } -} - -// MARK: - Weekday -enum Weekday: Int, CaseIterable, Codable, Hashable { - case monday = 0 - case tuesday = 1 - case wednesday = 2 - case thursday = 3 - case friday = 4 - case saturday = 5 - case sunday = 6 - - var letter: String { - switch self { - case .monday: return "M" - case .tuesday: return "T" - case .wednesday: return "W" - case .thursday: return "T" - case .friday: return "F" - case .saturday: return "S" - case .sunday: return "S" - } - } - - var fullName: String { - switch self { - case .monday: return "Monday" - case .tuesday: return "Tuesday" - case .wednesday: return "Wednesday" - case .thursday: return "Thursday" - case .friday: return "Friday" - case .saturday: return "Saturday" - case .sunday: return "Sunday" - } - } -} - -// MARK: - Schedule -struct Schedule: Identifiable, Codable, Equatable { - let id: UUID - var timeSlots: [TimeSlot] - var enabledDays: Set - var isExpanded: Bool - var isEnabled: Bool - - init(id: UUID = UUID(), timeSlots: [TimeSlot] = [], enabledDays: Set = [], isExpanded: Bool = false, isEnabled: Bool = false) { - self.id = id - self.timeSlots = timeSlots - self.enabledDays = enabledDays - self.isExpanded = isExpanded - self.isEnabled = isEnabled - } - - var summaryString: String { - timeSlots.map { $0.displayString }.joined(separator: " ") - } - - var enabledDaysString: String { - Weekday.allCases.compactMap { enabledDays.contains($0) ? $0.letter : nil }.joined() - } -} // MARK: - About Page Item enum AboutItemType { diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift index 09473d01..74bd34f0 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift @@ -1328,6 +1328,13 @@ struct NewContentView: View { } } .onAppear { + print("onAppear viewModel.eventRunnerHandler") + viewModel.eventRunnerHandler? = { eventreq in + print("New Event schedule started: \(eventreq.startTime) - \(eventreq.endTime)") + let minutes = Double(eventreq.endTime.minutes(from: eventreq.startTime, wrapAroundMidnight: true)) + await startBlocking(minutes: minutes) + } + // Don't focus slider on startup - let tips modal show first if needed // Focus will be set after tips are dismissed } @@ -1342,6 +1349,10 @@ struct NewContentView: View { } private func startBlocking() { + startBlocking(minutes: minutes) + } + + private func startBlocking(minutes: Double) { withAnimation(DesignSystem.animationNormal) { isBlocking = true viewModel.updateBlockList(newBlockedDomains: blockedURLs, time: minutes) @@ -1358,7 +1369,7 @@ struct NewContentView: View { } } } - + private func stopBlocking() { withAnimation(DesignSystem.animationNormal) { isBlocking = false diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift index 4e7d44d2..c6d8ff79 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift @@ -13,7 +13,8 @@ struct BlockScheduleView: View { @Binding var currentScreen: AppScreen @Binding var blockingMode: BlockingMode @Binding var isScheduleActive: Bool - + @EnvironmentObject var viewModel: FilterViewModel + @State private var schedules: [Schedule] = [] // Helper to check if any schedule is active @@ -25,22 +26,12 @@ struct BlockScheduleView: View { } } - // Load schedules from UserDefaults private func loadSchedules() { - if let data = UserDefaults.standard.data(forKey: "schedules"), - let decoded = try? JSONDecoder().decode([Schedule].self, from: data) { - schedules = decoded - } else { - // Initialize with one empty schedule if no saved data - schedules = [Schedule(timeSlots: [], enabledDays: [])] - } + schedules = EventSchedulerStore.loadSchedules() } - // Save schedules to UserDefaults private func saveSchedules() { - if let encoded = try? JSONEncoder().encode(schedules) { - UserDefaults.standard.set(encoded, forKey: "schedules") - } + EventSchedulerStore.saveSchedules(schedules: schedules) } var body: some View { @@ -137,6 +128,10 @@ struct BlockScheduleView: View { loadSchedules() updateScheduleStatus() } + .onDisappear { + print("Schedule off-screen") + viewModel.updateScheduledEvents() + } } } @@ -175,7 +170,7 @@ struct ScheduleRow: View { // Weekday circles HStack(spacing: 6) { - ForEach(Weekday.allCases, id: \.self) { weekday in + ForEach(SCWeekday.allCases, id: \.self) { weekday in WeekdayCircle( weekday: weekday, isEnabled: schedule.enabledDays.contains(weekday), @@ -307,7 +302,7 @@ struct ScheduleRow: View { // MARK: - Weekday Circle struct WeekdayCircle: View { - let weekday: Weekday + let weekday: SCWeekday let isEnabled: Bool let blockingMode: BlockingMode let onToggle: () -> Void From 9e3e767b34c1ddfd3c40159a668c3c117d712a39 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 6 Apr 2026 21:47:58 +0530 Subject: [PATCH 30/64] updated chrome script for more better handlings --- .../dynamic-url-blocker/background.js | 213 +++++++++--------- 1 file changed, 103 insertions(+), 110 deletions(-) diff --git a/Self-Control-Extension/dynamic-url-blocker/background.js b/Self-Control-Extension/dynamic-url-blocker/background.js index 2aebc197..ff6c1a58 100644 --- a/Self-Control-Extension/dynamic-url-blocker/background.js +++ b/Self-Control-Extension/dynamic-url-blocker/background.js @@ -1,39 +1,35 @@ +// Dynamic URL Blocker — Manifest V3 Service Worker + +// ---------------- HELPERS ---------------- + +const API_URL = "http://127.0.0.1:8532/chrome"; +const ALARM_NAME = "syncBlockedDomains"; +const REFRESH_SECONDS = 10; -// Dynamic URL Blocker — background service worker (Manifest V3) -// ---- Helpers ---- function normalizeDomain(input) { - // strip protocol, path, query, hash let domain = (input || "").trim().toLowerCase(); domain = domain.replace(/^https?:\/\//, ""); domain = domain.replace(/^www\./, ""); - domain = domain.split("/")[0].split("?")[0].split("#")[0]; - // allow punycode etc. Keep only allowed domain chars, dots, and dashes - domain = domain.replace(/[^a-z0-9\.\-]/g, ""); - return domain; + domain = domain.split("/")[0]; + return domain.replace(/[^a-z0-9.\-]/g, ""); } function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } -// Build a regex that matches the domain and any subdomain, main-frame only. function regexForDomain(domain) { - const d = escapeRegex(domain); - // ^https?://([sub.]* )?domain(/|:|$) - return `^https?:\\/\\/([a-z0-9-]+\\.)*${d}(\\/|:|$)`; + return `^https?:\\/\\/([a-z0-9-]+\\.)*${escapeRegex(domain)}(\\/|:|$)`; } -// Simple deterministic hash for rule IDs (32-bit positive) function hash32(str) { let h = 5381; for (let i = 0; i < str.length; i++) { - h = ((h << 5) + h) ^ str.charCodeAt(i); // djb2 xor - h = h | 0; // force 32-bit + h = ((h << 5) + h) ^ str.charCodeAt(i); + h |= 0; } - // make positive and avoid 0 & small ids - h = Math.abs(h); - return (h % 2000000000) + 1000; // keep under Chrome's int32 max + return (Math.abs(h) % 2000000000) + 1000; } function ruleForDomain(domain) { @@ -48,146 +44,143 @@ function ruleForDomain(domain) { }; } -async function fetchData() { +// ---------------- SERVER FETCH ---------------- +async function fetchBlockedDomains() { try { - const response = await fetch("http://127.0.0.1:8532/chrome"); - const data = await response.json(); - console.log("Array from Swift:", data); - - // // iterate - // data.forEach(item => { - // console.log(`${item. } is ${item.status}`); - // }); - // Parse into JS object -// const data = JSON.parse(jsonString); - -// Access blocked domain list -const blockedDomains = data.blocked; - -console.log(blockedDomains); -// 👉 ["domain1.com", "domain2.com", "domain3.com"] - -// Example: loop over them -blockedDomains.forEach(domain => { - console.log("Blocked:", domain); -}); - return blockedDomains; + const res = await fetch(API_URL); + const data = await res.json(); + return data.blocked || []; } catch (err) { - console.log("Fetch failed:", err); + console.error("Fetch failed:", err); return []; } } +// ---------------- RULE MANAGEMENT ---------------- -async function getDynamicRuleIds() { +async function getCurrentRuleIds() { const rules = await chrome.declarativeNetRequest.getDynamicRules(); - return rules.map(r => r.id); + return new Set(rules.map(r => r.id)); } -async function applyRulesFrom(blockedDomains) { - const desiredRules = blockedDomains.map(ruleForDomain); +async function applyRules(domains) { + + const desiredRules = domains.map(ruleForDomain); const desiredIds = new Set(desiredRules.map(r => r.id)); - const currentIds = new Set(await getDynamicRuleIds()); + const currentIds = await getCurrentRuleIds(); - // Remove any rules that shouldn't exist const removeRuleIds = [...currentIds].filter(id => !desiredIds.has(id)); - - // Add rules that don't yet exist const addRules = desiredRules.filter(r => !currentIds.has(r.id)); - await chrome.declarativeNetRequest.updateDynamicRules({ - removeRuleIds, - addRules - }); -} + if (removeRuleIds.length || addRules.length) { + + await chrome.declarativeNetRequest.updateDynamicRules({ + removeRuleIds, + addRules + }); -async function loadBlockedDomains() { - const domains = await fetchData(); - return domains; - // return new Promise(resolve => { - // blockedDomains - // chrome.storage.local.get({ blockedSites: [] }, (data) => { - // resolve(data.blockedSites || []); - // }); - // }); + console.log("Rules updated", { + added: addRules.length, + removed: removeRuleIds.length + }); + } } -async function saveBlockedDomains(domains) { - return new Promise(resolve => { - chrome.storage.local.set({ blockedSites: domains }, () => resolve()); - }); +// ---------------- SYNC FUNCTION ---------------- + +async function syncBlockedDomains() { + + const domains = await fetchBlockedDomains(); + + await chrome.storage.local.set({ blockedSites: domains }); + + await applyRules(domains); + + console.log("Blocked domains synced:", domains); } -// ---- Lifecycle ---- +// ---------------- EXTENSION LIFECYCLE ---------------- + +// install chrome.runtime.onInstalled.addListener(async () => { - const domains = await loadBlockedDomains(); - await applyRulesFrom(domains); - console.log("Dynamic URL Blocker installed. Domains:", domains); - // fetchData(); -}); -chrome.tabs.onCreated.addListener((tab) => { - (async () => { - console.log("New tab:", tab.id); - const domains = await loadBlockedDomains(); - await applyRulesFrom(domains); - // Example: get tab info with async API - let tabInfo = await chrome.tabs.get(tab.id); - console.log("Fetched tab info:", tabInfo); - })(); + await syncBlockedDomains(); + + chrome.alarms.create(ALARM_NAME, { + periodInMinutes: 1 + }); + + console.log("Extension installed"); }); +// fast refresh +setInterval(syncBlockedDomains, REFRESH_SECONDS * 1000); -chrome.windows.onCreated.addListener((window) => { - (async () => { - const domains = await loadBlockedDomains(); - await applyRulesFrom(domains); - // Example: get tab info with async API - let windowInfo = await chrome.tabs.get(window.id); - console.log("New Window created:", windowInfo); - })(); +// browser startup +chrome.runtime.onStartup.addListener(syncBlockedDomains); + +// periodic refresh +chrome.alarms.onAlarm.addListener(alarm => { + if (alarm.name === ALARM_NAME) { + syncBlockedDomains(); + } }); -// ---- Messaging API for popup ---- +// ---------------- POPUP MESSAGE API ---------------- + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + (async () => { - if (message?.action === "list") { - const domains = await loadBlockedDomains(); + + let domains = (await chrome.storage.local.get("blockedSites")).blockedSites || []; + + if (message.action === "list") { sendResponse({ success: true, domains }); return; } - if (message?.action === "add") { - let domain = normalizeDomain(message.site || ""); + if (message.action === "add") { + + const domain = normalizeDomain(message.site); + if (!domain) { - sendResponse({ success: false, error: "Please enter a valid domain (e.g., example.com)." }); + sendResponse({ success: false, error: "Invalid domain" }); return; } - let domains = await loadBlockedDomains(); + if (!domains.includes(domain)) { domains.push(domain); - await saveBlockedDomains(domains); - await applyRulesFrom(domains); - sendResponse({ success: true, domains }); - } else { - sendResponse({ success: false, error: "Already blocked." }); + await chrome.storage.local.set({ blockedSites: domains }); + await applyRules(domains); } + + sendResponse({ success: true, domains }); return; } - if (message?.action === "remove") { - const domain = normalizeDomain(message.site || ""); - let domains = await loadBlockedDomains(); + if (message.action === "remove") { + + const domain = normalizeDomain(message.site); + domains = domains.filter(d => d !== domain); - await saveBlockedDomains(domains); - await applyRulesFrom(domains); + + await chrome.storage.local.set({ blockedSites: domains }); + + await applyRules(domains); + sendResponse({ success: true, domains }); return; } - sendResponse({ success: false, error: "Unknown action." }); + if (message.action === "refresh") { + await syncBlockedDomains(); + sendResponse({ success: true }); + return; + } + + sendResponse({ success: false }); + })(); - // Indicate we'll respond asynchronously return true; -}); +}); \ No newline at end of file From a412173509a736dd9e5d93ca28dd8fbbb7186888 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 6 Apr 2026 21:48:14 +0530 Subject: [PATCH 31/64] added new persmissions --- Self-Control-Extension/dynamic-url-blocker/manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Self-Control-Extension/dynamic-url-blocker/manifest.json b/Self-Control-Extension/dynamic-url-blocker/manifest.json index e64a8f3e..c4fae9a6 100644 --- a/Self-Control-Extension/dynamic-url-blocker/manifest.json +++ b/Self-Control-Extension/dynamic-url-blocker/manifest.json @@ -6,7 +6,8 @@ "permissions": [ "declarativeNetRequest", "storage", - "scripting" + "scripting", + "alarms" ], "host_permissions": [ "http://localhost/*", From 7f952da3a4e9fc48b566c279557a1e11435f792b Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 6 Apr 2026 21:48:34 +0530 Subject: [PATCH 32/64] log and signing fixed --- .../SelfControl/org.eyebeam.selfcontrold/ConsoleLog.m | 2 +- .../SelfControl/org.eyebeam.selfcontrold/SCDaemon.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.m index dca0d2e1..807e98fb 100644 --- a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.m +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/ConsoleLog.m @@ -11,7 +11,7 @@ @implementation ConsoleLog + (void)log:(NSString *)message { - os_log(OS_LOG_DEFAULT, "ConsoleLog: %@", message); + os_log(OS_LOG_DEFAULT, "ConsoleLog: [SC] 🔍] %{public}@", message); } @end diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.m index c85d1a99..5c7e8434 100644 --- a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.m +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemon.m @@ -166,7 +166,7 @@ - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConne SecRequirementRef isSelfControlApp; // versions before 4.0 didn't have hardened code signing, so aren't trustworthy to talk to the daemon // (plus the daemon didn't exist before 4.0 so there's really no reason they should want to run it!) - SecRequirementCreateWithString(CFSTR("anchor apple generic and (identifier \"org.eyebeam.SelfControl\" or identifier \"org.eyebeam.selfcontrol-cli\" or identifier \"com.application.SelfControl.corebits\") and info [CFBundleVersion] >= \"407\""), kSecCSDefaultFlags, &isSelfControlApp); + SecRequirementCreateWithString(CFSTR("anchor apple generic and (identifier \"org.eyebeam.SelfControl\" or identifier \"org.eyebeam.selfcontrol-cli\" or identifier \"com.application.SelfControl.corebits\") and info [CFBundleVersion] >= \"10\""), kSecCSDefaultFlags, &isSelfControlApp); OSStatus clientValidityStatus = SecCodeCheckValidity(guest, kSecCSDefaultFlags, isSelfControlApp); CFRelease(guest); From 2197318a3eda42e8f44541525ca6de4df2c986b2 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 6 Apr 2026 21:48:57 +0530 Subject: [PATCH 33/64] updated app version in return call --- .../SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.m index 7c3f1853..6355dd5f 100644 --- a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.m +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/SCDaemonXPC.m @@ -84,7 +84,7 @@ - (void)getVersionWithReply:(void(^)(NSString * version))reply { // We specifically don't check for authorization here. Everyone is always allowed to get // the version of the helper tool. - reply(@"4.0.2"); + reply(@"410"); } @end From 2fa274a13a661afbba6d450e11938c3e87ee6eab Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Tue, 7 Apr 2026 21:53:59 +0530 Subject: [PATCH 34/64] implemented notification and sounds alerts --- .../SafariExtensionViewController.xib | 13 +++++--- .../SelfControl/SelfControl/AppDelegate.swift | 1 + .../SelfControl/SelfControl/Info.plist | 12 +++++-- .../SelfControl/Main/FilterViewModel.swift | 11 ++++--- .../LocalNotificationManager.swift | 32 +++++++++++++++++++ .../Preferences/ProxyPreferences.swift | 14 ++++++++ .../SelfControlUI/Utils/Strings.swift | 2 ++ .../Views/AdvancedSettingsView.swift | 5 ++- 8 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Preferences/LocalNotificationManager.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Base.lproj/SafariExtensionViewController.xib b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Base.lproj/SafariExtensionViewController.xib index 559d8ba7..f9bc2547 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Base.lproj/SafariExtensionViewController.xib +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Base.lproj/SafariExtensionViewController.xib @@ -1,10 +1,12 @@ - - + + - + + + - + @@ -15,7 +17,7 @@ - + @@ -25,6 +27,7 @@ + diff --git a/Self-Control-Extension/SelfControl/SelfControl/AppDelegate.swift b/Self-Control-Extension/SelfControl/SelfControl/AppDelegate.swift index 0ac17838..88d8fc11 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/AppDelegate.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/AppDelegate.swift @@ -14,6 +14,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { print("App did finish launching") + LocalNotificationManager.requestAuthorization() } func applicationWillTerminate(_ notification: Notification) { diff --git a/Self-Control-Extension/SelfControl/SelfControl/Info.plist b/Self-Control-Extension/SelfControl/SelfControl/Info.plist index 1a3cd53a..c0149117 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Info.plist +++ b/Self-Control-Extension/SelfControl/SelfControl/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + 1.12 CFBundleVersion - 1 + 12 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright @@ -37,8 +37,14 @@ SMPrivilegedExecutables org.eyebeam.selfcontrold - anchor apple generic and identifier "org.eyebeam.selfcontrold" + identifier "org.eyebeam.selfcontrold" and anchor apple generic and certificate leaf[subject.OU] = "X6FQ433AWK" + SUEnableAutomaticChecks + + SUFeedURL + https://selfcontrolapp.com/appcast.xml + SUPublicDSAKeyFile + dsa_pub.pem UTExportedTypeDeclarations diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index 911c73d9..4a166b9c 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -102,7 +102,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques override init() { super.init() - ProxyPreferences.reset() //TODO: remove +// ProxyPreferences.reset() //TODO: remove onInit() SafariExtensionManager.shared.onExtensionStateChange = { print("SafariExtensionManager.shared.onChange++") @@ -205,11 +205,8 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } if isSafariExtensionInstalled == true { - withAnimation(.easeInOut(duration: 3)) { print(" isSafariExtensionInstalled == true true in Safari") self.viewState = .filter - } - } else { withAnimation(.easeInOut(duration: 3)) { print("viewState = .installChromeExtension true in Safari") @@ -489,6 +486,12 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(_enable: false) BlockListManager.deactivateSafariBlocking() chromeService.deactivateSafariBlocking() + if ProxyPreferences.playSoundOnCompletion { + NSSound.playDefaultSound() + } + if ProxyPreferences.showNotificationOnCompletion { + LocalNotificationManager.scheduleNotification() + } } // MARK: - Timer management diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/LocalNotificationManager.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/LocalNotificationManager.swift new file mode 100644 index 00000000..60eb3e66 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Preferences/LocalNotificationManager.swift @@ -0,0 +1,32 @@ +// +// LocalNotificationManager.swift +// SelfControl +// +// Created by Satendra Singh on 03/04/26. +// +import UserNotifications + +final class LocalNotificationManager { + class func requestAuthorization() { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { granted, error in + if let error = error { + print("Error requesting notification authorization: \(error)") + } + } + } + + class func scheduleNotification(title: String = "Your SelfControl block has ended!", body: String = "All sites are now accessible.", timeInterval: TimeInterval = 0.1) { + let content = UNMutableNotificationContent() + content.title = title + content.body = body + content.sound = .none + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false) + let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) + UNUserNotificationCenter.current().add(request) { error in + if let error = error { + print("Error scheduling notification: \(error)") + } + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift index 6f6c9822..9cd96c1b 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift @@ -15,6 +15,8 @@ struct ProxyPreferences { private static let isChromeExtensionKey: String = "isChromeExtensionKey" private static let isSafariExtInstallKey: String = "isSafariExtInstallKey" private static let isChromeExtInstallKey: String = "isChromeExtInstallKey" + private static let playSoundOnCompletionkey: String = "playSoundOnCompletion" + private static let showNotificationOnCompletionkey: String = "showNotificationOnCompletion" private static let defaults = UserDefaults.standard static func getBlockedDomains() -> [String] { @@ -55,6 +57,14 @@ struct ProxyPreferences { return UserDefaults.standard.bool(forKey: isChromeExtInstallKey) } + static var playSoundOnCompletion: Bool { + return UserDefaults.standard.bool(forKey: playSoundOnCompletionkey) + } + + static var showNotificationOnCompletion: Bool { + return UserDefaults.standard.bool(forKey: showNotificationOnCompletionkey) + } + static func setChromeExtensionInstalled() { UserDefaults.standard.set(true, forKey: isChromeExtInstallKey) } @@ -65,4 +75,8 @@ struct ProxyPreferences { UserDefaults.standard.removeObject(forKey: isSafariExtensionKey) UserDefaults.standard.removeObject(forKey: isChromeExtensionKey) } + + static var soundNames: [String] { + ["Basso", "Blow", "Bottle", "Frog", "Funk", "Glass", "Hero", "Morse", "Ping", "Pop", "Purr", "Sosumi", "Submarine", "Tink"] + } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift index b1b2b769..8ed75d2f 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift @@ -159,6 +159,8 @@ struct Strings { static let autoCheckUpdates = "Automatically check for updates" static let autoSendErrorReports = "Automatically send anonymized error reports" static let playSoundOnCompletion = "Play sound on completion" + static let showNotificationOnCompletion = "Show Notification on completion" + static let timerFloatsOnTop = "Timer window should float on top" static let showCountdownInDock = "Show countdown in Dock" static let hideSecondsInCountdown = "Hide seconds in countdown" diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift index 0b1949f4..0b3ab7c5 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift @@ -27,6 +27,8 @@ struct AdvancedSettingsView: View { @AppStorage("autoCheckUpdates") private var autoCheckUpdates = true @AppStorage("sendErrorReports") private var sendErrorReports = false @AppStorage("playSoundOnCompletion") private var playSoundOnCompletion = true + @AppStorage("showNotificationOnCompletion") private var showNotificationOnCompletion = true + @AppStorage("timerFloatsOnTop") private var timerFloatsOnTop = false @AppStorage("showCountdownInDock") private var showCountdownInDock = true @@ -107,8 +109,9 @@ struct AdvancedSettingsView: View { VStack(spacing: 0) { settingRow(title: Strings.AdvancedSettings.autoCheckUpdates, isOn: $autoCheckUpdates, showDivider: true) - settingRow(title: Strings.AdvancedSettings.autoSendErrorReports, isOn: $sendErrorReports, showDivider: true) +// settingRow(title: Strings.AdvancedSettings.autoSendErrorReports, isOn: $sendErrorReports, showDivider: true) settingRow(title: Strings.AdvancedSettings.playSoundOnCompletion, isOn: $playSoundOnCompletion, showDivider: false) + settingRow(title: Strings.AdvancedSettings.showNotificationOnCompletion, isOn: $showNotificationOnCompletion, showDivider: false) } } .padding(DesignSystem.spacingMedium) From 6cf8198170540ea97b8bdaf673ccd8c9c5b646a2 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Tue, 7 Apr 2026 21:55:58 +0530 Subject: [PATCH 35/64] Fixed issues --- .../xcschemes/SelfControl.xcscheme | 1 - .../Main/SafariExtensionWebView.swift | 3 - .../SelfControl/Preferences/Sound.swift | 40 +++++++++ .../SCDaemonSupport/SCDaemonHelper.swift | 2 +- .../Components/TextPlaceholder.swift | 81 +++++++++++++++++++ .../SelfControlUI/Models/AppModels.swift | 8 +- .../SelfControlUI/NewContentView.swift | 1 + 7 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Preferences/Sound.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/TextPlaceholder.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme index 37807739..6c292bc1 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme @@ -60,7 +60,6 @@ allowLocationSimulation = "YES"> diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift index b3c0459c..ef00748c 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift @@ -57,9 +57,6 @@ final class WebPageViewModel: NSObject, WKNavigationDelegate, WKScriptMessageHan super.init() self.webView.navigationDelegate = self self.webView.configuration.userContentController.add(self, name: "controller") - extensionManager.onChange = { [weak self] in - self?.updateSafariExtensionState(state: true) - } } private let extensionIdentifier = SafariExtensionConstants.identifier diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/Sound.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/Sound.swift new file mode 100644 index 00000000..ee6a3536 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Preferences/Sound.swift @@ -0,0 +1,40 @@ +// +// Sound.swift +// SelfControl +// +// Created by Satendra Singh on 02/04/26. +// + +import AppKit + +public extension NSSound { + + enum Sound: String { + case basso = "Basso" + case blow = "Blow" + case bottle = "Bottle" + case frog = "Frog" + case funk = "Funk" + case glass = "Glass" + case hero = "Hero" + case morse = "Morse" + case ping = "Ping" + case pop = "Pop" + case purr = "Purr" + case sosumi = "Sosumi" + case submarine = "Submarine" + case tink = "Tink" + } + + var soundName: String? { + return self.name + } + + static func play(_ sound: Sound) { + NSSound(named: NSSound.Name(sound.rawValue))?.play() + } + + static func playDefaultSound() { + play(.basso) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SCDaemonHelper.swift b/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SCDaemonHelper.swift index 774576de..b43d50c5 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SCDaemonHelper.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SCDaemonHelper.swift @@ -11,7 +11,7 @@ final class SSCDaemonHelper { let xpc = SCXPCClient() init() { - + } func install(blockedDomains: [String], time: Date) { diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/TextPlaceholder.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/TextPlaceholder.swift new file mode 100644 index 00000000..ac0dc8eb --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/TextPlaceholder.swift @@ -0,0 +1,81 @@ +// +// Untitled.swift +// SelfControl +// +// Created by Satendra Singh on 31/03/26. +// + +import SwiftUI + +// MARK: - Generic placeholder overlay (pure SwiftUI) + +public extension View { + /// Overlays a placeholder view on top of `self` when the provided condition is true. + /// This keeps taps going to the underlying field by disabling hit testing for the placeholder. + /// + /// Example: + /// TextField("", text: $text) + /// .placeholder(when: text.isEmpty, alignment: .leading) { + /// Text("Email") + /// .foregroundColor(.secondary) + /// .font(.body) + /// } + /// + /// - Parameters: + /// - shouldShow: When true, the placeholder is visible. + /// - alignment: Alignment for the placeholder inside the overlay. + /// - placeholder: The placeholder view builder. + /// - Returns: A view with the placeholder overlay applied. + func placeholder( + when shouldShow: Bool, + alignment: Alignment = .leading, + @ViewBuilder placeholder: () -> Placeholder + ) -> some View { + ZStack(alignment: alignment) { + if shouldShow { + placeholder() + .allowsHitTesting(false) // so taps focus the TextField + } + self + } + } + + /// Convenience for showing a simple `Text` placeholder with color and optional font, + /// using the same ZStack logic while keeping call sites concise. + /// + /// Example: + /// TextField("", text: $text) + /// .textPlaceholder("Email", + /// when: text.isEmpty, + /// color: .secondary, + /// font: .body, + /// verticalPadding: 8) + /// + /// - Parameters: + /// - title: The placeholder string to display. + /// - shouldShow: When true, the placeholder is visible. + /// - alignment: Alignment for the placeholder inside the overlay. + /// - color: Placeholder text color. + /// - font: Optional placeholder font. If nil, inherits from environment. + /// - verticalPadding: Optional vertical padding to match your field’s insets. + /// - Returns: A view with the placeholder overlay applied. + func textPlaceholder( + _ title: String, + when shouldShow: Bool, + alignment: Alignment = .leading, + color: Color = .secondary, + font: Font? = nil, + verticalPadding: CGFloat? = nil + ) -> some View { + placeholder(when: shouldShow, alignment: alignment) { + var text = Text(title).foregroundColor(color) + if let font { + text.font(font) + } + if let verticalPadding { + text.padding(.vertical, verticalPadding) + } + text + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift index feeb70a3..f6c48d9a 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift @@ -13,17 +13,19 @@ struct BlockedURL: Identifiable, Codable, Equatable { var domain: String var paths: [String] var isEnabled: Bool - - init(id: UUID = UUID(), domain: String, paths: [String] = [], isEnabled: Bool = true) { + var blockEntireDomain: Bool + + init(id: UUID = UUID(), domain: String, paths: [String] = [], isEnabled: Bool = true, blockEntireDomain: Bool = false) { self.id = id self.domain = domain self.paths = paths self.isEnabled = isEnabled + self.blockEntireDomain = blockEntireDomain } var urls: [String]? { if isEnabled == false { return nil } - if paths.count == 0 { + if paths.count == 0 || blockEntireDomain { return [domain] } else { return paths.map { path in diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift index 74bd34f0..dec098c8 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift @@ -1401,6 +1401,7 @@ struct NewContentView: View { stopCountdownTimer = nil stopCountdownTime = 600 showingStopConfirmation = false + viewModel.deactivateNetworkBlocking() } private func resumeBlocking() { From d4e42a448aee153777cba4f976bf48b0206b82ee Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Tue, 7 Apr 2026 21:56:17 +0530 Subject: [PATCH 36/64] fixed UI issus --- .../SelfControlUI/Views/BlocklistEditorView.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift index 1da485ec..b60e6b32 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift @@ -157,6 +157,7 @@ struct BlocklistEditorView: View { } .buttonStyle(.plain) .focusable(false) + .disabled(isBlockingMode) .onHover { hovering in withAnimation(DesignSystem.animationFast) { isHoveringImport = hovering @@ -266,10 +267,17 @@ struct BlocklistEditorView: View { // TextField - animated transition Group { if isSearchMode { + TextField(Strings.BlocklistEditor.searchPlaceholder, text: $searchText) .textFieldStyle(.plain) .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) .foregroundColor(DesignSystem.textPrimary) + .placeholder(when: searchText.isEmpty, alignment: .leading) { + Text(Strings.BlocklistEditor.searchPlaceholder) + .foregroundColor(DesignSystem.disabledText) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .padding(.vertical, 8) // match your field’s vertical insets + } } else { TextField(Strings.BlocklistEditor.addWebsitePlaceholder, text: $newSite, onCommit: { addWebsite() @@ -277,6 +285,12 @@ struct BlocklistEditorView: View { .textFieldStyle(.plain) .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) .foregroundColor(DesignSystem.textPrimary) + .placeholder(when: newSite.isEmpty, alignment: .leading) { + Text(Strings.BlocklistEditor.addWebsitePlaceholder) + .foregroundColor(DesignSystem.disabledText) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .padding(.vertical, 8) // match your field’s vertical insets + } } } .transition(.opacity.combined(with: .move(edge: .trailing))) @@ -714,6 +728,7 @@ struct BlocklistEditorView: View { } .buttonStyle(.plain) .focusable(false) + .disabled(isBlockingMode) .onHover { hovering in withAnimation(DesignSystem.animationFast) { isHoveringBulkEdit = hovering From efe339c0f2792b7a33c4f6183beb1a3f43c1b057 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Tue, 7 Apr 2026 22:24:08 +0530 Subject: [PATCH 37/64] Fixed issue and preferences --- ButtonWithPopupMenu.m | 2 +- .../SelfControl.xcodeproj/project.pbxproj | 89 +++++++++++++++---- .../org.eyebeam.selfcontrold.xcscheme | 11 ++- 3 files changed, 79 insertions(+), 23 deletions(-) diff --git a/ButtonWithPopupMenu.m b/ButtonWithPopupMenu.m index 277b1db0..f5c768ea 100755 --- a/ButtonWithPopupMenu.m +++ b/ButtonWithPopupMenu.m @@ -54,4 +54,4 @@ - (void)dealloc { } -@end \ No newline at end of file +@end diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index 4168784f..8a06858d 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 77BA53092D9D5E2A00863476 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77BA53082D9D5E2A00863476 /* NetworkExtension.framework */; }; 9A441CE32E36024E00A521CC /* com.application.SelfControl.corebits.network.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9AA492AE2F56E94200452252 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 9AA492AD2F56E94200452252 /* Sparkle */; }; 9AC9E59E2F04357E0004D39C /* org.eyebeam.selfcontrold in Copy Daemon Launch Service */ = {isa = PBXBuildFile; fileRef = 9AC9E4EA2F041D570004D39C /* org.eyebeam.selfcontrold */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 9AD524CE2EC9F15500AAD5BF /* SelfControlWebExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9AD524B12EC9B0EA00AAD5BF /* SelfControlWebExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -187,6 +188,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9AA492AE2F56E94200452252 /* Sparkle in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -273,6 +275,7 @@ ); name = SelfControl; packageProductDependencies = ( + 9AA492AD2F56E94200452252 /* Sparkle */, ); productName = SelfControl; productReference = 77BA52D62D9D5D7700863476 /* SelfControl.app */; @@ -377,6 +380,9 @@ ); mainGroup = 77BA52CD2D9D5D7700863476; minimizedProjectReferenceProxies = 1; + packageReferences = ( + 9AA492AC2F56E94200452252 /* XCRemoteSwiftPackageReference "Sparkle" */, + ); preferredProjectObjectVersion = 77; productRefGroup = 77BA52D72D9D5D7700863476 /* Products */; projectDirPath = ""; @@ -588,30 +594,29 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = SelfControl/SelfControl.entitlements; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 26210; + CURRENT_PROJECT_VERSION = 31711; DEVELOPMENT_ASSET_PATHS = "\"SelfControl/Preview Content\""; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; + DEVELOPMENT_TEAM = X6FQ433AWK; ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GCC_PREFIX_HEADER = org.eyebeam.selfcontrold/SCDaemon_Prefix.pch; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = SelfControl/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.1; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Self Control app dev"; + PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -624,30 +629,29 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = SelfControl/SelfControl.entitlements; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; + CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 26210; + CURRENT_PROJECT_VERSION = 31711; DEVELOPMENT_ASSET_PATHS = "\"SelfControl/Preview Content\""; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; + DEVELOPMENT_TEAM = X6FQ433AWK; ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GCC_PREFIX_HEADER = org.eyebeam.selfcontrold/SCDaemon_Prefix.pch; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = SelfControl/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.1; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Self Control app dev"; + PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -725,10 +729,18 @@ 9AC9E4EF2F041D570004D39C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_STYLE = Automatic; + AUTOMATION_APPLE_EVENTS = NO; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 410; DEVELOPMENT_TEAM = X6FQ433AWK; - ENABLE_HARDENED_RUNTIME = YES; + ENABLE_HARDENED_RUNTIME = NO; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO; GCC_PREFIX_HEADER = org.eyebeam.selfcontrold/SCDaemon_Prefix.pch; INFOPLIST_FILE = "org.eyebeam.selfcontrold/selfcontrold-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 13.5; @@ -746,6 +758,13 @@ ); PRODUCT_BUNDLE_IDENTIFIER = org.eyebeam.selfcontrold; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO; + RUNTIME_EXCEPTION_ALLOW_JIT = NO; + RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO; + RUNTIME_EXCEPTION_DEBUGGING_TOOL = NO; + RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = NO; SKIP_INSTALL = YES; }; name = Debug; @@ -753,10 +772,18 @@ 9AC9E4F02F041D570004D39C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_STYLE = Automatic; + AUTOMATION_APPLE_EVENTS = NO; + CODE_SIGN_IDENTITY = "Developer ID Application: CoreBits Software Solutions Private Limited (X6FQ433AWK)"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 410; DEVELOPMENT_TEAM = X6FQ433AWK; - ENABLE_HARDENED_RUNTIME = YES; + ENABLE_HARDENED_RUNTIME = NO; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO; GCC_PREFIX_HEADER = org.eyebeam.selfcontrold/SCDaemon_Prefix.pch; INFOPLIST_FILE = "org.eyebeam.selfcontrold/selfcontrold-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 13.5; @@ -774,6 +801,13 @@ ); PRODUCT_BUNDLE_IDENTIFIER = org.eyebeam.selfcontrold; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO; + RUNTIME_EXCEPTION_ALLOW_JIT = NO; + RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO; + RUNTIME_EXCEPTION_DEBUGGING_TOOL = NO; + RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = NO; SKIP_INSTALL = YES; }; name = Release; @@ -927,6 +961,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 9AA492AC2F56E94200452252 /* XCRemoteSwiftPackageReference "Sparkle" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sparkle-project/Sparkle"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.9.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 9AA492AD2F56E94200452252 /* Sparkle */ = { + isa = XCSwiftPackageProductDependency; + package = 9AA492AC2F56E94200452252 /* XCRemoteSwiftPackageReference "Sparkle" */; + productName = Sparkle; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 77BA52CE2D9D5D7700863476 /* Project object */; } diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/org.eyebeam.selfcontrold.xcscheme b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/org.eyebeam.selfcontrold.xcscheme index fa398599..05067a7b 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/org.eyebeam.selfcontrold.xcscheme +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/org.eyebeam.selfcontrold.xcscheme @@ -34,15 +34,18 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - launchStyle = "0" + launchStyle = "1" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES" viewDebuggingEnabled = "No"> - + + + - + Date: Tue, 7 Apr 2026 22:38:06 +0530 Subject: [PATCH 38/64] cleanup and fixed some issues --- .../Views/DomainDetailView.swift | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift index bf2422e3..20cf79af 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift @@ -36,12 +36,11 @@ struct DomainDetailView: View { // Initialize blockEntireDomain from UserDefaults or based on domain state private func initializeBlockEntireDomain() { + blockEntireDomain = UserDefaults.standard.bool(forKey: storageKey) if let domain = domain { // If domain has paths, always show paths tab (override stored preference) if !domain.paths.isEmpty { - blockEntireDomain = false // Update stored preference to match - UserDefaults.standard.set(false, forKey: storageKey) } else { // No paths - check stored preference or default to true if UserDefaults.standard.object(forKey: storageKey) != nil { @@ -49,7 +48,7 @@ struct DomainDetailView: View { } else { // No stored preference - default to true (block entire domain) blockEntireDomain = true - UserDefaults.standard.set(true, forKey: storageKey) + saveBlockEntireDomainPreference() } } } @@ -58,6 +57,10 @@ struct DomainDetailView: View { // Save the preference when it changes private func saveBlockEntireDomainPreference() { UserDefaults.standard.set(blockEntireDomain, forKey: storageKey) + if let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { + blockedURLs[index].blockEntireDomain = blockEntireDomain + saveChanges() + } } var body: some View { @@ -216,13 +219,20 @@ struct DomainDetailView: View { .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) .foregroundColor(DesignSystem.textPrimary) - TextField(Strings.DomainDetail.addPathPlaceholder, text: $newPath, onCommit: { - addPath() - }) + ZStack(alignment: .leading) { + TextField("", text: $newPath, onCommit: { + addPath() + }) .textFieldStyle(.plain) .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) .foregroundColor(DesignSystem.textPrimary) - + .placeholder(when: newPath.isEmpty, alignment: .leading) { + Text(Strings.DomainDetail.addPathPlaceholder) + .foregroundColor(DesignSystem.disabledText) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .padding(.vertical, 8) // match your field’s vertical insets + } + } Spacer() .frame(minWidth: 8) From 96e2895a5065dbd1d388304ada410ee72637c99c Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Tue, 7 Apr 2026 22:38:21 +0530 Subject: [PATCH 39/64] fixed filter isues --- .../FilterDataProvider.swift | 93 ++++++++----------- 1 file changed, 38 insertions(+), 55 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift index 3f3ded1f..4fd0efab 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift @@ -19,67 +19,50 @@ class FilterDataProvider: NEFilterDataProvider { // MARK: - Filter Lifecycle static let localPort = "8888" - override func startFilter(completionHandler: @escaping (Error?) -> Void) { - os_log("[SC] 🔍] FilterDataProvider: Starting filter", log: OSLog.default, type: .info) + os_log("[SC] 🔍 FilterDataProvider: Starting filter", log: OSLog.default, type: .info) let blockedHosts = IPCConnection.shared.blockedUrls - - os_log("[SC] 🔍] Bloked blockedUrls: %{public}@", log: OSLog.default, type: .error, blockedHosts) - - // Filter incoming TCP connections on port 8888 - -// let filterRules = blockedHosts.map { address -> NEFilterRule in -// // let localNetwork = NWHostEndpoint(hostname: address as! String, port: FilterDataProvider.localPort) -// let inboundNetworkRule = NENetworkRule(remoteNetwork: address, -// remotePrefix: 0, -// localNetwork: nil, -// localPrefix: 0, -// protocol: .any, -// direction: .outbound) -// return NEFilterRule(networkRule: inboundNetworkRule, action: .filterData) -// } - // Filter incoming TCP connections on port 8888 -// let filterRules = ["0.0.0.0", "::"].map { address -> NEFilterRule in -// let filterRules = ["*"].map { address -> NEFilterRule in -// let localNetwork = NWHostEndpoint(hostname: address, port: "*") -// let inboundNetworkRule = NENetworkRule(remoteNetwork: nil, -// remotePrefix: 0, -// localNetwork: localNetwork, -// localPrefix: 0, -// protocol: .any, -// direction: .outbound) -// return NEFilterRule(networkRule: inboundNetworkRule, action: .filterData) -// } -// - let filterRules = blockedHosts.map { address -> NEFilterRule in - let localNetwork = NWHostEndpoint(hostname: address, port: "*") - let inboundNetworkRule = NENetworkRule(remoteNetwork: nil, - remotePrefix: 0, - localNetwork: localNetwork, - localPrefix: 0, - protocol: .any, - direction: .outbound) - return NEFilterRule(networkRule: inboundNetworkRule, action: .filterData) + + os_log("[SC] 🔍 Blocked blockedUrls: %{public}@", log: OSLog.default, type: .info, String(describing: blockedHosts)) + + // Ensure blockedHosts is an array of String + var filterRules: [NEFilterRule] = blockedHosts.compactMap { address in + guard let host = address as? String else { return nil } + // For hostname-based rules, you must use NENetworkRule with nil networks (matches all) + let networkRule = NENetworkRule( + remoteNetwork: nil, + remotePrefix: 0, + localNetwork: nil, + localPrefix: 0, + protocol: .any, + direction: .outbound + ) + return NEFilterRule(networkRule: networkRule, action: .filterData) + } + + // Ensure at least one rule exists as required by NEFilterSettings + if filterRules.isEmpty { + // Add a default rule (matches all traffic, for example, and allows it) + let defaultNetworkRule = NENetworkRule( + remoteNetwork: nil, + remotePrefix: 0, + localNetwork: nil, + localPrefix: 0, + protocol: .any, + direction: .any + ) + let defaultRule = NEFilterRule(networkRule: defaultNetworkRule, action: .allow) + filterRules = [defaultRule] } - - // Create a rule matching all outbound traffic. -// let networkRule = NENetworkRule(remoteNetwork: nil, -// remotePrefix: 0, -// localNetwork: nil, -// localPrefix: 0, -// protocol: .any, -// direction: .outbound) -// let filterRule = NEFilterRule(networkRule: networkRule, action: .filterData) -// let filterSettings = NEFilterSettings(rules: [filterRule], defaultAction: .allow) let filterSettings = NEFilterSettings(rules: filterRules, defaultAction: .allow) - - apply(filterSettings) { error in - if let error = error { - os_log("[SC] 🔍] Error applying filter settings: %@", log: OSLog.default, type: .error, error.localizedDescription) + + apply(filterSettings) { error in + if let error = error { + os_log("[SC] 🔍 Error applying filter settings: %{public}@", log: OSLog.default, type: .error, error.localizedDescription) + } + completionHandler(error) } - completionHandler(error) - } } override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { From 0b05383f31f5fde6036fc5d17b7e2ff61e080c46 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Thu, 30 Apr 2026 23:14:23 +0530 Subject: [PATCH 40/64] Implemented notifications, sound , dock and schedule view handlings for the app. --- .../Resources/script.js | 49 +- .../SelfControl/SelfControl/Info.plist | 4 +- .../Main/FilterViewModel+Dock.swift | 69 + .../Main/FilterViewModel+EventScheduler.swift | 1 + .../SelfControl/Main/FilterViewModel.swift | 24 +- .../LocalNotificationManager.swift | 2 +- .../Preferences/ProxyPreferences.swift | 12 +- .../SelfControl/SelfControl/SCUIUtility.swift | 22 + .../SelfControl/SelfControl/SCUtility.swift | 21 + .../SelfControlUI/NewContentView.swift | 5 + .../Views/AdvancedSettingsView.swift | 4 +- .../Views/BlockScheduleView.swift | 8 +- .../Views/BlocklistEditorView.swift | 2013 ++++++++--------- .../Views/DomainDetailView.swift | 5 +- .../Views/IntensityDropdownOverlay.swift | 1 - .../SelfControl/Sparkle/dsa_pub.pem | 36 + .../SelfControlExtension/IPCConnection.swift | 2 +- 17 files changed, 1151 insertions(+), 1127 deletions(-) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+Dock.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SCUIUtility.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/SCUtility.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Sparkle/dsa_pub.pem diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js index 73296b47..07bd8f66 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js @@ -222,6 +222,36 @@ // } // }); //} +// Simple in-memory block list (replace with native messaging later) +//const BLOCKED_DOMAINS = [ ]; +//function isBlocked(url) { +// try { +// const u = new URL(url); +// return BLOCKED_DOMAINS.some(domain => +// u.hostname === domain || u.hostname.endsWith("." + domain) +// ); +// } catch (e) { +// return false; +// } +//} + + +// Fires for: +// - already open tabs +// - reloads +// - back/forward +// - SPA navigations +//browser.webNavigation.onCommitted.addListener((details) => { +// if (details.frameId !== 0) return; // main frame only +// +// if (isBlocked(details.url)) { +// browser.tabs.update(details.tabId, { +// url: "blocked.html" +// }); +// } +//}); + + safari.extension.dispatchMessage("ready"); (function () { @@ -240,6 +270,10 @@ safari.extension.dispatchMessage("ready"); console.error("[SC] redirect error:", err); } } +// if (event.name === "updateBlockedList") { +// BLOCKED_DOMAINS.length = 0; +// BLOCKED_DOMAINS.push(...message.domains); +// } }); // Send PAGE_VISIT message @@ -255,10 +289,21 @@ safari.extension.dispatchMessage("ready"); console.error("[SC] notifySwift error:", err); } } + + function notifySwiftDetails(details) { + // main frame only + if (details.frameId !== 0) return; - // Observe navigation changes (simplified) + browser.runtime.sendMessage({ + type: "navigation", + url: details.url + }); + } + + const browser = window.browser || window.chrome || window.safari; window.addEventListener("load", notifySwift); window.addEventListener("popstate", notifySwift); document.addEventListener("click", () => setTimeout(notifySwift, 10), true); - +// browser.webNavigation.onCommitted.addListener(notifySwiftDetails); })(); +//TODO: https://chatgpt.com/c/6963cb8a-349c-8320-a036-7f6b9828043a?ref=mini diff --git a/Self-Control-Extension/SelfControl/SelfControl/Info.plist b/Self-Control-Extension/SelfControl/SelfControl/Info.plist index c0149117..539358cf 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Info.plist +++ b/Self-Control-Extension/SelfControl/SelfControl/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.12 + 1.14 CFBundleVersion - 12 + 14 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+Dock.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+Dock.swift new file mode 100644 index 00000000..bc811a3c --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+Dock.swift @@ -0,0 +1,69 @@ +// +// FilterViewModel+Dock.swift +// SelfControl +// +// Created by Satendra Singh on 12/04/26. +// + +import Foundation +import AppKit + +extension FilterViewModel { + + func startShowingCountDownInDock() { + dockTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateDockCountDown), userInfo: nil, repeats: true) + } + + func stopShowingCountDownInDock() { + dockTimer?.invalidate() + NSApp?.dockTile.badgeLabel = nil + } + + @objc func updateDockCountDown() { + let blockEndingDate: Date = timerFireDate ?? .now + let blockingSecond: Int = Int(blockEndingDate.timeIntervalSinceNow) + var numSeconds: Int = Int(blockEndingDate.timeIntervalSinceNow) + var numMinutes: Int = blockingSecond + var numHours: Int = 0 + + numHours = numSeconds / 3600 + numSeconds %= 3600 + numMinutes = numSeconds / 60 + numSeconds %= 60 + if UserDefaults.standard.bool(forKey: "BadgeApplicationIcon"), + blockingSecond > 0 { + // Round up minutes when showing mm:ss style without seconds + var minutes = numMinutes + if numSeconds > 0 && minutes != 59 { minutes += 1 } + + let badgeString = String(format: "%02d:%02d", numHours, minutes) + print("Badge: \(badgeString)") + Task { @MainActor in + ensureDockIconVisible() + setDockBadge(badgeString) + } + } else { + // Clear the badge when not using badging + Task { @MainActor in + setDockBadge(nil) + } + } + } + + @MainActor + func ensureDockIconVisible() { + let app = NSApplication.shared + if app.activationPolicy() != .regular { + _ = app.setActivationPolicy(.regular) + } + } + + + @MainActor + func setDockBadge(_ text: String?) { + // text: use nil to clear, non-empty string to show + let app = NSApplication.shared + app.dockTile.badgeLabel = (text?.isEmpty == false) ? text : nil + app.dockTile.display() + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+EventScheduler.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+EventScheduler.swift index fee6987b..74fbe5fe 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+EventScheduler.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+EventScheduler.swift @@ -33,6 +33,7 @@ extension FilterViewModel { Task { await self.eventRunner?.stop() } + startEventScheduler() } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index 4a166b9c..33711173 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -45,6 +45,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } } @Published var viewState: SelfControlViewState = .installNetworkExtension + @Published var isNetworkExtensionSkipped: Bool = false { didSet { self.viewState = .filter @@ -67,8 +68,8 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques // Timer to manage delayed actions based on `delay` (in minutes) private var blockTimer: Timer? - private var timerFireDate: Date? - + var timerFireDate: Date? + var dockTimer :Timer? // Date formatter used to log entries lazy var dateFormatter: DateFormatter = { let formatter = DateFormatter() @@ -479,6 +480,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(_enable: true) BlockListManager.activateSafariBlocking() chromeService.activateSafariBlocking() + startShowingCountDownInDock() } func deactivateNetworkBlocking() { @@ -492,6 +494,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques if ProxyPreferences.showNotificationOnCompletion { LocalNotificationManager.scheduleNotification() } + stopShowingCountDownInDock() } // MARK: - Timer management @@ -543,25 +546,28 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques selfControlDaemon.updateBlocklist(newBlockedDomains) } - func updateBlockList(newBlockedDomains: [BlockedURL], time: Double ) { + @MainActor func updateBlockList(newBlockedDomains: [BlockedURL], time: Double ) { self.delay = time - let urls = newBlockedDomains.compactMap(\.urls) - let flattened: [String] = urls.flatMap { $0 } - ProxyPreferences.setBlockedDomains(flattened) - if status == .stopped { installLegacyLaunched(futureDuration: Date.now.addingTimeInterval(delay*60)) } else { if startTimerWithSelectedDelay() == false { return } - - setBlockedUrls(urls: flattened) + saveAndupdateBlockList(newBlockedDomains) if startTimerWithSelectedDelay() == false { return } activateNetworkBlocking() } } + @MainActor func saveAndupdateBlockList(_ newBlockedDomains: [BlockedURL]) { + blockerStorage?.set(blockedURLs) + let urls = newBlockedDomains.compactMap(\.urls) + let flattened: [String] = urls.flatMap { $0 } + ProxyPreferences.setBlockedDomains(flattened) + setBlockedUrls(urls: flattened) + } + func updateScheduledEvents() { startEventScheduler() } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/LocalNotificationManager.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/LocalNotificationManager.swift index 60eb3e66..8e99d7c4 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Preferences/LocalNotificationManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Preferences/LocalNotificationManager.swift @@ -8,7 +8,7 @@ import UserNotifications final class LocalNotificationManager { class func requestAuthorization() { - UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { granted, error in + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in if let error = error { print("Error requesting notification authorization: \(error)") } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift index 9cd96c1b..04fc5c3d 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift @@ -17,7 +17,8 @@ struct ProxyPreferences { private static let isChromeExtInstallKey: String = "isChromeExtInstallKey" private static let playSoundOnCompletionkey: String = "playSoundOnCompletion" private static let showNotificationOnCompletionkey: String = "showNotificationOnCompletion" - + private static let verifyNetworkBeforeBlock: String = "VerifyInternetConnection" + private static let defaults = UserDefaults.standard static func getBlockedDomains() -> [String] { return defaults.stringArray(forKey: blockedDomainsKey) ?? [] @@ -29,7 +30,6 @@ struct ProxyPreferences { static func setSafariExtensionState(_ isEnabled: Bool) { defaults.set(isEnabled, forKey: isSafariExtensionKey) - } static func setChromeExtensionState(_ isEnabled: Bool) { @@ -69,6 +69,14 @@ struct ProxyPreferences { UserDefaults.standard.set(true, forKey: isChromeExtInstallKey) } + static var showVerifyNetworkAlertBeforeBlock: Bool { + return UserDefaults.standard.bool(forKey: verifyNetworkBeforeBlock) + } + + static func setShowVerifyNetworkAlertBeforeBlock(_ isEnabled: Bool) { + defaults.set(isEnabled, forKey: verifyNetworkBeforeBlock) + } + static func reset() { UserDefaults.standard.removeObject(forKey: isChromeExtInstallKey) UserDefaults.standard.removeObject(forKey: isSafariExtInstallKey) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCUIUtility.swift b/Self-Control-Extension/SelfControl/SelfControl/SCUIUtility.swift new file mode 100644 index 00000000..a9d353d0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SCUIUtility.swift @@ -0,0 +1,22 @@ +// +// SCUIUtility.swift +// SelfControl +// +// Created by Satendra Singh on 28/04/26. +// +import AppKit + +final class SCUIUtility { + static func checkNetworkAndShowNetworkAlert() -> Bool { + + if !SCUtility.networkConnectionIsAvailable() { + let alert = NSAlert() + alert.messageText = NSLocalizedString("No network connection detected", comment: "No network connection detected message") + alert.informativeText = NSLocalizedString("A block cannot be started without a working network connection. You can override this setting in Preferences.", comment: "Message when network connection is unavailable") + alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK button")) + alert.runModal() + return false + } + return true + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCUtility.swift b/Self-Control-Extension/SelfControl/SelfControl/SCUtility.swift new file mode 100644 index 00000000..b5f82e79 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/SCUtility.swift @@ -0,0 +1,21 @@ +// +// SCUtility.swift +// SelfControl +// +// Created by Satendra Singh on 26/04/26. +// + +import SystemConfiguration + +final class SCUtility { + + static func networkConnectionIsAvailable() -> Bool { + var flags = SCNetworkReachabilityFlags() + + guard let target = SCNetworkReachabilityCreateWithName(nil, "google.com"), + SCNetworkReachabilityGetFlags(target, &flags) else { + return false + } + return flags.contains(.reachable) && !flags.contains(.connectionRequired) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift index dec098c8..e5bce932 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift @@ -1353,6 +1353,11 @@ struct NewContentView: View { } private func startBlocking(minutes: Double) { + if ProxyPreferences.showVerifyNetworkAlertBeforeBlock { + if SCUIUtility.checkNetworkAndShowNetworkAlert() == false { + return + } + } withAnimation(DesignSystem.animationNormal) { isBlocking = true viewModel.updateBlockList(newBlockedDomains: blockedURLs, time: minutes) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift index 0b3ab7c5..996df35f 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift @@ -14,7 +14,7 @@ struct AdvancedSettingsView: View { let isBlockingMode: Bool // Network Settings - @AppStorage("verifyConnection") private var verifyConnection = true + @AppStorage("VerifyInternetConnection") private var verifyConnection = true @AppStorage("clearCache") private var clearCache = true @AppStorage("allowLocalNetworks") private var allowLocalNetworks = true @@ -30,7 +30,7 @@ struct AdvancedSettingsView: View { @AppStorage("showNotificationOnCompletion") private var showNotificationOnCompletion = true @AppStorage("timerFloatsOnTop") private var timerFloatsOnTop = false - @AppStorage("showCountdownInDock") private var showCountdownInDock = true + @AppStorage("BadgeApplicationIcon") private var showCountdownInDock = true // Display Settings @AppStorage("hideSeconds") private var hideSeconds = false diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift index c6d8ff79..f896b325 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift @@ -98,8 +98,10 @@ struct BlockScheduleView: View { onDelete: { withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) { schedules.removeAll { $0.id == schedule.id } - saveSchedules() - updateScheduleStatus() +// Task { +// saveSchedules() +// updateScheduleStatus() +// } } } ) @@ -174,7 +176,7 @@ struct ScheduleRow: View { WeekdayCircle( weekday: weekday, isEnabled: schedule.enabledDays.contains(weekday), - blockingMode: blockingMode, + blockingMode: blockingMode,// onToggle: { withAnimation(DesignSystem.animationFast) { if schedule.enabledDays.contains(weekday) { diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift index b60e6b32..43318161 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift @@ -6,6 +6,7 @@ // import SwiftUI + struct BlocklistEditorView: View { @EnvironmentObject var viewModel: FilterViewModel @@ -18,6 +19,8 @@ struct BlocklistEditorView: View { @Binding var blockingMode: BlockingMode @Binding var currentScreen: AppScreen @Binding var showingTips: Bool + + // Top-level UI state @State private var newSite: String = "" @State private var newPath: String = "" @State private var searchText: String = "" @@ -26,51 +29,235 @@ struct BlocklistEditorView: View { @State private var focusedField: EditorFocusField? @State private var showingAddSiteConfirmation = false @State private var pendingSiteToAdd: String? = nil + + // Bulk-edit + selection @State private var isBulkEditMode: Bool = false @State private var selectedURLs: Set = [] @State private var lastSelectedIndex: Int? = nil + + // Delete confirmation @State private var showingDeleteConfirmation: Bool = false + + // Search/add UI @State private var isSearchMode: Bool = false + + // Hover states @State private var isHoveringSuggestedChip = false @State private var isHoveringBulkEdit = false @State private var isHoveringImport = false @State private var isHoveringExport = false @State private var isHoveringAdd = false - + + // Suggested scroll + @State private var firstVisibleSuggestedID: String? + enum EditorFocusField: Hashable { case addWebsite case search case addPath case doneButton } - + // Suggested sites for quick adding let suggestedSites = DomainConstants.suggestedSites - + + // MARK: - Derived state + var filteredURLs: [BlockedURL] { if searchText.isEmpty { return blockedURLs.sorted { $0.domain < $1.domain } } - return blockedURLs.filter { url in - url.domain.localizedCaseInsensitiveContains(searchText) || - url.paths.contains { $0.localizedCaseInsensitiveContains(searchText) } - }.sorted { $0.domain < $1.domain } + return blockedURLs + .filter { url in + url.domain.localizedCaseInsensitiveContains(searchText) || + url.paths.contains { $0.localizedCaseInsensitiveContains(searchText) } + } + .sorted { $0.domain < $1.domain } } - + var totalCount: Int { blockedURLs.count + blockedURLs.reduce(0) { $0 + $1.paths.count } } - + + var headerSubtitleText: String { + let enabledCount = blockedURLs.filter { $0.isEnabled }.count + let pathCount = blockedURLs.reduce(0) { $0 + $1.paths.count } + + if enabledCount != blockedURLs.count { + return "\(enabledCount)/\(blockedURLs.count) enabled" + (pathCount > 0 ? ", \(pathCount) paths" : "") + } else if pathCount > 0 { + return "\(blockedURLs.count) domain\(blockedURLs.count == 1 ? "" : "s"), \(pathCount) path\(pathCount == 1 ? "" : "s")" + } else { + return "\(blockedURLs.count) website\(blockedURLs.count == 1 ? "" : "s")" + } + } + + // MARK: - Body + + var body: some View { + ZStack { + DesignSystem.backgroundGradient + .ignoresSafeArea(.all) + .frame(maxWidth: .infinity, maxHeight: .infinity) + + VStack(spacing: 0) { + BlocklistHeader( + title: blockingMode == .blocklist ? Strings.BlocklistEditor.title : Strings.BlocklistEditor.allowlist, + onBack: { + selectedDomain = nil + currentScreen = .main + }, + onImport: { importurls() }, + onExport: { exporturls() }, + isBlockingMode: isBlockingMode, + isHoveringImport: $isHoveringImport, + isHoveringExport: $isHoveringExport + ) + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + + Divider().background(DesignSystem.borderPrimary) + + if blockingMode == .allowlist { + AllowlistBanner() + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.top, DesignSystem.spacingMedium) + } + + AddSearchBar( + isSearchMode: $isSearchMode, + focusedField: $focusedField, + searchText: $searchText, + newSite: $newSite, + isHoveringAdd: $isHoveringAdd, + onAdd: { addWebsite() } + ) + .padding(.horizontal, DesignSystem.spacingLarge) + .padding(.vertical, DesignSystem.spacingSmall) + + Divider().background(DesignSystem.borderPrimary) + + DomainListSection( + filteredURLs: filteredURLs, + blockedURLs: $blockedURLs, + isBlockingMode: isBlockingMode, + isSearchMode: isSearchMode, + searchText: searchText, + showSuggested: $showSuggested, + suggestedSites: suggestedSites, + isHoveringSuggestedChip: $isHoveringSuggestedChip, + isBulkEditMode: $isBulkEditMode, + isHoveringBulkEdit: $isHoveringBulkEdit, + selectedURLs: $selectedURLs, + lastSelectedIndex: $lastSelectedIndex, + onToggleBulkEdit: toggleBulkEdit, + onSelectRow: handleRowSelection(urlId:index:isShiftPressed:), + onNavigateToDetail: { id in currentScreen = .domainDetail(id) }, + onRequestAddSuggested: handleAddSuggested(site:) + ) + .padding(DesignSystem.spacingXLarge) + + BottomActionBarView( + filteredURLs: filteredURLs, + selectedURLs: $selectedURLs, + isBulkEditMode: isBulkEditMode, + onToggleAllFiltered: toggleAllFilteredSelection, + onDelete: { showingDeleteConfirmation = true } + ) + } + + if showingDeleteConfirmation { + DeleteConfirmationModal( + count: selectedURLs.count, + onCancel: { showingDeleteConfirmation = false }, + onConfirm: { + deleteSelectedURLs() + showingDeleteConfirmation = false + } + ) + } + } + .alert( + Strings.BlocklistEditor.addToExistingBlockTitle, + isPresented: $showingAddSiteConfirmation, + presenting: pendingSiteToAdd + ) { site in + Button(Strings.BlocklistEditor.addSite) { + confirmAddWebsite() + } + Button(Strings.Common.cancel, role: .cancel) { + pendingSiteToAdd = nil + } + } message: { site in + Text(Strings.BlocklistEditor.addToExistingBlockMessage(site, blockingMode: blockingMode)) + } + + .onAppear { + // Reset to list view and focus on add website field when editor opens + selectedDomain = nil + focusedField = .addWebsite + } + } + + // MARK: - Actions + + func handleRowSelection(urlId: UUID, index: Int, isShiftPressed: Bool) { + handleCheckboxClick(for: urlId, at: index, isShiftPressed: isShiftPressed) + } + + func toggleBulkEdit() { + withAnimation { + isBulkEditMode.toggle() + if !isBulkEditMode { + selectedURLs.removeAll() + lastSelectedIndex = nil + } + } + } + + func toggleAllFilteredSelection() { + withAnimation(DesignSystem.animationFast) { + let filteredIDs = Set(filteredURLs.map { $0.id }) + let allFilteredSelected = !filteredURLs.isEmpty && filteredIDs.isSubset(of: selectedURLs) + + if allFilteredSelected { + for id in filteredIDs { + selectedURLs.remove(id) + } + } else { + for id in filteredIDs { + selectedURLs.insert(id) + } + } + } + } + + func handleAddSuggested(site: String) { + let isAlreadyBlocked = blockedURLs.contains { $0.domain == site } + guard !isAlreadyBlocked else { return } + + if isBlockingMode { + pendingSiteToAdd = site + DispatchQueue.main.async { + showingAddSiteConfirmation = true + } + } else { + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + blockedURLs.append(BlockedURL(domain: site)) + } + } + } + func handleCheckboxClick(for urlId: UUID, at index: Int, isShiftPressed: Bool = false) { if isShiftPressed, let lastIndex = lastSelectedIndex { - // Shift-select: select range from lastSelectedIndex to current index let startIndex = min(lastIndex, index) let endIndex = max(lastIndex, index) let range = startIndex...endIndex - + let isCurrentlySelected = selectedURLs.contains(urlId) - - // Toggle all items in range + for i in range { let itemId = filteredURLs[i].id if isCurrentlySelected { @@ -80,7 +267,6 @@ struct BlocklistEditorView: View { } } } else { - // Normal click: toggle single item if selectedURLs.contains(urlId) { selectedURLs.remove(urlId) } else { @@ -89,593 +275,486 @@ struct BlocklistEditorView: View { lastSelectedIndex = index } } - + func deleteSelectedURLs() { withAnimation(DesignSystem.animationFast) { - // Create a new array without the selected items blockedURLs = blockedURLs.filter { !selectedURLs.contains($0.id) } selectedURLs.removeAll() - - // Clear bulk edit mode if no URLs left + if blockedURLs.isEmpty { isBulkEditMode = false } } } - - var headerSubtitleText: String { - let enabledCount = blockedURLs.filter { $0.isEnabled }.count - let pathCount = blockedURLs.reduce(0) { $0 + $1.paths.count } - - if enabledCount != blockedURLs.count { - return "\(enabledCount)/\(blockedURLs.count) enabled" + (pathCount > 0 ? ", \(pathCount) paths" : "") - } else if pathCount > 0 { - return "\(blockedURLs.count) domain\(blockedURLs.count == 1 ? "" : "s"), \(pathCount) path\(pathCount == 1 ? "" : "s")" + + // MARK: - Domain/path editing + + private func addWebsite() { + let input = newSite.trimmingCharacters(in: .whitespaces) + if input.isEmpty { return } + + let (domain, path) = BlockedURLsHelpers.parseDomainAndPath(from: input) + if domain.isEmpty { return } + + var domainId: UUID + if let existingDomain = blockedURLs.first(where: { $0.domain == domain }) { + domainId = existingDomain.id } else { - return "\(blockedURLs.count) website\(blockedURLs.count == 1 ? "" : "s")" - } - } - - var headerView: some View { - HStack(alignment: .center) { - // Back button - Button(action: { - selectedDomain = nil - currentScreen = .main - }) { - HStack(spacing: 4) { - Image(systemName: "chevron.left") - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) - Text(Strings.Common.back) - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + if isBlockingMode { + pendingSiteToAdd = input + showingAddSiteConfirmation = true + return + } else { + let newDomain = BlockedURL(domain: domain) + domainId = newDomain.id + withAnimation { + blockedURLs.append(newDomain) } - .foregroundColor(DesignSystem.textPrimary) } - .buttonStyle(.plain) - .keyboardShortcut(.cancelAction) - - Spacer() - - // Import button - Button(action: { - importurls() - }) { - HStack(spacing: 4) { - Image(systemName: "square.and.arrow.down") - .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) - Text(Strings.Common.importText) - .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + } + + if let pathToAdd = path { + if let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { + let formattedPath = pathToAdd.hasPrefix("/") || pathToAdd.contains(".") ? pathToAdd : "/\(pathToAdd)" + if !blockedURLs[index].paths.contains(formattedPath) { + withAnimation { + blockedURLs[index].paths.append(formattedPath) + saveChanges() + newSite = "" + currentScreen = .domainDetail(domainId) + } + return + } else { + withAnimation { + newSite = "" + currentScreen = .domainDetail(domainId) + } + return } - .foregroundColor(DesignSystem.textPrimary) - .padding(.horizontal, DesignSystem.spacingSmall) - .padding(.vertical, DesignSystem.spacingXSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .fill(isHoveringImport ? DesignSystem.hoverBackground : Color.clear) - ) - .modifier(DesignSystem.buttonStyle()) } - .buttonStyle(.plain) - .focusable(false) - .disabled(isBlockingMode) - .onHover { hovering in - withAnimation(DesignSystem.animationFast) { - isHoveringImport = hovering - } + } + + let domainExisted = blockedURLs.contains(where: { $0.id == domainId && $0.domain == domain }) + if !domainExisted { + withAnimation { + newSite = "" + currentScreen = .domainDetail(domainId) } - - // Export button - Button(action: { - exporturls() - }) { - HStack(spacing: 4) { - Image(systemName: "square.and.arrow.up") - .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) - Text(Strings.Common.export) - .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) - } - .foregroundColor(DesignSystem.textPrimary) - .padding(.horizontal, DesignSystem.spacingSmall) - .padding(.vertical, DesignSystem.spacingXSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .fill(isHoveringExport ? DesignSystem.hoverBackground : Color.clear) - ) - .modifier(DesignSystem.buttonStyle()) + } else { + newSite = "" + } + } + + private func confirmAddWebsite() { + guard let input = pendingSiteToAdd else { return } + let (domain, path) = BlockedURLsHelpers.parseDomainAndPath(from: input) + + if domain.isEmpty { + pendingSiteToAdd = nil + return + } + + var domainId: UUID + if let existingDomain = blockedURLs.first(where: { $0.domain == domain }) { + domainId = existingDomain.id + } else { + let newDomain = BlockedURL(domain: domain) + domainId = newDomain.id + withAnimation { + blockedURLs.append(newDomain) } - .buttonStyle(.plain) - .focusable(false) - .onHover { hovering in - withAnimation(DesignSystem.animationFast) { - isHoveringExport = hovering + } + + if let pathToAdd = path { + if let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { + let formattedPath = pathToAdd.hasPrefix("/") || pathToAdd.contains(".") ? pathToAdd : "/\(pathToAdd)" + if !blockedURLs[index].paths.contains(formattedPath) { + withAnimation { + blockedURLs[index].paths.append(formattedPath) + saveChanges() + newSite = "" + pendingSiteToAdd = nil + currentScreen = .domainDetail(domainId) + } + return + } else { + withAnimation { + newSite = "" + pendingSiteToAdd = nil + currentScreen = .domainDetail(domainId) + } + return } } } - .overlay( - Text(blockingMode == .blocklist ? Strings.BlocklistEditor.title : Strings.BlocklistEditor.allowlist) - .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge, weight: DesignSystem.fontWeightSemibold)) + + let domainExisted = blockedURLs.contains(where: { $0.id == domainId && $0.domain == domain }) + if !domainExisted { + withAnimation { + newSite = "" + pendingSiteToAdd = nil + currentScreen = .domainDetail(domainId) + } + } else { + withAnimation { + newSite = "" + pendingSiteToAdd = nil + } + } + } + + private func addPath(to urlId: UUID) { + let cleanPath = newPath.trimmingCharacters(in: .whitespaces) + if !cleanPath.isEmpty, let index = blockedURLs.firstIndex(where: { $0.id == urlId }) { + let formattedPath = cleanPath.hasPrefix("/") || cleanPath.contains(".") ? cleanPath : "/\(cleanPath)" + + if !blockedURLs[index].paths.contains(formattedPath) { + withAnimation { + blockedURLs[index].paths.append(formattedPath) + saveChanges() + newPath = "" + } + } + } + } + + private func saveChanges() { + viewModel.saveAndupdateBlockList(blockedURLs) + } +} + +// MARK: - Import/Export + +extension BlocklistEditorView { + func importurls() { + if let window = NSApplication.shared.keyWindow { + Task { @MainActor in + do { + let imported = try await ImportExportManager.importBlockedUrls(presentingWindow: window) + self.blockedURLs = imported + } catch { + NSLog("Import failed: \(error.localizedDescription)") + } + } + } else { + Task { @MainActor in + do { + let imported = try await ImportExportManager.importBlockedUrls() + self.blockedURLs = imported + } catch { + NSLog("Import failed: \(error.localizedDescription)") + } + } + } + } + + func exporturls() { + ImportExportManager.exportBlockedUrls(blockedURLs: self.blockedURLs) + } +} + +// MARK: - Subviews + +private struct BlocklistHeader: View { + let title: String + let onBack: () -> Void + let onImport: () -> Void + let onExport: () -> Void + let isBlockingMode: Bool + + @Binding var isHoveringImport: Bool + @Binding var isHoveringExport: Bool + + var body: some View { + HStack(alignment: .center) { + Button(action: onBack) { + HStack(spacing: 4) { + Image(systemName: "chevron.left") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + Text(Strings.Common.back) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textPrimary) + } + .buttonStyle(.plain) + .keyboardShortcut(.cancelAction) + + Spacer() + + Button(action: onImport) { + HStack(spacing: 4) { + Image(systemName: "square.and.arrow.down") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Text(Strings.Common.importText) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringImport ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .focusable(false) + .disabled(isBlockingMode) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringImport = hovering + } + } + + Button(action: onExport) { + HStack(spacing: 4) { + Image(systemName: "square.and.arrow.up") + .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) + Text(Strings.Common.export) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + } + .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringExport ? DesignSystem.hoverBackground : Color.clear) + ) + .modifier(DesignSystem.buttonStyle()) + } + .buttonStyle(.plain) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringExport = hovering + } + } + } + .overlay( + Text(title) + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge, weight: DesignSystem.fontWeightSemibold)) .foregroundColor(DesignSystem.textPrimary) .frame(maxWidth: .infinity, alignment: .center) ) } - +} + +private struct AllowlistBanner: View { var body: some View { - ZStack { - // Dark gradient background - DesignSystem.backgroundGradient - .ignoresSafeArea(.all) - .frame(maxWidth: .infinity, maxHeight: .infinity) - - VStack(spacing: 0) { - // Compact header - headerView - .padding(.horizontal, DesignSystem.spacingLarge) - .padding(.vertical, DesignSystem.spacingSmall) - - Divider() - .background(DesignSystem.borderPrimary) - - // Allowlist mode warning banner - if blockingMode == .allowlist { - HStack(spacing: 10) { - Image(systemName: "exclamationmark.triangle.fill") + HStack(spacing: 10) { + Image(systemName: "exclamationmark.triangle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.textPrimary) + + VStack(alignment: .leading, spacing: 2) { + Text(Strings.BlocklistEditor.allowlistModeActive) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightBold)) + .foregroundColor(DesignSystem.textPrimary) + Text(Strings.BlocklistEditor.allowlistModeDescription) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textSecondary) + } + + Spacer() + } + .padding(DesignSystem.spacingMedium) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.hoverBorder.opacity(DesignSystem.opacityHigher), lineWidth: 1) + ) + ) + } +} + +private struct AddSearchBar: View { + @Binding var isSearchMode: Bool + @Binding var focusedField: BlocklistEditorView.EditorFocusField? + @Binding var searchText: String + @Binding var newSite: String + @Binding var isHoveringAdd: Bool + + let onAdd: () -> Void + + var body: some View { + HStack(spacing: DesignSystem.spacingSmall) { + HStack(spacing: 8) { + Group { + if isSearchMode { + Image(systemName: "magnifyingglass") + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) + .foregroundColor(DesignSystem.textPrimary) + } else { + Image(systemName: "plus.circle.fill") .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) .foregroundColor(DesignSystem.textPrimary) - - VStack(alignment: .leading, spacing: 2) { - Text(Strings.BlocklistEditor.allowlistModeActive) - .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightBold)) - .foregroundColor(DesignSystem.textPrimary) - Text(Strings.BlocklistEditor.allowlistModeDescription) - .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) - .foregroundColor(DesignSystem.textSecondary) - } - - Spacer() } - .padding(DesignSystem.spacingMedium) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .fill(DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium)) - .overlay( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .stroke(DesignSystem.hoverBorder.opacity(DesignSystem.opacityHigher), lineWidth: 1) - ) - ) - .padding(.horizontal, DesignSystem.spacingLarge) - .padding(.top, DesignSystem.spacingMedium) } - - // Combined add and search section - HStack(spacing: DesignSystem.spacingSmall) { - // Add website / Search (toggles based on isSearchMode) - HStack(spacing: 8) { - // Icon - animated transition - Group { - if isSearchMode { - Image(systemName: "magnifyingglass") - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) - .foregroundColor(DesignSystem.textPrimary) - } else { - Image(systemName: "plus.circle.fill") - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) - .foregroundColor(DesignSystem.textPrimary) - } - } - .transition(.opacity.combined(with: .scale(scale: 0.8))) - - // TextField - animated transition - Group { - if isSearchMode { - - TextField(Strings.BlocklistEditor.searchPlaceholder, text: $searchText) - .textFieldStyle(.plain) - .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) - .foregroundColor(DesignSystem.textPrimary) - .placeholder(when: searchText.isEmpty, alignment: .leading) { - Text(Strings.BlocklistEditor.searchPlaceholder) - .foregroundColor(DesignSystem.disabledText) - .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) - .padding(.vertical, 8) // match your field’s vertical insets - } - } else { - TextField(Strings.BlocklistEditor.addWebsitePlaceholder, text: $newSite, onCommit: { - addWebsite() - }) - .textFieldStyle(.plain) + .transition(.opacity.combined(with: .scale(scale: 0.8))) + + Group { + if isSearchMode { + TextField(Strings.BlocklistEditor.searchPlaceholder, text: $searchText) + .textFieldStyle(.plain) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textPrimary) + .placeholder(when: searchText.isEmpty, alignment: .leading) { + Text(Strings.BlocklistEditor.searchPlaceholder) + .foregroundColor(DesignSystem.disabledText) .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) - .foregroundColor(DesignSystem.textPrimary) - .placeholder(when: newSite.isEmpty, alignment: .leading) { - Text(Strings.BlocklistEditor.addWebsitePlaceholder) - .foregroundColor(DesignSystem.disabledText) - .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) - .padding(.vertical, 8) // match your field’s vertical insets - } - } - } - .transition(.opacity.combined(with: .move(edge: .trailing))) - - // Spacer to push button to the right - if isSearchMode { - Spacer() - .frame(minWidth: 8) - } - - // Close/Add button area - maintains consistent width - Group { - if isSearchMode { - Button(action: { - withAnimation(DesignSystem.animationNormal) { - isSearchMode = false - searchText = "" - focusedField = .addWebsite - } - }) { - Text(Strings.Common.cancel) - .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) - .foregroundColor(DesignSystem.textTertiary) - .padding(.horizontal, DesignSystem.spacingSmall) - .padding(.vertical, DesignSystem.spacingXSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .fill(isHoveringAdd ? DesignSystem.hoverBackground : Color.clear) - ) - .modifier(DesignSystem.buttonStyle()) - } - .buttonStyle(.plain) - .help(Strings.Help.closeSearch) - .onHover { hovering in - withAnimation(DesignSystem.animationFast) { - isHoveringAdd = hovering - } + .padding(.vertical, 8) } } else { - Button(action: { - addWebsite() - }) { - Text(Strings.Common.add) - .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) - .foregroundColor(DesignSystem.textTertiary) - .padding(.horizontal, DesignSystem.spacingSmall) - .padding(.vertical, DesignSystem.spacingXSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .fill(isHoveringAdd ? DesignSystem.hoverBackground : Color.clear) - ) - .modifier(DesignSystem.buttonStyle()) - } - .buttonStyle(.plain) - .disabled(newSite.trimmingCharacters(in: .whitespaces).isEmpty) - .opacity(newSite.trimmingCharacters(in: .whitespaces).isEmpty ? DesignSystem.opacityHigh : DesignSystem.opacityFull) - .focusable(false) - .onHover { hovering in - withAnimation(DesignSystem.animationFast) { - isHoveringAdd = hovering - } - } - .help(Strings.Help.addSite) - } - } - .transition(.opacity.combined(with: .scale(scale: 0.9))) - } - .padding(.horizontal, DesignSystem.spacingSmall) - .padding(.vertical, DesignSystem.spacingSmallMedium) - .frame(height: 44) // Fixed height to prevent size changes - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .fill(DesignSystem.backgroundPrimary) - .overlay( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .stroke((isSearchMode && focusedField == .search) || (!isSearchMode && focusedField == .addWebsite) ? DesignSystem.textPrimary : DesignSystem.borderPrimary, lineWidth: 1) - ) - ) - .animation(DesignSystem.animationNormal, value: isSearchMode) - .animation(DesignSystem.animationNormal, value: searchText.isEmpty) - - // Search icon (replaces gear icon) - minimal spacing - Button(action: { - withAnimation(DesignSystem.animationNormal) { - isSearchMode.toggle() - if isSearchMode { - focusedField = .search - } else { - searchText = "" - focusedField = .addWebsite + TextField(Strings.BlocklistEditor.addWebsitePlaceholder, text: $newSite, onCommit: onAdd) + .textFieldStyle(.plain) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .foregroundColor(DesignSystem.textPrimary) + .placeholder(when: newSite.isEmpty, alignment: .leading) { + Text(Strings.BlocklistEditor.addWebsitePlaceholder) + .foregroundColor(DesignSystem.disabledText) + .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) + .padding(.vertical, 8) } - } - }) { - Image(systemName: "magnifyingglass") - .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge)) - .foregroundColor(isSearchMode ? DesignSystem.textPrimary : DesignSystem.textTertiary) - .frame(width: 32, height: 32) } - .buttonStyle(.plain) - .help(isSearchMode ? Strings.Help.closeSearch : Strings.Help.search) - .padding(.leading, DesignSystem.spacingXXSmall) } - .padding(.horizontal, DesignSystem.spacingLarge) - .padding(.vertical, DesignSystem.spacingSmall) - - Divider() - .background(DesignSystem.borderPrimary) - - // Hierarchical tree list - urlListView - - // Bottom action bar - bottomActionBar - } - - // Delete confirmation modal overlay - placed last to appear on top - if showingDeleteConfirmation { - DesignSystem.overlayBackground - .ignoresSafeArea() - .onTapGesture { - showingDeleteConfirmation = false - } - - VStack(spacing: DesignSystem.spacingMedium) { - let count = selectedURLs.count - - VStack(spacing: 8) { - Image(systemName: "exclamationmark.triangle.fill") - .font(DesignSystem.font(size: DesignSystem.fontSizeDisplay)) - .foregroundColor(DesignSystem.destructiveColor) - - Text(Strings.BlocklistEditor.deleteSiteTitle(count)) - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) - .foregroundColor(DesignSystem.textPrimary) - - Text(Strings.BlocklistEditor.deleteSiteMessage(count)) - .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) - .foregroundColor(DesignSystem.textSecondary) - .multilineTextAlignment(.center) - .fixedSize(horizontal: false, vertical: true) - } - - HStack(spacing: DesignSystem.spacingSmall) { + .transition(.opacity.combined(with: .move(edge: .trailing))) + + if isSearchMode { + Spacer().frame(minWidth: 8) + } + + Group { + if isSearchMode { Button(action: { - showingDeleteConfirmation = false + withAnimation(DesignSystem.animationNormal) { + isSearchMode = false + searchText = "" + focusedField = .addWebsite + } }) { Text(Strings.Common.cancel) .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) - .foregroundColor(DesignSystem.textPrimary) - .frame(width: 80) + .foregroundColor(DesignSystem.textTertiary) + .padding(.horizontal, DesignSystem.spacingSmall) .padding(.vertical, DesignSystem.spacingXSmall) .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .fill(DesignSystem.backgroundTertiary) + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringAdd ? DesignSystem.hoverBackground : Color.clear) ) + .modifier(DesignSystem.buttonStyle()) } .buttonStyle(.plain) - - Button(action: { - deleteSelectedURLs() - showingDeleteConfirmation = false - }) { - Text(Strings.Common.delete) + .help(Strings.Help.closeSearch) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringAdd = hovering + } + } + } else { + Button(action: onAdd) { + Text(Strings.Common.add) .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) - .foregroundColor(DesignSystem.textPrimary) - .frame(width: 80) + .foregroundColor(DesignSystem.textTertiary) + .padding(.horizontal, DesignSystem.spacingSmall) .padding(.vertical, DesignSystem.spacingXSmall) .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .fill(DesignSystem.destructiveColor) + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(isHoveringAdd ? DesignSystem.hoverBackground : Color.clear) ) + .modifier(DesignSystem.buttonStyle()) } .buttonStyle(.plain) + .disabled(newSite.trimmingCharacters(in: .whitespaces).isEmpty) + .opacity(newSite.trimmingCharacters(in: .whitespaces).isEmpty ? DesignSystem.opacityHigh : DesignSystem.opacityFull) + .focusable(false) + .onHover { hovering in + withAnimation(DesignSystem.animationFast) { + isHoveringAdd = hovering + } + } + .help(Strings.Help.addSite) } } - .padding(DesignSystem.spacingMedium) - .frame(maxWidth: 320) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .fill(DesignSystem.backgroundPrimary) - .overlay( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .stroke(DesignSystem.borderPrimary, lineWidth: 1) - ) - ) - .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityHigh), radius: 15, x: 0, y: 5) - .zIndex(999) + .transition(.opacity.combined(with: .scale(scale: 0.9))) } - } - .alert(isPresented: $showingAddSiteConfirmation) { - let site = pendingSiteToAdd ?? "this site" - return Alert( - title: Text(Strings.BlocklistEditor.addToExistingBlockTitle), - message: Text(Strings.BlocklistEditor.addToExistingBlockMessage(site, blockingMode: blockingMode)), - primaryButton: .default(Text(Strings.BlocklistEditor.addSite), action: { - confirmAddWebsite() - }), - secondaryButton: .cancel(Text(Strings.Common.cancel), action: { - pendingSiteToAdd = nil - }) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingSmallMedium) + .frame(height: 44) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke((isSearchMode && focusedField == .search) || (!isSearchMode && focusedField == .addWebsite) ? DesignSystem.textPrimary : DesignSystem.borderPrimary, lineWidth: 1) + ) ) - } - .onAppear { - // Reset to list view and focus on add website field when editor opens - selectedDomain = nil - focusedField = .addWebsite - } - } - - var addWebsiteSection_UNUSED: some View { - VStack(spacing: DesignSystem.spacingMedium) { - HStack(spacing: DesignSystem.spacingSmall) { - Image(systemName: "plus.circle.fill") - .font(DesignSystem.font(size: DesignSystem.fontSizeHeading)) - .foregroundColor(DesignSystem.textPrimary) - - TextField(Strings.BlocklistEditor.addWebsiteExamplePlaceholder, text: $newSite, onCommit: { addWebsite() }) - .textFieldStyle(.plain) - .font(DesignSystem.font(size: DesignSystem.fontSizeXLarge)) - .foregroundColor(DesignSystem.textPrimary) - .padding(DesignSystem.spacingSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .fill(DesignSystem.backgroundPrimary) - .overlay( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .stroke(focusedField == .addWebsite ? DesignSystem.textPrimary : DesignSystem.borderPrimary, lineWidth: focusedField == .addWebsite ? 2 : 1) - ) - ) - - - Button(action: addWebsite) { - Text(Strings.Common.add) - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) - .foregroundColor(DesignSystem.textPrimary) - .padding(.horizontal, DesignSystem.spacingLarge) - .padding(.vertical, DesignSystem.spacingSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .fill(DesignSystem.textPrimary) - ) - } - .buttonStyle(.plain) - .disabled(newSite.isEmpty) - .opacity(newSite.isEmpty ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) - } - - // Suggested chip - if !suggestedSites.isEmpty { - VStack(alignment: .leading, spacing: DesignSystem.spacingSmall) { - Text(Strings.BlocklistEditor.quickAdd) - .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) - .foregroundColor(DesignSystem.textTertiary) - .textCase(.uppercase) - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 10) { - Button(action: { - showSuggested.toggle() - }) { - HStack(spacing: 6) { - Image(systemName: "lightbulb") - .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) - Text(Strings.BlocklistEditor.suggested) - .font(DesignSystem.font(size: DesignSystem.fontSizeMedium, weight: DesignSystem.fontWeightMedium)) - } - .foregroundColor(showSuggested ? DesignSystem.textTertiary : DesignSystem.textSecondary) - .padding(.horizontal, DesignSystem.spacingMedium) - .padding(.vertical, DesignSystem.spacingXSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .fill(showSuggested ? DesignSystem.backgroundPrimary : DesignSystem.backgroundTertiary) - ) - } - .buttonStyle(.plain) - } - } - - // Show all suggested sites - if showSuggested { - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 8) { - ForEach(suggestedSites, id: \.self) { site in - let isAlreadyBlocked = blockedURLs.contains { $0.domain == site } - Button(action: { - if !isAlreadyBlocked { - // Add without animation to prevent blink - // showSuggested is @AppStorage so it persists automatically - var transaction = Transaction() - transaction.disablesAnimations = true - withTransaction(transaction) { - blockedURLs.append(BlockedURL(domain: site)) - } - } - }) { - HStack(spacing: 6) { - Text(site) - .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) - Image(systemName: isAlreadyBlocked ? "checkmark.circle.fill" : "plus.circle") - .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) - } - .foregroundColor(DesignSystem.textPrimary) - .padding(.horizontal, DesignSystem.spacingSmall) - .padding(.vertical, DesignSystem.spacingXSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .fill(DesignSystem.backgroundPrimary) - .overlay( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .stroke(isAlreadyBlocked ? DesignSystem.hoverBorder.opacity(DesignSystem.opacityHigher) : DesignSystem.borderPrimary, lineWidth: 1) - ) - ) - } - .buttonStyle(.plain) - .disabled(isAlreadyBlocked) - } - } - } - } - } - } - } - } - - var searchBarView_UNUSED: some View { - HStack(spacing: DesignSystem.spacingSmall) { - Image(systemName: "magnifyingglass") - .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge)) - .foregroundColor(DesignSystem.textTertiary) - - TextField(Strings.BlocklistEditor.searchBlockedSitesPlaceholder, text: $searchText) - .textFieldStyle(.plain) - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) - .foregroundColor(DesignSystem.textPrimary) - - - if !searchText.isEmpty { - Button(action: { searchText = "" }) { - Image(systemName: "xmark.circle.fill") - .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge)) - .foregroundColor(DesignSystem.textQuaternary) - } - .buttonStyle(.plain) + .animation(DesignSystem.animationNormal, value: isSearchMode) + .animation(DesignSystem.animationNormal, value: searchText.isEmpty) + + Button(action: { + withAnimation(DesignSystem.animationNormal) { + isSearchMode.toggle() + if isSearchMode { + focusedField = .search + } else { + searchText = "" + focusedField = .addWebsite } } - .padding(DesignSystem.spacingSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .fill(DesignSystem.backgroundPrimary) - .overlay( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .stroke(focusedField == .search ? DesignSystem.textPrimary : Color.clear, lineWidth: 2) - ) - ) - .padding(.horizontal, DesignSystem.spacingXLarge) - .padding(.vertical, DesignSystem.spacingMedium) - } - - var urlListView: some View { - domainListView + }) { + Image(systemName: "magnifyingglass") + .font(DesignSystem.font(size: DesignSystem.fontSizeXXLarge)) + .foregroundColor(isSearchMode ? DesignSystem.textPrimary : DesignSystem.textTertiary) + .frame(width: 32, height: 32) + } + .buttonStyle(.plain) + .help(isSearchMode ? Strings.Help.closeSearch : Strings.Help.search) + .padding(.leading, DesignSystem.spacingXXSmall) + } } - - var domainListView: some View { +} + +private struct DomainListSection: View { + let filteredURLs: [BlockedURL] + @Binding var blockedURLs: [BlockedURL] + + let isBlockingMode: Bool + let isSearchMode: Bool + let searchText: String + + @Binding var showSuggested: Bool + let suggestedSites: [String] + @Binding var isHoveringSuggestedChip: Bool + + @Binding var isBulkEditMode: Bool + @Binding var isHoveringBulkEdit: Bool + @Binding var selectedURLs: Set + @Binding var lastSelectedIndex: Int? + + let onToggleBulkEdit: () -> Void + let onSelectRow: (_ urlId: UUID, _ index: Int, _ isShiftPressed: Bool) -> Void + let onNavigateToDetail: (UUID) -> Void + let onRequestAddSuggested: (String) -> Void + + var body: some View { ScrollView { VStack(spacing: 0) { - // Count header with Suggested and Bulk Edit buttons if !filteredURLs.isEmpty || (!suggestedSites.isEmpty && searchText.isEmpty && !isSearchMode) { ZStack { - // Centered text if !filteredURLs.isEmpty { Text(Strings.BlocklistEditor.siteCount(blockedURLs.count)) .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) .foregroundColor(DesignSystem.textTertiary) .tracking(1.2) } - - // Left and right aligned buttons + HStack { - // Left-aligned Suggested button if !suggestedSites.isEmpty && searchText.isEmpty && !isSearchMode { - Button(action: { - showSuggested.toggle() - }) { + Button(action: { showSuggested.toggle() }) { HStack(spacing: 4) { - Image(systemName: showSuggested ? "lightbulb" : "lightbulb") + Image(systemName: "lightbulb") .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) Text(Strings.BlocklistEditor.suggested) .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) @@ -697,20 +776,11 @@ struct BlocklistEditorView: View { } } } - + Spacer() - - // Right-aligned Bulk Edit button + if !filteredURLs.isEmpty { - Button(action: { - withAnimation { - isBulkEditMode.toggle() - if !isBulkEditMode { - selectedURLs.removeAll() - lastSelectedIndex = nil - } - } - }) { + Button(action: onToggleBulkEdit) { HStack(spacing: 4) { Image(systemName: isBulkEditMode ? "checkmark.circle.fill" : "checkmark.circle") .font(DesignSystem.font(size: DesignSystem.fontSizeBase)) @@ -728,7 +798,6 @@ struct BlocklistEditorView: View { } .buttonStyle(.plain) .focusable(false) - .disabled(isBlockingMode) .onHover { hovering in withAnimation(DesignSystem.animationFast) { isHoveringBulkEdit = hovering @@ -740,412 +809,231 @@ struct BlocklistEditorView: View { } .frame(maxWidth: .infinity) .padding(.top, DesignSystem.spacingMedium) - - // Show all suggested sites below the header line + if showSuggested && !suggestedSites.isEmpty && searchText.isEmpty && !isSearchMode { - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 6) { - ForEach(Array(suggestedSites), id: \.self) { site in - let isAlreadyBlocked = blockedURLs.contains { $0.domain == site } - - Button(action: { - guard !isAlreadyBlocked else { return } - - if isBlockingMode { - // Show confirmation when blocking is active - pendingSiteToAdd = site - // Small delay to ensure state is set before showing alert - DispatchQueue.main.async { - showingAddSiteConfirmation = true - } - } else { - // Add directly when not blocking - no animation to prevent blink - // showSuggested is @AppStorage so it persists automatically - var transaction = Transaction() - transaction.disablesAnimations = true - withTransaction(transaction) { - blockedURLs.append(BlockedURL(domain: site)) - } - } - }) { - HStack(spacing: 4) { - Text(site) - .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) - if isAlreadyBlocked { - Image(systemName: "checkmark.circle.fill") - .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall)) - } - } - .foregroundColor(DesignSystem.textPrimary) - .padding(.horizontal, DesignSystem.spacingSmallMedium) - .padding(.vertical, DesignSystem.spacingXXSmall) - .background( - Capsule() - .fill(isAlreadyBlocked ? DesignSystem.hoverBackground : DesignSystem.backgroundTertiary) - .overlay( - Capsule() - .stroke(isAlreadyBlocked ? DesignSystem.textPrimary.opacity(DesignSystem.disabledOpacity) : Color.clear, lineWidth: 1) - ) - ) - } - .buttonStyle(.plain) - .disabled(isAlreadyBlocked) - } - } - .padding(.horizontal, DesignSystem.spacingLarge) - } + SuggestedChipsScroll( + suggestedSites: suggestedSites, + blockedURLs: blockedURLs, + onTap: onRequestAddSuggested + ) .padding(.top, DesignSystem.spacingSmall) .padding(.bottom, DesignSystem.spacingSmall) } } - + LazyVStack(spacing: 6) { if filteredURLs.isEmpty { - VStack(spacing: DesignSystem.spacingSmall) { - Image(systemName: searchText.isEmpty ? "shield.slash" : "magnifyingglass") - .font(DesignSystem.font(size: DesignSystem.fontSizeDisplayXXL)) - .foregroundColor(DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium)) - Text(searchText.isEmpty ? Strings.BlocklistEditor.noBlockedSites : Strings.BlocklistEditor.noResultsFound) - .font(DesignSystem.font(size: DesignSystem.fontSizeXLarge)) - .foregroundColor(DesignSystem.textTertiary) - } + EmptyListView(isSearching: !searchText.isEmpty) .frame(maxWidth: .infinity) .padding(.top, DesignSystem.spacingXLargePlusMedium) - } else { - let enumeratedURLs = Array(filteredURLs.enumerated()) - ForEach(enumeratedURLs, id: \.element.id) { index, url in - HStack(alignment: .center, spacing: DesignSystem.spacingSmall) { - // Checkbox in bulk edit mode, toggle otherwise - Group { - if isBulkEditMode { - let isSelected = selectedURLs.contains(url.id) - - Button(action: { - // Check shift key from modifier flags - let isShiftPressed = NSEvent.modifierFlags.contains(.shift) - handleCheckboxClick(for: url.id, at: index, isShiftPressed: isShiftPressed) - }) { - Image(systemName: isSelected ? "checkmark.square.fill" : "square") - .font(DesignSystem.font(size: DesignSystem.fontSizeTitle)) - .foregroundColor(isSelected ? DesignSystem.textPrimary : DesignSystem.textQuaternary) - .frame(width: 20, height: 20, alignment: .center) - } - .buttonStyle(.plain) - } else { - // Toggle switch (can only enable during blocking, not disable) - let isDisabledDuringBlock = isBlockingMode && url.isEnabled - - Toggle("", isOn: Binding( - get: { - url.isEnabled - }, - set: { newValue in - // Only allow enabling during active block, not disabling - if isBlockingMode && !newValue { - // Prevent disabling - don't do anything - return - } - - // Allow enabling during block or any toggle when not blocking - if let index = blockedURLs.firstIndex(where: { $0.id == url.id }) { - var transaction = Transaction() - transaction.disablesAnimations = true - withTransaction(transaction) { - blockedURLs[index].isEnabled = newValue - } - } - } - )) - .toggleStyle(SwitchToggleStyle(tint: Color(red: 0.85, green: 0.95, blue: 1.0))) - .labelsHidden() - .disabled(isDisabledDuringBlock) - .opacity(isDisabledDuringBlock ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) - .help(isDisabledDuringBlock ? Strings.Help.cannotDisableDuringBlock : "") - .buttonStyle(.plain) - } + } else { + let enumeratedURLs = Array(filteredURLs.enumerated()) + ForEach(enumeratedURLs, id: \.element.id) { index, url in + DomainRow( + url: url, + index: index, + isBulkEditMode: isBulkEditMode, + isBlockingMode: isBlockingMode, + isSelected: selectedURLs.contains(url.id), + onToggleSelect: { id, idx in + let isShift = NSEvent.modifierFlags.contains(.shift) + onSelectRow(id, idx, isShift) + }, + onTapRow: { + if isBulkEditMode { + let isShift = NSEvent.modifierFlags.contains(.shift) + onSelectRow(url.id, index, isShift) + } else { + onNavigateToDetail(url.id) } - .frame(width: 44, height: 24, alignment: .center) - - Button(action: { - if isBulkEditMode { - // Check shift key from modifier flags - let isShiftPressed = NSEvent.modifierFlags.contains(.shift) - handleCheckboxClick(for: url.id, at: index, isShiftPressed: isShiftPressed) - } else { - currentScreen = .domainDetail(url.id) - } - }) { - HStack(spacing: DesignSystem.spacingSmall) { - VStack(alignment: .leading, spacing: 2) { - HStack(spacing: 4) { - Text(url.domain) - .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) - .foregroundColor(url.isEnabled ? DesignSystem.textPrimary : DesignSystem.textTertiary) - - // Show lock icon when disabled during active block - if isBlockingMode && url.isEnabled { - Image(systemName: "lock.fill") - .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall)) - .foregroundColor(DesignSystem.accentOrangeMuted) - - Text(Strings.BlocklistEditor.locked) - .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightRegular)) - .foregroundColor(DesignSystem.accentOrangeMuted) - } - } - - if !url.paths.isEmpty { - Text(Strings.BlocklistEditor.pathCount(url.paths.count)) - .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall)) - .foregroundColor(DesignSystem.textTertiary) - } - } - - Spacer() - - Image(systemName: "chevron.right") - .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) - .foregroundColor(DesignSystem.textQuaternary) - } - .contentShape(Rectangle()) + }, + onToggleEnabled: { newValue in + guard let idx = blockedURLs.firstIndex(where: { $0.id == url.id }) else { return } + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + blockedURLs[idx].isEnabled = newValue } - .buttonStyle(.plain) } - .frame(minHeight: 20) - .padding(.horizontal, DesignSystem.spacingSmall) - .padding(.vertical, DesignSystem.spacingXSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .fill(DesignSystem.backgroundPrimary.opacity(url.isEnabled ? 0.6 : 0.3)) - .overlay( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .stroke(url.isEnabled ? DesignSystem.borderSecondary : DesignSystem.textPrimary.opacity(DesignSystem.opacitySubtle), lineWidth: 1) - ) - ) - .opacity(url.isEnabled ? DesignSystem.opacityFull : DesignSystem.opacityAlmost) - } + ) } } - .padding(DesignSystem.spacingXLarge) + } } } } - - var domainDetailView_UNUSED: some View { - VStack(spacing: 0) { - if let domainId = selectedDomain, - let domain = blockedURLs.first(where: { $0.id == domainId }) { - // Header with back button - HStack { - Button(action: { - withAnimation(DesignSystem.animationFast) { - selectedDomain = nil - } - }) { - HStack(spacing: 6) { - Image(systemName: "chevron.left") - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) - Text(Strings.Common.back) - .font(DesignSystem.font(size: DesignSystem.fontSizeXLarge, weight: DesignSystem.fontWeightMedium)) +} + +private struct SuggestedChipsScroll: View { + let suggestedSites: [String] + let blockedURLs: [BlockedURL] + let onTap: (String) -> Void + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 6) { + ForEach(Array(suggestedSites), id: \.self) { site in + let isAlreadyBlocked = blockedURLs.contains { $0.domain == site } + + Button(action: { onTap(site) }) { + HStack(spacing: 4) { + Text(site) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + if isAlreadyBlocked { + Image(systemName: "checkmark.circle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall)) + } } .foregroundColor(DesignSystem.textPrimary) + .padding(.horizontal, DesignSystem.spacingSmallMedium) + .padding(.vertical, DesignSystem.spacingXXSmall) + .background( + Capsule() + .fill(isAlreadyBlocked ? DesignSystem.hoverBackground : DesignSystem.backgroundTertiary) + .overlay( + Capsule() + .stroke(isAlreadyBlocked ? DesignSystem.textPrimary.opacity(DesignSystem.disabledOpacity) : Color.clear, lineWidth: 1) + ) + ) } .buttonStyle(.plain) - - Spacer() + .disabled(isAlreadyBlocked) } - .padding(.horizontal, DesignSystem.spacingLarge) - .padding(.vertical, DesignSystem.spacingSmall) - - Divider() - .background(DesignSystem.borderPrimary) - - ScrollView { - VStack(spacing: DesignSystem.spacingMedium) { - // Domain info - VStack(alignment: .leading, spacing: DesignSystem.spacingSmall) { - HStack { - Image(systemName: "globe") - .font(DesignSystem.font(size: DesignSystem.fontSizeDisplay)) - .foregroundColor(DesignSystem.textPrimary) - - Text(domain.domain) - .font(DesignSystem.font(size: DesignSystem.fontSizeDisplay, weight: DesignSystem.fontWeightBold)) - .foregroundColor(DesignSystem.textPrimary) - - Spacer() - - if !isBlockingMode { - Button(action: { - withAnimation { - blockedURLs.removeAll { $0.id == domainId } - selectedDomain = nil - } - }) { - HStack(spacing: 6) { - Image(systemName: "trash") - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) - Text(Strings.Common.delete) - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) - } - .foregroundColor(DesignSystem.textSecondary) - .padding(.horizontal, DesignSystem.spacingMedium) - .padding(.vertical, DesignSystem.spacingSmall) - .modifier(DesignSystem.buttonStyle(isDestructive: true)) - } - .buttonStyle(.plain) - } + } + .padding(.horizontal, DesignSystem.spacingLarge) + } + } +} + +private struct EmptyListView: View { + let isSearching: Bool + + var body: some View { + VStack(spacing: DesignSystem.spacingSmall) { + Image(systemName: isSearching ? "magnifyingglass" : "shield.slash") + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplayXXL)) + .foregroundColor(DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium)) + Text(isSearching ? Strings.BlocklistEditor.noResultsFound : Strings.BlocklistEditor.noBlockedSites) + .font(DesignSystem.font(size: DesignSystem.fontSizeXLarge)) + .foregroundColor(DesignSystem.textTertiary) + } + } +} + +private struct DomainRow: View { + let url: BlockedURL + let index: Int + let isBulkEditMode: Bool + let isBlockingMode: Bool + let isSelected: Bool + + let onToggleSelect: (_ id: UUID, _ index: Int) -> Void + let onTapRow: () -> Void + let onToggleEnabled: (_ newValue: Bool) -> Void + + var body: some View { + HStack(alignment: .center, spacing: DesignSystem.spacingSmall) { + Group { + if isBulkEditMode { + Button(action: { onToggleSelect(url.id, index) }) { + Image(systemName: isSelected ? "checkmark.square.fill" : "square") + .font(DesignSystem.font(size: DesignSystem.fontSizeTitle)) + .foregroundColor(isSelected ? DesignSystem.textPrimary : DesignSystem.textQuaternary) + .frame(width: 20, height: 20, alignment: .center) + } + .buttonStyle(.plain) + } else { + let isDisabledDuringBlock = isBlockingMode && url.isEnabled + Toggle("", isOn: Binding( + get: { url.isEnabled }, + set: { newValue in + if isBlockingMode && !newValue { + return } - - Text(Strings.DomainDetail.managePathsDescription) - .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) - .foregroundColor(DesignSystem.textTertiary) + onToggleEnabled(newValue) } - .padding(DesignSystem.spacingXLarge) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusLarge) - .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) - ) - - // Add path section - VStack(alignment: .leading, spacing: DesignSystem.spacingMedium) { - Text(Strings.DomainDetail.addPathOrSubdomain) - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) - .foregroundColor(DesignSystem.textPrimary) - - HStack(spacing: DesignSystem.spacingSmall) { - TextField(Strings.DomainDetail.addPathExamplePlaceholder, text: $newPath, onCommit: { addPath(to: domainId) }) - .textFieldStyle(.plain) - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) - .foregroundColor(DesignSystem.textPrimary) - .padding(DesignSystem.spacingSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .fill(DesignSystem.backgroundPrimary) - .overlay( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .stroke(focusedField == .addPath ? DesignSystem.textPrimary : DesignSystem.borderPrimary, lineWidth: focusedField == .addPath ? 2 : 1) - ) - ) - - - Button(action: { - addPath(to: domainId) - }) { - Text(Strings.Common.add) - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) - .foregroundColor(DesignSystem.textPrimary) - .padding(.horizontal, DesignSystem.spacingLarge) - .padding(.vertical, DesignSystem.spacingSmall) - .background(Color.green) - .cornerRadius(DesignSystem.radiusSmall) - } - .buttonStyle(.plain) - .disabled(newPath.isEmpty) - .opacity(newPath.isEmpty ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + )) + .toggleStyle(SwitchToggleStyle(tint: Color(red: 0.85, green: 0.95, blue: 1.0))) + .labelsHidden() + .disabled(isDisabledDuringBlock) + .opacity(isDisabledDuringBlock ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) + .help(isDisabledDuringBlock ? Strings.Help.cannotDisableDuringBlock : "") + .buttonStyle(.plain) + } + } + .frame(width: 44, height: 24, alignment: .center) + + Button(action: onTapRow) { + HStack(spacing: DesignSystem.spacingSmall) { + VStack(alignment: .leading, spacing: 2) { + HStack(spacing: 4) { + Text(url.domain) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(url.isEnabled ? DesignSystem.textPrimary : DesignSystem.textTertiary) + + if isBlockingMode && url.isEnabled { + Image(systemName: "lock.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall)) + .foregroundColor(DesignSystem.accentOrangeMuted) + + Text(Strings.BlocklistEditor.locked) + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall, weight: DesignSystem.fontWeightRegular)) + .foregroundColor(DesignSystem.accentOrangeMuted) } } - .padding(DesignSystem.spacingXLarge) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusLarge) - .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigh)) - .overlay( - RoundedRectangle(cornerRadius: DesignSystem.radiusLarge) - .stroke(DesignSystem.hoverBorder.opacity(DesignSystem.opacityHigher), lineWidth: 1) - ) - ) - - // Paths list - if !domain.paths.isEmpty { - VStack(alignment: .leading, spacing: DesignSystem.spacingMedium) { - Text(Strings.DomainDetail.pathsCount(domain.paths.count)) - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) - .foregroundColor(DesignSystem.textPrimary) - - VStack(spacing: DesignSystem.spacingXSmall) { - ForEach(domain.paths, id: \.self) { path in - HStack(spacing: DesignSystem.spacingSmall) { - Image(systemName: "doc.text") - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) - .foregroundColor(DesignSystem.accentPurpleMuted) - - Text(path) - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) - .foregroundColor(DesignSystem.textPrimary) - - Spacer() - - if !isBlockingMode { - Button(action: { - withAnimation { - if let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { - blockedURLs[index].paths.removeAll { $0 == path } - saveChanges() - } - } - }) { - Image(systemName: "trash") - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) - .foregroundColor(DesignSystem.destructiveColor.opacity(DesignSystem.opacityHigher)) - } - .buttonStyle(.plain) - } - } - .padding(DesignSystem.spacingMedium) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .fill(DesignSystem.backgroundSecondary.opacity(DesignSystem.opacityHigher)) - ) - } - } - } - .padding(.horizontal, DesignSystem.spacingLarge) - .padding(.vertical, DesignSystem.spacingSmall) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusLarge) - .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigh)) - ) + + if !url.paths.isEmpty { + Text(Strings.BlocklistEditor.pathCount(url.paths.count)) + .font(DesignSystem.font(size: DesignSystem.fontSizeXSmall)) + .foregroundColor(DesignSystem.textTertiary) } } - .padding(DesignSystem.spacingXLarge) + + Spacer() + + Image(systemName: "chevron.right") + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textQuaternary) } + .contentShape(Rectangle()) } + .buttonStyle(.plain) } + .frame(minHeight: 20) + .padding(.horizontal, DesignSystem.spacingSmall) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundPrimary.opacity(url.isEnabled ? 0.6 : 0.3)) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(url.isEnabled ? DesignSystem.borderSecondary : DesignSystem.textPrimary.opacity(DesignSystem.opacitySubtle), lineWidth: 1) + ) + ) + .opacity(url.isEnabled ? DesignSystem.opacityFull : DesignSystem.opacityAlmost) } - - var bottomActionBar: some View { +} + +private struct BottomActionBarView: View { + let filteredURLs: [BlockedURL] + @Binding var selectedURLs: Set + let isBulkEditMode: Bool + + let onToggleAllFiltered: () -> Void + let onDelete: () -> Void + + var body: some View { Group { if isBulkEditMode { VStack(spacing: 0) { - // Divider - Divider() - .background(DesignSystem.borderPrimary) - - // Floating action bar + Divider().background(DesignSystem.borderPrimary) + ZStack { HStack(spacing: DesignSystem.spacingMedium) { - // Select All button - Button(action: { - withAnimation(DesignSystem.animationFast) { - let filteredIDs = Set(filteredURLs.map { $0.id }) - let allFilteredSelected = !filteredURLs.isEmpty && filteredIDs.isSubset(of: selectedURLs) - - if allFilteredSelected { - // Deselect all filtered items (but keep any selections outside filtered view) - for id in filteredIDs { - selectedURLs.remove(id) - } - } else { - // Select all filtered items (and keep any existing selections) - for id in filteredIDs { - selectedURLs.insert(id) - } - } - } - }) { + Button(action: onToggleAllFiltered) { let filteredIDs = Set(filteredURLs.map { $0.id }) let allFilteredSelected = !filteredURLs.isEmpty && filteredIDs.isSubset(of: selectedURLs) - + HStack(spacing: 6) { Image(systemName: allFilteredSelected ? "checkmark.square.fill" : "square") .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) @@ -1160,13 +1048,10 @@ struct BlocklistEditorView: View { .buttonStyle(.plain) .disabled(filteredURLs.isEmpty) .opacity(filteredURLs.isEmpty ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) - + Spacer() - - // Delete button - Button(action: { - showingDeleteConfirmation = true - }) { + + Button(action: onDelete) { HStack(spacing: 6) { Image(systemName: "trash") .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) @@ -1182,8 +1067,7 @@ struct BlocklistEditorView: View { .disabled(selectedURLs.isEmpty) .opacity(selectedURLs.isEmpty ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) } - - // Selected count - centered + if !selectedURLs.isEmpty { Text(Strings.BlocklistEditor.selectedCount(selectedURLs.count)) .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) @@ -1194,11 +1078,8 @@ struct BlocklistEditorView: View { .padding(.vertical, DesignSystem.spacingMedium) .background( ZStack(alignment: .top) { - Rectangle() - .fill(DesignSystem.backgroundPrimary) - Rectangle() - .fill(DesignSystem.borderPrimary) - .frame(height: 1) + Rectangle().fill(DesignSystem.backgroundPrimary) + Rectangle().fill(DesignSystem.borderPrimary).frame(height: 1) } ) } @@ -1207,195 +1088,121 @@ struct BlocklistEditorView: View { } } } - - private func addWebsite() { - let input = newSite.trimmingCharacters(in: .whitespaces) - if input.isEmpty { - return - } - - let (domain, path) = BlockedURLsHelpers.parseDomainAndPath(from: input) - - if domain.isEmpty { - return - } - - // Find existing domain or create new one - var domainId: UUID - if let existingDomain = blockedURLs.first(where: { $0.domain == domain }) { - domainId = existingDomain.id - } else { - // Domain doesn't exist, check if we should add it - if isBlockingMode { - // Show confirmation when blocking is active - pendingSiteToAdd = input - showingAddSiteConfirmation = true - return - } else { - // Add new domain - let newDomain = BlockedURL(domain: domain) - domainId = newDomain.id - withAnimation { - blockedURLs.append(newDomain) - } - } - } - - // If there's a path, add it to the domain - if let pathToAdd = path { - if let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { - let formattedPath = pathToAdd.hasPrefix("/") || pathToAdd.contains(".") ? pathToAdd : "/\(pathToAdd)" - if !blockedURLs[index].paths.contains(formattedPath) { - withAnimation { - blockedURLs[index].paths.append(formattedPath) - saveChanges() - newSite = "" - // Navigate to domain detail view to show the path was added - currentScreen = .domainDetail(domainId) - } - return - } else { - // Path already exists, navigate to detail view anyway - withAnimation { - newSite = "" - currentScreen = .domainDetail(domainId) - } - return - } - } - } - - // If no path, navigate to detail view if domain was new - let domainExisted = blockedURLs.contains(where: { $0.id == domainId && $0.domain == domain }) - if !domainExisted { - // New domain was added, navigate to detail view - withAnimation { - newSite = "" - currentScreen = .domainDetail(domainId) - } - } else { - newSite = "" - } - } - - private func confirmAddWebsite() { - guard let input = pendingSiteToAdd else { return } - let (domain, path) = BlockedURLsHelpers.parseDomainAndPath(from: input) - - if domain.isEmpty { - pendingSiteToAdd = nil - return - } - - // Find existing domain or create new one - var domainId: UUID - if let existingDomain = blockedURLs.first(where: { $0.domain == domain }) { - domainId = existingDomain.id - } else { - // Add new domain - let newDomain = BlockedURL(domain: domain) - domainId = newDomain.id - withAnimation { - blockedURLs.append(newDomain) - } - } - - // If there's a path, add it to the domain - if let pathToAdd = path { - if let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { - let formattedPath = pathToAdd.hasPrefix("/") || pathToAdd.contains(".") ? pathToAdd : "/\(pathToAdd)" - if !blockedURLs[index].paths.contains(formattedPath) { - withAnimation { - blockedURLs[index].paths.append(formattedPath) - saveChanges() - newSite = "" - pendingSiteToAdd = nil - // Navigate to domain detail view to show the path was added - currentScreen = .domainDetail(domainId) +} + +private struct DeleteConfirmationModal: View { + let count: Int + let onCancel: () -> Void + let onConfirm: () -> Void + + var body: some View { + DesignSystem.overlayBackground + .ignoresSafeArea() + .overlay( + VStack(spacing: DesignSystem.spacingMedium) { + VStack(spacing: 8) { + Image(systemName: "exclamationmark.triangle.fill") + .font(DesignSystem.font(size: DesignSystem.fontSizeDisplay)) + .foregroundColor(DesignSystem.destructiveColor) + + Text(Strings.BlocklistEditor.deleteSiteTitle(count)) + .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightSemibold)) + .foregroundColor(DesignSystem.textPrimary) + + Text(Strings.BlocklistEditor.deleteSiteMessage(count)) + .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) + .foregroundColor(DesignSystem.textSecondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) } - return - } else { - // Path already exists, navigate to detail view anyway - withAnimation { - newSite = "" - pendingSiteToAdd = nil - currentScreen = .domainDetail(domainId) + + HStack(spacing: DesignSystem.spacingSmall) { + Button(action: onCancel) { + Text(Strings.Common.cancel) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 80) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.backgroundTertiary) + ) + } + .buttonStyle(.plain) + + Button(action: onConfirm) { + Text(Strings.Common.delete) + .font(DesignSystem.font(size: DesignSystem.fontSizeBase, weight: DesignSystem.fontWeightMedium)) + .foregroundColor(DesignSystem.textPrimary) + .frame(width: 80) + .padding(.vertical, DesignSystem.spacingXSmall) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) + .fill(DesignSystem.destructiveColor) + ) + } + .buttonStyle(.plain) } - return - } - } - } - - // If no path, navigate to detail view if domain was new - let domainExisted = blockedURLs.contains(where: { $0.id == domainId && $0.domain == domain }) - if !domainExisted { - // New domain was added, navigate to detail view - withAnimation { - newSite = "" - pendingSiteToAdd = nil - currentScreen = .domainDetail(domainId) - } - } else { - withAnimation { - newSite = "" - pendingSiteToAdd = nil - } - } - } - - - private func addPath(to urlId: UUID) { - let cleanPath = newPath.trimmingCharacters(in: .whitespaces) - if !cleanPath.isEmpty, let index = blockedURLs.firstIndex(where: { $0.id == urlId }) { - // Ensure path starts with / (unless it's a subdomain) - let formattedPath = cleanPath.hasPrefix("/") || cleanPath.contains(".") ? cleanPath : "/\(cleanPath)" - - if !blockedURLs[index].paths.contains(formattedPath) { - withAnimation { - blockedURLs[index].paths.append(formattedPath) - saveChanges() - newPath = "" } - } - } + .padding(DesignSystem.spacingMedium) + .frame(maxWidth: 320) + .background( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .fill(DesignSystem.backgroundPrimary) + .overlay( + RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) + .stroke(DesignSystem.borderPrimary, lineWidth: 1) + ) + ) + .shadow(color: DesignSystem.overlayBackground.opacity(DesignSystem.opacityHigh), radius: 15, x: 0, y: 5) + ) + .zIndex(999) } - - private func saveChanges() { - viewModel.blockerStorage?.set(blockedURLs) +} + +// MARK: - Suggested chips geometry helpers (retained for potential reuse) + +private struct VisibleHItem: Equatable { + let id: String + let minX: CGFloat +} + +private struct FirstVisibleHPreferenceKey: PreferenceKey { + static var defaultValue: [VisibleHItem] = [] + static func reduce(value: inout [VisibleHItem], nextValue: () -> [VisibleHItem]) { + value.append(contentsOf: nextValue()) } } -extension BlocklistEditorView { - - func importurls() { - - if let window = NSApplication.shared.keyWindow { - Task { @MainActor in - do { - let imported = try await ImportExportManager.importBlockedUrls(presentingWindow: window) - self.blockedURLs = imported - // Merge or replace your in-memory list here - } catch { - // Handle cancel or decoding errors - NSLog("Import failed: \(error.localizedDescription)") +// MARK: - onChangeCompat + +extension View { + @ViewBuilder + func onChangeCompat( + of value: Value, + initial: Bool = false, + _ action: @escaping (_ oldValue: Value?, _ newValue: Value) -> Void + ) -> some View { + if #available(macOS 14.0, *) { + if initial { + self.onChange(of: value, initial: true) { old, new in + action(old, new) + } + } else { + self.onChange(of: value) { old, new in + action(old, new) } } } else { - // Modal (no sheet) - Task { @MainActor in - do { - let imported = try await ImportExportManager.importBlockedUrls() - // Merge or replace your in-memory list here - self.blockedURLs = imported - } catch { - // Handle cancel or decoding errors - NSLog("Import failed: \(error.localizedDescription)") + self + .onChange(of: value, perform: { new in + action(nil, new) + }) + .onAppear { + if initial { + action(nil, value) + } } - } } } - - func exporturls() { - ImportExportManager.exportBlockedUrls(blockedURLs: self.blockedURLs) - } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift index 20cf79af..ced1f32e 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift @@ -377,7 +377,10 @@ struct DomainDetailView: View { let cleanPath = newPath.trimmingCharacters(in: .whitespaces) let formattedPath = cleanPath.hasPrefix("/") ? cleanPath : "/" + cleanPath - if !formattedPath.isEmpty, let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { + guard !formattedPath.isEmpty else { return } + guard formattedPath.count > 1 else { return } + + if let index = blockedURLs.firstIndex(where: { $0.id == domainId }) { if !blockedURLs[index].paths.contains(formattedPath) { withAnimation { blockedURLs[index].paths.append(formattedPath) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/IntensityDropdownOverlay.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/IntensityDropdownOverlay.swift index 0881c6a7..7a94d4b0 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/IntensityDropdownOverlay.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/IntensityDropdownOverlay.swift @@ -90,4 +90,3 @@ struct IntensityDropdownOverlay: View { } } } - diff --git a/Self-Control-Extension/SelfControl/SelfControl/Sparkle/dsa_pub.pem b/Self-Control-Extension/SelfControl/SelfControl/Sparkle/dsa_pub.pem new file mode 100644 index 00000000..950143dc --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Sparkle/dsa_pub.pem @@ -0,0 +1,36 @@ +-----BEGIN PUBLIC KEY----- +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 +CDx3nE+2CbIFoCzJUlEdTgwaHp/vmUKxMmG8 +-----END PUBLIC KEY----- diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift index 9de01755..4dbedf26 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift @@ -117,7 +117,7 @@ class IPCConnection: NSObject { return } providerProxy.register(completionHandler) -// providerProxy.setBlockedURLs(blockedUrls) + providerProxy.setBlockedURLs(blockedUrls) } /** From bd9c994e18e5e6cae9fef09a855f59c72322dc81 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sat, 2 May 2026 00:04:54 +0530 Subject: [PATCH 41/64] restructure code to make it more modular --- .../SelfControl.xcodeproj/project.pbxproj | 10 +- .../SelfControl/{ => Main}/AppDelegate.swift | 1 + .../ChromeExtensionRequestListner.swift | 0 .../SelfControl/Main/ContentView.swift | 126 ------------------ .../SelfControl/Main/DNSResolver.swift | 125 ----------------- .../EventScheduler/EventScheduler.swift | 0 .../EventScheduler/EventSchedulerRunner.swift | 0 .../EventScheduler/EventSchedulerStore.swift | 0 .../SelfControl/Main/FilterViewModel.swift | 28 ++-- .../Preferences/AppPreferences.swift} | 2 +- .../LocalNotificationManager.swift | 0 .../{ => Main}/Preferences/Sound.swift | 0 .../SCDaemonSupport/SCDaemonHelper.swift | 0 .../SelfControl-Bridging-Header.h | 0 .../{ => Main}/SCDaemonSupport/Test.h | 0 .../{ => Main}/SCDaemonSupport/Test.m | 0 .../SelfControl/Main/SCDurationSlider.swift | 88 ------------ .../Main/SafariExtensionWebView.swift | 4 +- .../{ => Main}/SelfControl.entitlements | 0 .../{ => Main}/SelfControlApp.swift | 18 +-- .../Components/CustomComponents.swift | 0 .../Components/TextPlaceholder.swift | 0 .../Helpers/BlockedURLsHelpers.swift | 0 .../Helpers/BlockingStateHelpers.swift | 0 .../Helpers/IntensityHelpers.swift | 0 .../Helpers/ScheduleBlockManager.swift | 0 .../SelfControlUI/Helpers/TimeHelpers.swift | 0 .../SelfControlUI/Models/AppModels.swift | 0 .../Models/BlockedURLStore.swift | 0 .../SelfControlUI/NewContentView.swift | 2 +- .../{ => Main/SelfControlUI}/NewViewUI.swift | 0 .../Onboarding/AppOnboardingView.swift | 0 .../Onboarding/ClickableLinkButton.swift | 0 .../OnBoardingInstallChromeExt.swift | 0 .../OnBoardingInstallNetworkExt.swift | 0 .../OnBoardingInstallSafariExt.swift | 0 .../Onboarding/OnboardingStepView.swift | 0 .../SelfControlUI/Utils/DesignSystem.swift | 0 .../SelfControlUI/Utils/DomainConstants.swift | 0 .../SelfControlUI/Utils/Strings.swift | 0 .../SelfControlUI/Views/AboutView.swift | 0 .../Views/AdvancedSettingsView.swift | 0 .../Views/BlockScheduleView.swift | 0 .../Views/BlocklistEditorView.swift | 0 .../Views/DomainDetailView.swift | 0 .../Views/ExtendTimerOverlayView.swift | 0 .../Views/IntensityDropdownOverlay.swift | 0 .../Views/NonDraggableBackgroundView.swift | 0 .../Views/StopConfirmationOverlayView.swift | 0 .../SelfControlUI/Views/TipsOverlayView.swift | 0 .../Preferences/PreferencesView.swift | 68 ---------- .../SelfControlUI/SelfControlUIApp.swift | 109 --------------- 52 files changed, 23 insertions(+), 558 deletions(-) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/AppDelegate.swift (95%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/ChromeExtensionSupport/ChromeExtensionRequestListner.swift (100%) delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/DNSResolver.swift rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/EventScheduler/EventScheduler.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/EventScheduler/EventSchedulerRunner.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/EventScheduler/EventSchedulerStore.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{Preferences/ProxyPreferences.swift => Main/Preferences/AppPreferences.swift} (99%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/Preferences/LocalNotificationManager.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/Preferences/Sound.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SCDaemonSupport/SCDaemonHelper.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SCDaemonSupport/SelfControl-Bridging-Header.h (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SCDaemonSupport/Test.h (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SCDaemonSupport/Test.m (100%) delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SCDurationSlider.swift rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControl.entitlements (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlApp.swift (91%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Components/CustomComponents.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Components/TextPlaceholder.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Helpers/BlockedURLsHelpers.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Helpers/BlockingStateHelpers.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Helpers/IntensityHelpers.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Helpers/ScheduleBlockManager.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Helpers/TimeHelpers.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Models/AppModels.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Models/BlockedURLStore.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/NewContentView.swift (99%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main/SelfControlUI}/NewViewUI.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/Main/{ => SelfControlUI}/Onboarding/AppOnboardingView.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/Main/{ => SelfControlUI}/Onboarding/ClickableLinkButton.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/Main/{ => SelfControlUI}/Onboarding/OnBoardingInstallChromeExt.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/Main/{ => SelfControlUI}/Onboarding/OnBoardingInstallNetworkExt.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/Main/{ => SelfControlUI}/Onboarding/OnBoardingInstallSafariExt.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/Main/{ => SelfControlUI}/Onboarding/OnboardingStepView.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Utils/DesignSystem.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Utils/DomainConstants.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Utils/Strings.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Views/AboutView.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Views/AdvancedSettingsView.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Views/BlockScheduleView.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Views/BlocklistEditorView.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Views/DomainDetailView.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Views/ExtendTimerOverlayView.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Views/IntensityDropdownOverlay.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Views/NonDraggableBackgroundView.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Views/StopConfirmationOverlayView.swift (100%) rename Self-Control-Extension/SelfControl/SelfControl/{ => Main}/SelfControlUI/Views/TipsOverlayView.swift (100%) delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/SelfControlUIApp.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index 8a06858d..7f10f215 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -103,7 +103,7 @@ 9A6873742E280E320054AD3F /* Exceptions for "SelfControl" folder in "SelfControlNetworkExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - Preferences/ProxyPreferences.swift, + Main/Preferences/AppPreferences.swift, ); target = 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */; }; @@ -593,7 +593,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = SelfControl/SelfControl.entitlements; + CODE_SIGN_ENTITLEMENTS = SelfControl/Main/SelfControl.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; @@ -618,7 +618,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OBJC_BRIDGING_HEADER = "SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h"; + SWIFT_OBJC_BRIDGING_HEADER = "SelfControl/Main/SCDaemonSupport/SelfControl-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Debug; @@ -628,7 +628,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = SelfControl/SelfControl.entitlements; + CODE_SIGN_ENTITLEMENTS = SelfControl/Main/SelfControl.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; @@ -653,7 +653,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OBJC_BRIDGING_HEADER = "SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h"; + SWIFT_OBJC_BRIDGING_HEADER = "SelfControl/Main/SCDaemonSupport/SelfControl-Bridging-Header.h"; SWIFT_VERSION = 5.0; }; name = Release; diff --git a/Self-Control-Extension/SelfControl/SelfControl/AppDelegate.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/AppDelegate.swift similarity index 95% rename from Self-Control-Extension/SelfControl/SelfControl/AppDelegate.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/AppDelegate.swift index 88d8fc11..fdc02ef7 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/AppDelegate.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/AppDelegate.swift @@ -14,6 +14,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { print("App did finish launching") + AppMover.moveIfNeeded() LocalNotificationManager.requestAuthorization() } diff --git a/Self-Control-Extension/SelfControl/SelfControl/ChromeExtensionSupport/ChromeExtensionRequestListner.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/ChromeExtensionSupport/ChromeExtensionRequestListner.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/ChromeExtensionSupport/ChromeExtensionRequestListner.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/ChromeExtensionSupport/ChromeExtensionRequestListner.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift deleted file mode 100644 index f6a1512a..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/ContentView.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// ContentView.swift -// SelfControl -// -// Created by Egzon Arifi on 02/04/2025. -// - -import SwiftUI -import NetworkExtension -import SystemExtensions -import os.log -import Cocoa - -struct ContentView: View { - @EnvironmentObject var viewModel: FilterViewModel - @Environment(\.openWindow) private var openWindow - @State private var newDomain = "" - - var body: some View { - VStack(spacing: 20) { - // Status indicator with image and text. - HStack { - statusView - Button { - viewModel.activateExtension() - } label: { - Text("Install and Start Network Extension") - } - // Start/Stop buttons. - HStack { - if viewModel.status == .stopped { - Button("Start") { - // viewModel.startFilter() - viewModel.installLegacyLaunched(futureDuration: Date.now.addingTimeInterval(viewModel.delay*60)) - } - } - if viewModel.status == .running { - Button("Stop") { - viewModel.stopFilter() - } - } - } - } - HStack { - Spacer() - Slider(value: $viewModel.delay, in: 1...30) { - Text("Time: \(viewModel.delay, specifier: "%.1f") Minutes") - } - Spacer() - } - - Button { - if viewModel.isActiveBlocking == true { - viewModel.deactivateNetworkBlocking() - viewModel.cancelTimer() - } else { - if viewModel.status == .stopped { - viewModel.installLegacyLaunched(futureDuration: Date.now.addingTimeInterval(viewModel.delay*60)) - } else { - if viewModel.startTimerWithSelectedDelay() == false { - return - } - viewModel.activateNetworkBlocking() - } - } - } label: { - Text(viewModel.isActiveBlocking ? "Deactivate Block" : "Activate Block") - } - - // Show a progress indicator when in the indeterminate state. - if viewModel.status == .indeterminate { - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - } -// Button { -// viewModel.setBlockedUrls(urls: ProxyPreferences.getBlockedDomains()) -// } label: { -// Text("Enable Url Blocking") -// } - HStack { - Button("Edit Blocklist") { - openWindow(id: "preferences") - } - Spacer() - } -// SafariExtensionWebView() -// .frame(minHeight: 100) - - HStack { - TextField("Enter Url to test block", text: $newDomain) - Button("Test Url Blocking") { - viewModel.checkUrlRequest(url: newDomain) - } - } - Spacer() - AppOnboardingView() - } - .padding() - .frame(minWidth: 150, minHeight: 150) - .onDisappear { -// let urls = ProxyPreferences.getBlockedDomains() - - } - .onAppear { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - AppMover.moveIfNeeded() - } - } - } -} - -private extension ContentView { - var statusView: some View { - HStack { - viewModel.status.color - .clipShape(Circle()) - .frame(width: 20, height: 20) - Text("Status: \(viewModel.status.text)") - } - } -} - -#Preview { - ContentView() -} - diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/DNSResolver.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/DNSResolver.swift deleted file mode 100644 index be301c43..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/DNSResolver.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// DNSResolver.swift -// SelfControl -// -// Created by Satendra Singh on 21/10/25. -// - - -import Foundation -import Network - -actor DNSResolverActor { - var resolvedIPs: Set = [] - func resolve(hostURL: [String]) async -> Set { - var intendedHosts: Set = [] - for url in hostURL { - print("Resolving: \(url)") - if let host = DNSResolver.getHost(from: url) { - intendedHosts.insert(host) - } - } - - resolvedIPs = [] - for host in intendedHosts { - let ips = await DNSResolver.resolve(hostname: host) - print("host: \(host), ips: \(ips)") - resolvedIPs.formUnion(ips) - } - return resolvedIPs - } -} - -/// A modern async DNS resolver using Network.framework -final class DNSResolver { - static func getHost(from input: String) -> String? { - var string = input - if !string.contains("://") { - string = "https://" + string - } - return URL(string: string)?.host - } - - static func resolve(hostURL: String, timeout: TimeInterval = 5.0) async -> [String] { - guard let host = getHost(from: hostURL) else { return [] } - return await resolve(hostname: host, timeout: timeout) - } - /// Resolves IPv4/IPv6 addresses for a given domain asynchronously. - /// - Parameters: - /// - hostname: The domain name (e.g. "facebook.com"). - /// - timeout: Optional timeout in seconds (default 5s). - /// - Returns: Array of resolved IP addresses as strings. - static func resolve(hostname: String, timeout: TimeInterval = 5.0) async -> [String] { - await withCheckedContinuation { continuation in - let params = NWParameters.tcp - params.allowLocalEndpointReuse = true - - // We use the resolver service to resolve DNS asynchronously - let endpoint = NWEndpoint.hostPort(host: .name(hostname, nil), port: 80) - - let resolver = NWConnection(to: endpoint, using: params) - - var didResume = false - - @Sendable func finish(_ ips: [String]) { - guard !didResume else { return } - didResume = true - continuation.resume(returning: ips) - resolver.cancel() - } - - resolver.stateUpdateHandler = { state in - switch state { - case .ready: - // Once ready, we can extract resolved IPs from the endpoint - if let remote = resolver.currentPath?.remoteEndpoint, - case let .hostPort(host, _) = remote { - switch host { - case .ipv4(let addr): - finish([ipv4ToString(addr)]) - case .ipv6(let addr): - finish([ipv6ToString(addr)]) - default: - finish([]) - } - } else { - finish([]) - } - - case .failed(_): - finish([]) - default: - break - } - } - - // Start resolution - resolver.start(queue: .global(qos: .userInitiated)) - - // Timeout handler - DispatchQueue.global().asyncAfter(deadline: .now() + timeout) { [weak resolver] in - // If still not finished, cancel and return empty - resolver?.cancel() - finish([]) - } - } - } - - /// Converts IPv4 address to string. - nonisolated private static func ipv4ToString(_ addr: IPv4Address) -> String { - let bytes = [UInt8](addr.rawValue) - guard bytes.count == 4 else { return "" } - return bytes.map(String.init).joined(separator: ".") - } - - /// Converts IPv6 address to string. - nonisolated private static func ipv6ToString(_ addr: IPv6Address) -> String { - let bytes = [UInt8](addr.rawValue) - guard bytes.count == 16 else { return "" } - let segments = stride(from: 0, to: bytes.count, by: 2).map { - (UInt16(bytes[$0]) << 8) | UInt16(bytes[$0 + 1]) - } - // Basic hex formatting; does not perform zero-compression (::) - return segments.map { String(format: "%x", $0) }.joined(separator: ":") - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventScheduler.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventScheduler.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventScheduler.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventScheduler.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerRunner.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerRunner.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerRunner.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerRunner.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerStore.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerStore.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/EventScheduler/EventSchedulerStore.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerStore.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index 33711173..412b9667 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -52,11 +52,11 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } } - private var isSafariExtensionInstalled: Bool = ProxyPreferences.isSafariExtensionInstalled - private var isChromeExtensionInstalled: Bool = ProxyPreferences.isChromeExtensionInstalled + private var isSafariExtensionInstalled: Bool = AppPreferences.isSafariExtensionInstalled + private var isChromeExtensionInstalled: Bool = AppPreferences.isChromeExtensionInstalled var eventRunner: EventSchedulerRunner? = nil var eventRunnerHandler: EventSchedulerRunner.EventHandler? - @State private var domains = ProxyPreferences.getBlockedDomains() + @State private var domains = AppPreferences.getBlockedDomains() private let chromeService = ChromeExtensionRequestListner() @State var blockedURLs: [BlockedURL] = [] var blockerStorage: BlockedURLStore? @@ -108,7 +108,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques SafariExtensionManager.shared.onExtensionStateChange = { print("SafariExtensionManager.shared.onChange++") self.isSafariExtensionInstalled = true - ProxyPreferences.setSafariExtensionInstalled() + AppPreferences.setSafariExtensionInstalled() Task { @MainActor in self.updateSafariExtensionViewStatus() } @@ -116,7 +116,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques self.chromeService.onExtensionStateChange = { print("Chrome.shared.onChange++") self.isChromeExtensionInstalled = true - ProxyPreferences.setChromeExtensionInstalled() + AppPreferences.setChromeExtensionInstalled() Task { @MainActor in self.updateChromeExtensionViewStatus() } @@ -124,7 +124,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques SafariExtensionManager.shared.resetExtensionState() self.extensionIdentifier = extensionBundle.bundleIdentifier self.chromeService.blockeddomainFetcher = { - return ProxyPreferences.getBlockedDomains() + return AppPreferences.getBlockedDomains() } self.chromeService.startListening() @@ -239,11 +239,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func setBlockedUrls(urls: [String]) { IPCConnection.shared.enableURLBlocking(urls) -// Task { -// let ips: Set = await DNSResolverActor().resolve(hostURL: urls) -// print("Resolved app:\(ips)") -// setIPAddressesToBlock(addresses: Array(ips)) -// } + if status == .stopped { //If legacy blocking if isActiveBlocking { //if is active blocking updateLegacyBlockedList(newBlockedDomains: urls) @@ -331,7 +327,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques IPCConnection.shared.register(withExtension: extensionBundle, delegate: self) { success in DispatchQueue.main.async { self.status = success ? .running : .stopped - self.setBlockedUrls(urls: ProxyPreferences.getBlockedDomains()) + self.setBlockedUrls(urls: AppPreferences.getBlockedDomains()) self.refreshExtensionState() } // setBlockedURLs([]) @@ -488,10 +484,10 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(_enable: false) BlockListManager.deactivateSafariBlocking() chromeService.deactivateSafariBlocking() - if ProxyPreferences.playSoundOnCompletion { + if AppPreferences.playSoundOnCompletion { NSSound.playDefaultSound() } - if ProxyPreferences.showNotificationOnCompletion { + if AppPreferences.showNotificationOnCompletion { LocalNotificationManager.scheduleNotification() } stopShowingCountDownInDock() @@ -539,7 +535,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } func installLegacyLaunched(futureDuration: Date) { - selfControlDaemon.install(blockedDomains: ProxyPreferences.getBlockedDomains(), time: futureDuration) + selfControlDaemon.install(blockedDomains: AppPreferences.getBlockedDomains(), time: futureDuration) } func updateLegacyBlockedList(newBlockedDomains: [String]) { @@ -564,7 +560,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques blockerStorage?.set(blockedURLs) let urls = newBlockedDomains.compactMap(\.urls) let flattened: [String] = urls.flatMap { $0 } - ProxyPreferences.setBlockedDomains(flattened) + AppPreferences.setBlockedDomains(flattened) setBlockedUrls(urls: flattened) } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/AppPreferences.swift similarity index 99% rename from Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/AppPreferences.swift index 04fc5c3d..99fee2e3 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Preferences/ProxyPreferences.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/AppPreferences.swift @@ -8,7 +8,7 @@ import Foundation -struct ProxyPreferences { +struct AppPreferences { // static let appGroup = "X6FQ433AWK.com.application.SelfControl.Extension" private static let blockedDomainsKey = "BlockedDomains" private static let isSafariExtensionKey: String = "isSafariExtensionKey" diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/LocalNotificationManager.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/LocalNotificationManager.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/Preferences/LocalNotificationManager.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/LocalNotificationManager.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/Sound.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/Sound.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/Preferences/Sound.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/Sound.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SCDaemonHelper.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/SCDaemonHelper.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SCDaemonHelper.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/SCDaemonHelper.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h b/Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/SelfControl-Bridging-Header.h similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/SelfControl-Bridging-Header.h rename to Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/SelfControl-Bridging-Header.h diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.h b/Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/Test.h similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.h rename to Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/Test.h diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.m b/Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/Test.m similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SCDaemonSupport/Test.m rename to Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/Test.m diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SCDurationSlider.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SCDurationSlider.swift deleted file mode 100644 index 4b2487e0..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SCDurationSlider.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// SCDurationSlider.swift -// SelfControl -// -// Created by Satendra Singh on 31/08/25. -// - -import Foundation - - -//import Foundation -//import AppKit - -//class SCTimeIntervalFormatter { -// -// func string(for obj: Any?) -> String { -// guard let number = obj as? NSNumber else { -// return "" -// } -// return formatSeconds(number.doubleValue) -// } -// -// func formatSeconds(_ seconds: TimeInterval) -> String { -// let useModernBehavior = NSAppKitVersion.current >= NSAppKitVersion.macOS10_8 -// if useModernBehavior { -// return formatSecondsUsingModernBehavior(seconds) -// } else { -// return formatSecondsUsingLegacyBehavior(seconds) -// } -// } -// -// private func formatSecondsUsingModernBehavior(_ seconds: TimeInterval) -> String { -// struct FormatterHolder { -// static let formatter: TTTTimeIntervalFormatter = { -// let formatter = NSDateFormatter() -// formatter.pastDeicticExpression = "" -// formatter.presentDeicticExpression = "" -// formatter.futureDeicticExpression = "" -// formatter.significantUnits = [.year, .month, .day, .hour, .minute] -// formatter.numberOfSignificantUnits = 0 -// formatter.leastSignificantUnit = .minute -// return formatter -// }() -// } -// -// var formatted = FormatterHolder.formatter.string(forTimeInterval: seconds) ?? "" -// if formatted.isEmpty { -// formatted = stringIndicatingZeroMinutes() -// } -// -// return formatted -// } -// -// private func formatSecondsUsingLegacyBehavior(_ seconds: TimeInterval) -> String { -// let numMinutes = Int(seconds / 60) -// let formatDays = numMinutes / 1440 -// let formatHours = (numMinutes % 1440) / 60 -// let formatMinutes = numMinutes % 60 -// -// var timeString = "" -// -// if numMinutes > 0 { -// if formatDays > 0 { -// timeString = "\(formatDays) " + (formatDays == 1 ? NSLocalizedString("day", comment: "Single day time string") : NSLocalizedString("days", comment: "Plural days time string")) -// } -// if formatHours > 0 { -// let hoursPart = "\(formatHours) " + (formatHours == 1 ? NSLocalizedString("hour", comment: "Single hour time string") : NSLocalizedString("hours", comment: "Plural hours time string")) -// timeString += (formatDays > 0 ? ", " : "") + hoursPart -// } -// if formatMinutes > 0 { -// let minutesPart = "\(formatMinutes) " + (formatMinutes == 1 ? NSLocalizedString("minute", comment: "Single minute time string") : NSLocalizedString("minutes", comment: "Plural minutes time string")) -// timeString += ((formatHours > 0 || formatDays > 0) ? ", " : "") + minutesPart -// } -// } else { -// timeString = stringIndicatingZeroMinutes() -// } -// -// return timeString -// } -// -// private func stringIndicatingZeroMinutes() -> String { -// return String( -// format: "0 %@ (%@)", -// NSLocalizedString("minutes", comment: "Plural minutes time string"), -// NSLocalizedString("disabled", comment: "Shows that SelfControl is disabled") -// ) -// } -//} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift index ef00748c..464baa41 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift @@ -87,7 +87,7 @@ final class WebPageViewModel: NSObject, WKNavigationDelegate, WKScriptMessageHan private func updateSafariExtensionState(state: Bool) { - ProxyPreferences.setSafariExtensionState(state) + AppPreferences.setSafariExtensionState(state) IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: state) if state == true { updateBlocker() @@ -95,7 +95,7 @@ final class WebPageViewModel: NSObject, WKNavigationDelegate, WKScriptMessageHan } func updateBlocker() { - let urls = ProxyPreferences.getBlockedDomains() + let urls = AppPreferences.getBlockedDomains() print("URLS: \(urls)") BlockListManager.updateSafariBlockList(blockedPaths: urls, appGroup: extensionIdentifier, extensionIdentifier: extensionIdentifier) } diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControl.entitlements b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControl.entitlements similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControl.entitlements rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControl.entitlements diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlApp.swift similarity index 91% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlApp.swift index fb954f0a..150d9387 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlApp.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlApp.swift @@ -12,7 +12,6 @@ struct SelfControlApp: App { @StateObject var viewModel = FilterViewModel() @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - var body: some Scene { WindowGroup { switch viewModel.viewState { @@ -96,23 +95,8 @@ struct SelfControlApp: App { } } } - Window("Preferences View", id: "preferences") { - PreferencesView() // Your view to be presented in the new window - .environmentObject(viewModel) // Inject the object into the environment - } - .windowStyle(.automatic) } - - private var oldView: some View { - ContentView() - .environmentObject(viewModel) // Inject the object into the environment - .onAppear(perform: { - appDelegate.onAppClose = { - viewModel.stopFilter() - } - }) - } - + func resolveIPToHostname(ipAddress: String) -> String? { let hostRef = CFHostCreateWithName(nil, ipAddress as CFString).takeRetainedValue() diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/CustomComponents.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Components/CustomComponents.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/CustomComponents.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Components/CustomComponents.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/TextPlaceholder.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Components/TextPlaceholder.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Components/TextPlaceholder.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Components/TextPlaceholder.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/BlockedURLsHelpers.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Helpers/BlockedURLsHelpers.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/BlockedURLsHelpers.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Helpers/BlockedURLsHelpers.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/BlockingStateHelpers.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Helpers/BlockingStateHelpers.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/BlockingStateHelpers.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Helpers/BlockingStateHelpers.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/IntensityHelpers.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Helpers/IntensityHelpers.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/IntensityHelpers.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Helpers/IntensityHelpers.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/ScheduleBlockManager.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Helpers/ScheduleBlockManager.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/ScheduleBlockManager.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Helpers/ScheduleBlockManager.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/TimeHelpers.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Helpers/TimeHelpers.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Helpers/TimeHelpers.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Helpers/TimeHelpers.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Models/AppModels.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/AppModels.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Models/AppModels.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/BlockedURLStore.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Models/BlockedURLStore.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Models/BlockedURLStore.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Models/BlockedURLStore.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewContentView.swift similarity index 99% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewContentView.swift index e5bce932..f9591761 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/NewContentView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewContentView.swift @@ -1353,7 +1353,7 @@ struct NewContentView: View { } private func startBlocking(minutes: Double) { - if ProxyPreferences.showVerifyNetworkAlertBeforeBlock { + if AppPreferences.showVerifyNetworkAlertBeforeBlock { if SCUIUtility.checkNetworkAndShowNetworkAlert() == false { return } diff --git a/Self-Control-Extension/SelfControl/SelfControl/NewViewUI.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewViewUI.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/NewViewUI.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewViewUI.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/AppOnboardingView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/AppOnboardingView.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/AppOnboardingView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/AppOnboardingView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/ClickableLinkButton.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/ClickableLinkButton.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/ClickableLinkButton.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/ClickableLinkButton.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallChromeExt.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallChromeExt.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallChromeExt.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallChromeExt.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallNetworkExt.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallNetworkExt.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallNetworkExt.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallNetworkExt.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallSafariExt.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallSafariExt.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnBoardingInstallSafariExt.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallSafariExt.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnboardingStepView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnboardingStepView.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/Main/Onboarding/OnboardingStepView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnboardingStepView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DesignSystem.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Utils/DesignSystem.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DesignSystem.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Utils/DesignSystem.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DomainConstants.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Utils/DomainConstants.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/DomainConstants.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Utils/DomainConstants.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Utils/Strings.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Utils/Strings.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Utils/Strings.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AboutView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/AboutView.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AboutView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/AboutView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/AdvancedSettingsView.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/AdvancedSettingsView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/AdvancedSettingsView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlockScheduleView.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlockScheduleView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlockScheduleView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/BlocklistEditorView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/DomainDetailView.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/DomainDetailView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/DomainDetailView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/ExtendTimerOverlayView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/ExtendTimerOverlayView.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/ExtendTimerOverlayView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/ExtendTimerOverlayView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/IntensityDropdownOverlay.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/IntensityDropdownOverlay.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/IntensityDropdownOverlay.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/IntensityDropdownOverlay.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/NonDraggableBackgroundView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/NonDraggableBackgroundView.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/NonDraggableBackgroundView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/NonDraggableBackgroundView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/StopConfirmationOverlayView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/StopConfirmationOverlayView.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/StopConfirmationOverlayView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/StopConfirmationOverlayView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/TipsOverlayView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/TipsOverlayView.swift similarity index 100% rename from Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/Views/TipsOverlayView.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/TipsOverlayView.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift b/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift deleted file mode 100644 index a57e5edf..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Preferences/PreferencesView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// PreferencesView.swift -// SelfControl -// -// Created by Satendra Singh on 12/07/25. -// - -import SwiftUI - -struct PreferencesView: View { - @State private var domains = ProxyPreferences.getBlockedDomains() - @State private var newDomain = "" - @EnvironmentObject var viewModel: FilterViewModel - - var body: some View { - VStack { - List { - ForEach(domains, id: \.self) { domain in - HStack { - Text(domain) - Spacer() - Button(action: { - deleteItem(domain: domain) - }) { - Image(systemName: "trash") - .foregroundColor(.red) - } - .buttonStyle(BorderlessButtonStyle()) // ensures button works inside List - } - } - .onDelete(perform: delete) // still supports swipe-to-delete - } - - HStack { - TextField("Add Domain", text: $newDomain) - Button("Add") { - addDomain() - } - } - - Button("Save Preferences") { - ProxyPreferences.setBlockedDomains(domains) - viewModel.setBlockedUrls(urls: domains) - } - } - .padding() - .frame(width: 400, height: 300) - } - - func addDomain() { - guard !newDomain.isEmpty else { return } -// guard let domainValue = newDomain.domainString else { return } - domains.append(newDomain) - newDomain = "" - ProxyPreferences.setBlockedDomains(domains) - } - - func delete(at offsets: IndexSet) { - domains.remove(atOffsets: offsets) - ProxyPreferences.setBlockedDomains(domains) - } - - func deleteItem(domain: String) { - if let index = domains.firstIndex(of: domain) { - domains.remove(at: index) - } - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/SelfControlUIApp.swift b/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/SelfControlUIApp.swift deleted file mode 100644 index b92f9f2b..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/SelfControlUI/SelfControlUIApp.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// SelfControlUIApp.swift -// SelfControlUI -// -// Created by Vidya Giri on 10/30/25. -// -// -//import SwiftUI -//import AppKit -// -//@main -//struct SelfControlUIApp: App { -// var body: some Scene { -// let windowGroup = WindowGroup { -// ContentView() -// .background(WindowTitleBarHider()) -// } -// .commands { -// CommandGroup(replacing: .newItem) { } -// CommandGroup(replacing: .appInfo) { -// Button(Strings.AppMenu.aboutSelfControl) { -// NotificationCenter.default.post(name: NSNotification.Name("ShowAboutScreen"), object: nil) -// } -// -// Divider() -// -// Button(Strings.AppMenu.editSiteList) { -// NotificationCenter.default.post(name: NSNotification.Name("ShowEditListScreen"), object: nil) -// } -// .keyboardShortcut("e", modifiers: .command) -// -// Button(Strings.AppMenu.editBlockSchedule) { -// NotificationCenter.default.post(name: NSNotification.Name("ShowBlockScheduleScreen"), object: nil) -// } -// .keyboardShortcut("b", modifiers: .command) -// -// Button(Strings.AppMenu.moreSettings) { -// NotificationCenter.default.post(name: NSNotification.Name("ShowAdvancedSettingsScreen"), object: nil) -// } -// .keyboardShortcut(",", modifiers: .command) -// -// -// -// Divider() -// -// Button(Strings.AppMenu.donate) { -// if let url = URL(string: Strings.AppMenu.URLs.donate) { -// NSWorkspace.shared.open(url) -// } -// } -// } -// CommandGroup(replacing: .help) { -// Button(Strings.AppMenu.gettingStartedTips) { -// NotificationCenter.default.post(name: NSNotification.Name("ShowTipsScreen"), object: nil) -// } -// .keyboardShortcut("t", modifiers: .command) -// -// Divider() -// -// Button(Strings.AppMenu.selfControlHelp) { -// if let url = URL(string: Strings.AppMenu.URLs.help) { -// NSWorkspace.shared.open(url) -// } -// } -// .keyboardShortcut("?", modifiers: .command) -// -// Button(Strings.AppMenu.faq) { -// if let url = URL(string: Strings.AppMenu.URLs.faq) { -// NSWorkspace.shared.open(url) -// } -// } -// } -// } -// -// return windowGroup -// } -//} -// -//// MARK: - Window Configuration Helper (macOS 11 compatible) -//private struct WindowTitleBarHider: NSViewRepresentable { -// func makeNSView(context: Context) -> NSView { -// let view = NSView() -// DispatchQueue.main.async { -// if let window = view.window { -// configure(window: window) -// } else if let window = NSApplication.shared.windows.first { -// configure(window: window) -// } -// } -// return view -// } -// -// func updateNSView(_ nsView: NSView, context: Context) { -// if let window = nsView.window { -// configure(window: window) -// } -// } -// -// private func configure(window: NSWindow) { -// window.titleVisibility = .hidden -// window.titlebarAppearsTransparent = true -// window.isMovableByWindowBackground = true -// window.styleMask.insert(.fullSizeContentView) -// // Set a slightly smaller default content size -// if window.contentView?.frame.size.width ?? 0 > 0 { -// window.setContentSize(NSSize(width: 600, height: 450)) -// } -// } -//} From 06dcbc7c77d2ab32c65eff1fee148df71a85897d Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 18 May 2026 22:33:58 +0530 Subject: [PATCH 42/64] updated file paths and implemented backed service to support functionalities --- .../SelfControl.xcodeproj/project.pbxproj | 192 ++++- .../xcschemes/SelfControl.xcscheme | 1 + .../SelfControlBGLaunchService.xcscheme | 78 ++ .../SelfControl/Main/AppDelegate.swift | 14 + .../EventScheduler/EventSchedulerStore.swift | 3 + .../SelfControl/Main/FilterViewModel.swift | 17 +- .../Main/SafariExtensionWebView.swift | 2 +- ...erConnection+SafariExtensionMessages.swift | 33 + .../HelperConnection.swift | 186 +++++ .../LaunchAgentManager.swift | 105 +++ .../SelfControlServiceSupport.swift | 39 + .../BlockListManager.swift | 10 +- .../SelfControlBGService/AppDelegate.swift | 52 ++ ...elperService+SafariExtensionMessages.swift | 47 ++ .../CoreService/HelperService.swift | 117 +++ .../EventScheduler/EventScheduler.swift | 342 +++++++++ .../EventScheduler/EventSchedulerRunner.swift | 195 +++++ .../EventScheduler/EventSchedulerStore.swift | 104 +++ .../IPCConnection.swift | 196 +++++ .../Shared/AppToExtensionExtension.swift | 35 + .../Shared/HelperServiceProtocol.swift | 25 + .../SelfControlBGService/main.swift | 14 + .../BlockOrAllowList.swift | 147 ++++ .../Constants.swift | 59 ++ .../FilterDataProvider.swift | 514 +++++++++++++ .../HostToIpMapping.swift | 82 ++ .../IPCConnection.swift | 212 ++++++ .../IPCConnectionProxy.swift | 36 + .../SelfControlNetworkExtension/Info.plist | 16 + .../Process/Binary.swift | 175 +++++ .../Process/Process.swift | 330 ++++++++ .../Process/SourceAppAuditTokenBuilder.swift | 61 ++ .../Process/Utilities.swift | 714 ++++++++++++++++++ .../SelfControlNetworkExtension/README.md | 8 + .../RequestSourceAppValidator.swift | 38 + .../ReverseDomainMapper.swift | 57 ++ .../SelfControlExtension.entitlements | 14 + .../String+URL.swift | 35 + .../TLDURLToDomain.swift | 49 ++ .../SelfControlNetworkExtension/main.swift | 18 + 40 files changed, 4339 insertions(+), 33 deletions(-) create mode 100644 Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlBGLaunchService.xcscheme create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchAgentManager.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/SelfControlServiceSupport.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/AppDelegate.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventScheduler.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerStore.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/Services/SafariExtensionService/IPCConnection.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/main.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/BlockOrAllowList.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Constants.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/HostToIpMapping.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnectionProxy.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Info.plist create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Binary.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Process.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/SourceAppAuditTokenBuilder.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Utilities.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/README.md create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/RequestSourceAppValidator.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/ReverseDomainMapper.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/SelfControlExtension.entitlements create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/String+URL.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/TLDURLToDomain.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/main.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index 7f10f215..5496cb33 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 9AA492AE2F56E94200452252 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 9AA492AD2F56E94200452252 /* Sparkle */; }; 9AC9E59E2F04357E0004D39C /* org.eyebeam.selfcontrold in Copy Daemon Launch Service */ = {isa = PBXBuildFile; fileRef = 9AC9E4EA2F041D570004D39C /* org.eyebeam.selfcontrold */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 9AD524CE2EC9F15500AAD5BF /* SelfControlWebExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9AD524B12EC9B0EA00AAD5BF /* SelfControlWebExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9AEB2C6E2FB249C00024E93A /* SelfControlBGService in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9A33B1E02FAF2127002AAA0F /* SelfControlBGService */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -36,9 +37,35 @@ remoteGlobalIDString = 9AD524B02EC9B0EA00AAD5BF; remoteInfo = "SelfControl Rules Extension"; }; + 9AEB2C6F2FB249CC0024E93A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 77BA52CE2D9D5D7700863476 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A33B1DF2FAF2127002AAA0F; + remoteInfo = SelfControlBGService; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 9A33B1DE2FAF2127002AAA0F /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; + 9A33B1E72FAF2153002AAA0F /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = Contents/Library/LaunchAgents; + dstSubfolderSpec = 1; + files = ( + 9AEB2C6E2FB249C00024E93A /* SelfControlBGService in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9AC9E4E82F041D570004D39C /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -87,19 +114,37 @@ 77BA52D62D9D5D7700863476 /* SelfControl.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SelfControl.app; sourceTree = BUILT_PRODUCTS_DIR; }; 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */ = {isa = PBXFileReference; explicitFileType = "wrapper.system-extension"; includeInIndex = 0; path = com.application.SelfControl.corebits.network.systemextension; sourceTree = BUILT_PRODUCTS_DIR; }; 77BA53082D9D5E2A00863476 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; + 9A33B1E02FAF2127002AAA0F /* SelfControlBGService */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SelfControlBGService; sourceTree = BUILT_PRODUCTS_DIR; }; 9A7FFA0B2E91950D007D4A0D /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 9AC9E4EA2F041D570004D39C /* org.eyebeam.selfcontrold */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = org.eyebeam.selfcontrold; sourceTree = BUILT_PRODUCTS_DIR; }; 9AD524B12EC9B0EA00AAD5BF /* SelfControlWebExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SelfControlWebExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 77BA53142D9D5E2A00863476 /* Exceptions for "SelfControlExtension" folder in "SelfControlNetworkExtension" target */ = { + 77BA53142D9D5E2A00863476 /* Exceptions for "SelfControlNetworkExtension" folder in "SelfControlNetworkExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Info.plist, + IPCConnectionProxy.swift, ); target = 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */; }; + 9A240F092FB042C900A06370 /* Exceptions for "SelfControlBGService" folder in "SelfControl" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + com.application.SelfControl.corebits.bgservice.plist, + Shared/AppToExtensionExtension.swift, + Shared/HelperServiceProtocol.swift, + ); + target = 77BA52D52D9D5D7700863476 /* SelfControl */; + }; + 9A33B1F52FAFA3FE002AAA0F /* Exceptions for "SelfControl" folder in "SelfControl" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Main/SelfControlServiceSupport/SelfControlServiceSupport.swift, + ); + target = 77BA52D52D9D5D7700863476 /* SelfControl */; + }; 9A6873742E280E320054AD3F /* Exceptions for "SelfControl" folder in "SelfControlNetworkExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( @@ -107,11 +152,11 @@ ); target = 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */; }; - 9A6873762E2814B30054AD3F /* Exceptions for "SelfControlExtension" folder in "SelfControl" target */ = { + 9A6873762E2814B30054AD3F /* Exceptions for "SelfControlNetworkExtension" folder in "SelfControl" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( BlockOrAllowList.swift, - IPCConnection.swift, + IPCConnectionProxy.swift, "String+URL.swift", ); target = 77BA52D52D9D5D7700863476 /* SelfControl */; @@ -137,6 +182,13 @@ ); target = 77BA52D52D9D5D7700863476 /* SelfControl */; }; + 9ACDADBC2FB9DFB400013EC8 /* Exceptions for "SelfControlBGService" folder in "SelfControlNetworkExtension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Shared/AppToExtensionExtension.swift, + ); + target = 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */; + }; 9AD524C32EC9B0EA00AAD5BF /* Exceptions for "SelfControl Rules Extension" folder in "SelfControlWebExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( @@ -150,18 +202,28 @@ 77BA52D82D9D5D7700863476 /* SelfControl */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( + 9A33B1F52FAFA3FE002AAA0F /* Exceptions for "SelfControl" folder in "SelfControl" target */, 9A6873742E280E320054AD3F /* Exceptions for "SelfControl" folder in "SelfControlNetworkExtension" target */, ); path = SelfControl; sourceTree = ""; }; - 77BA530A2D9D5E2A00863476 /* SelfControlExtension */ = { + 77BA530A2D9D5E2A00863476 /* SelfControlNetworkExtension */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 9A6873762E2814B30054AD3F /* Exceptions for "SelfControlNetworkExtension" folder in "SelfControl" target */, + 77BA53142D9D5E2A00863476 /* Exceptions for "SelfControlNetworkExtension" folder in "SelfControlNetworkExtension" target */, + ); + path = SelfControlNetworkExtension; + sourceTree = ""; + }; + 9A33B1E12FAF2127002AAA0F /* SelfControlBGService */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( - 9A6873762E2814B30054AD3F /* Exceptions for "SelfControlExtension" folder in "SelfControl" target */, - 77BA53142D9D5E2A00863476 /* Exceptions for "SelfControlExtension" folder in "SelfControlNetworkExtension" target */, + 9A240F092FB042C900A06370 /* Exceptions for "SelfControlBGService" folder in "SelfControl" target */, + 9ACDADBC2FB9DFB400013EC8 /* Exceptions for "SelfControlBGService" folder in "SelfControlNetworkExtension" target */, ); - path = SelfControlExtension; + path = SelfControlBGService; sourceTree = ""; }; 9AC9E4EB2F041D570004D39C /* org.eyebeam.selfcontrold */ = { @@ -200,6 +262,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9A33B1DD2FAF2127002AAA0F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9AC9E4E72F041D570004D39C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -221,9 +290,10 @@ isa = PBXGroup; children = ( 77BA52D82D9D5D7700863476 /* SelfControl */, - 77BA530A2D9D5E2A00863476 /* SelfControlExtension */, + 77BA530A2D9D5E2A00863476 /* SelfControlNetworkExtension */, 9AD524B52EC9B0EA00AAD5BF /* SelfControl Rules Extension */, 9AC9E4EB2F041D570004D39C /* org.eyebeam.selfcontrold */, + 9A33B1E12FAF2127002AAA0F /* SelfControlBGService */, 77BA53072D9D5E2A00863476 /* Frameworks */, 77BA52D72D9D5D7700863476 /* Products */, ); @@ -236,6 +306,7 @@ 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */, 9AD524B12EC9B0EA00AAD5BF /* SelfControlWebExtension.appex */, 9AC9E4EA2F041D570004D39C /* org.eyebeam.selfcontrold */, + 9A33B1E02FAF2127002AAA0F /* SelfControlBGService */, ); name = Products; sourceTree = ""; @@ -262,10 +333,12 @@ 9AFC129E2E355671003B5455 /* Embed System Extensions */, 9AD524D12EC9F15500AAD5BF /* Embed Foundation Extensions */, 9AC9E59D2F0435560004D39C /* Copy Daemon Launch Service */, + 9A33B1E72FAF2153002AAA0F /* CopyFiles */, ); buildRules = ( ); dependencies = ( + 9AEB2C702FB249CC0024E93A /* PBXTargetDependency */, 9AC9E59B2F0434F00004D39C /* PBXTargetDependency */, 9A441CE22E36024300A521CC /* PBXTargetDependency */, 9AD524D02EC9F15500AAD5BF /* PBXTargetDependency */, @@ -294,7 +367,7 @@ dependencies = ( ); fileSystemSynchronizedGroups = ( - 77BA530A2D9D5E2A00863476 /* SelfControlExtension */, + 77BA530A2D9D5E2A00863476 /* SelfControlNetworkExtension */, ); name = SelfControlNetworkExtension; packageProductDependencies = ( @@ -303,6 +376,28 @@ productReference = 77BA53062D9D5E2A00863476 /* com.application.SelfControl.corebits.network.systemextension */; productType = "com.apple.product-type.system-extension"; }; + 9A33B1DF2FAF2127002AAA0F /* SelfControlBGService */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A33B1E62FAF2127002AAA0F /* Build configuration list for PBXNativeTarget "SelfControlBGService" */; + buildPhases = ( + 9A33B1DC2FAF2127002AAA0F /* Sources */, + 9A33B1DD2FAF2127002AAA0F /* Frameworks */, + 9A33B1DE2FAF2127002AAA0F /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 9A33B1E12FAF2127002AAA0F /* SelfControlBGService */, + ); + name = SelfControlBGService; + packageProductDependencies = ( + ); + productName = SelfControlBGService; + productReference = 9A33B1E02FAF2127002AAA0F /* SelfControlBGService */; + productType = "com.apple.product-type.tool"; + }; 9AC9E4E92F041D570004D39C /* org.eyebeam.selfcontrold */ = { isa = PBXNativeTarget; buildConfigurationList = 9AC9E4EE2F041D570004D39C /* Build configuration list for PBXNativeTarget "org.eyebeam.selfcontrold" */; @@ -363,6 +458,9 @@ 77BA53052D9D5E2A00863476 = { CreatedOnToolsVersion = 16.2; }; + 9A33B1DF2FAF2127002AAA0F = { + CreatedOnToolsVersion = 26.0; + }; 9AC9E4E92F041D570004D39C = { CreatedOnToolsVersion = 26.0; }; @@ -392,6 +490,7 @@ 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */, 9AD524B02EC9B0EA00AAD5BF /* SelfControlWebExtension */, 9AC9E4E92F041D570004D39C /* org.eyebeam.selfcontrold */, + 9A33B1DF2FAF2127002AAA0F /* SelfControlBGService */, ); }; /* End PBXProject section */ @@ -435,6 +534,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9A33B1DC2FAF2127002AAA0F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9AC9E4E62F041D570004D39C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -467,6 +573,11 @@ target = 9AD524B02EC9B0EA00AAD5BF /* SelfControlWebExtension */; targetProxy = 9AD524CF2EC9F15500AAD5BF /* PBXContainerItemProxy */; }; + 9AEB2C702FB249CC0024E93A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A33B1DF2FAF2127002AAA0F /* SelfControlBGService */; + targetProxy = 9AEB2C6F2FB249CC0024E93A /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -661,16 +772,15 @@ 77BA53162D9D5E2A00863476 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_ENTITLEMENTS = SelfControlExtension/SelfControlExtension.entitlements; + CODE_SIGN_ENTITLEMENTS = SelfControlNetworkExtension/SelfControlExtension.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 26210; DEVELOPMENT_TEAM = X6FQ433AWK; - "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = SelfControlExtension/Info.plist; + INFOPLIST_FILE = SelfControlNetworkExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = SelfControlNetworkExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "DNS lookup"; @@ -685,7 +795,7 @@ OTHER_LDFLAGS = "-lbsm"; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits.network; PRODUCT_NAME = "$(inherited)"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Self Control Dev ext"; + PROVISIONING_PROFILE_SPECIFIER = "Self Control Dev ext"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -695,16 +805,15 @@ 77BA53172D9D5E2A00863476 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_ENTITLEMENTS = SelfControlExtension/SelfControlExtension.entitlements; + CODE_SIGN_ENTITLEMENTS = SelfControlNetworkExtension/SelfControlExtension.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 26210; DEVELOPMENT_TEAM = X6FQ433AWK; - "DEVELOPMENT_TEAM[sdk=macosx*]" = X6FQ433AWK; ENABLE_APP_SANDBOX = NO; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = SelfControlExtension/Info.plist; + INFOPLIST_FILE = SelfControlNetworkExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = SelfControlNetworkExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "DNS lookup"; @@ -719,13 +828,53 @@ OTHER_LDFLAGS = "-lbsm"; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits.network; PRODUCT_NAME = "$(inherited)"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Self Control Dev ext"; + PROVISIONING_PROFILE_SPECIFIER = "Self Control Dev ext"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; }; name = Release; }; + 9A33B1E42FAF2127002AAA0F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = X6FQ433AWK; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SelfControlBGService/Info.plist; + MACOSX_DEPLOYMENT_TARGET = 13.5; + PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits.bgservice; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 9A33B1E52FAF2127002AAA0F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = X6FQ433AWK; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SelfControlBGService/Info.plist; + MACOSX_DEPLOYMENT_TARGET = 13.5; + PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits.bgservice; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; 9AC9E4EF2F041D570004D39C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -942,6 +1091,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 9A33B1E62FAF2127002AAA0F /* Build configuration list for PBXNativeTarget "SelfControlBGService" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A33B1E42FAF2127002AAA0F /* Debug */, + 9A33B1E52FAF2127002AAA0F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 9AC9E4EE2F041D570004D39C /* Build configuration list for PBXNativeTarget "org.eyebeam.selfcontrold" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme index 6c292bc1..754925a4 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControl.xcscheme @@ -60,6 +60,7 @@ allowLocationSimulation = "YES"> diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlBGLaunchService.xcscheme b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlBGLaunchService.xcscheme new file mode 100644 index 00000000..1024547b --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/xcshareddata/xcschemes/SelfControlBGLaunchService.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/AppDelegate.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/AppDelegate.swift index fdc02ef7..07030e4d 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/AppDelegate.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/AppDelegate.swift @@ -14,9 +14,23 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { print("App did finish launching") + enableHelper() AppMover.moveIfNeeded() LocalNotificationManager.requestAuthorization() } + + private func enableHelper() { + Task { + do { + try await HelperConnection.shared.installLoginItemIfNeeded() + HelperConnection.shared.connect() + } catch { + print("installLoginItemIfNeeded Error: \(error)") + } +// let error = +// print("Install Error: \(String(describing: error))") + } + } func applicationWillTerminate(_ notification: Notification) { print("App will terminate") diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerStore.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerStore.swift index 3d721012..e55198d9 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerStore.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerStore.swift @@ -20,6 +20,9 @@ struct EventSchedulerStore { // Save schedules to UserDefaults static func saveSchedules(schedules: [Schedule]) { if let encoded = try? JSONEncoder().encode(schedules) { + HelperConnection.shared.saveSchedules(schedules: encoded) { result in + print("saveSchedules: To service: \(result)") + } UserDefaults.standard.set(encoded, forKey: "schedules") } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index 412b9667..338310f6 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -238,7 +238,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func setBlockedUrls(urls: [String]) { - IPCConnection.shared.enableURLBlocking(urls) + IPCConnectionProxy().setBlockedURLs(urls) if status == .stopped { //If legacy blocking if isActiveBlocking { //if is active blocking @@ -247,10 +247,6 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } // State might change due to Safari integration } - - func setIPAddressesToBlock(addresses: [String]) { - IPCConnection.shared.enableIPAddressesBlocking(addresses) - } func updateStatus() { if NEFilterManager.shared().isEnabled { @@ -323,15 +319,16 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } func registerWithProvider() { + print("+registerWithProvider") // Assuming an IPCConnection singleton similar to the AppKit sample - IPCConnection.shared.register(withExtension: extensionBundle, delegate: self) { success in - DispatchQueue.main.async { + IPCConnectionProxy().register(completionHandler: { success in + DispatchQueue.main.async { self.status = success ? .running : .stopped self.setBlockedUrls(urls: AppPreferences.getBlockedDomains()) self.refreshExtensionState() } // setBlockedURLs([]) - } + }) } func activateExtension() { @@ -473,7 +470,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func activateNetworkBlocking() { print("activateNetworkBlocking+++++") - _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(_enable: true) + IPCConnectionProxy().setEnableService(true) BlockListManager.activateSafariBlocking() chromeService.activateSafariBlocking() startShowingCountDownInDock() @@ -481,7 +478,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func deactivateNetworkBlocking() { print("deactivateNetworkBlocking+++++") - _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(_enable: false) + IPCConnectionProxy().setEnableService(false) BlockListManager.deactivateSafariBlocking() chromeService.deactivateSafariBlocking() if AppPreferences.playSoundOnCompletion { diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift index 464baa41..de49e78f 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift @@ -88,7 +88,7 @@ final class WebPageViewModel: NSObject, WKNavigationDelegate, WKScriptMessageHan private func updateSafariExtensionState(state: Bool) { AppPreferences.setSafariExtensionState(state) - IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: state) + IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: state) if state == true { updateBlocker() } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift new file mode 100644 index 00000000..9ab0a56b --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift @@ -0,0 +1,33 @@ +// +// File.swift +// SelfControl +// +// Created by Satendra Singh on 17/05/26. +// + +extension HelperConnection: AppToExtensionExtension { + func register(_ completionHandler: @escaping (Bool) -> Void) { + bgProxyServiceConnection()?.register(completionHandler) + } + + func setBlockedURLs(_ urls: [String]) { + bgProxyServiceConnection()?.setBlockedURLs(urls) + } + + func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { + bgProxyServiceConnection()?.setActiveBrowserExtension(extensionTypeRawValue, state: state) + } + + func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool){ + bgProxyServiceConnection()?.sendMessageToSetActiveBrowserExtension(extensionTypeRawValue, state: state) + } + + func setEnableService(_ enable: Bool) { + bgProxyServiceConnection()?.setEnableService(enable) + } + + func register(completionHandler: @escaping (Bool) -> Void) { + bgProxyServiceConnection()?.register(completionHandler: completionHandler) + } + +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift new file mode 100644 index 00000000..f8a9974e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift @@ -0,0 +1,186 @@ +// +// HelperConnection.swift +// SelfControl +// +// Created by Satendra Singh on 08/05/26. +// + +// MainApp/HelperConnection.swift +import Cocoa +import ServiceManagement + +final class HelperConnection: NSObject, HelperClientProtocol, NSSecureCoding { + static let shared = HelperConnection() + static var supportsSecureCoding: Bool = true + + private let helperBundleID = "com.application.SelfControl.corebits.bgservice" // HelperApp’s bundle identifier + private let machServiceName = "com.application.SelfControl.corebits.bgservice.xpc" + + private var connection: NSXPCConnection? + private var exportedConnection: NSXPCListenerEndpoint? + + private let stateQueue = DispatchQueue(label: "com.application.SelfControl.corebits.bgservice.connection", attributes: .concurrent) + + override init() { } + + required init?(coder: NSCoder) { + return nil + } + + func encode(with coder: NSCoder) { + + } + // MARK: - Public API + + func installLoginItemIfNeeded() async throws { + do { + try LaunchAgentManager.install() + } catch { + throw error + } + } + + func uninstallLoginItem() throws { + let loginItem = SMAppService.loginItem(identifier: helperBundleID) + try loginItem.unregister() + } + + func connect() { + stateQueue.sync(flags: .barrier) { + guard self.connection == nil else { return } + + let conn = NSXPCConnection(machServiceName: machServiceName, options: []) + // Remote interface: the helper’s service protocol + conn.remoteObjectInterface = NSXPCInterface(with: HelperServiceProtocol.self) + // Exported interface: our client protocol to receive callbacks + let clientInterface = NSXPCInterface(with: HelperClientProtocol.self) + conn.exportedInterface = clientInterface + conn.exportedObject = self + // exportedConnection = listener.endpoint + + conn.interruptionHandler = { [weak self] in + print("Conn interruptionHandler") + // Temporary loss of connection; try to reconnect + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self?.reconnect() + } + } + + conn.invalidationHandler = { [weak self] in + print("Conn invalidationHandler") + + // Connection invalidated; tear down and reconnect + self?.stateQueue.sync(flags: .barrier) { + self?.connection = nil + } + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self?.connect() + } + } + + conn.resume() + self.connection = conn + // Register for callbacks + bgProxyServiceConnection()?.registerClient() + bgProxyServiceConnection()?.startMonitoring { status in + print("startMonitoring: Status Received: \(status)") + } + } + } + + func disconnect() { + stateQueue.sync(flags: .barrier) { + self.connection?.invalidate() + self.connection = nil + } + } + + func saveSchedules(schedules: Data, reply: @escaping (Bool) -> Void) { + stateQueue.async(flags: .barrier) { + self.bgProxyServiceConnection()?.saveSchedules(schedules: schedules, reply: reply) + } + } + + func loadSchedules(reply: @escaping (_ schedules: Data) -> Void) { + //TODO: + } + + // Convenience wrappers using async/await + func startMonitoring() async throws { + try await withCheckedThrowingContinuation { cont in + self.withProxy(error: { cont.resume(throwing: $0) }) { proxy in + proxy.startMonitoring { _ in cont.resume() } + } + } + } + + func stopMonitoring() async throws { + try await withCheckedThrowingContinuation { cont in + self.withProxy(error: { cont.resume(throwing: $0) }) { proxy in + proxy.stopMonitoring { _ in cont.resume() } + } + } + } + + func currentStatus() async throws -> String { + try await withCheckedThrowingContinuation { cont in + self.withProxy(error: { cont.resume(throwing: $0) }) { proxy in + proxy.currentStatus { status in cont.resume(returning: status) } + } + } + } + + func performWork(_ input: String) async throws { + try await withCheckedThrowingContinuation { cont in + self.withProxy(error: { cont.resume(throwing: $0) }) { proxy in + proxy.performWork(input) { _ in cont.resume() } + } + } + } + + // MARK: - HelperClientProtocol (callbacks from helper) + + func didUpdateStatus(_ status: String) { + print("didUpdateStatus: \(status)") + // Update UI or post notifications as needed + NotificationCenter.default.post(name: .helperStatusChanged, object: status) + } + + func didEmitEvent(_ message: String) { + print("didEmitEvent: \(message)") + NotificationCenter.default.post(name: .helperEvent, object: message) + } + + // MARK: - Internal + + private func reconnect() { + disconnect() + connect() + } + + private func withProxy(error: ((Error) -> Void)? = nil, _ body: (HelperServiceProtocol) -> Void) { + var proxy: AnyObject? + stateQueue.sync { + proxy = self.connection?.remoteObjectProxyWithErrorHandler { err in + error?(err) + } as AnyObject? + } + if let proxy = proxy as? HelperServiceProtocol { + body(proxy) + } else { + error?(NSError(domain: "HelperConnection", code: 1, userInfo: [NSLocalizedDescriptionKey: "No XPC connection"])) + } + } + + func bgProxyServiceConnection() -> HelperServiceProtocol? { + var proxy: AnyObject? + proxy = self.connection?.remoteObjectProxyWithErrorHandler { err in + print("Conn Error:\(err)") } as AnyObject? + return proxy as? HelperServiceProtocol + } +} + +extension Notification.Name { + static let helperStatusChanged = Notification.Name("HelperStatusChanged") + static let helperEvent = Notification.Name("HelperEvent") +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchAgentManager.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchAgentManager.swift new file mode 100644 index 00000000..4cd90a18 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchAgentManager.swift @@ -0,0 +1,105 @@ +// +// LaunchAgentManager.swift +// SelfControl +// +// Created by Satendra Singh on 13/05/26. +// + + +import Foundation + +final class LaunchAgentManager { + + static let label = "com.application.SelfControl.corebits.bgservice" + static let plistName = "com.application.SelfControl.corebits.bgservice" + + static func install() throws { + + let fm = FileManager.default + + let sourcePlist = Bundle.main.url( + forResource: plistName, + withExtension: "plist" + )! + + let launchAgentsDir = fm.homeDirectoryForCurrentUser + .appendingPathComponent("Library/LaunchAgents") + + let destinationPlist = launchAgentsDir + .appendingPathComponent("\(plistName).plist") + + // Create LaunchAgents directory if needed + try fm.createDirectory( + at: launchAgentsDir, + withIntermediateDirectories: true + ) + + // Replace old plist + if fm.fileExists(atPath: destinationPlist.path) { + try fm.removeItem(at: destinationPlist) + } + + try fm.copyItem( + at: sourcePlist, + to: destinationPlist + ) + + // unload existing + try runLaunchctl([ + "unload", + destinationPlist.path + ]) + + // load new + try runLaunchctl([ + "load", + destinationPlist.path + ]) + + print("LaunchAgent installed") + } + + static func uninstall() throws { + + let fm = FileManager.default + + let plist = fm.homeDirectoryForCurrentUser + .appendingPathComponent( + "Library/LaunchAgents/\(plistName).plist" + ) + + try runLaunchctl([ + "unload", + plist.path + ]) + + try fm.removeItem(at: plist) + + print("LaunchAgent removed") + } + + @discardableResult + static func runLaunchctl(_ args: [String]) throws -> String { + + let process = Process() + + process.executableURL = URL( + fileURLWithPath: "/bin/launchctl" + ) + + process.arguments = args + + let pipe = Pipe() + + process.standardOutput = pipe + process.standardError = pipe + + try process.run() + + process.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + + return String(data: data, encoding: .utf8) ?? "" + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/SelfControlServiceSupport.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/SelfControlServiceSupport.swift new file mode 100644 index 00000000..7155d55e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/SelfControlServiceSupport.swift @@ -0,0 +1,39 @@ +// +// SelfControlServiceSupport.swift +// SelfControl +// +// Created by Satendra Singh on 03/05/26. +// + +import Cocoa + +final class SelfControlServiceSupport: NSObject, NSXPCListenerDelegate { + // Use a reverse-DNS mach service name. This must match your helper’s Info.plist. + private let listener = NSXPCListener(machServiceName: AppToServiceMessagesConstants.bundleID) +// private let service = HelperService() + + func applicationDidFinishLaunching(_ notification: Notification) { + listener.delegate = self + listener.resume() + // Agent app has no UI; it just keeps running. + } + + func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { + // Configure interfaces for bidirectional communication. + let exportedInterface = NSXPCInterface(with: AppToServiceMessages.self) + let clientInterface = NSXPCInterface(with: AppToServiceMessages.self) +// exportedInterface.setInterface(clientInterface, for: #selector(SelfControlServiceProtocol.registerClient(_:)), argumentIndex: 0, ofReply: false) + + newConnection.exportedInterface = exportedInterface +// newConnection.exportedObject = service + + newConnection.invalidationHandler = { [weak self] in + // Optionally clean up client references if you track them per-connection + _ = self // keep capture + } + newConnection.interruptionHandler = { /* handle temporary interruptions */ } + + newConnection.resume() + return true + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift index b221f304..f040506c 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift @@ -77,14 +77,14 @@ enum BlockListManager { print("Safari Extension State :\(state?.isEnabled ?? false)") Task { @MainActor in if NetworkExtensionState.shared.isEnabled == true { - _ = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: state?.isEnabled ?? false) + _ = IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: state?.isEnabled ?? false) NetworkExtensionState.shared.isSafariExtensionEnabled = state?.isEnabled ?? false NetworkExtensionState.shared.printAll() } } } // } - SFSafariApplication.dispatchMessage( + SFSafariApplication.dispatchMessage( withName: SafariConst.MessagesName.reloadList.rawValue, toExtensionWithIdentifier: SafariConst.identifier, userInfo: ["changed": true]) { error in @@ -93,7 +93,7 @@ enum BlockListManager { if error == nil { Task { @MainActor in if NetworkExtensionState.shared.isEnabled == true && NetworkExtensionState.shared.isSafariExtensionEnabled == false { //possibly extension is ready now - _ = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: true) + _ = IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: true) NetworkExtensionState.shared.isSafariExtensionEnabled = true NetworkExtensionState.shared.printAll() } @@ -108,7 +108,7 @@ enum BlockListManager { static func updateExtensionState() { Task { @MainActor in if NetworkExtensionState.shared.isEnabled == true { - _ = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: SafariExtensionManager.shared.isExtensionReady) + IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: SafariExtensionManager.shared.isExtensionReady) } } } @@ -122,7 +122,7 @@ enum BlockListManager { static func deactivateSafariBlocking() { updateExtensionState() SafariExtensionManager.shared.disableExtension() - _ = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: SafariExtensionManager.shared.isExtensionReady) + _ = IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: SafariExtensionManager.shared.isExtensionReady) os_log("Safari Message toExtensionWithIdentifier deactivateSafariBlocking called:") } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/AppDelegate.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/AppDelegate.swift new file mode 100644 index 00000000..0acc0534 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/AppDelegate.swift @@ -0,0 +1,52 @@ +// HelperApp/AppDelegate.swift +import Cocoa +//import Shared +import OSLog + +final class AppDelegate: NSObject, NSApplicationDelegate, NSXPCListenerDelegate { + // Use a reverse-DNS mach service name. This must match your helper’s Info.plist. + private let listener = NSXPCListener(machServiceName: "com.application.SelfControl.corebits.bgservice.xpc") + private let service = HelperService() + static let shared = AppDelegate() + override init() { + print("AppDelegate: init") + os_log("[SC] 🔍] BG AppDelegate: init") + + super.init() + listener.delegate = self + listener.resume() + } + + func applicationDidFinishLaunching(_ notification: Notification) { +// listener.delegate = self +// listener.resume() + // Agent app has no UI; it just keeps running. + } + + func listener( + _ listener: NSXPCListener, + shouldAcceptNewConnection newConnection: NSXPCConnection + ) -> Bool { + + print("shouldAcceptNewConnection") + os_log("[SC] 🔍] BG shouldAcceptNewConnection") + + let exportedInterface = + NSXPCInterface(with: HelperServiceProtocol.self) + + newConnection.exportedInterface = exportedInterface + newConnection.exportedObject = service + newConnection.remoteObjectInterface = NSXPCInterface(with: HelperClientProtocol.self) + newConnection.invalidationHandler = { + os_log("[SC] 🔍] BG newConnection.invalidationHandler") + } + + newConnection.interruptionHandler = { + os_log("[SC] 🔍] BG newConnection.interruptionHandler") + } + service.clientConnection = newConnection + newConnection.resume() + + return true + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift new file mode 100644 index 00000000..f9a32b0f --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift @@ -0,0 +1,47 @@ +// +// HelperService+AppToExtensionExtension.swift +// SelfControl +// +// Created by Satendra Singh on 17/05/26. +// +import OSLog +import Foundation + +extension HelperService: AppToExtensionExtension { + + func register(_ completionHandler: @escaping (Bool) -> Void) { + queue.async { + IPCConnection.shared.register(completionHandler) + } + } + + func setBlockedURLs(_ urls: [String]) { + queue.async { + IPCConnection.shared.enableURLBlocking(urls) + } + } + + func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { + queue.async { + IPCConnection.shared.setActiveBrowserExtension(extensionTypeRawValue, state: state) + } + } + + func setEnableService(_ enable: Bool) { + queue.async { + _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(enable) + } + } + + func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { + queue.async { + _ = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(extensionTypeRawValue, state: state) + } + } + + func register(completionHandler: @escaping (Bool) -> Void) { + queue.async { + IPCConnection.shared.register(completionHandler: completionHandler) + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift new file mode 100644 index 00000000..3d9a976a --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift @@ -0,0 +1,117 @@ +// +// HelperService.swift +// SelfControl +// +// Created by Satendra Singh on 17/05/26. +// +import OSLog +import Foundation + +final class HelperService: NSObject, HelperServiceProtocol { + private var isMonitoring = false + private var clients = NSHashTable.weakObjects() + let queue = DispatchQueue(label: "com.application.SelfControl.corebits.bgservice.queue") + var clientConnection: NSXPCConnection? + + func registerClient() { + os_log("[SC] 🔍] BG registerClient") + queue.async { +// self.clients.add(client) +// client.didUpdateStatus(self.isMonitoring ? "running" : "stopped") + print("Register Client received") + + } + self.sendPing() + + } + + private func proxyConnectionService() -> HelperClientProtocol? { + var proxy: AnyObject? + guard let conn = clientConnection else { + os_log("[SC] 🔍] BG No client connection") + return nil + } + proxy = conn.remoteObjectProxyWithErrorHandler { err in + os_log("[SC] 🔍] BG Client proxy error:\(err)") + } as AnyObject? + return proxy as? HelperClientProtocol + } + + func sendPing() { + os_log("[SC] 🔍] BG HELPER sendPing") + let schedules = EventSchedulerStore.loadSchedules() + var details :String = "" + for schedule in schedules { + os_log("[SC] 🔍] BG Schedule: \(schedule.summaryString)") + details.append(schedule.summaryString + "\n") + } + proxyConnectionService()?.didEmitEvent("Hello from helper: \(schedules.count), details: \(details)") + } + + + func startMonitoring(reply: @escaping (Bool) -> Void) { + os_log("[SC] 🔍] BG startMonitoring") + queue.async { + guard !self.isMonitoring else { reply(true); return } + self.isMonitoring = true + self.broadcastStatus() + // Start your actual background work here (timers, file watchers, etc.) + reply(true) + } + } + + func stopMonitoring(reply: @escaping (Bool) -> Void) { + os_log("[SC] 🔍] BG stopMonitoring") + + queue.async { + guard self.isMonitoring else { reply(true); return } + self.isMonitoring = false + // Stop your background work + self.broadcastStatus() + reply(true) + } + } + + func currentStatus(reply: @escaping (String) -> Void) { + queue.async { + reply(self.isMonitoring ? "running" : "stopped") + } + } + + func performWork(_ input: String, reply: @escaping (Bool) -> Void) { + queue.async { + // Do some work… + self.broadcastEvent("Processed: \(input)") + reply(true) + } + } + + private func broadcastStatus() { + let status = self.isMonitoring ? "running" : "stopped" + for client in self.clients.allObjects { + (client as? HelperClientProtocol)?.didUpdateStatus(status) + } + } + + private func broadcastEvent(_ message: String) { + for client in self.clients.allObjects { + (client as? HelperClientProtocol)?.didEmitEvent(message) + } + } + + func saveSchedules(schedules: Data, reply: @escaping (Bool) -> Void) { + queue.async { + EventSchedulerStore.saveSchedulesData(schedules: schedules) + self.broadcastEvent("Schedules saved") + self.sendPing() + reply(true) + } + } + + func loadSchedules(reply: @escaping (Data) -> Void) { + queue.async { + self.broadcastEvent("Schedules loaded") + reply(Data()) + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventScheduler.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventScheduler.swift new file mode 100644 index 00000000..c16efccd --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventScheduler.swift @@ -0,0 +1,342 @@ +// +// EventScheduler.swift +// SelfControl +// +// Created by Satendra Singh on 23/02/26. +// + +import Foundation + + +// A simple, concurrency-safe scheduler for recurring events by SCSCWeekday and time. +// It validates time ranges and prevents overlapping events on the same day. + +public enum SchedulerError: Error, LocalizedError, Equatable { + case invalidTimeRange + case conflict(conflictingEvents: [Event]) + case eventNotFound + + public var errorDescription: String? { + switch self { + case .invalidTimeRange: + return "The start time must be earlier than the end time." + case .conflict(let conflicts): + let names = conflicts.map { $0.title }.joined(separator: ", ") + return conflicts.isEmpty + ? "The event conflicts with an existing event." + : "The event conflicts with the following events: \(names)" + case .eventNotFound: + return "The event could not be found." + } + } +} + +/// SCWeekday aligned with the user's Calendar (1 = Sunday in many locales). +/// We normalize to a fixed enumeration order (Monday...Sunday) for consistency. +public enum SCWeekday: Int, CaseIterable, Codable, Hashable, Sendable { + case monday = 2 + case tuesday = 3 + case wednesday = 4 + case thursday = 5 + case friday = 6 + case saturday = 7 + case sunday = 1 + +// public var displayName: String { +// switch self { +// case .monday: return "Monday" +// case .tuesday: return "Tuesday" +// case .wednesday: return "Wednesday" +// case .thursday: return "Thursday" +// case .friday: return "Friday" +// case .saturday: return "Saturday" +// case .sunday: return "Sunday" +// } +// } + + var letter: String { + switch self { + case .monday: return "M" + case .tuesday: return "T" + case .wednesday: return "W" + case .thursday: return "T" + case .friday: return "F" + case .saturday: return "S" + case .sunday: return "S" + } + } + + var fullName: String { + switch self { + case .monday: return "Monday" + case .tuesday: return "Tuesday" + case .wednesday: return "Wednesday" + case .thursday: return "Thursday" + case .friday: return "Friday" + case .saturday: return "Saturday" + case .sunday: return "Sunday" + } + } + + /// Returns the SCWeekday for a given Date using the provided Calendar. + public static func from(date: Date, calendar: Calendar = .current) -> SCWeekday { + let SCWeekdayNumber = calendar.component(.weekday, from: date) + return SCWeekday(rawValue: SCWeekdayNumber) ?? .monday + } +} + +/// Represents a wall-clock time without a specific date or time zone. +/// Valid range: hour 0...23, minute 0...59 +public struct TimeOfDay: Codable, Hashable, Sendable, Comparable { + public let hour: Int + public let minute: Int + + public init(hour: Int, minute: Int) { + precondition((0...23).contains(hour), "Hour must be 0...23") + precondition((0...59).contains(minute), "Minute must be 0...59") + self.hour = hour + self.minute = minute + } + + public var minutesSinceMidnight: Int { + hour * 60 + minute + } + + public static func < (lhs: TimeOfDay, rhs: TimeOfDay) -> Bool { + lhs.minutesSinceMidnight < rhs.minutesSinceMidnight + } + + + /// Total minutes since midnight (00:00). + var totalMinutesSinceMidnight: Int { + hour * 60 + minute + } + + /// Returns the number of minutes from `other` to `self`. + /// - Parameters: + /// - other: The starting time of day. + /// - wrapAroundMidnight: If true, negative differences will be wrapped + /// by adding 24 hours (1440 minutes). This is useful when you want the + /// forward difference within the same 24-hour cycle. + /// - Returns: The minute difference as an `Int`. + /// + /// Examples: + /// - 10:30.minutes(from: 09:15) == 75 + /// - 01:00.minutes(from: 23:30, + /// wrapAroundMidnight: true) == 90 + /// - 01:00.minutes(from: 23:30, wrapAroundMidnight: false) == -1410 + func minutes(from other: TimeOfDay, wrapAroundMidnight: Bool = false) -> Int { + let diff = self.totalMinutesSinceMidnight - other.totalMinutesSinceMidnight + guard wrapAroundMidnight, diff < 0 else { return diff } + return diff + 24 * 60 + } + + /// Convenience: minutes until another time (alias with reversed arguments). + /// Positive when `other` is later than `self` on the same day. + func minutes(until other: TimeOfDay, wrapAroundMidnight: Bool = false) -> Int { + other.minutes(from: self, wrapAroundMidnight: wrapAroundMidnight) + } + + public func formatted(locale: Locale = .current) -> String { + var comps = DateComponents() + comps.hour = hour + comps.minute = minute + let cal = Calendar(identifier: .gregorian) + let date = cal.date(from: comps) ?? Date() + let formatter = DateFormatter() + formatter.locale = locale + formatter.timeStyle = .short + formatter.dateStyle = .none + return formatter.string(from: date) + } +} + +/// A recurring event that happens on one or more SCWeekdays at a given time range. +public struct Event: Identifiable, Codable, Hashable, Sendable { + public let id: UUID + public var title: String = "" + public var days: Set + public var startTime: TimeOfDay + public var endTime: TimeOfDay + public var userInfo: [String: String]? // Optional metadata + + public init( + id: UUID = UUID(), + title: String = "", + days: Set, + startTime: TimeOfDay, + endTime: TimeOfDay, + userInfo: [String: String]? = nil + ) throws { + guard startTime < endTime else { + throw SchedulerError.invalidTimeRange + } + self.id = id + self.title = title + self.days = days + self.startTime = startTime + self.endTime = endTime + self.userInfo = userInfo + } + + /// Returns true if this event occurs on the given SCWeekday. + public func occurs(on day: SCWeekday) -> Bool { + days.contains(day) + } + + /// Overlap check with another event on a specific SCWeekday. + /// Adjacent ranges (end == start) are not considered overlapping. + public func overlaps(with other: Event, on day: SCWeekday) -> Bool { + guard self.occurs(on: day), other.occurs(on: day) else { return false } + let aStart = self.startTime.minutesSinceMidnight + let aEnd = self.endTime.minutesSinceMidnight + let bStart = other.startTime.minutesSinceMidnight + let bEnd = other.endTime.minutesSinceMidnight + return aStart < bEnd && aEnd > bStart + } +} + +/// A concurrency-safe scheduler to add, update, and query recurring events by SCWeekday and time. +public actor EventScheduler { + public init(calendar: Calendar = .current) { + self.calendar = calendar + } + + public let calendar: Calendar + private var eventsByID: [UUID: Event] = [:] + + // MARK: - CRUD + + /// Schedules a new event. Throws if the time range is invalid or conflicts with existing events. + @discardableResult + public func schedule( + title: String = "", + days: Set, + startTime: TimeOfDay, + endTime: TimeOfDay, + userInfo: [String: String]? = nil + ) throws -> Event { + let newEvent = try Event(title: title, days: days, startTime: startTime, endTime: endTime, userInfo: userInfo) + try assertNoConflicts(with: newEvent, excludingID: nil) + eventsByID[newEvent.id] = newEvent + return newEvent + } + + @discardableResult + public func schedule( + newEvent: Event + ) throws -> Event { + try assertNoConflicts(with: newEvent, excludingID: nil) + eventsByID[newEvent.id] = newEvent + return newEvent + } + + /// Updates an existing event. Throws if not found or if the updated event conflicts. + public func update( + id: UUID, + title: String? = nil, + days: Set? = nil, + startTime: TimeOfDay? = nil, + endTime: TimeOfDay? = nil, + userInfo: [String: String]? = nil + ) throws -> Event { + guard var existing = eventsByID[id] else { + throw SchedulerError.eventNotFound + } + + let newTitle = title ?? existing.title + let newDays = days ?? existing.days + let newStart = startTime ?? existing.startTime + let newEnd = endTime ?? existing.endTime + let newUserInfo = userInfo ?? existing.userInfo + + let updated = try Event(id: id, title: newTitle, days: newDays, startTime: newStart, endTime: newEnd, userInfo: newUserInfo) + try assertNoConflicts(with: updated, excludingID: id) + eventsByID[id] = updated + return updated + } + + /// Removes an event by ID. Returns the removed event or throws if not found. + @discardableResult + public func remove(id: UUID) throws -> Event { + guard let removed = eventsByID.removeValue(forKey: id) else { + throw SchedulerError.eventNotFound + } + return removed + } + + /// Returns all scheduled events sorted by title. + public func allEvents() -> [Event] { + eventsByID.values.sorted { $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedAscending } + } + + /// Returns events that occur on the specified SCWeekday, sorted by start time. + public func events(on day: SCWeekday) -> [Event] { + eventsByID.values + .filter { $0.occurs(on: day) } + .sorted { lhs, rhs in + if lhs.startTime == rhs.startTime { + return lhs.title.localizedCaseInsensitiveCompare(rhs.title) == .orderedAscending + } + return lhs.startTime < rhs.startTime + } + } + + // MARK: - Conflict Checking + + /// Returns any events that would conflict with the provided event. + public func conflicts(for event: Event, excludingID: UUID? = nil) -> [Event] { + eventsByID.values + .filter { existing in + if let exclude = excludingID, existing.id == exclude { return false } + // Only check days that overlap + let sharedDays = existing.days.intersection(event.days) + guard !sharedDays.isEmpty else { return false } + return sharedDays.contains { event.overlaps(with: existing, on: $0) } + } + .sorted { $0.startTime < $1.startTime } + } + + private func assertNoConflicts(with event: Event, excludingID: UUID?) throws { + let conflicts = conflicts(for: event, excludingID: excludingID) + if !conflicts.isEmpty { + throw SchedulerError.conflict(conflictingEvents: conflicts) + } + } + + // MARK: - Query Helpers + + /// Returns the next DateInterval this event will occur after the given date, if any. + /// This uses the scheduler's calendar to resolve the next occurrence in the current week or later. + public func nextOccurrence(of eventID: UUID, after date: Date = Date()) -> DateInterval? { + guard let event = eventsByID[eventID] else { return nil } + return nextOccurrence(of: event, after: date) + } + + /// Returns the next DateInterval an event will occur after the given date, if any. + public func nextOccurrence(of event: Event, after date: Date = Date()) -> DateInterval? { + // Search up to 8 weeks ahead to be safe for unusual calendars + for dayOffset in 0..<(7 * 8) { + guard let candidateDay = calendar.date(byAdding: .day, value: dayOffset, to: date) else { continue } + let SCWeekday = SCWeekday.from(date: candidateDay, calendar: calendar) + guard event.occurs(on: SCWeekday) else { continue } + + var comps = calendar.dateComponents([.year, .month, .day], from: candidateDay) + comps.hour = event.startTime.hour + comps.minute = event.startTime.minute + comps.second = 0 + let start = calendar.date(from: comps) + + comps.hour = event.endTime.hour + comps.minute = event.endTime.minute + let end = calendar.date(from: comps) + + if let start, let end, end > date { + // If the start is in the past but end is in the future, return the remaining portion. + let actualStart = max(start, date) + return DateInterval(start: actualStart, end: end) + } + } + return nil + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift new file mode 100644 index 00000000..b10998bd --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift @@ -0,0 +1,195 @@ +// +// EventSchedulerRunner.swift +// SelfControl +// +// Created by Satendra Singh on 23/02/26. +// + +import Foundation + +// A firing engine that uses EventScheduler as storage and fires events at their scheduled times. +// This actor observes the scheduler and invokes registered handlers when events start. +// +public actor EventSchedulerRunner { + public typealias EventHandler = @Sendable (Event) async -> Void + + public init(scheduler: EventScheduler, fireTolerance: TimeInterval = 1.0) { + self.scheduler = scheduler + self.fireTolerance = fireTolerance + } + + private let scheduler: EventScheduler + private let fireTolerance: TimeInterval + + private var timer: Timer? + private var perEventHandlers: [UUID: EventHandler] = [:] + private var globalHandlers: [UUID: EventHandler] = [:] // keyed by token + private var scheduledEvent: Event? + + deinit { + timer?.invalidate() + timer = nil + } + + // MARK: - Control + + public func start() async { + guard timer == nil else { return } + await scheduleNextTimer() + } + + public func stop() { + timer?.invalidate() + timer = nil + scheduledEvent = nil + } + + /// Restart timer to quickly reflect changes (handlers or events). + private func rescheduleLoop() async { + await scheduleNextTimer() + } + + // MARK: - Handlers + + public func setHandler(for eventID: UUID, handler: EventHandler?) async { + if let handler { + perEventHandlers[eventID] = handler + } else { + perEventHandlers.removeValue(forKey: eventID) + } + await rescheduleLoop() + } + + @discardableResult + public func addGlobalHandler(_ handler: @escaping EventHandler) async -> UUID { + let token = UUID() + globalHandlers[token] = handler + // await rescheduleLoop() + return token + } + + public func removeGlobalHandler(token: UUID) async { + globalHandlers.removeValue(forKey: token) + await rescheduleLoop() + } + + // MARK: - Timer scheduling + + private func scheduleNextTimer() async { + // Cancel existing timer, if any + timer?.invalidate() + timer = nil + + let now = Date() + let calendar = await scheduler.calendar + let events = await scheduler.allEvents() + + // Compute earliest next start among all events + let next = earliestNextStart(after: now, events: events, calendar: calendar) + + // If there are no upcoming events, poll again in a short while + let interval: TimeInterval + if let nextDate = next.0 { + interval = max(0.0, nextDate.timeIntervalSinceNow) + } else { + interval = 0.3 + } + + // Create a one-shot timer to fire at the next event (or retry interval) + let newTimer = Timer(timeInterval: interval, repeats: false) { [weak self] _ in + guard let self else { return } + // Hop back to the actor to handle firing and rescheduling + Task { + await self.timerDidFire() + } + } + scheduledEvent = next.1 + // Ensure the timer fires during UI interactions as well + newTimer.tolerance = min(fireTolerance, max(0.0, interval * 0.90)) + RunLoop.main.add(newTimer, forMode: .common) + self.timer = newTimer + } + + private func timerDidFire() async { + let fireDate = Date() + let calendar = await scheduler.calendar + let events = await scheduler.allEvents() + + await fireEventsStarting(around: fireDate, events: events, calendar: calendar, tolerance: fireTolerance) + + // Schedule the next timer based on the latest data + await scheduleNextTimer() + } + + // MARK: - Event computations + + private func earliestNextStart(after date: Date, events: [Event], calendar: Calendar) -> (Date?, Event?) { + var best: Date? + var bestEvent: Event? + for event in events { + if let start = nextStart(of: event, after: date, calendar: calendar) { + if let b = best { + if start < b { + best = start + bestEvent = event + } + } else { + best = start + bestEvent = event + } + } + } + return (best, bestEvent) + } + + private func nextStart(of event: Event, after date: Date, calendar: Calendar) -> Date? { + // Search up to 8 weeks ahead + for dayOffset in 0..<(7 * 8) { + guard let candidateDay = calendar.date(byAdding: .day, value: dayOffset, to: date) else { continue } + let weekday = SCWeekday.from(date: candidateDay, calendar: calendar) + guard event.occurs(on: weekday) else { continue } + + var comps = calendar.dateComponents([.year, .month, .day], from: candidateDay) + comps.hour = event.startTime.hour + comps.minute = event.startTime.minute + comps.second = 0 + if let start = calendar.date(from: comps), start > date { + return start + } + } + return nil + } + + private func fireEventsStarting(around date: Date, events: [Event], calendar: Calendar, tolerance: TimeInterval) async { + // Determine which events start at this time within tolerance +// let starting = events.compactMap { event -> Event? in +// let weekday = SCWeekday.from(date: date, calendar: calendar) +// guard event.occurs(on: weekday) else { return nil } +// var comps = calendar.dateComponents([.year, .month, .day], from: date) +// comps.hour = event.startTime.hour +// comps.minute = event.startTime.minute +// comps.second = 0 +// guard let start = calendar.date(from: comps) else { return nil } +// return abs(start.timeIntervalSince(date)) <= tolerance ? event : nil +// } + + guard let firedEvent = scheduledEvent else { return } + + // Capture handlers to call outside the actor's critical path + let global = globalHandlers.values +// for event in starting { + var handlers: [EventHandler] = [] + if let h = perEventHandlers[firedEvent.id] { + handlers.append(h) + } + handlers.append(contentsOf: global) +// if handlers.isEmpty { continue } + + for handler in handlers { + Task.detached(priority: .userInitiated) { + await handler(firedEvent) + } + } +// } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerStore.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerStore.swift new file mode 100644 index 00000000..2c52910c --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerStore.swift @@ -0,0 +1,104 @@ +// +// EventSchedulerStore.swift +// SelfControl +// +// Created by Satendra Singh on 25/02/26. +// + +import Foundation + +struct EventSchedulerStore { + // Load schedules from UserDefaults + static func loadSchedules() -> [Schedule] { + if let data = UserDefaults.standard.data(forKey: "schedules"), + let decoded = try? JSONDecoder().decode([Schedule].self, from: data) { + return decoded + } else { + // Initialize with one empty schedule if no saved data + return [Schedule(timeSlots: [], enabledDays: [])] + } + } + + // Save schedules to UserDefaults + static func saveSchedules(schedules: [Schedule]) { + if let encoded = try? JSONEncoder().encode(schedules) { + saveSchedulesData(schedules: encoded) + } + } + + static func saveSchedulesData(schedules: Data) { + UserDefaults.standard.set(schedules, forKey: "schedules") + } + +} + +// MARK: - Schedule +struct Schedule: Identifiable, Codable, Equatable { + let id: UUID + var timeSlots: [TimeSlot] + var enabledDays: Set + var isExpanded: Bool + var isEnabled: Bool + + init(id: UUID = UUID(), timeSlots: [TimeSlot] = [], enabledDays: Set = [], isExpanded: Bool = false, isEnabled: Bool = false) { + self.id = id + self.timeSlots = timeSlots + self.enabledDays = enabledDays + self.isExpanded = isExpanded + self.isEnabled = isEnabled + } + + var summaryString: String { + timeSlots.map { $0.displayString }.joined(separator: " ") + } + + var enabledDaysString: String { + SCWeekday.allCases.compactMap { enabledDays.contains($0) ? $0.letter : nil }.joined() + } +} + +// MARK: - Schedule Models +struct TimeSlot: Identifiable, Codable, Equatable { + let id: UUID + var startHour: Int + var startMinute: Int + var endHour: Int + var endMinute: Int + + init(id: UUID = UUID(), startHour: Int, startMinute: Int, endHour: Int, endMinute: Int) { + self.id = id + self.startHour = startHour + self.startMinute = startMinute + self.endHour = endHour + self.endMinute = endMinute + } + + // Helper to format hour in 12-hour format + private func formatHour12(_ hour24: Int) -> (hour: Int, isPM: Bool) { + if hour24 == 0 { + return (12, false) + } else if hour24 < 12 { + return (hour24, false) + } else if hour24 == 12 { + return (12, true) + } else { + return (hour24 - 12, true) + } + } + + var startTimeString: String { + let (hour12, isPM) = formatHour12(startHour) + let amPm = isPM ? "PM" : "AM" + return String(format: "%d:%02d %@", hour12, startMinute, amPm) + } + + var endTimeString: String { + let (hour12, isPM) = formatHour12(endHour) + let amPm = isPM ? "PM" : "AM" + return String(format: "%d:%02d %@", hour12, endMinute, amPm) + } + + var displayString: String { + "\(startTimeString)-\(endTimeString)" + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/SafariExtensionService/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/SafariExtensionService/IPCConnection.swift new file mode 100644 index 00000000..0a81d5fb --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/SafariExtensionService/IPCConnection.swift @@ -0,0 +1,196 @@ +/* + See the LICENSE.txt file for this sample’s licensing information. + + Abstract: + This file contains the implementation of the app <-> provider IPC connection + */ + +import Foundation +import os.log +import Network + +/// The IPCConnection class is used by both the app and the system extension to communicate with each other +class IPCConnection: NSObject, NSSecureCoding { + static var supportsSecureCoding: Bool = true + + override init() { + } + + required init?(coder: NSCoder) { + return nil + } + + func encode(with coder: NSCoder) { + + } + // MARK: Properties + + var listener: NSXPCListener? + var currentConnection: NSXPCConnection? + weak var delegate: ExtensionToApp? + static let shared = IPCConnection() +// var blockedUrls: [String] = ProxyPreferences.getBlockedDomains() + var blockedUrls: [String] = [String]() + // Published extension state for UI/observers + + // MARK: Methods + + /// This method is called by the app to register with the provider running in the system extension. + func register(completionHandler: @escaping (Bool) -> Void) { + +// self.delegate = delegate + os_log("[SC] 🔍] register(withExtension") + + guard currentConnection == nil else { + os_log("[SC] 🔍] Already registered with the provider") + completionHandler(true) + return + } + + let newConnection = NSXPCConnection(machServiceName: "X6FQ433AWK.com.application.SelfControl.corebits.network", options: []) + + // The exported object is the delegate. + newConnection.exportedInterface = NSXPCInterface(with: ExtensionToApp.self) + newConnection.exportedObject = self + + // The remote object is the provider's IPCConnection instance. + newConnection.remoteObjectInterface = NSXPCInterface(with: AppToExtensionExtension.self) + + currentConnection = newConnection + newConnection.resume() + + guard let providerProxy = newConnection.remoteObjectProxyWithErrorHandler({ registerError in + os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) + self.currentConnection?.invalidate() + self.currentConnection = nil + completionHandler(false) + }) as? AppToExtensionExtension else { + os_log("Failed to create a remote object proxy for the provider") + return + } + providerProxy.register(completionHandler) + providerProxy.setBlockedURLs(blockedUrls) + } + + /** + This method is called by the provider to cause the app (if it is registered) to display a prompt to the user asking + for a decision about a connection. + */ + func promptUser(aboutFlow flowInfo: [String: String], responseHandler:@escaping (Bool) -> Void) -> Bool { + + guard let connection = currentConnection else { + os_log("[SC] 🔍] Cannot prompt user because the app isn't registered") + return false + } + + guard let appProxy = connection.remoteObjectProxyWithErrorHandler({ promptError in + os_log("[SC] 🔍] Failed to prompt the user: %{public}@", promptError.localizedDescription) + self.currentConnection = nil + responseHandler(true) + }) as? ExtensionToApp else { + os_log("Failed to create a remote object proxy for the app") + return false + } + + appProxy.promptUser(aboutFlow: flowInfo, responseHandler: responseHandler) + + return true + } +} + +extension IPCConnection: NSXPCListenerDelegate { + + // MARK: NSXPCListenerDelegate + + func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { + + // The exported object is this IPCConnection instance. + newConnection.exportedInterface = NSXPCInterface(with: AppToExtensionExtension.self) + newConnection.exportedObject = self + + // The remote object is the delegate of the app's IPCConnection instance. + newConnection.remoteObjectInterface = NSXPCInterface(with: ExtensionToApp.self) + + newConnection.invalidationHandler = { + self.currentConnection = nil + } + + newConnection.interruptionHandler = { + self.currentConnection = nil + } + + currentConnection = newConnection + newConnection.resume() + + return true + } + + func enableURLBlocking(_ urls: [String]) { + os_log("[SC] 🔍] Enabling URL blocking") + guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in + os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) + }) as? AppToExtensionExtension else { + os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") + return + } + providerProxy.setBlockedURLs(urls) + } + + func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { + os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension:\(extensionTypeRawValue), state:\(state)") + guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in + os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension: %{public}@", registerError.localizedDescription) + }) as? AppToExtensionExtension else { + os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") + return + } + providerProxy.setActiveBrowserExtension(extensionTypeRawValue, state: state) + } + + func sendMessageToEnableNetworkExtension(_ enable: Bool) -> Bool { + os_log("[SC] 🔍] sendMessageToEnableNetworkExtension state %{public}d", enable) + guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in + os_log("[SC] 🔍] sendMessageToEnableNetworkExtension: %{public}@", registerError.localizedDescription) + }) as? AppToExtensionExtension else { + os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") + return false + } + providerProxy.setEnableService(enable) + return true + } +} + + +extension IPCConnection: AppToExtensionExtension { + func setEnableService(_ enable: Bool) { + os_log("[SC] 🔍] setEnableService") + + } //Unused cleanup later + + func setBlockedURLs(_ urls: [String]) { + os_log("[SC] 🔍] setBlockedURLs") + } //Unused cleanup later + + func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { + os_log("[SC] 🔍] setActiveBrowserExtension") + } //Unused cleanup later + + // MARK: ProviderCommunication + + func register(_ completionHandler: @escaping (Bool) -> Void) { + + os_log("[SC] 🔍] App registered") + completionHandler(true) + } +} + +extension IPCConnection: ExtensionToApp { + + func promptUser(aboutFlow flowInfo: [String: String], responseHandler: @escaping (Bool) -> Void) { + os_log("[SC] 🔍] promptUser") + + } + func didSetUrls() { + os_log("[SC] 🔍] didSetUrls") + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift new file mode 100644 index 00000000..487f689b --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift @@ -0,0 +1,35 @@ +// +// SafariExtensionMessages.swift +// SelfControl +// +// Created by Satendra Singh on 17/05/26. +// +import Foundation + +enum ActiveBrowserExtensios: String { + case safari + case chrome +} + + +enum FlowInfoKey: String { + case localPort + case remoteAddress +} + +/// App --> Provider IPC, to be implememted in Extension +/// // Use Objective-C bridgeable type for XPC surface +@objc public protocol AppToExtensionExtension { + func register(completionHandler: @escaping (Bool) -> Void) + func register(_ completionHandler: @escaping (Bool) -> Void) + func setBlockedURLs(_ urls: [String]) + func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) + func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) + func setEnableService(_ enable: Bool) +} + +/// Provider --> App IPC to Be implememted in the app +@objc public protocol ExtensionToApp { + func promptUser(aboutFlow flowInfo: [String: String], responseHandler: @escaping (Bool) -> Void) + func didSetUrls() +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift new file mode 100644 index 00000000..9e0f8969 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift @@ -0,0 +1,25 @@ +// Shared/HelperServiceProtocol.swift +import Foundation + +// The client protocol allows the helper to send events to the UI app. +@objc public protocol HelperClientProtocol { + func didUpdateStatus(_ status: String) + func didEmitEvent(_ message: String) +} + +// The service protocol defines the operations the UI app can call on the helper. +@objc public protocol HelperServiceProtocol: AppToExtensionExtension { + // One-time registration so the helper can call back into the UI app. + // func registerClient() + func registerClient() + + func startMonitoring(reply: @escaping (Bool) -> Void) + func stopMonitoring(reply: @escaping (Bool) -> Void) + func currentStatus(reply: @escaping (String) -> Void) + // Example command that produces an async result pushed back to the client + func performWork(_ input: String, reply: @escaping (Bool) -> Void) + + //Schedule Save/load + func saveSchedules(schedules: Data, reply: @escaping (Bool) -> Void) + func loadSchedules(reply: @escaping (_ schedules: Data) -> Void) +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/main.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/main.swift new file mode 100644 index 00000000..0a9e6c8f --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/main.swift @@ -0,0 +1,14 @@ +// +// main.swift +// SelfControlBGService +// +// Created by Satendra Singh on 09/05/26. +// + +import Foundation +import OSLog + +os_log("[SC] 🔍] BG AshouldAcceptNewConnection") +print("Hello, World!") +let deletegate = AppDelegate.shared +RunLoop.current.run() diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/BlockOrAllowList.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/BlockOrAllowList.swift new file mode 100644 index 00000000..5a1b0659 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/BlockOrAllowList.swift @@ -0,0 +1,147 @@ +// +// BlockOrAllowList.swift +// SelfControl +// +// Created by Satendra Singh on 24/09/25. +// + + +import Foundation +import os.log +import NetworkExtension + +class BlockOrAllowList: NSObject { + + // MARK: - Properties + + var items: Set +// var lastModified: Date? + + // Serial queue for thread safety (replace @synchronized) +// private let queue = DispatchQueue(label: "BlockOrAllowList.serial") + + // MARK: - Initializer + + init(items: [String]) { + self.items = Set(items) + super.init() + } + + // MARK: - Methods + +// var isRemote: Bool { +// return path.hasPrefix("http://") || path.hasPrefix("https://") +// } + +// func load(_ path: String) { +// queue.sync { +// self.path = path +// self.items.removeAll() +// +// guard !path.isEmpty else { +// os_log("no list specified...", log: .default, type: .debug) +// return +// } +// +// var listString: String? +// var error: Error? +// +// if isRemote { +// os_log("(re)loading (remote) list", log: .default, type: .debug) +// if let url = URL(string: path) { +// do { +// listString = try String(contentsOf: url, encoding: .utf8) +// } catch let e { +// error = e +// } +// } +// if let error = error { +// os_log("ERROR: failed to (re)load (remote) list, %{public}@ (error: %{public}@)", log: .default, type: .error, path, String(describing: error)) +// return +// } +// // (Re)load remote URL once a day +// DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 24*60*60) { +// self.load(self.path) +// } +// } else { +// os_log("(re)loading (local) list, %{public}@", log: .default, type: .debug, path) +// do { +// listString = try String(contentsOfFile: path, encoding: .utf8) +// } catch let e { +// error = e +// } +// if let error = error { +// os_log("ERROR: failed to (re)load (local) list, %{public}@ (error: %{public}@)", log: .default, type: .error, path, String(describing: error)) +// return +// } +// if let attrs = try? FileManager.default.attributesOfItem(atPath: path), +// let fileModified = attrs[.modificationDate] as? Date { +// self.lastModified = fileModified +// } +// } +// +// if let listString = listString { +// let lines = listString.components(separatedBy: .newlines) +// let filtered = lines.map { $0.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() } +// .filter { !$0.isEmpty && !$0.hasPrefix("#") } +// self.items = Set(filtered) +// os_log("SC] 🔍(re)loaded %lu list items", log: .default, type: .debug, self.items.count) +// } +// } +// } + + /// Check if flow matches item on block or allow list + func isMatch(_ flow: NEFilterSocketFlow) -> Bool { + // Only access properties and perform mutation in the queue (thread safety) +// return queue.sync { + + var endpointNames = Set() + + // Get remote endpoint host + if let url = flow.url, let urlString = url.absoluteString.lowercased() as String? { + endpointNames.insert(urlString) + } + if let url = flow.url, let host = url.host?.lowercased() { + endpointNames.insert(host) + } + if let remoteEndpoint = flow.remoteEndpoint as? NWHostEndpoint { + endpointNames.insert(remoteEndpoint.hostname.lowercased()) + } + + // macOS 11+ specific property + if #available(macOS 11, *) { + if let remoteHostname = flow.remoteHostname?.lowercased() { + endpointNames.insert(remoteHostname) + if remoteHostname.hasPrefix("www.") { + let noWWW = String(remoteHostname.dropFirst(4)) + endpointNames.insert(noWWW) + } + } + } + os_log("[SC] 🔍] BlockList endpoint names : %{public}@", log: OSLog.default, type: .info, endpointNames.debugDescription) + + // Find matches + let matches = items.intersection(endpointNames) + if !matches.isEmpty { + os_log("SC] 🔍 endpoint names %{public}@ matched the following list items %{public}@", log: .default, type: .debug, String(describing: endpointNames), String(describing: matches)) + return true + } + + return false +// } + } +} + +// MARK: - NEFilterSocketFlow Extensions + +//extension NEFilterSocketFlow { +// /// You must implement these extensions if your Objective-C code provides them! +// @objc public override var url: URL? { +// // Implement according to your codebase (category/extension in Objective-C) +// return nil // Placeholder +// } +// @objc var remoteHostname: String? { +// // Implement according to your codebase (category/extension in Objective-C) +// return nil // Placeholder +// } +//} diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Constants.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Constants.swift new file mode 100644 index 00000000..115dff7d --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Constants.swift @@ -0,0 +1,59 @@ +// +// Constants.swift +// SelfControl +// +// Created by Satendra Singh on 11/10/25. +// +import os.log + +enum Constants { + /// File name for the JSON file with Safari rules. + static let KEY_UUID = "uuid" + static let KEY_PROCESS_ID = "pid" + static let KEY_PROCESS_ARGS = "args" + static let KEY_PROCESS_NAME = "name" + static let KEY_PROCESS_PATH = "path" + static let KEY_INDEX = "index" + static let KEY_PATH = "paths" + static let KEY_CS_SIGNER = "signatureSigner" + static let KEY_CS_ID = "signatureIdentifier" + static let KEY_CS_INFO = "signingInfo" + static let KEY_CS_AUTHS = "signatureAuthorities" +} + +enum AllowedProcess: String { + case safari = "com.apple.Safari" + case chrome = "com.google.Chrome" + static var allCases: [AllowedProcess] = [.chrome, .safari] + static func isAllowed(_ bundleID: String) -> Bool { + allCases.contains(where: { $0.rawValue == bundleID }) + } + + static func isAllowedProcess(_ process: Process) -> Bool { + let ancestors: [String] = process.ancestors?.value(forKey: "name") as? [String] ?? [] + return allCases.contains(where: { $0.rawValue == process.bundleID }) || ancestors.contains(where: { $0.lowercased().contains("chrome") || $0.lowercased().contains("safari") }) + } + + static func isAllowedProcess(_ process: Process, isSafariExtensionActive: Bool = false, isChromeExtensionActive: Bool = false) -> Bool { + // Determine if the process is Safari or Chrome by bundle ID or ancestor name. + let ancestors: [String] = process.ancestors?.value(forKey: "name") as? [String] ?? [] + let isSafariProcess = + process.bundleID == AllowedProcess.safari.rawValue || + ancestors.contains(where: { $0.lowercased().contains("safari") }) + let isChromeProcess = + process.bundleID == AllowedProcess.chrome.rawValue || + ancestors.contains(where: { $0.lowercased().contains("chrome") }) + + // Allow only if the corresponding extension is active. +// os_log("[SC] 🔍] isSafariProcess: \(isSafariProcess), isChromeProcess: \(isChromeProcess)") + + if isSafariProcess { + return isSafariExtensionActive + } + if isChromeProcess { + return isChromeExtensionActive + } + return false + } + +} diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift new file mode 100644 index 00000000..f2fb7929 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift @@ -0,0 +1,514 @@ +import NetworkExtension +import os.log +import dnssd + +/// FilterDataProvider is a NEFilterDataProvider subclass that intercepts flows and applies a test rule. +class FilterDataProvider: NEFilterDataProvider { + // MARK: - Properties +// private let listner = PlistListner() + /// A dictionary for storing flows related to the same process. + private var relatedFlows: [String: [NEFilterSocketFlow]] = [:] + + // MARK: - Initialization + + override init() { + os_log("[SC] 🔍 NE FilterDataProvider: init") + super.init() +// listner.startListening() + } + + // MARK: - Filter Lifecycle + static let localPort = "8888" + override func startFilter(completionHandler: @escaping (Error?) -> Void) { + os_log("[SC] 🔍 NE FilterDataProvider: Starting filter", log: OSLog.default, type: .info) + let blockedHosts = IPCConnection.shared.blockedUrls + + os_log("[SC] 🔍 NE Blocked blockedUrls: %{public}@", log: OSLog.default, type: .info, String(describing: blockedHosts)) + + // Ensure blockedHosts is an array of String + var filterRules: [NEFilterRule] = blockedHosts.compactMap { address in + let host = address + // For hostname-based rules, you must use NENetworkRule with nil networks (matches all) + let networkRule = NENetworkRule( + remoteNetwork: nil, + remotePrefix: 0, + localNetwork: nil, + localPrefix: 0, + protocol: .any, + direction: .outbound + ) + return NEFilterRule(networkRule: networkRule, action: .filterData) + } + + // Ensure at least one rule exists as required by NEFilterSettings + if filterRules.isEmpty { + // Add a default rule (matches all traffic, for example, and allows it) + let defaultNetworkRule = NENetworkRule( + remoteNetwork: nil, + remotePrefix: 0, + localNetwork: nil, + localPrefix: 0, + protocol: .any, + direction: .any + ) + let defaultRule = NEFilterRule(networkRule: defaultNetworkRule, action: .allow) + filterRules = [defaultRule] + } + + let filterSettings = NEFilterSettings(rules: filterRules, defaultAction: .allow) + + apply(filterSettings) { error in + if let error = error { + os_log("[SC] 🔍 NE Error applying filter settings: %{public}@", log: OSLog.default, type: .error, error.localizedDescription) + } + completionHandler(error) + } + } + + override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { + os_log("[SC] 🔍 NE] FilterDataProvider: Stopping filter with reason %d", log: OSLog.default, type: .info, reason.rawValue) + completionHandler() + } + + // MARK: - Flow Handlings + + + func reverseDNSLookup(ip: String) -> String? { + var hints = addrinfo(ai_flags: AI_NUMERICHOST, ai_family: AF_UNSPEC, + ai_socktype: SOCK_STREAM, ai_protocol: IPPROTO_TCP, + ai_addrlen: 0, ai_canonname: nil, + ai_addr: nil, ai_next: nil) + var res: UnsafeMutablePointer? + + if getaddrinfo(ip, nil, &hints, &res) == 0, let addr = res?.pointee.ai_addr { + var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + if getnameinfo(addr, socklen_t(addr.pointee.sa_len), + &hostBuffer, socklen_t(hostBuffer.count), + nil, 0, NI_NAMEREQD) == 0 { + return String(cString: hostBuffer) + } + } + return nil + } + + func extractHost(from request: String) -> String? { + for line in request.split(separator: "\r\n") { + if line.lowercased().hasPrefix("host:") { + return line.replacingOccurrences(of: "Host:", with: "", options: .caseInsensitive) + .trimmingCharacters(in: .whitespaces) + } + } + return nil + } + + func extractPath(from request: String) -> String? { + guard let firstLine = request.split(separator: "\r\n").first else { return nil } + let comps = firstLine.split(separator: " ") + return comps.count > 1 ? String(comps[1]) : nil + } + + func extractSNI(fromTLSData data: Data) -> String? { + // TLS record starts with 0x16 (Handshake), version bytes, then length + guard data.count > 5, data[0] == 0x16 else { return nil } + + var offset = 5 // Skip record header + + // Verify handshake type = ClientHello (0x01) + guard data.count > offset, data[offset] == 0x01 else { return nil } + offset += 4 // Skip type + length (3 bytes) + + // Skip protocol version (2), random (32), session id length + session id + guard data.count > offset + 34 else { return nil } + offset += 34 + guard data.count > offset else { return nil } + + // Skip session ID + if offset < data.count { + let sessionIDLength = Int(data[offset]) + offset += 1 + sessionIDLength + } + + // Skip cipher suites + guard offset + 2 <= data.count else { return nil } + let cipherSuiteLength = Int(data[offset]) << 8 | Int(data[offset + 1]) + offset += 2 + cipherSuiteLength + + // Skip compression methods + guard offset < data.count else { return nil } + let compressionLength = Int(data[offset]) + offset += 1 + compressionLength + + // Skip extensions length + guard offset + 2 <= data.count else { return nil } + let extensionsLength = Int(data[offset]) << 8 | Int(data[offset + 1]) + offset += 2 + guard offset + extensionsLength <= data.count else { return nil } + + var extOffset = offset + while extOffset + 4 <= offset + extensionsLength { + let extType = Int(data[extOffset]) << 8 | Int(data[extOffset + 1]) + let extLen = Int(data[extOffset + 2]) << 8 | Int(data[extOffset + 3]) + extOffset += 4 + + // Extension type 0 = Server Name + if extType == 0 { + // Parse Server Name extension + var nameOffset = extOffset + 2 // skip list length + while nameOffset + 3 < extOffset + extLen { + let nameType = data[nameOffset] + let nameLen = Int(data[nameOffset + 1]) << 8 | Int(data[nameOffset + 2]) + nameOffset += 3 + if nameType == 0, nameOffset + nameLen <= data.count { + let nameData = data.subdata(in: nameOffset ..< nameOffset + nameLen) + return String(data: nameData, encoding: .utf8) + } + nameOffset += nameLen + } + } + + extOffset += extLen + } + return nil + } + + override func handleOutboundData( + from flow: NEFilterFlow, + readBytesStartOffset offset: Int, + readBytes data: Data + ) -> NEFilterDataVerdict { + var result: NEFilterDataVerdict = .allow() + + guard IPCConnection.shared.isServiceActive else { //Service is inactive + return result + } + + guard IPCConnection.shared.blockedUrls.count > 0 else { //No signinficant urls to block + return result + } + + if RequestSourceAppValidator.isAllowedHost(flow: flow) { + return result + } + + guard let socketFlow = flow as? NEFilterSocketFlow else { + os_log("[SC] 🔍 NE] Not a socket flow. Allowing.", log: OSLog.default, type: .info) + return result + } + if socketFlow.direction != .outbound { + os_log("[SC] 🔍 NE] Not a inbound socket flow. Allowing.", log: OSLog.default, type: .info) + return result + } + + // guard let socketFlow = flow as? NEFilterSocketFlow else { + // return .allow() + // } + let requestString = String(data: data, encoding: .utf8) + if let requestString { + os_log("[SC] 🔍 NE] data HTTP Request: %{public}@", requestString) + } + + // Try HTTP detection + if let requestString = requestString, + requestString.hasPrefix("GET ") || requestString.hasPrefix("POST ") { + + if let host = extractHost(from: requestString), + let path = extractPath(from: requestString) { + let urlString = "http://\(host)\(path)" + os_log("[SC] 🔍 NE] data HTTP Request: %{public}@", urlString) + } + + } else if let sni = extractSNI(fromTLSData: data) { + os_log("[SC] 🔍 NE] data TLS SNI Host: %{public}@", sni) + guard let hostDomain = TLDURLToDomain.getURLDomain(from: sni) else { + return result + } + os_log("[SC] 🔍 NE] data hostDomain: %{public}@", hostDomain) + + for url in IPCConnection.shared.blockedUrls { + if url.contains(hostDomain) { + os_log("[SC] 🔍 NE] data Blocking flow Host match SNI:%{public}@, host:%{public}@", sni, hostDomain) + result = .drop() + break + } + } + } + + return result + } + + // Called for each new flow. + override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { + os_log("[SC] 🔍 NE] FilterDataProvider: handleNewFlow invoked", log: OSLog.default, type: .debug) + guard IPCConnection.shared.isServiceActive else { + return .allow() + } + // Provide peek sizes required by this overload + os_log("[SC] 🔍 NE] FilterDataProvider: handleNewFlow invoked", log: OSLog.default, type: .debug) + guard IPCConnection.shared.blockedUrls.count > 0 else { //No signinficant urls to block + return .allow() + } + + if RequestSourceAppValidator.isAllowedHost(flow: flow) { + return .allow() + } + + guard let socketFlow = flow as? NEFilterSocketFlow else { + return .filterDataVerdict(withFilterInbound: false, + peekInboundBytes: 0, + filterOutbound: true, + peekOutboundBytes: Int.max) + +// return .allow() + } + + // Try to extract SNI hostname (HTTPS) + guard let hostname = socketFlow.remoteHostname else { + return .filterDataVerdict(withFilterInbound: false, + peekInboundBytes: 0, + filterOutbound: true, + peekOutboundBytes: Int.max) + } + + // Fallback to IP address if SNI is not present +// let remoteIP = (socketFlow.remoteEndpoint as? NWHostEndpoint)?.hostname ?? hostname + +// let host = hostname.isEmpty ? remoteIP : hostname + let lower = hostname.lowercased() + os_log("[SC] 🔍 NE] Host SNI host:%{public}@", lower) + guard let hostDomain = TLDURLToDomain.getURLDomain(from: lower) else { + return .filterDataVerdict(withFilterInbound: false, + peekInboundBytes: 0, + filterOutbound: true, + peekOutboundBytes: Int.max) + } + os_log("[SC] 🔍 NE] Host SNI domain:%{public}@", hostDomain) + for url in IPCConnection.shared.blockedUrls { + if url.contains(hostDomain) { + os_log("[SC] 🔍 NE] Blocking flow Host:%{public}@", hostDomain) + return .drop() + } + } + + os_log("[SC] 🔍 NE] Not a blocked domain. Allowing.", log: OSLog.default, type: .info) + return .allow() + + guard let socketFlow = flow as? NEFilterSocketFlow else { + os_log("[SC] 🔍 NE] Not a socket flow. Allowing.", log: OSLog.default, type: .info) + return .allow() + } + if socketFlow.direction != .outbound { + os_log("[SC] 🔍 NE] Not a inbound socket flow. Allowing.", log: OSLog.default, type: .info) + return .allow() + } + var remoteHost = "" + if let flowURL = flow.url { + remoteHost = flowURL.absoluteString + os_log("[SC] 🔍 NE] flow.url: \(remoteHost)") + } else { + if let remoteEndpoint = socketFlow.remoteEndpoint as? NWHostEndpoint { + remoteHost = remoteEndpoint.hostname + let port = remoteEndpoint.port + os_log("[SC] 🔍 NE] NWEndpoint to host: %{public}@, URL: %{public}@, url: %{public}@", log: OSLog.default, type: .debug, remoteHost, port, flow.url?.host() ?? "") + } + } + + + //todo: check host name, work on reverse rule if domain match and if path is nil or blocked, drop connection. +// +// if IPCConnection.shared.blockedList.isMatch(flow as! NEFilterSocketFlow) { +// os_log("[SC] 🔍 NE] Dropped", log: OSLog.default, type: .info) +// return .drop() +// } +// os_log("[SC] 🔍 NE] Allowed", log: OSLog.default, type: .info) +// +// return .allow() + + os_log("[SC] 🔍 NE] Flow from remote endpoint: %{public}@, URL: %{public}@", log: OSLog.default, type: .debug, socketFlow.remoteEndpoint.debugDescription, flow.url?.description ?? "nil") + + if remoteHost.isEmpty { //Unable to get host + return .allow() + } + if remoteHost.isValidIpAddress { + if IPCConnection.shared.blockedIPAddresses.contains(remoteHost) { + os_log("[SC] 🔍 NE] Blocking flow IP match: %{public}@", remoteHost) + return .drop() + } + if let host = ReverseDomainMapper.reverseDNSUsingGetNameInfo(ipAddress: remoteHost) { + os_log("[SC] 🔍 NE] Converted IP: %{public}@ to: %{public}@", remoteHost, host) + remoteHost = host + } else { //unable to convert, simple ignore + return .allow() + } + } else { + return .allow() + } + guard let hostDomain = TLDURLToDomain.getURLDomain(from: remoteHost) else { + return .allow() + } + for url in IPCConnection.shared.blockedUrls { + if url.contains(hostDomain) { + os_log("[SC] 🔍 NE] Blocking flow Host match %{public}@, %{public}@", remoteHost, hostDomain) + return .drop() + } + } + + return .allow() + + // Process the flow and decide a verdict. + let verdict = processEvent(for: socketFlow) + os_log("[SC] 🔍 NE] Verdict for flow: %{public}@", log: OSLog.default, type: .debug, verdict.debugDescription) + return verdict + } + +// override func handleInboundDataComplete(for flow: NEFilterFlow) -> NEFilterDataVerdict { +// if let socketFlow = flow as? NEFilterSocketFlow { +// if let data = socketFlow.readData { +// if let requestString = String(data: data, encoding: .utf8) { +// if requestString.hasPrefix("GET") || requestString.hasPrefix("POST") { +// print("Request: \(requestString)") +// // Parse the path here +// } +// } +// } +// } +// return .allow() +// } +// +// override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { +// return .filterDataVerdict(withFilterInbound: true, +// peekInboundBytes: 1024, +// filterOutbound: true, +// peekOutboundBytes: 1024) +// } + + + override func handleInboundData(from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes: Data) -> NEFilterDataVerdict { + if let requestString = String(data: readBytes, encoding: .utf8) { + print("Inbound data: \(requestString)") + os_log("[SC] 🔍 NE] handleInboundData: %{public}@", requestString) + + if requestString.contains("facebook.com/friends") { + return .drop() + } + } + return .allow() + } + + override func displayMessage(_ message: String, completionHandler: @escaping (Bool) -> Void) { + return completionHandler(true) + } + +// override func handleOutboundData(from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes: Data) -> NEFilterDataVerdict { +// if let urlString = flow.url?.absoluteString { +// os_log("[SC] 🔍 NE] handleOutboundData URL: %{public}@", urlString) +// if urlString.contains("facebook.com/friends") { +// return .drop() +// } +// } +// +// if let requestString = String(data: readBytes, encoding: .utf8) { +// print("Outbound data: \(requestString)") +// os_log("[SC] 🔍 NE] handleOutboundData: %{public}@", requestString) +// +// if requestString.contains("facebook.com/friends") { +// return .drop() +// } +// } +// return .allow() +// } + +// override func handleOutboundData(from flow: NEFilterFlow, +// readBytesStartOffset offset: Int, +// readBytes: Data, +// completionHandler: @escaping (NEFilterDataVerdict) -> Void) { +// +// if let requestString = String(data: readBytes, encoding: .utf8) { +// print("Outbound data: \(requestString)") +// +// if requestString.contains("facebook.com") { +// completionHandler(.drop()) +// return +// } +// } +// +// // If undecided, ask for more data +// completionHandler(.allow()) +// } + + /// Processes the flow and returns a verdict. + /// This is a simplified test rule that blocks flows destined for "example.com". + private func processEvent(for flow: NEFilterSocketFlow) -> NEFilterNewFlowVerdict { + guard let endpoint = flow.remoteEndpoint as? NWHostEndpoint else { + return .allow() + } + os_log("[SC] 🔍 NE] processEvent endpoint.hostname: %{public}@ ", endpoint.hostname) + os_log("[SC] 🔍 NE] processEvent remoteHostname: %{public}@ ", flow.remoteHostname ?? "NOTHING") + guard let host = flow.remoteHostname?.lowercased().domainString else { + os_log("[SC] 🔍 NE] processEvent No Host") + return .allow() + } +// os_log("[SC] 🔍 NE] This is localFlowEndpoint: %{public}@ ", flow.localFlowEndpoint?.debugDescription ?? "NOTHING") + let blockedHosts = IPCConnection.shared.blockedUrls + +// if host == "google.com" || host == "8.8.8.8" { + if blockedHosts.contains(host) { + os_log("[SC] 🔍 NE] processEvent: Blocking flow to processEvent %{public}@ ", host) + return .drop() +// return .allow() + } + // Optionally log other flows for debugging + os_log("[SC] 🔍 NE] processEvent: Allowing flow to %{public}@", log: OSLog.default, type: .info, host) + return .allow() + } + + + // MARK: - (Optional) Handling Related Flows & Alerts + + /// Adds a flow to a list of related flows for a given key. + private func addRelatedFlow(forKey key: String, flow: NEFilterSocketFlow) { + os_log("[SC] 🔍 NE] Adding related flow for key: %@", log: OSLog.default, type: .debug, key) + if relatedFlows[key] == nil { + relatedFlows[key] = [] + } + relatedFlows[key]?.append(flow) + } + + /// Processes related flows once a decision is made for a given key. + private func processRelatedFlows(forKey key: String) { + guard let flows = relatedFlows[key] else { + os_log("[SC] 🔍 NE] No related flows for key: %@", log: OSLog.default, type: .debug, key) + return + } + for flow in flows { + let verdict = processEvent(for: flow) + resumeFlow(flow, with: verdict) + } + relatedFlows[key] = nil + } + + /// A stub method for resuming a flow with a verdict. + private func resumeFlow(_ flow: NEFilterSocketFlow, with verdict: NEFilterNewFlowVerdict) { + // In a complete implementation, this would resume the paused flow with the provided verdict. + os_log("[SC] 🔍 NE] Resuming flow %@ with verdict %@", log: OSLog.default, type: .info, flow.debugDescription, verdict.debugDescription) + } + + /// A stub method to simulate alerting the user. + /// In a complete implementation, this might trigger an IPC to your app for user intervention. + private func alertUser(for flow: NEFilterSocketFlow) { + os_log("[SC] 🔍 NE] Alert: User decision needed for flow %@", log: OSLog.default, type: .info, flow.debugDescription) + } + +// func processFromflow(flow: NEFilterFlow) -> Process? { +// guard let auditTokenData = flow.sourceAppAuditToken else { +// return nil +// } +// +// // Convert NSData → audit_token_t +// var auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in +// return ptr.load(as: audit_token_t.self) +// } +// return Process.init(&auditToken) +// } + } +//https://developer.chrome.com/docs/extensions/how-to/distribute/install-extensions + diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/HostToIpMapping.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/HostToIpMapping.swift new file mode 100644 index 00000000..8857b4f0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/HostToIpMapping.swift @@ -0,0 +1,82 @@ +// +// HostToIpMapping.swift +// SelfControl +// +// Created by Satendra Singh on 20/10/25. +// + +import Foundation + +class HostToIpMapping { + static func string(for addressData: Data) throws -> String { + var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + + let result = addressData.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> Int32 in + guard let addrPtr = ptr.baseAddress else { + return EINVAL + } + return getnameinfo( + addrPtr.assumingMemoryBound(to: sockaddr.self), + socklen_t(addressData.count), + &hostBuffer, + socklen_t(hostBuffer.count), + nil, + 0, + NI_NUMERICHOST + ) + } + + if result != 0 { + let message = String(cString: gai_strerror(result)) + throw NSError(domain: "HostToIpMappingError", code: Int(result), userInfo: [ + NSLocalizedDescriptionKey: message + ]) + } + + return String(cString: hostBuffer) + } + + static func ipAddresses(for domainName: String) -> [String] { + let startTime = Date() + + // ✅ Correct initialization + let cfHostOpt: CFHost? = CFHostCreateWithName(kCFAllocatorDefault, domainName as CFString).takeRetainedValue() + guard let cfHost = cfHostOpt else { + print("HostToIpMapping: Failed to create CFHost for \(domainName)") + return [] + } + + var streamError = CFStreamError() + let success = CFHostStartInfoResolution(cfHost, .addresses, &streamError) + + if !success || streamError.error != 0 { + print("HostToIpMapping: Warning: failed to resolve addresses for \(domainName) with stream error \(streamError.error)") + return [] + } + + // ✅ Use takeUnretainedValue safely + guard let addressArray = CFHostGetAddressing(cfHost, nil)? + .takeUnretainedValue() as? [Data] else { + print("HostToIpMapping: Warning: failed to resolve addresses for \(domainName)") + return [] + } + + var stringAddresses: [String] = [] + + for addrData in addressArray { + do { + let ip = try string(for: addrData) + stringAddresses.append(ip) + } catch { + print("HostToIpMapping: Warning: Failed to parse IP struct for \(domainName) with error: \(error.localizedDescription)") + } + } + + let elapsed = Date().timeIntervalSince(startTime) + if elapsed > 2.5 { + print("HostToIpMapping: Warning: took \(elapsed) seconds to resolve \(domainName)") + } + + return stringAddresses + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift new file mode 100644 index 00000000..c88693a2 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift @@ -0,0 +1,212 @@ +/* + See the LICENSE.txt file for this sample’s licensing information. + + Abstract: + This file contains the implementation of the app <-> provider IPC connection + */ + +import Foundation +import os.log +import Network + +/// The IPCConnection class is used by both the app and the system extension to communicate with each other +class IPCConnection: NSObject { + + // MARK: Properties + + var listener: NSXPCListener? + var currentConnection: NSXPCConnection? + weak var delegate: ExtensionToApp? + static let shared = IPCConnection() +// var blockedUrls: [String] = ProxyPreferences.getBlockedDomains() + var blockedUrls: [String] = [String]() + var blockedList = BlockOrAllowList(items: []) + var blockedIPAddresses: Set = [] + private(set) var isSafariExtensionEnable: Bool = false + private(set) var isGoogleChromeEnabled: Bool = false + // Published extension state for UI/observers + private(set) var isServiceActive: Bool = false + + // MARK: Methods + + /** + The NetworkExtension framework registers a Mach service with the name in the system extension's NEMachServiceName Info.plist key. + The Mach service name must be prefixed with one of the app groups in the system extension's com.apple.security.application-groups entitlement. + Any process in the same app group can use the Mach service to communicate with the system extension. + */ + private func extensionMachServiceName(from bundle: Bundle) -> String { + + guard let networkExtensionKeys = bundle.object(forInfoDictionaryKey: "NetworkExtension") as? [String: Any], + let machServiceName = networkExtensionKeys["NEMachServiceName"] as? String else { +// fatalError("Mach service name is missing from the Info.plist") + os_log("[SC] 🔍] Mach service name is missing from the Info.plist") + return "" + } + + return machServiceName + } + + func startListener() { + + let machServiceName = extensionMachServiceName(from: Bundle.main) + //X6FQ433AWK.com.application.SelfControl.corebits.network + os_log("[SC] 🔍] Starting XPC listener for mach service %{public}@", machServiceName) + + let newListener = NSXPCListener(machServiceName: machServiceName) + newListener.delegate = self + newListener.resume() + listener = newListener + } + + /// This method is called by the app to register with the provider running in the system extension. + func register(completionHandler: @escaping (Bool) -> Void) { } + + /** + This method is called by the provider to cause the app (if it is registered) to display a prompt to the user asking + for a decision about a connection. + */ + func promptUser(aboutFlow flowInfo: [String: String], responseHandler:@escaping (Bool) -> Void) -> Bool { + + guard let connection = currentConnection else { + os_log("[SC] 🔍] Cannot prompt user because the app isn't registered") + return false + } + + guard let appProxy = connection.remoteObjectProxyWithErrorHandler({ promptError in + os_log("[SC] 🔍] Failed to prompt the user: %{public}@", promptError.localizedDescription) + self.currentConnection = nil + responseHandler(true) + }) as? ExtensionToApp else { + os_log("Failed to create a remote object proxy for the app") + return false + } + + appProxy.promptUser(aboutFlow: flowInfo, responseHandler: responseHandler) + + return true + } +} + +extension IPCConnection: NSXPCListenerDelegate { + + // MARK: NSXPCListenerDelegate + + func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { + os_log("[SC] 🔍] NE shouldAcceptNewConnection:" ) + // The exported object is this IPCConnection instance. + newConnection.exportedInterface = NSXPCInterface(with: AppToExtensionExtension.self) + newConnection.exportedObject = self + + // The remote object is the delegate of the app's IPCConnection instance. + newConnection.remoteObjectInterface = NSXPCInterface(with: ExtensionToApp.self) + + newConnection.invalidationHandler = { + os_log("[SC] 🔍] NE invalidationHandler:" ) + self.currentConnection = nil + } + + newConnection.interruptionHandler = { + os_log("[SC] 🔍] NE interruptionHandler:" ) + self.currentConnection = nil + } + + currentConnection = newConnection + newConnection.resume() + + return true + } + +// func enableURLBlocking(_ urls: [String]) { +// os_log("[SC] 🔍] Enabling URL blocking") +// guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in +// os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) +// }) as? AppToExtensionExtension else { +// os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") +// return +// } +// providerProxy.setBlockedURLs(urls) +// } + + func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { + os_log("[SC] 🔍] NE sendMessageToSetActiveBrowserExtension:\(extensionTypeRawValue), state:\(state)") + guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in + os_log("[SC] 🔍] NE sendMessageToSetActiveBrowserExtension: %{public}@", registerError.localizedDescription) + }) as? AppToExtensionExtension else { + os_log("[SC] 🔍] NE Failed to create a remote object proxy for the provider") + return + } + providerProxy.setActiveBrowserExtension(extensionTypeRawValue, state: state) + } + + func sendMessageToEnableNetworkExtension(_enable: Bool) -> Bool { + os_log("[SC] 🔍] NE sendMessageToEnableNetworkExtension state:\(_enable)") + guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in + os_log("[SC] 🔍] NE sendMessageToEnableNetworkExtension: %{public}@", registerError.localizedDescription) + }) as? AppToExtensionExtension else { + os_log("[SC] 🔍] NE Failed to create a remote object proxy for the provider") + return false + } + providerProxy.setEnableService(_enable) + return true + } +} + + +extension IPCConnection: AppToExtensionExtension { + + func setEnableService(_ enable: Bool) { + os_log("[SC] 🔍] NE setEnableService: %{public}d", enable) + self.isServiceActive = enable + } + + func setBlockedIPAddresses(_ ips: [String]) { + blockedIPAddresses = Set(ips) + os_log("[SC] 🔍] NE setBlockedIPAddresses: %{public}@", blockedIPAddresses) + } + + func setBlockedURLs(_ urls: [String]) { + os_log("[SC] 🔍] NE Extension Received Blocking: %{public}@",urls) + blockedUrls = urls +// delegate?.didSetUrls() + blockedList = BlockOrAllowList(items: blockedUrls) + + guard let connection = currentConnection else { + os_log("[SC] 🔍] NE Cannot update blocked urls, app isn't registered") + return + } + + guard let appProxy = connection.remoteObjectProxyWithErrorHandler({ promptError in + os_log("[SC] 🔍] NE Failed to create a remote object proxy for the app: %{public}@", promptError.localizedDescription) +// self.currentConnection = nil +// responseHandler(true) + }) as? ExtensionToApp else { + os_log("[SC] 🔍] NE Failed to create a remote object proxy for the app") + return + } + appProxy.didSetUrls() + } + + func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { + // Map the raw value to the Swift enum if possible + os_log("[SC] 🔍] NE Received extension state to %{public}@", extensionTypeRawValue) + if let mapped = ActiveBrowserExtensios(rawValue: extensionTypeRawValue) { + switch mapped { + case .safari: + self.isSafariExtensionEnable = state + case .chrome: + self.isGoogleChromeEnabled = state + } + os_log("[SC] 🔍] NE Set browser extension state to %{public}@, state: %{public}d", extensionTypeRawValue, state) + } else { + os_log("[SC] 🔍] NE Unknown browser extension type %{public}@", extensionTypeRawValue) + } + } + + // MARK: ProviderCommunication + + func register(_ completionHandler: @escaping (Bool) -> Void) { + + os_log("[SC] 🔍] App registered") + completionHandler(true) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnectionProxy.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnectionProxy.swift new file mode 100644 index 00000000..1bbece35 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnectionProxy.swift @@ -0,0 +1,36 @@ +// +// IPCConnectionProxy.swift +// SelfControl +// +// Created by Satendra Singh on 17/05/26. +// + +import Foundation + +class IPCConnectionProxy: NSObject, AppToExtensionExtension { + + func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { + HelperConnection.shared.sendMessageToSetActiveBrowserExtension(extensionTypeRawValue, state: state) + } + + + func register(completionHandler: @escaping (Bool) -> Void) { + HelperConnection.shared.register(completionHandler: completionHandler) + } + + func register(_ completionHandler: @escaping (Bool) -> Void) { + HelperConnection.shared.register(completionHandler) + } + + func setBlockedURLs(_ urls: [String]) { + HelperConnection.shared.setBlockedURLs(urls) + } + + func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { + HelperConnection.shared.setActiveBrowserExtension(extensionTypeRawValue, state: state) + } + + func setEnableService(_ enable: Bool) { + HelperConnection.shared.setEnableService(enable) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Info.plist b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Info.plist new file mode 100644 index 00000000..cf77feef --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Info.plist @@ -0,0 +1,16 @@ + + + + + NetworkExtension + + NEMachServiceName + $(TeamIdentifierPrefix)com.application.SelfControl.corebits.network + NEProviderClasses + + com.apple.networkextension.filter-data + $(PRODUCT_MODULE_NAME).FilterDataProvider + + + + diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Binary.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Binary.swift new file mode 100644 index 00000000..3397339e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Binary.swift @@ -0,0 +1,175 @@ +// +// Binary.swift +// SelfControl +// +// Created by Satendra Singh on 10/10/25. +// + +import Foundation +import AppKit +import OSLog +import Security +import CoreServices // for MDItem + +@objcMembers +class Binary: NSObject { + + // MARK: - Properties (mirroring Binary.h) + + dynamic var path: String + dynamic var name: String = "" + dynamic var icon: NSImage = NSImage() + dynamic var attributes: NSDictionary? + dynamic var metadata: NSDictionary? + dynamic var bundle: Bundle? + dynamic var csInfo: NSMutableDictionary = NSMutableDictionary() + dynamic var sha256: NSMutableString = NSMutableString() + + // MARK: - Init + + init(_ path: String) { + self.path = (path as NSString).resolvingSymlinksInPath + super.init() + + // Try load app bundle (nil for non-apps) + self.getBundle() + + // Get name + self.getName() + + // File attributes + self.getAttributes() + + // Spotlight metadata + self.getMetadata() + } + + // MARK: - Bundle + + // Try load app bundle; nil for non-apps + func getBundle() { + // First try direct path + if let b = Bundle(path: self.path) { + self.bundle = b + return + } + // Else find dynamically + self.bundle = Utilities().findAppBundle(self.path) + } + + // MARK: - Name + + // Figure out binary's name via bundle CFBundleName or lastPathComponent + func getName() { + if let bundle = self.bundle, + let bundleName = bundle.infoDictionary?["CFBundleName"] as? String { + self.name = bundleName + } else { + self.name = (self.path as NSString).lastPathComponent + } + } + + // MARK: - Attributes + + func getAttributes() { + self.attributes = try? FileManager.default.attributesOfItem(atPath: self.path) as NSDictionary + } + + // MARK: - Spotlight metadata + + func getMetadata() { + // MDItemCreate wants CFString path + let cfPath = self.path as CFString + guard let mdItem = MDItemCreate(kCFAllocatorDefault, cfPath) else { + return + } + defer { } + + guard let attributeNames = MDItemCopyAttributeNames(mdItem) else { + return + } + defer { } + + if let attrs = MDItemCopyAttributes(mdItem, attributeNames) { + self.metadata = attrs as NSDictionary + } + } + + // MARK: - Icon + + // Get an icon for the process: app’s icon or system one + func getIcon() { + // Skip short/non-absolute paths if no bundle (system logs errors otherwise) + if !self.path.hasPrefix("/"), self.bundle == nil { + return + } + + // For apps, try CFBundleIconFile + if let bundle = self.bundle { + if let iconFile = bundle.infoDictionary?["CFBundleIconFile"] as? String { + let iconExt = (iconFile as NSString).pathExtension.isEmpty ? "icns" : (iconFile as NSString).pathExtension + let iconBase = (iconFile as NSString).deletingPathExtension + if let iconPath = bundle.path(forResource: iconBase, ofType: iconExt) { + if let img = NSImage(contentsOfFile: iconPath) { + self.icon = img + } + } + } + } + + // Fallback to workspace icon + if self.bundle == nil || self.icon.size == .zero { + self.icon = NSWorkspace.shared.icon(forFile: self.path) + } + + // Standard size 128x128 + self.icon.size = NSSize(width: 128, height: 128) + } + + // MARK: - Code signing (static) + + // You likely have this helper already, but defining its expected signature for clarity: + func extractSigningInfo(_ code: SecStaticCode?, _ path: String, _ flags: SecCSFlags) -> [String: Any]? { + var staticCode: SecStaticCode? + let url = URL(fileURLWithPath: path) as CFURL + let status = SecStaticCodeCreateWithPath(url, SecCSFlags(), &staticCode) + guard status == errSecSuccess, let codeRef = staticCode else { + return nil + } + + var signingInfo: CFDictionary? + let infoStatus = SecCodeCopySigningInformation(codeRef, flags, &signingInfo) + guard infoStatus == errSecSuccess, let info = signingInfo as? [String: Any] else { + return nil + } + return info + } + + // Your corrected function: + func generateSigningInfo(_ flags: SecCSFlags) { + // These constants are C macros in Security framework headers, not imported automatically: + let kSecCodeInfoStatus = "Status" // corresponds to kSecCodeInfoStatus in Security framework + let KEY_CS_STATUS = kSecCodeInfoStatus // for compatibility with your existing code + + // Instead of 'nil', explicitly pass an optional SecStaticCode? = nil + if let extracted = extractSigningInfo(nil as SecStaticCode?, self.path, flags), + let statusNum = extracted[KEY_CS_STATUS] as? NSNumber, + statusNum.intValue == Int(noErr) { + // Bridge [String: Any] to NSMutableDictionary + self.csInfo = NSMutableDictionary(dictionary: extracted) + } else { + // logging commented out in original code + } + } + + // MARK: - Description + + override var description: String { + return String(format: "name: %@\npath: %@\nattributes: %@\nsigning info: %@\nmetadata: %@", + self.name, + self.path, + self.attributes ?? [:], + self.csInfo, + self.metadata ?? [:]) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Process.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Process.swift new file mode 100644 index 00000000..823bb5b2 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Process.swift @@ -0,0 +1,330 @@ +// +// Process.swift +// SelfControl +// +// Created by Satendra Singh on 10/10/25. +// + +import Foundation +import OSLog +import Security +import Darwin +import NetworkExtension + +@objcMembers +class Process: NSObject { + + // MARK: - Properties (mirroring Process.h) + + dynamic var pid: pid_t = -1 + dynamic var uid: uid_t = UInt32(bitPattern: -1) + dynamic var type: UInt16 = 0 + dynamic var exit: UInt32 = UInt32(bitPattern: -1) + dynamic var deleted: Bool = false + + dynamic var name: String? + dynamic var path: String? + dynamic var arguments: NSMutableArray? = NSMutableArray() + dynamic var ancestors: NSMutableArray? = NSMutableArray() + dynamic var csInfo: NSMutableDictionary? + dynamic var key: String = "" + dynamic var bundleID: String = "" + dynamic var binary: Binary = Binary.init("") + dynamic var timestamp: Date = Date() + + // MARK: - Init + + override init() { + super.init() + self.arguments = NSMutableArray() + self.ancestors = NSMutableArray() + self.timestamp = Date() + self.pid = -1 + self.uid = UInt32(bitPattern: -1) + self.exit = UInt32(bitPattern: -1) + } + + // Init with audit token pointer + // Matches -(id)init:(audit_token_t*)token + convenience init?(_ token: UnsafePointer) { + self.init() + + // Save pid + let tokenVal = token.pointee + let procPID = audit_token_to_pid(tokenVal) + self.pid = procPID + if self.pid == 0 { +// os_log_error(logHandle, "ERROR: 'audit_token_to_pid' returned NULL") + return nil + } + + // Get path via SecCode (also sets deleted flag) + self.getPath(token) + if (self.path ?? "").isEmpty { +// os_log_error(logHandle, "ERROR: failed to find path for process %d", self.pid) + return nil + } + + // Set name + if self.deleted != true { + self.name = Utilities().getProcessName(0, self.path!) + } else { + self.name = Utilities().getProcessName(self.pid, self.path!) + } + + // Get user + self.uid = audit_token_to_euid(tokenVal) + self.bundleID = Utilities().getBundleID(self.path ?? "") ?? "" + // Generate (dynamic) code information + self.generateSigningInfo(token) + + // Generate key + self.key = self.generateKey() + + // Init binary + self.binary = Binary(self.path ?? "") + + // PID specific logic: args and ancestors + self.getArgs() + self.ancestors = Utilities().generateProcessHierarchy(self.pid) + + // Verify pid-version (avoid pid reuse) + if let currentTokenNSData = Utilities().tokenForPid(self.pid), + currentTokenNSData.length == MemoryLayout.size { + let currentTokenData = Data(referencing: currentTokenNSData) + currentTokenData.withUnsafeBytes { (rawPtr: UnsafeRawBufferPointer) in + if let curPtr = rawPtr.baseAddress?.assumingMemoryBound(to: audit_token_t.self) { + let origVersion = audit_token_to_pidversion(tokenVal) + let curVersion = audit_token_to_pidversion(curPtr.pointee) + if origVersion != curVersion { +// os_log_error(logHandle, "ERROR: audit token mismatch ...pid re-used?") + self.arguments = nil + self.ancestors = nil + } + } + } + } + + // Process alive check + if Utilities().isAlive(self.pid) != true { +// os_log_error(logHandle, "ERROR: process (%d)%{public}@ has already exited", self.pid, self.path ?? "") + return nil + } + } + + // MARK: - Key generation + + // Matches -(NSString*)generateKey + func generateKey() -> String { + var id: String = "" + var signer: Int = 0 + enum Signer: Int { + case None = 0 + case Apple + case AppStore + case DevID + case AdHoc + } + + if let cs = self.csInfo { + if let v = cs[Constants.KEY_CS_SIGNER] as? NSNumber { + signer = v.intValue + } + + // Apple/App Store: just use cs id + if signer == Signer.Apple.rawValue || signer == Signer.AppStore.rawValue { + if let csid = cs[Constants.KEY_CS_ID] as? String, !csid.isEmpty { + id = csid + } + } + // Dev ID: use cs id + leaf signer + else if signer == Int(Signer.DevID.rawValue) { + if let csid = cs[Constants.KEY_CS_ID] as? String, !csid.isEmpty, + let auths = cs[Constants.KEY_CS_AUTHS] as? [Any], let first = auths.first as? String, !first.isEmpty { + id = "\(csid):\(first)" + } + } + } + + if id.isEmpty { + id = self.path ?? "" + } + +// os_log_debug(logHandle, "generated process key: %{public}@", id) + return id + } + + // MARK: - Path + + // Matches -(void)getPath:(audit_token_t*)token + func getPath(_ token: UnsafePointer) { + var status: OSStatus = errSecParam + var code: SecCode? + var staticCode: SecStaticCode? + var pathURL: CFURL? + + // SecCodeCopyGuestWithAttributes using audit token + let attrs: [String: Any] = [kSecGuestAttributeAudit as String: Data(bytes: token, count: MemoryLayout.size)] + status = SecCodeCopyGuestWithAttributes(nil, attrs as CFDictionary, SecCSFlags(), &code) + if status == errSecSuccess, let code = code { + // Convert dynamic code (SecCode) to static code (SecStaticCode) + status = SecCodeCopyStaticCode(code, SecCSFlags(), &staticCode) + if status == errSecSuccess, let staticCode = staticCode { + status = SecCodeCopyPath(staticCode, SecCSFlags(), &pathURL) + if status == errSecSuccess, let url = pathURL as NSURL? { + self.path = (url as URL).path + } else { +// os_log_error(logHandle, "ERROR: 'SecCodeCopyPath' failed with': %#x", status) + } + } else { +// os_log_error(logHandle, "ERROR: 'SecCodeCopyStaticCode' failed with': %#x", status) + } + } else { +// os_log_error(logHandle, "ERROR: 'SecCodeCopyGuestWithAttributes' failed with': %#x", status) + } + + // Deleted binary? + if status == OSStatus(kPOSIXErrorENOENT) { +// os_log_debug(logHandle, "process %d's binary appears to be deleted", self.pid) + self.deleted = true + } + + // Fallback + if pathURL == nil { + self.path = Utilities().getProcessPath(self.pid) + } + + if let p = self.path { + self.path = (p as NSString).resolvingSymlinksInPath + } + + // No CFRelease in Swift ARC; CoreFoundation objects are memory-managed automatically. + } + + // MARK: - Args + + // Matches -(void)getArgs + func getArgs() { + var mib: [Int32] = [CTL_KERN, KERN_ARGMAX, 0] + var systemMaxArgs: Int32 = 0 + var size = MemoryLayout.size(ofValue: systemMaxArgs) + + // Get system arg max + if sysctl(&mib, 2, &systemMaxArgs, &size, nil, 0) == -1 { + return + } + + guard systemMaxArgs > 0 else { return } + let bufSize = Int(systemMaxArgs) + let processArgs = UnsafeMutablePointer.allocate(capacity: bufSize) + defer { processArgs.deallocate() } + + mib = [CTL_KERN, KERN_PROCARGS2, Int32(self.pid)] + size = bufSize + + if sysctl(&mib, 3, processArgs, &size, nil, 0) == -1 { + return + } + + if size <= MemoryLayout.size { + return + } + + // numberOfArgs at start + var numberOfArgs: Int32 = 0 + memcpy(&numberOfArgs, processArgs, MemoryLayout.size) + + // parser after numberOfArgs + var parser = processArgs.advanced(by: MemoryLayout.size) + let endPtr = processArgs.advanced(by: size) + + // Skip executable path (NULL-terminated) + while parser < endPtr { + if parser.pointee == 0 { break } + parser = parser.advanced(by: 1) + } + if parser == endPtr { return } + + // Skip trailing NULLs to argv[0] + while parser < endPtr { + if parser.pointee != 0 { break } + parser = parser.advanced(by: 1) + } + if parser == endPtr { return } + + // Now parse args until count reached + var argStart = parser + let argsArray = NSMutableArray() + while parser < endPtr { + if parser.pointee == 0 { + if let arg = String(validatingUTF8: UnsafePointer(argStart)) { + argsArray.add(arg) + } + parser = parser.advanced(by: 1) + argStart = parser + if argsArray.count == Int(numberOfArgs) { + break + } + continue + } + parser = parser.advanced(by: 1) + } + + self.arguments = argsArray + } + + // MARK: - Code signing + + // Local key mapping for Security's kSecCodeInfoStatus + private let KEY_CS_STATUS = "Status" + + // Helper to extract signing info from an audit token (dynamic code) + private func extractSigningInfo(_ token: UnsafeMutablePointer, + _ requirement: SecRequirement?, + _ flags: SecCSFlags) -> NSMutableDictionary? { + // Build attributes with audit token + let attrs: [String: Any] = [kSecGuestAttributeAudit as String: + Data(bytes: token, count: MemoryLayout.size)] + var code: SecCode? + var status = SecCodeCopyGuestWithAttributes(nil, attrs as CFDictionary, flags, &code) + guard status == errSecSuccess, let codeRef = code else { + return nil + } + + // Convert SecCode (dynamic) to SecStaticCode before querying signing info + var staticCode: SecStaticCode? + status = SecCodeCopyStaticCode(codeRef, flags, &staticCode) + guard status == errSecSuccess, let staticCodeRef = staticCode else { + return nil + } + + var info: CFDictionary? + status = SecCodeCopySigningInformation(staticCodeRef, flags, &info) + guard status == errSecSuccess, let dict = info as? [String: Any] else { + return nil + } + return NSMutableDictionary(dictionary: dict) + } + + // Matches -(void)generateSigningInfo:(audit_token_t*)token + func generateSigningInfo(_ token: UnsafePointer) { + // Pass typed nil for requirement to avoid "nil requires a contextual type" + let requirement: SecRequirement? = nil + if let extracted = extractSigningInfo(UnsafeMutablePointer(mutating: token), requirement, SecCSFlags()) { + if let statusNum = extracted[KEY_CS_STATUS] as? NSNumber, statusNum.intValue == Int(noErr) { + self.csInfo = extracted + } else { +// os_log_error(logHandle, "ERROR: invalid code signing information for %{public}@: %{public}@", self.path ?? "", extracted) + } + } else { +// os_log_error(logHandle, "ERROR: failed to extract code signing information for %{public}@", self.path ?? "") + } + } + + // MARK: - Description + + override var description: String { + return String(format: "pid: %d\npath: %@\nuser: %d\nargs: %@\nancestors: %@\n signing info: %@\n binary:\n%@", self.pid, self.path ?? "nil", self.uid, self.arguments ?? [], self.ancestors ?? [], self.csInfo ?? [:], self.binary.description) + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/SourceAppAuditTokenBuilder.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/SourceAppAuditTokenBuilder.swift new file mode 100644 index 00000000..68b96414 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/SourceAppAuditTokenBuilder.swift @@ -0,0 +1,61 @@ +// +// SourceAppAuditTokenBuilder.swift +// SelfControlExtension +// +// Created by Satendra Singh on 10/10/25. +// + +import NetworkExtension +import Security +import Foundation +import AppKit + +struct SourceAppAuditTokenBuilder { + + static func getAppInfo(from flow: NEFilterFlow) -> (bundleID: String?, appName: String?) { + guard let auditTokenData = flow.sourceAppAuditToken else { + return (nil, nil) + } + + // Convert NSData → audit_token_t + let auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in + return ptr.load(as: audit_token_t.self) + } + + // MARK: 1. Extract PID from audit token + let pid = audit_token_to_pid(auditToken) + + // MARK: 2. Use SecTask to get bundle identifier + var bundleID: String? = nil + if let secTask = SecTaskCreateWithAuditToken(nil, auditToken) { + // macOS doesn’t define kSecEntitlementApplicationIdentifier constant, + // so use the raw entitlement string instead: + let entitlementKey = "application-identifier" as CFString + bundleID = SecTaskCopyValueForEntitlement(secTask, entitlementKey, nil) as? String + } + + // MARK: 3. Try to get app name from bundle ID + var appName: String? = nil + if let bundleID = bundleID, + let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID) { + appName = appURL.deletingPathExtension().lastPathComponent + } + + // MARK: 4. Fallback: get executable name via proc_pidpath() + if appName == nil { + var pathBuffer = [CChar](repeating: 0, count: Int(PATH_MAX)) + let result = proc_pidpath(pid, &pathBuffer, UInt32(pathBuffer.count)) + if result > 0 { + let path = String(cString: pathBuffer) + appName = URL(fileURLWithPath: path).lastPathComponent + } + } + + return (bundleID, appName) + } + + // Helper: extract PID from audit_token_t + private static func audit_token_to_pid(_ token: audit_token_t) -> pid_t { + return pid_t(token.val.0) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Utilities.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Utilities.swift new file mode 100644 index 00000000..0ee82162 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Utilities.swift @@ -0,0 +1,714 @@ +// +// File.swift +// SelfControl +// +// Created by Satendra Singh on 10/10/25. +// + + +// Swift port of utilities.m +// +// Note: This file relies on C system APIs. Ensure your bridging header +// imports the needed headers: libproc.h, sys/sysctl.h, CommonCrypto/CommonCrypto.h, +// mach/mach.h, Security/Security.h, Carbon/Carbon.h, CoreServices/CoreServices.h, CFNetwork/CFHost.h +// + +import AppKit +import Foundation +import OSLog +import SystemConfiguration +import Darwin +import CoreServices // for MDItem +import Security +import os.log +import os + +// Define C macro from libproc.h (not imported automatically into Swift) +private let PROC_PIDPATHINFO_MAXSIZE: Int = 4096 + +// MARK: - Globals + +final class Utilities { + // Provided elsewhere in project + + let logger = Logger(subsystem: "com.example.myapp", category: "network") + + // MARK: - Version / Bundle + + @objc + public func getAppVersion() -> String? { + return Bundle.main.infoDictionary?["CFBundleVersion"] as? String + } + + @objc + public func getBundleExecutable(_ appPath: String) -> String? { + guard let bundle = Bundle(path: appPath) else { + logger.error("ERROR: failed to load app bundle for %{public}\(appPath)") + return nil + } + return (bundle.executablePath as NSString?)?.resolvingSymlinksInPath + } + + // MARK: - Parent process (Carbon/deprecated) + + @objc + public func getRealParent(_ pid: pid_t) -> NSDictionary? { + let PROC_PIDPATHINFO_MAXSIZE = 4096 + + // Step 1: Get parent PID + var info = kinfo_proc() + var size = MemoryLayout.stride + var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid] + + guard sysctl(&mib, u_int(mib.count), &info, &size, nil, 0) == 0 else { + return nil + } + + let ppid = info.kp_eproc.e_ppid + if ppid == 0 { return nil } + + // Step 2: Get parent process path + var pathBuffer = [CChar](repeating: 0, count: Int(PROC_PIDPATHINFO_MAXSIZE)) + let result = proc_pidpath(ppid, &pathBuffer, UInt32(pathBuffer.count)) + let path = (result > 0) ? String(cString: pathBuffer) : nil + + // Step 3: Try to get bundle info via NSRunningApplication + var appName: String? = nil + var bundleID: String? = nil + + if let app = NSRunningApplication(processIdentifier: ppid) { + appName = app.localizedName + bundleID = app.bundleIdentifier + } + + // Step 4: Construct NSDictionary for compatibility + let dict: NSDictionary = [ + "ParentPID": NSNumber(value: ppid), + "ParentPath": path ?? "", + "ParentAppName": appName ?? "", + "ParentBundleID": bundleID ?? "" + ] + + return dict + } + + + // MARK: - Process hierarchy + + @objc + public func generateProcessHierarchy(_ child: pid_t) -> NSMutableArray { + let ancestors = NSMutableArray() + typealias RPIDFunc = @convention(c) (pid_t) -> pid_t + let rpidSym = dlsym(dlopen(nil, RTLD_NOW), "responsibility_get_pid_responsible_for_pid") + let getRPID = rpidSym.map { unsafeBitCast($0, to: RPIDFunc.self) } + + var currentPID = child + + while true { + let currentPath = getProcessPath(currentPID) ?? NSLocalizedString("unknown", comment: "unknown") + let currentName = getProcessName(0, currentPath) ?? NSLocalizedString("unknown", comment: "unknown") + ancestors.insert([ + Constants.KEY_PROCESS_ID: NSNumber(value: currentPID), + Constants.KEY_PROCESS_PATH: currentPath, + Constants.KEY_PROCESS_NAME: currentName + ], at: 0) + + var parentPID: pid_t = 0 + + if getuid() != 0 { + if let parent = getRealParent(currentPID), let p = parent["pid"] as? NSNumber { + parentPID = pid_t(truncating: p) + } + } + + if parentPID == 0, let getRPID = getRPID { + parentPID = getRPID(currentPID) + } + + if parentPID <= 0 || parentPID == currentPID { + parentPID = getParent(Int32(currentPID)) + } + + if parentPID <= 0 || parentPID == currentPID { + break + } + + currentPID = parentPID + } + + // add KEY_INDEX for UI + for i in 0.. String? { + var uid: uid_t = 0 + var gid: gid_t = 0 + + // explicitly type `nil` as `SCDynamicStore?` + if let cfUser = SCDynamicStoreCopyConsoleUser(nil as SCDynamicStore?, &uid, &gid) { + return cfUser as String + } + return nil + } + + // MARK: - Process name + + @objc + public func getProcessName(_ pid: pid_t, _ path: String) -> String? { + + let PROC_PIDPATHINFO_MAXSIZE = 4096 // actual constant value from libproc.h + + if pid != 0 { + var nameBuf = [CChar](repeating: 0, count: Int(PROC_PIDPATHINFO_MAXSIZE)) + let status = proc_name(pid, &nameBuf, UInt32(nameBuf.count)) + if status >= 0 { + return String(cString: nameBuf) + } + } + + if let bundle = findAppBundle(path), + let name = bundle.infoDictionary?["CFBundleName"] as? String { + return name + } + + return (path as NSString).lastPathComponent + } + + @objc + public func getBundleID(_ path: String) -> String? { + if let bundle = findAppBundle(path), + let name = bundle.infoDictionary?["CFBundleIdentifier"] as? String { + return name + } + return nil + } + + // MARK: - Find app bundle + + @objc + public func findAppBundle(_ path: String) -> Bundle? { + let standardized = ((path as NSString).standardizingPath as NSString).resolvingSymlinksInPath + var appPath: NSString = standardized as NSString + + while true { + if let bundle = Bundle(path: appPath as String) { + if bundle.bundlePath == standardized { return bundle } + if bundle.executablePath == standardized { return bundle } + } + + let next = appPath.deletingLastPathComponent as NSString + if next.length == 0 || next as String == "/" { break } + appPath = next + } + + return nil + } + + // MARK: - Process path + + @objc + public func getProcessPath(_ pid: pid_t) -> String? { + var buf = [CChar](repeating: 0, count: Int(PROC_PIDPATHINFO_MAXSIZE)) + let status = proc_pidpath(pid, &buf, UInt32(buf.count)) + if status > 0 { + return String(cString: buf) + } else { +// os_log_error(logHandle, "ERROR: for process %d, 'proc_pidpath' failed with %d (errno: %d)", pid, status, errno) + + var mib = [CTL_KERN, KERN_ARGMAX, 0] + var argMax: Int = 0 + var size = MemoryLayout.size(ofValue: argMax) + if sysctl(&mib, 2, &argMax, &size, nil, 0) == -1 { return nil } + + let argBuf = UnsafeMutablePointer.allocate(capacity: argMax) + defer { argBuf.deallocate() } + + mib = [CTL_KERN, KERN_PROCARGS2, Int32(pid)] + size = argMax + if sysctl(&mib, 3, argBuf, &size, nil, 0) == -1 { return nil } + if size <= MemoryLayout.size { return nil } + + // argv0 path follows int argc + let pathPtr = argBuf.advanced(by: MemoryLayout.size) + let p = String(cString: pathPtr) + + if p.hasPrefix("./") { + let trimmed = String(p.dropFirst(2)) + if let cwd = getProcessCWD(pid) { + return (cwd as NSString).appendingPathComponent(trimmed) + } + } + + return p + } + } + + // MARK: - CWD + + @objc + public func getProcessCWD(_ pid: pid_t) -> String? { + var vpi = proc_vnodepathinfo() + let status = withUnsafeMutablePointer(to: &vpi) { ptr -> Int32 in + return proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, ptr, Int32(MemoryLayout.size)) + } + if status > 0 { + return withUnsafePointer(to: vpi.pvi_cdir.vip_path) { ptr in + return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)) + } + } + return nil + } + + // MARK: - PIDs for path/user + + @objc + public func getProcessIDs(_ processPath: String, _ userID: Int32) -> NSMutableArray { + let result = NSMutableArray() + + let count = proc_listallpids(nil, 0) + guard count > 0 else { return result } + + let pids = UnsafeMutablePointer.allocate(capacity: Int(count)) + defer { pids.deallocate() } + + let status = proc_listallpids(pids, count * Int32(MemoryLayout.size)) + guard status >= 0 else { return result } + + var mib = [CTL_KERN, KERN_PROC, KERN_PROC_PID, 0] + var kproc = kinfo_proc() + let kprocSize = MemoryLayout.size + + for i in 0.. NSImage? { + // invalid path -> generic application icon + guard FileManager.default.fileExists(atPath: path) else { + let icon = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kGenericApplicationIcon))) + icon.size = NSSize(width: 128, height: 128) + return icon + } + + if let bundle = findAppBundle(path) { + if let icon = NSWorkspace.shared.icon(forFile: bundle.bundlePath) as NSImage? { + return icon + } + + if let iconFile = bundle.infoDictionary?["CFBundleIconFile"] as? NSString { + var ext = iconFile.pathExtension + if ext.isEmpty { ext = "icns" } + if let iconPath = bundle.path(forResource: iconFile.deletingPathExtension, ofType: ext) { + return NSImage(contentsOfFile: iconPath) + } + } + } + + var icon = NSWorkspace.shared.icon(forFile: path) + // replace generic document icon with application icon + let documentIcon = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kGenericDocumentIcon))) + if icon == documentIcon { + icon = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kGenericApplicationIcon))) + } + icon.size = NSSize(width: 128, height: 128) + return icon + } + + // MARK: - Make modal + + @objc + public func makeModal(_ controller: NSWindowController) { + var window: NSWindow? + + for _ in 0..<20 { + DispatchQueue.main.sync { + window = controller.window + } + if window == nil { + Thread.sleep(forTimeInterval: 0.05) + continue + } + DispatchQueue.main.sync { + NSApplication.shared.runModal(for: controller.window!) + } + break + } + } + + // MARK: - Find processes by name + + @objc + public func findProcesses(_ processName: String) -> NSMutableArray { + let processes = NSMutableArray() + let count = proc_listpids(UInt32(PROC_ALL_PIDS), 0, nil, 0) + guard count > 0 else { return processes } + + let pids = UnsafeMutablePointer.allocate(capacity: Int(count)) + defer { pids.deallocate() } + + let status = proc_listpids(UInt32(PROC_ALL_PIDS), 0, pids, count * Int32(MemoryLayout.size)) + guard status >= 0 else { return processes } + + for i in 0.. Date? { +// os_log_debug(logHandle, "extracting 'kMDItemDateAdded' for %{public}@", file) +// +// let url: URL +// if let bundle = findAppBundle(file) { +// url = bundle.bundleURL +// } else { +// url = URL(fileURLWithPath: file) +// } +// +// guard let item = MDItemCreateWithURL(nil, url as CFURL) else { return nil } +// defer { CFRelease(item) } +// +// if let date = MDItemCopyAttribute(item, kMDItemDateAdded)?.takeRetainedValue() as? Date { +// os_log_debug(logHandle, "extacted date, %{public}@, for %{public}@", String(describing: date), file) +// return date +// } else { +// os_log_debug(logHandle, "'kMDItemDateAdded' is nil ...falling back to 'kMDItemFSCreationDate'") +// if let date = MDItemCopyAttribute(item, kMDItemFSCreationDate)?.takeRetainedValue() as? Date { +// os_log_debug(logHandle, "extacted date, %{public}@, for %{public}@", String(describing: date), file) +// return date +// } +// } +// return nil +// } + + // MARK: - Parent PID + + @objc + public func getParent(_ pid: Int32) -> pid_t { + var kproc = kinfo_proc() + var size = MemoryLayout.size + var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid] // 👈 consistent Int32s + let res = sysctl(&mib, u_int(mib.count), &kproc, &size, nil, 0) + if res == 0 && size != 0 { + let ppid = kproc.kp_eproc.e_ppid +// os_log_debug(logHandle, "extracted parent ID %d for process: %d", ppid, pid) + return ppid + } + return -1 + } + + // MARK: - Dark mode + + @objc + public func isDarkMode() -> Bool { + return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" + } + + // MARK: - String default + + @objc + public func valueForStringItem(_ item: String?) -> String { + return item ?? NSLocalizedString("unknown", comment: "unknown") + } + + // MARK: - Alerts + + @objc + public func showAlert(_ style: NSAlert.Style, _ messageText: String, _ informativeText: String?, _ buttons: [String]) -> NSApplication.ModalResponse { + let alert = NSAlert() + alert.alertStyle = style + alert.messageText = messageText + if let info = informativeText { + alert.informativeText = info + } + for title in buttons { + alert.addButton(withTitle: title) + } + if let first = alert.buttons.first { + first.keyEquivalent = "\r" + } + + NSApp.setActivationPolicy(.regular) + if #available(macOS 14.0, *) { + NSApp.activate() + } else { + NSApp.activate(ignoringOtherApps: true) + } + NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) + + alert.window.makeKeyAndOrderFront(nil) + alert.window.center() + + let response = alert.runModal() +// (NSApp.delegate as? AppDelegate)?.setActivationPolicy() + return response + } + + // MARK: - Audit token for pid + + // Swift doesn't expose this C macro — we define it manually. + let TASK_AUDIT_TOKEN_COUNT = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) + + @objc + public func tokenForPid(_ pid: pid_t) -> NSData? { + var task: mach_port_t = 0 + var token = audit_token_t() + var size = TASK_AUDIT_TOKEN_COUNT + +// os_log_debug(logHandle, "retrieving audit token for %d", pid) + + var kr = task_name_for_pid(mach_task_self_, pid, &task) + guard kr == KERN_SUCCESS else { +// os_log_error(logHandle, "ERROR: 'task_name_for_pid' failed with %x", kr) + return nil + } + defer { mach_port_deallocate(mach_task_self_, task) } + + kr = withUnsafeMutablePointer(to: &token) { ptr in + ptr.withMemoryRebound(to: integer_t.self, capacity: Int(size)) { intPtr in + return task_info(task, task_flavor_t(TASK_AUDIT_TOKEN), intPtr, &size) + } + } + + guard kr == KERN_SUCCESS else { +// os_log_error(logHandle, "ERROR: 'task_info' failed with %x", kr) + return nil + } + +// os_log_debug(logHandle, "retrieved audit token") + return NSData(bytes: &token, length: MemoryLayout.size) + } + + // MARK: - Reverse DNS resolve + + @objc + public func resolveAddress(_ ipAddr: String) -> NSArray? { +// os_log_debug(logHandle, "(attempting to) reverse resolve %{public}@", ipAddr) + + var hints = addrinfo(ai_flags: AI_NUMERICHOST, ai_family: PF_UNSPEC, ai_socktype: SOCK_STREAM, ai_protocol: 0, ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil) + var res: UnsafeMutablePointer? + guard getaddrinfo(ipAddr, nil, &hints, &res) == 0, let result = res else { + return nil + } + defer { freeaddrinfo(result) } + + guard let addrData = CFDataCreate(nil, UnsafePointer(OpaquePointer(result.pointee.ai_addr)), CFIndex(result.pointee.ai_addrlen)) else { + return nil + } + defer { /*addrData*/ } + + let host = CFHostCreateWithAddress(kCFAllocatorDefault, addrData).takeRetainedValue() + var streamErr = CFStreamError() + guard CFHostStartInfoResolution(host, .names, &streamErr) else { + return nil + } + guard let names = CFHostGetNames(host, nil)?.takeUnretainedValue() as NSArray? else { + return nil + } + return names + } + + // MARK: - Process alive + + @objc + public func isAlive(_ processID: pid_t) -> Bool { + errno = 0 + _ = kill(processID, 0) + if errno == ESRCH { return false } + + var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, Int32(processID)] + var info = kinfo_proc() + var size = MemoryLayout.size + if sysctl(&mib, 4, &info, &size, nil, 0) == 0 { + // SZOMB check + if (UInt8(info.kp_proc.p_stat) & UInt8(SZOMB)) == UInt8(SZOMB) { + return false + } + } + return true + } + + // MARK: - Simulator app? + + @objc + public func isSimulatorApp(_ path: String) -> Bool { +// os_log_debug(logHandle, "checking if %{public}@ is a simulator application", path) + guard let bundle = findAppBundle(path) else { return false } + guard let platforms = bundle.infoDictionary?["CFBundleSupportedPlatforms"] as? [String], !platforms.isEmpty else { + return false + } +// os_log_debug(logHandle, "supported platforms: %{public}@", String(describing: platforms)) + let set = Set(platforms) + return set.isSubset(of: Set(["iPhoneSimulator", "AppleTVSimulator"])) + } + + // MARK: - Launched by user? + + @objc + public func launchedByUser() -> Bool { + guard let parent = getRealParent(getpid()) else { return false } + let bid = parent["CFBundleIdentifier"] as? NSString + if bid == "com.apple.dock" || bid == "com.apple.finder" || bid == "com.apple.Terminal" { + return true + } + return false + } + + // MARK: - Fade out + + @objc + public func fadeOut(_ window: NSWindow, _ duration: Float) { + NSAnimationContext.runAnimationGroup { ctx in + ctx.duration = TimeInterval(duration) + window.animator().alphaValue = 0.0 + } completionHandler: { + window.close() + } + } + + // MARK: - Code-signing info match + +// @objc +// public func matchesCSInfo(_ csInfo1: NSDictionary?, _ csInfo2: NSDictionary?) -> Bool { +// var status1 = -1, status2 = -1 +// var signer1 = -1, signer2 = -1 +// var id1: String?, id2: String? +// var auths1: [Any]?, auths2: [Any]? +// +// if let n = csInfo1?[KEY_CS_STATUS] as? NSNumber { status1 = n.intValue } +// if let n = csInfo2?[KEY_CS_STATUS] as? NSNumber { status2 = n.intValue } +// if status1 != status2 { +// os_log_error(logHandle, "ERROR: code signing mismatch (signing status): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) +// return false +// } +// +// if let n = csInfo1?[KEY_CS_SIGNER] as? NSNumber { signer1 = n.intValue } +// if let n = csInfo2?[KEY_CS_SIGNER] as? NSNumber { signer2 = n.intValue } +// if signer1 != signer2 { +// if (signer1 == Apple && signer2 == AppStore) || (signer1 == AppStore && signer2 == Apple) { +// os_log_error(logHandle, "ignoring case where Apple App moved to/from Mac App Store: %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) +// } else { +// os_log_error(logHandle, "ERROR: code signing mismatch (signer): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) +// return false +// } +// } +// +// if let s = csInfo1?[KEY_CS_ID] as? String { id1 = s } +// if let s = csInfo2?[KEY_CS_ID] as? String { id2 = s } +// if (id1 != nil || id2 != nil), id1 != id2 { +// os_log_error(logHandle, "ERROR: code signing mismatch (signing ID): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) +// return false +// } +// +// if let a = csInfo1?[KEY_CS_AUTHS] as? [Any] { auths1 = a } +// if let a = csInfo2?[KEY_CS_AUTHS] as? [Any] { auths2 = a } +// if (auths1 != nil || auths2 != nil) && !(auths1 as NSArray? ?? []).isEqual(to: auths2 ?? []) { +// os_log_error(logHandle, "ERROR: code signing mismatch (signing auths): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) +// return false +// } +// +// return true +// } + + // MARK: - Escape JSON + + @objc + public func toEscapedJSON(_ input: String) -> String? { + do { + let data = try JSONSerialization.data(withJSONObject: input, options: .fragmentsAllowed) + return String(data: data, encoding: .utf8) + } catch { +// os_log_error(logHandle, "ERROR: failed to convert/escape %{public}@ to JSON (error: %{public}@)", input, error.localizedDescription) + return nil + } + } + + // MARK: - Absolute date from HH:mm (next 24h) + + @objc + public func absoluteDate(_ date: Date) -> Date { +// os_log_debug(logHandle, "function '%{public}s' invoked with %{public}@", #function, String(describing: date)) + + let now = Date() + let cal = Calendar.current + + let comps = cal.dateComponents([.hour, .minute], from: date) + var nowComps = cal.dateComponents([.year, .month, .day, .hour, .minute], from: now) + nowComps.hour = comps.hour + nowComps.minute = comps.minute + + var absDate = cal.date(from: nowComps) ?? now + if absDate < now { + absDate = cal.date(byAdding: .day, value: 1, to: absDate) ?? absDate + } + return absDate + } + + // MARK: - Internal volume? + + @objc + public func isInternalProcess(_ path: String) -> Bool { + var isInternal: AnyObject? + do { + var url = URL(fileURLWithPath: path) + try (url as NSURL).getResourceValue(&isInternal, forKey: URLResourceKey.volumeIsInternalKey) + return (isInternal as? NSNumber)?.boolValue ?? false + } catch { +// os_log_error(logHandle, "ERROR: 'getResourceValue'/'NSURLVolumeIsInternalKey' failed with %@", error.localizedDescription) + return false + } + } + + +} + diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/README.md b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/README.md new file mode 100644 index 00000000..234977c0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/README.md @@ -0,0 +1,8 @@ +# SelfControlExtension + +This directory contains the NetworkExtension implementation for SelfControl, responsible for URL and domain filtering. + +## Structure + +- `FilterProvider/` - NetworkExtension filter provider implementation +- `Configuration/` - Extension configuration code \ No newline at end of file diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/RequestSourceAppValidator.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/RequestSourceAppValidator.swift new file mode 100644 index 00000000..3ef45685 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/RequestSourceAppValidator.swift @@ -0,0 +1,38 @@ +// +// HostAppValidator.swift +// SelfControl +// +// Created by Satendra Singh on 29/11/25. +// + +import NetworkExtension +import OSLog + +struct RequestSourceAppValidator { + + static func isAllowedHost(flow: NEFilterFlow) -> Bool { + let process = processFromflow(flow: flow) +// os_log(" FilterDataProvider: appIdAndName %{public}@, %{public}@", log: OSLog.default, type: .debug, process?.name ?? "", process?.path ?? "") + os_log(" FilterDataProvider: app %{public}@", log: OSLog.default, type: .debug, process?.description ?? "") + let isSafariExtensionEnabled = IPCConnection.shared.isSafariExtensionEnable + let isChromeExtensionEnabled = IPCConnection.shared.isGoogleChromeEnabled + + if let process = process, AllowedProcess.isAllowedProcess(process, isSafariExtensionActive: isSafariExtensionEnabled, isChromeExtensionActive: isChromeExtensionEnabled) { +// os_log("[SC] 🔍] handleNewFlow: allowing as it is in allowed list.") + return true + } + return false + } + + static func processFromflow(flow: NEFilterFlow) -> Process? { + guard let auditTokenData = flow.sourceAppAuditToken else { + return nil + } + + // Convert NSData → audit_token_t + var auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in + return ptr.load(as: audit_token_t.self) + } + return Process.init(&auditToken) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/ReverseDomainMapper.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/ReverseDomainMapper.swift new file mode 100644 index 00000000..0610f54e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/ReverseDomainMapper.swift @@ -0,0 +1,57 @@ +// +// ReverseDomainMapper.swift +// SelfControlExtension +// +// Created by Satendra Singh on 14/10/25. +// + +import Foundation + +final class ReverseDomainMapper { + static func reverseDNSUsingGetNameInfo(ipAddress: String) -> String? { + var addr = sockaddr_in() + addr.sin_len = UInt8(MemoryLayout.size) + addr.sin_family = sa_family_t(AF_INET) + inet_pton(AF_INET, ipAddress, &addr.sin_addr) + + var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + + // Precompute length so we don't read from `addr` inside the closure. + let addrLen = socklen_t(addr.sin_len) + + let result: Int32 = withUnsafePointer(to: &addr) { ptr in + ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in + getnameinfo(saPtr, + addrLen, + &hostname, socklen_t(hostname.count), + nil, 0, + NI_NAMEREQD) + } + } + + guard result == 0 else { return nil } + return String(cString: hostname) + } +} + +enum Regex { + static let ipAddress = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" + static let hostname = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$" +} + +extension String { + var isValidIpAddress: Bool { + return self.matches(pattern: Regex.ipAddress) + } + + var isValidHostname: Bool { + return self.matches(pattern: Regex.hostname) + } + + private func matches(pattern: String) -> Bool { + return self.range(of: pattern, + options: .regularExpression, + range: nil, + locale: nil) != nil + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/SelfControlExtension.entitlements b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/SelfControlExtension.entitlements new file mode 100644 index 00000000..f6ded53d --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/SelfControlExtension.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.networking.networkextension + + content-filter-provider-systemextension + + com.apple.security.application-groups + + $(TeamIdentifierPrefix)com.application.SelfControl.corebits + + + diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/String+URL.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/String+URL.swift new file mode 100644 index 00000000..b2758df0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/String+URL.swift @@ -0,0 +1,35 @@ +// +// String+URL.swift +// SelfControlExtension +// +// Created by Satendra Singh on 05/08/25. +// + +import Foundation + +extension String { + var domainString: String? { + Self.extractDomain(from: self) + } + + static func extractDomain(from urlString: String) -> String? { + var formattedURLString = urlString + if !formattedURLString.lowercased().hasPrefix("http://") && + !formattedURLString.lowercased().hasPrefix("https://") { + formattedURLString = "https://" + formattedURLString + } + + guard let url = URL(string: formattedURLString), + let host = url.host else { + return nil + } + + // Optional: extract root domain (e.g., "facebook.com" from "sub.facebook.com") + let components = host.components(separatedBy: ".") + if components.count >= 2 { + return components.suffix(2).joined(separator: ".") + } else { + return host + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/TLDURLToDomain.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/TLDURLToDomain.swift new file mode 100644 index 00000000..f652a950 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/TLDURLToDomain.swift @@ -0,0 +1,49 @@ +// +// TLDURLToDomain.swift +// SelfControlExtension +// +// Created by Satendra Singh on 15/10/25. +// + +import Foundation + +final class TLDURLToDomain { + func domain(for tldURL: URL) -> String? { + return tldURL.host + } + + private func extractDomain(from url: URL) -> String? { + return url.host + } + + + func getDomain(from urlString: String) -> String? { + guard let url = URL(string: urlString) else { + print("Invalid URL string.") + return nil + } + + // Using URL.host + if let host = url.host { + return host + } + return nil + } + + static func getURLDomain(from input: String) -> String? { + // Normalize input to ensure it has a scheme + let formatted = input.contains("://") ? input : "https://\(input)" + guard let host = URL(string: formatted)?.host else { return nil } + + let parts = host.split(separator: ".") + guard parts.count >= 2 else { return host } + + // Return last two parts (e.g., facebook.com, google.co.uk) + if parts.count >= 3 && parts[parts.count - 2] == "co" { + return parts.suffix(3).joined(separator: ".") // handles e.g. google.co.uk + } else { + return parts.suffix(2).joined(separator: ".") + } + } + +} diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/main.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/main.swift new file mode 100644 index 00000000..ffadae74 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/main.swift @@ -0,0 +1,18 @@ +// +// main.swift +// SelfControlExtension +// +// Created by Egzon Arifi on 02/04/2025. +// + +import Foundation +import NetworkExtension +import os.log + +autoreleasepool { + os_log("[SC] 🔍] first light") + NEProvider.startSystemExtensionMode() + IPCConnection.shared.startListener() +} + +dispatchMain() From 42ea465ea5f4898075d606c1415b57630829b8c9 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 18 May 2026 22:34:29 +0530 Subject: [PATCH 43/64] relocated files to right folder --- .../SelfControlBGService/Info.plist | 44 ++ .../SelfControl.entitlements} | 3 + ...ation.SelfControl.corebits.bgservice.plist | 25 + .../BlockOrAllowList.swift | 147 ---- .../SelfControlExtension/Constants.swift | 59 -- .../FilterDataProvider.swift | 513 ------------- .../HostToIpMapping.swift | 82 -- .../SelfControlExtension/IPCConnection.swift | 284 ------- .../SelfControlExtension/Info.plist | 16 - .../SelfControlExtension/Process/Binary.swift | 175 ----- .../Process/Process.swift | 330 -------- .../Process/SourceAppAuditTokenBuilder.swift | 61 -- .../Process/Utilities.swift | 714 ------------------ .../SelfControlExtension/README.md | 8 - .../RequestSourceAppValidator.swift | 38 - .../ReverseDomainMapper.swift | 57 -- .../SelfControlExtension/String+URL.swift | 35 - .../SelfControlExtension/TLDURLToDomain.swift | 49 -- .../SelfControlExtension/main.swift | 18 - 19 files changed, 72 insertions(+), 2586 deletions(-) create mode 100755 Self-Control-Extension/SelfControl/SelfControlBGService/Info.plist rename Self-Control-Extension/SelfControl/{SelfControlExtension/SelfControlExtension.entitlements => SelfControlBGService/SelfControl.entitlements} (78%) create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/com.application.SelfControl.corebits.bgservice.plist delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/BlockOrAllowList.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/HostToIpMapping.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Info.plist delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Process/Binary.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Process/Process.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Process/SourceAppAuditTokenBuilder.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/Process/Utilities.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/README.md delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/ReverseDomainMapper.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/String+URL.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/TLDURLToDomain.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlExtension/main.swift diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Info.plist b/Self-Control-Extension/SelfControl/SelfControlBGService/Info.plist new file mode 100755 index 00000000..525265a0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Info.plist @@ -0,0 +1,44 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + SelfControlBGService + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + SelfControlIcon + CFBundleIdentifier + com.application.SelfControl.corebits.bgservice + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 4.0.2 + MachServices + + com.application.SelfControl.corebits.bgservice.xpc + + + CFBundleVersion + 410 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSHumanReadableCopyright + Free and open-source under the GPL. + SMAuthorizedClients + + anchor apple generic and (identifier "org.eyebeam.SelfControl" or identifier "org.eyebeam.selfcontrol-cli" or identifier "com.application.SelfControl.corebits") + + + diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/SelfControlExtension.entitlements b/Self-Control-Extension/SelfControl/SelfControlBGService/SelfControl.entitlements similarity index 78% rename from Self-Control-Extension/SelfControl/SelfControlExtension/SelfControlExtension.entitlements rename to Self-Control-Extension/SelfControl/SelfControlBGService/SelfControl.entitlements index f6ded53d..9cc108e5 100644 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/SelfControlExtension.entitlements +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/SelfControl.entitlements @@ -6,8 +6,11 @@ content-filter-provider-systemextension + com.apple.developer.system-extension.install + com.apple.security.application-groups + group.com.application.SelfControl.corebits $(TeamIdentifierPrefix)com.application.SelfControl.corebits diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/com.application.SelfControl.corebits.bgservice.plist b/Self-Control-Extension/SelfControl/SelfControlBGService/com.application.SelfControl.corebits.bgservice.plist new file mode 100644 index 00000000..749e3591 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/com.application.SelfControl.corebits.bgservice.plist @@ -0,0 +1,25 @@ + + + + + Label + com.application.SelfControl.corebits.bgservice + RunAtLoad + + KeepAlive + + StandardOutPath + /tmp/helper.log + StandardErrorPath + /tmp/helper.error.log + ProgramArguments + + /Applications/SelfControl.app/Contents/Library/LaunchAgents/SelfControlBGService + + MachServices + + com.application.SelfControl.corebits.bgservice.xpc + + + + diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/BlockOrAllowList.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/BlockOrAllowList.swift deleted file mode 100644 index 5a1b0659..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/BlockOrAllowList.swift +++ /dev/null @@ -1,147 +0,0 @@ -// -// BlockOrAllowList.swift -// SelfControl -// -// Created by Satendra Singh on 24/09/25. -// - - -import Foundation -import os.log -import NetworkExtension - -class BlockOrAllowList: NSObject { - - // MARK: - Properties - - var items: Set -// var lastModified: Date? - - // Serial queue for thread safety (replace @synchronized) -// private let queue = DispatchQueue(label: "BlockOrAllowList.serial") - - // MARK: - Initializer - - init(items: [String]) { - self.items = Set(items) - super.init() - } - - // MARK: - Methods - -// var isRemote: Bool { -// return path.hasPrefix("http://") || path.hasPrefix("https://") -// } - -// func load(_ path: String) { -// queue.sync { -// self.path = path -// self.items.removeAll() -// -// guard !path.isEmpty else { -// os_log("no list specified...", log: .default, type: .debug) -// return -// } -// -// var listString: String? -// var error: Error? -// -// if isRemote { -// os_log("(re)loading (remote) list", log: .default, type: .debug) -// if let url = URL(string: path) { -// do { -// listString = try String(contentsOf: url, encoding: .utf8) -// } catch let e { -// error = e -// } -// } -// if let error = error { -// os_log("ERROR: failed to (re)load (remote) list, %{public}@ (error: %{public}@)", log: .default, type: .error, path, String(describing: error)) -// return -// } -// // (Re)load remote URL once a day -// DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 24*60*60) { -// self.load(self.path) -// } -// } else { -// os_log("(re)loading (local) list, %{public}@", log: .default, type: .debug, path) -// do { -// listString = try String(contentsOfFile: path, encoding: .utf8) -// } catch let e { -// error = e -// } -// if let error = error { -// os_log("ERROR: failed to (re)load (local) list, %{public}@ (error: %{public}@)", log: .default, type: .error, path, String(describing: error)) -// return -// } -// if let attrs = try? FileManager.default.attributesOfItem(atPath: path), -// let fileModified = attrs[.modificationDate] as? Date { -// self.lastModified = fileModified -// } -// } -// -// if let listString = listString { -// let lines = listString.components(separatedBy: .newlines) -// let filtered = lines.map { $0.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() } -// .filter { !$0.isEmpty && !$0.hasPrefix("#") } -// self.items = Set(filtered) -// os_log("SC] 🔍(re)loaded %lu list items", log: .default, type: .debug, self.items.count) -// } -// } -// } - - /// Check if flow matches item on block or allow list - func isMatch(_ flow: NEFilterSocketFlow) -> Bool { - // Only access properties and perform mutation in the queue (thread safety) -// return queue.sync { - - var endpointNames = Set() - - // Get remote endpoint host - if let url = flow.url, let urlString = url.absoluteString.lowercased() as String? { - endpointNames.insert(urlString) - } - if let url = flow.url, let host = url.host?.lowercased() { - endpointNames.insert(host) - } - if let remoteEndpoint = flow.remoteEndpoint as? NWHostEndpoint { - endpointNames.insert(remoteEndpoint.hostname.lowercased()) - } - - // macOS 11+ specific property - if #available(macOS 11, *) { - if let remoteHostname = flow.remoteHostname?.lowercased() { - endpointNames.insert(remoteHostname) - if remoteHostname.hasPrefix("www.") { - let noWWW = String(remoteHostname.dropFirst(4)) - endpointNames.insert(noWWW) - } - } - } - os_log("[SC] 🔍] BlockList endpoint names : %{public}@", log: OSLog.default, type: .info, endpointNames.debugDescription) - - // Find matches - let matches = items.intersection(endpointNames) - if !matches.isEmpty { - os_log("SC] 🔍 endpoint names %{public}@ matched the following list items %{public}@", log: .default, type: .debug, String(describing: endpointNames), String(describing: matches)) - return true - } - - return false -// } - } -} - -// MARK: - NEFilterSocketFlow Extensions - -//extension NEFilterSocketFlow { -// /// You must implement these extensions if your Objective-C code provides them! -// @objc public override var url: URL? { -// // Implement according to your codebase (category/extension in Objective-C) -// return nil // Placeholder -// } -// @objc var remoteHostname: String? { -// // Implement according to your codebase (category/extension in Objective-C) -// return nil // Placeholder -// } -//} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift deleted file mode 100644 index 115dff7d..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/Constants.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// Constants.swift -// SelfControl -// -// Created by Satendra Singh on 11/10/25. -// -import os.log - -enum Constants { - /// File name for the JSON file with Safari rules. - static let KEY_UUID = "uuid" - static let KEY_PROCESS_ID = "pid" - static let KEY_PROCESS_ARGS = "args" - static let KEY_PROCESS_NAME = "name" - static let KEY_PROCESS_PATH = "path" - static let KEY_INDEX = "index" - static let KEY_PATH = "paths" - static let KEY_CS_SIGNER = "signatureSigner" - static let KEY_CS_ID = "signatureIdentifier" - static let KEY_CS_INFO = "signingInfo" - static let KEY_CS_AUTHS = "signatureAuthorities" -} - -enum AllowedProcess: String { - case safari = "com.apple.Safari" - case chrome = "com.google.Chrome" - static var allCases: [AllowedProcess] = [.chrome, .safari] - static func isAllowed(_ bundleID: String) -> Bool { - allCases.contains(where: { $0.rawValue == bundleID }) - } - - static func isAllowedProcess(_ process: Process) -> Bool { - let ancestors: [String] = process.ancestors?.value(forKey: "name") as? [String] ?? [] - return allCases.contains(where: { $0.rawValue == process.bundleID }) || ancestors.contains(where: { $0.lowercased().contains("chrome") || $0.lowercased().contains("safari") }) - } - - static func isAllowedProcess(_ process: Process, isSafariExtensionActive: Bool = false, isChromeExtensionActive: Bool = false) -> Bool { - // Determine if the process is Safari or Chrome by bundle ID or ancestor name. - let ancestors: [String] = process.ancestors?.value(forKey: "name") as? [String] ?? [] - let isSafariProcess = - process.bundleID == AllowedProcess.safari.rawValue || - ancestors.contains(where: { $0.lowercased().contains("safari") }) - let isChromeProcess = - process.bundleID == AllowedProcess.chrome.rawValue || - ancestors.contains(where: { $0.lowercased().contains("chrome") }) - - // Allow only if the corresponding extension is active. -// os_log("[SC] 🔍] isSafariProcess: \(isSafariProcess), isChromeProcess: \(isChromeProcess)") - - if isSafariProcess { - return isSafariExtensionActive - } - if isChromeProcess { - return isChromeExtensionActive - } - return false - } - -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift deleted file mode 100644 index 4fd0efab..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/FilterDataProvider.swift +++ /dev/null @@ -1,513 +0,0 @@ -import NetworkExtension -import os.log -import dnssd - -/// FilterDataProvider is a NEFilterDataProvider subclass that intercepts flows and applies a test rule. -class FilterDataProvider: NEFilterDataProvider { - // MARK: - Properties -// private let listner = PlistListner() - /// A dictionary for storing flows related to the same process. - private var relatedFlows: [String: [NEFilterSocketFlow]] = [:] - - // MARK: - Initialization - - override init() { - os_log("[SC] 🔍] FilterDataProvider: init") - super.init() -// listner.startListening() - } - - // MARK: - Filter Lifecycle - static let localPort = "8888" - override func startFilter(completionHandler: @escaping (Error?) -> Void) { - os_log("[SC] 🔍 FilterDataProvider: Starting filter", log: OSLog.default, type: .info) - let blockedHosts = IPCConnection.shared.blockedUrls - - os_log("[SC] 🔍 Blocked blockedUrls: %{public}@", log: OSLog.default, type: .info, String(describing: blockedHosts)) - - // Ensure blockedHosts is an array of String - var filterRules: [NEFilterRule] = blockedHosts.compactMap { address in - guard let host = address as? String else { return nil } - // For hostname-based rules, you must use NENetworkRule with nil networks (matches all) - let networkRule = NENetworkRule( - remoteNetwork: nil, - remotePrefix: 0, - localNetwork: nil, - localPrefix: 0, - protocol: .any, - direction: .outbound - ) - return NEFilterRule(networkRule: networkRule, action: .filterData) - } - - // Ensure at least one rule exists as required by NEFilterSettings - if filterRules.isEmpty { - // Add a default rule (matches all traffic, for example, and allows it) - let defaultNetworkRule = NENetworkRule( - remoteNetwork: nil, - remotePrefix: 0, - localNetwork: nil, - localPrefix: 0, - protocol: .any, - direction: .any - ) - let defaultRule = NEFilterRule(networkRule: defaultNetworkRule, action: .allow) - filterRules = [defaultRule] - } - - let filterSettings = NEFilterSettings(rules: filterRules, defaultAction: .allow) - - apply(filterSettings) { error in - if let error = error { - os_log("[SC] 🔍 Error applying filter settings: %{public}@", log: OSLog.default, type: .error, error.localizedDescription) - } - completionHandler(error) - } - } - - override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - os_log("[SC] 🔍] FilterDataProvider: Stopping filter with reason %d", log: OSLog.default, type: .info, reason.rawValue) - completionHandler() - } - - // MARK: - Flow Handlings - - - func reverseDNSLookup(ip: String) -> String? { - var hints = addrinfo(ai_flags: AI_NUMERICHOST, ai_family: AF_UNSPEC, - ai_socktype: SOCK_STREAM, ai_protocol: IPPROTO_TCP, - ai_addrlen: 0, ai_canonname: nil, - ai_addr: nil, ai_next: nil) - var res: UnsafeMutablePointer? - - if getaddrinfo(ip, nil, &hints, &res) == 0, let addr = res?.pointee.ai_addr { - var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - if getnameinfo(addr, socklen_t(addr.pointee.sa_len), - &hostBuffer, socklen_t(hostBuffer.count), - nil, 0, NI_NAMEREQD) == 0 { - return String(cString: hostBuffer) - } - } - return nil - } - - func extractHost(from request: String) -> String? { - for line in request.split(separator: "\r\n") { - if line.lowercased().hasPrefix("host:") { - return line.replacingOccurrences(of: "Host:", with: "", options: .caseInsensitive) - .trimmingCharacters(in: .whitespaces) - } - } - return nil - } - - func extractPath(from request: String) -> String? { - guard let firstLine = request.split(separator: "\r\n").first else { return nil } - let comps = firstLine.split(separator: " ") - return comps.count > 1 ? String(comps[1]) : nil - } - - func extractSNI(fromTLSData data: Data) -> String? { - // TLS record starts with 0x16 (Handshake), version bytes, then length - guard data.count > 5, data[0] == 0x16 else { return nil } - - var offset = 5 // Skip record header - - // Verify handshake type = ClientHello (0x01) - guard data.count > offset, data[offset] == 0x01 else { return nil } - offset += 4 // Skip type + length (3 bytes) - - // Skip protocol version (2), random (32), session id length + session id - guard data.count > offset + 34 else { return nil } - offset += 34 - guard data.count > offset else { return nil } - - // Skip session ID - if offset < data.count { - let sessionIDLength = Int(data[offset]) - offset += 1 + sessionIDLength - } - - // Skip cipher suites - guard offset + 2 <= data.count else { return nil } - let cipherSuiteLength = Int(data[offset]) << 8 | Int(data[offset + 1]) - offset += 2 + cipherSuiteLength - - // Skip compression methods - guard offset < data.count else { return nil } - let compressionLength = Int(data[offset]) - offset += 1 + compressionLength - - // Skip extensions length - guard offset + 2 <= data.count else { return nil } - let extensionsLength = Int(data[offset]) << 8 | Int(data[offset + 1]) - offset += 2 - guard offset + extensionsLength <= data.count else { return nil } - - var extOffset = offset - while extOffset + 4 <= offset + extensionsLength { - let extType = Int(data[extOffset]) << 8 | Int(data[extOffset + 1]) - let extLen = Int(data[extOffset + 2]) << 8 | Int(data[extOffset + 3]) - extOffset += 4 - - // Extension type 0 = Server Name - if extType == 0 { - // Parse Server Name extension - var nameOffset = extOffset + 2 // skip list length - while nameOffset + 3 < extOffset + extLen { - let nameType = data[nameOffset] - let nameLen = Int(data[nameOffset + 1]) << 8 | Int(data[nameOffset + 2]) - nameOffset += 3 - if nameType == 0, nameOffset + nameLen <= data.count { - let nameData = data.subdata(in: nameOffset ..< nameOffset + nameLen) - return String(data: nameData, encoding: .utf8) - } - nameOffset += nameLen - } - } - - extOffset += extLen - } - return nil - } - - override func handleOutboundData( - from flow: NEFilterFlow, - readBytesStartOffset offset: Int, - readBytes data: Data - ) -> NEFilterDataVerdict { - var result: NEFilterDataVerdict = .allow() - - guard IPCConnection.shared.isServiceActive else { //Service is inactive - return result - } - - guard IPCConnection.shared.blockedUrls.count > 0 else { //No signinficant urls to block - return result - } - - if RequestSourceAppValidator.isAllowedHost(flow: flow) { - return result - } - - guard let socketFlow = flow as? NEFilterSocketFlow else { - os_log("[SC] 🔍] Not a socket flow. Allowing.", log: OSLog.default, type: .info) - return result - } - if socketFlow.direction != .outbound { - os_log("[SC] 🔍] Not a inbound socket flow. Allowing.", log: OSLog.default, type: .info) - return result - } - - // guard let socketFlow = flow as? NEFilterSocketFlow else { - // return .allow() - // } - let requestString = String(data: data, encoding: .utf8) - if let requestString { - os_log("[SC] 🔍] data HTTP Request: %{public}@", requestString) - } - - // Try HTTP detection - if let requestString = requestString, - requestString.hasPrefix("GET ") || requestString.hasPrefix("POST ") { - - if let host = extractHost(from: requestString), - let path = extractPath(from: requestString) { - let urlString = "http://\(host)\(path)" - os_log("[SC] 🔍] data HTTP Request: %{public}@", urlString) - } - - } else if let sni = extractSNI(fromTLSData: data) { - os_log("[SC] 🔍] data TLS SNI Host: %{public}@", sni) - guard let hostDomain = TLDURLToDomain.getURLDomain(from: sni) else { - return result - } - os_log("[SC] 🔍] data hostDomain: %{public}@", hostDomain) - - for url in IPCConnection.shared.blockedUrls { - if url.contains(hostDomain) { - os_log("[SC] 🔍] data Blocking flow Host match SNI:%{public}@, host:%{public}@", sni, hostDomain) - result = .drop() - break - } - } - } - - return result - } - - // Called for each new flow. - override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { - guard IPCConnection.shared.isServiceActive else { - return .allow() - } - // Provide peek sizes required by this overload -// os_log("[SC] 🔍] FilterDataProvider: handleNewFlow invoked", log: OSLog.default, type: .debug) - guard IPCConnection.shared.blockedUrls.count > 0 else { //No signinficant urls to block - return .allow() - } - - if RequestSourceAppValidator.isAllowedHost(flow: flow) { - return .allow() - } - - guard let socketFlow = flow as? NEFilterSocketFlow else { - return .filterDataVerdict(withFilterInbound: false, - peekInboundBytes: 0, - filterOutbound: true, - peekOutboundBytes: Int.max) - -// return .allow() - } - - // Try to extract SNI hostname (HTTPS) - guard let hostname = socketFlow.remoteHostname else { - return .filterDataVerdict(withFilterInbound: false, - peekInboundBytes: 0, - filterOutbound: true, - peekOutboundBytes: Int.max) - } - - // Fallback to IP address if SNI is not present -// let remoteIP = (socketFlow.remoteEndpoint as? NWHostEndpoint)?.hostname ?? hostname - -// let host = hostname.isEmpty ? remoteIP : hostname - let lower = hostname.lowercased() - os_log("[SC] 🔍] Host SNI host:%{public}@", lower) - guard let hostDomain = TLDURLToDomain.getURLDomain(from: lower) else { - return .filterDataVerdict(withFilterInbound: false, - peekInboundBytes: 0, - filterOutbound: true, - peekOutboundBytes: Int.max) - } - os_log("[SC] 🔍] Host SNI domain:%{public}@", hostDomain) - for url in IPCConnection.shared.blockedUrls { - if url.contains(hostDomain) { - os_log("[SC] 🔍] Blocking flow Host:%{public}@", hostDomain) - return .drop() - } - } - - os_log("[SC] 🔍] Not a blocked domain. Allowing.", log: OSLog.default, type: .info) - return .allow() - - guard let socketFlow = flow as? NEFilterSocketFlow else { - os_log("[SC] 🔍] Not a socket flow. Allowing.", log: OSLog.default, type: .info) - return .allow() - } - if socketFlow.direction != .outbound { - os_log("[SC] 🔍] Not a inbound socket flow. Allowing.", log: OSLog.default, type: .info) - return .allow() - } - var remoteHost = "" - if let flowURL = flow.url { - remoteHost = flowURL.absoluteString - os_log("[SC] 🔍] flow.url: \(remoteHost)") - } else { - if let remoteEndpoint = socketFlow.remoteEndpoint as? NWHostEndpoint { - remoteHost = remoteEndpoint.hostname - let port = remoteEndpoint.port - os_log("[SC] 🔍] NWEndpoint to host: %{public}@, URL: %{public}@, url: %{public}@", log: OSLog.default, type: .debug, remoteHost, port, flow.url?.host() ?? "") - } - } - - - //todo: check host name, work on reverse rule if domain match and if path is nil or blocked, drop connection. -// -// if IPCConnection.shared.blockedList.isMatch(flow as! NEFilterSocketFlow) { -// os_log("[SC] 🔍] Dropped", log: OSLog.default, type: .info) -// return .drop() -// } -// os_log("[SC] 🔍] Allowed", log: OSLog.default, type: .info) -// -// return .allow() - - os_log("[SC] 🔍] Flow from remote endpoint: %{public}@, URL: %{public}@", log: OSLog.default, type: .debug, socketFlow.remoteEndpoint.debugDescription, flow.url?.description ?? "nil") - - if remoteHost.isEmpty { //Unable to get host - return .allow() - } - if remoteHost.isValidIpAddress { - if IPCConnection.shared.blockedIPAddresses.contains(remoteHost) { - os_log("[SC] 🔍] Blocking flow IP match: %{public}@", remoteHost) - return .drop() - } - if let host = ReverseDomainMapper.reverseDNSUsingGetNameInfo(ipAddress: remoteHost) { - os_log("[SC] 🔍] Converted IP: %{public}@ to: %{public}@", remoteHost, host) - remoteHost = host - } else { //unable to convert, simple ignore - return .allow() - } - } else { - return .allow() - } - guard let hostDomain = TLDURLToDomain.getURLDomain(from: remoteHost) else { - return .allow() - } - for url in IPCConnection.shared.blockedUrls { - if url.contains(hostDomain) { - os_log("[SC] 🔍] Blocking flow Host match %{public}@, %{public}@", remoteHost, hostDomain) - return .drop() - } - } - - return .allow() - - // Process the flow and decide a verdict. - let verdict = processEvent(for: socketFlow) - os_log("[SC] 🔍] Verdict for flow: %{public}@", log: OSLog.default, type: .debug, verdict.debugDescription) - return verdict - } - -// override func handleInboundDataComplete(for flow: NEFilterFlow) -> NEFilterDataVerdict { -// if let socketFlow = flow as? NEFilterSocketFlow { -// if let data = socketFlow.readData { -// if let requestString = String(data: data, encoding: .utf8) { -// if requestString.hasPrefix("GET") || requestString.hasPrefix("POST") { -// print("Request: \(requestString)") -// // Parse the path here -// } -// } -// } -// } -// return .allow() -// } -// -// override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { -// return .filterDataVerdict(withFilterInbound: true, -// peekInboundBytes: 1024, -// filterOutbound: true, -// peekOutboundBytes: 1024) -// } - - - override func handleInboundData(from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes: Data) -> NEFilterDataVerdict { - if let requestString = String(data: readBytes, encoding: .utf8) { - print("Inbound data: \(requestString)") - os_log("[SC] 🔍] handleInboundData: %{public}@", requestString) - - if requestString.contains("facebook.com/friends") { - return .drop() - } - } - return .allow() - } - - override func displayMessage(_ message: String, completionHandler: @escaping (Bool) -> Void) { - return completionHandler(true) - } - -// override func handleOutboundData(from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes: Data) -> NEFilterDataVerdict { -// if let urlString = flow.url?.absoluteString { -// os_log("[SC] 🔍] handleOutboundData URL: %{public}@", urlString) -// if urlString.contains("facebook.com/friends") { -// return .drop() -// } -// } -// -// if let requestString = String(data: readBytes, encoding: .utf8) { -// print("Outbound data: \(requestString)") -// os_log("[SC] 🔍] handleOutboundData: %{public}@", requestString) -// -// if requestString.contains("facebook.com/friends") { -// return .drop() -// } -// } -// return .allow() -// } - -// override func handleOutboundData(from flow: NEFilterFlow, -// readBytesStartOffset offset: Int, -// readBytes: Data, -// completionHandler: @escaping (NEFilterDataVerdict) -> Void) { -// -// if let requestString = String(data: readBytes, encoding: .utf8) { -// print("Outbound data: \(requestString)") -// -// if requestString.contains("facebook.com") { -// completionHandler(.drop()) -// return -// } -// } -// -// // If undecided, ask for more data -// completionHandler(.allow()) -// } - - /// Processes the flow and returns a verdict. - /// This is a simplified test rule that blocks flows destined for "example.com". - private func processEvent(for flow: NEFilterSocketFlow) -> NEFilterNewFlowVerdict { - guard let endpoint = flow.remoteEndpoint as? NWHostEndpoint else { - return .allow() - } - os_log("[SC] 🔍] processEvent endpoint.hostname: %{public}@ ", endpoint.hostname) - os_log("[SC] 🔍] processEvent remoteHostname: %{public}@ ", flow.remoteHostname ?? "NOTHING") - guard let host = flow.remoteHostname?.lowercased().domainString else { - os_log("[SC] 🔍] processEvent No Host") - return .allow() - } -// os_log("[SC] 🔍] This is localFlowEndpoint: %{public}@ ", flow.localFlowEndpoint?.debugDescription ?? "NOTHING") - let blockedHosts = IPCConnection.shared.blockedUrls - -// if host == "google.com" || host == "8.8.8.8" { - if blockedHosts.contains(host) { - os_log("[SC] 🔍] processEvent: Blocking flow to processEvent %{public}@ ", host) - return .drop() -// return .allow() - } - // Optionally log other flows for debugging - os_log("[SC] 🔍] processEvent: Allowing flow to %{public}@", log: OSLog.default, type: .info, host) - return .allow() - } - - - // MARK: - (Optional) Handling Related Flows & Alerts - - /// Adds a flow to a list of related flows for a given key. - private func addRelatedFlow(forKey key: String, flow: NEFilterSocketFlow) { - os_log("[SC] 🔍] Adding related flow for key: %@", log: OSLog.default, type: .debug, key) - if relatedFlows[key] == nil { - relatedFlows[key] = [] - } - relatedFlows[key]?.append(flow) - } - - /// Processes related flows once a decision is made for a given key. - private func processRelatedFlows(forKey key: String) { - guard let flows = relatedFlows[key] else { - os_log("[SC] 🔍] No related flows for key: %@", log: OSLog.default, type: .debug, key) - return - } - for flow in flows { - let verdict = processEvent(for: flow) - resumeFlow(flow, with: verdict) - } - relatedFlows[key] = nil - } - - /// A stub method for resuming a flow with a verdict. - private func resumeFlow(_ flow: NEFilterSocketFlow, with verdict: NEFilterNewFlowVerdict) { - // In a complete implementation, this would resume the paused flow with the provided verdict. - os_log("[SC] 🔍] Resuming flow %@ with verdict %@", log: OSLog.default, type: .info, flow.debugDescription, verdict.debugDescription) - } - - /// A stub method to simulate alerting the user. - /// In a complete implementation, this might trigger an IPC to your app for user intervention. - private func alertUser(for flow: NEFilterSocketFlow) { - os_log("[SC] 🔍] Alert: User decision needed for flow %@", log: OSLog.default, type: .info, flow.debugDescription) - } - -// func processFromflow(flow: NEFilterFlow) -> Process? { -// guard let auditTokenData = flow.sourceAppAuditToken else { -// return nil -// } -// -// // Convert NSData → audit_token_t -// var auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in -// return ptr.load(as: audit_token_t.self) -// } -// return Process.init(&auditToken) -// } - } -//https://developer.chrome.com/docs/extensions/how-to/distribute/install-extensions - diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/HostToIpMapping.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/HostToIpMapping.swift deleted file mode 100644 index 8857b4f0..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/HostToIpMapping.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// HostToIpMapping.swift -// SelfControl -// -// Created by Satendra Singh on 20/10/25. -// - -import Foundation - -class HostToIpMapping { - static func string(for addressData: Data) throws -> String { - var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - - let result = addressData.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> Int32 in - guard let addrPtr = ptr.baseAddress else { - return EINVAL - } - return getnameinfo( - addrPtr.assumingMemoryBound(to: sockaddr.self), - socklen_t(addressData.count), - &hostBuffer, - socklen_t(hostBuffer.count), - nil, - 0, - NI_NUMERICHOST - ) - } - - if result != 0 { - let message = String(cString: gai_strerror(result)) - throw NSError(domain: "HostToIpMappingError", code: Int(result), userInfo: [ - NSLocalizedDescriptionKey: message - ]) - } - - return String(cString: hostBuffer) - } - - static func ipAddresses(for domainName: String) -> [String] { - let startTime = Date() - - // ✅ Correct initialization - let cfHostOpt: CFHost? = CFHostCreateWithName(kCFAllocatorDefault, domainName as CFString).takeRetainedValue() - guard let cfHost = cfHostOpt else { - print("HostToIpMapping: Failed to create CFHost for \(domainName)") - return [] - } - - var streamError = CFStreamError() - let success = CFHostStartInfoResolution(cfHost, .addresses, &streamError) - - if !success || streamError.error != 0 { - print("HostToIpMapping: Warning: failed to resolve addresses for \(domainName) with stream error \(streamError.error)") - return [] - } - - // ✅ Use takeUnretainedValue safely - guard let addressArray = CFHostGetAddressing(cfHost, nil)? - .takeUnretainedValue() as? [Data] else { - print("HostToIpMapping: Warning: failed to resolve addresses for \(domainName)") - return [] - } - - var stringAddresses: [String] = [] - - for addrData in addressArray { - do { - let ip = try string(for: addrData) - stringAddresses.append(ip) - } catch { - print("HostToIpMapping: Warning: Failed to parse IP struct for \(domainName) with error: \(error.localizedDescription)") - } - } - - let elapsed = Date().timeIntervalSince(startTime) - if elapsed > 2.5 { - print("HostToIpMapping: Warning: took \(elapsed) seconds to resolve \(domainName)") - } - - return stringAddresses - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift deleted file mode 100644 index 4dbedf26..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/IPCConnection.swift +++ /dev/null @@ -1,284 +0,0 @@ -/* - See the LICENSE.txt file for this sample’s licensing information. - - Abstract: - This file contains the implementation of the app <-> provider IPC connection - */ - -import Foundation -import os.log -import Network - -enum ActiveBrowserExtensios: String { - case safari - case chrome -} - -/// App --> Provider IPC, to be implememted in Extension -@objc protocol AppToExtensionExtension { - func register(_ completionHandler: @escaping (Bool) -> Void) - func setBlockedURLs(_ urls: [String]) - func setBlockedIPAddresses(_ ips: [String]) - // Use Objective-C bridgeable type for XPC surface - func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) - func setEnableService(_ enable: Bool) -} - -/// Provider --> App IPC to Be implememted in the app -@objc protocol ExtensionToApp { - func promptUser(aboutFlow flowInfo: [String: String], responseHandler: @escaping (Bool) -> Void) - func didSetUrls() -} - -enum FlowInfoKey: String { - case localPort - case remoteAddress -} - -/// The IPCConnection class is used by both the app and the system extension to communicate with each other -class IPCConnection: NSObject { - - // MARK: Properties - - var listener: NSXPCListener? - var currentConnection: NSXPCConnection? - weak var delegate: ExtensionToApp? - static let shared = IPCConnection() -// var blockedUrls: [String] = ProxyPreferences.getBlockedDomains() - var blockedUrls: [String] = [String]() - var blockedList = BlockOrAllowList(items: []) - var blockedIPAddresses: Set = [] - private(set) var isSafariExtensionEnable: Bool = false - private(set) var isGoogleChromeEnabled: Bool = false - // Published extension state for UI/observers - private(set) var isServiceActive: Bool = false - - // MARK: Methods - - /** - The NetworkExtension framework registers a Mach service with the name in the system extension's NEMachServiceName Info.plist key. - The Mach service name must be prefixed with one of the app groups in the system extension's com.apple.security.application-groups entitlement. - Any process in the same app group can use the Mach service to communicate with the system extension. - */ - private func extensionMachServiceName(from bundle: Bundle) -> String { - - guard let networkExtensionKeys = bundle.object(forInfoDictionaryKey: "NetworkExtension") as? [String: Any], - let machServiceName = networkExtensionKeys["NEMachServiceName"] as? String else { -// fatalError("Mach service name is missing from the Info.plist") - os_log("[SC] 🔍] Mach service name is missing from the Info.plist") - return "" - } - - return machServiceName - } - - func startListener() { - - let machServiceName = extensionMachServiceName(from: Bundle.main) - os_log("[SC] 🔍] Starting XPC listener for mach service %@", machServiceName) - - let newListener = NSXPCListener(machServiceName: machServiceName) - newListener.delegate = self - newListener.resume() - listener = newListener - } - - /// This method is called by the app to register with the provider running in the system extension. - func register(withExtension bundle: Bundle, delegate: ExtensionToApp, completionHandler: @escaping (Bool) -> Void) { - - self.delegate = delegate - - guard currentConnection == nil else { - os_log("[SC] 🔍] Already registered with the provider") - completionHandler(true) - return - } - - let machServiceName = extensionMachServiceName(from: bundle) - let newConnection = NSXPCConnection(machServiceName: machServiceName, options: []) - - // The exported object is the delegate. - newConnection.exportedInterface = NSXPCInterface(with: ExtensionToApp.self) - newConnection.exportedObject = delegate - - // The remote object is the provider's IPCConnection instance. - newConnection.remoteObjectInterface = NSXPCInterface(with: AppToExtensionExtension.self) - - currentConnection = newConnection - newConnection.resume() - - guard let providerProxy = newConnection.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] Failed to register with the provider: %@", registerError.localizedDescription) - self.currentConnection?.invalidate() - self.currentConnection = nil - completionHandler(false) - }) as? AppToExtensionExtension else { - os_log("Failed to create a remote object proxy for the provider") - return - } - providerProxy.register(completionHandler) - providerProxy.setBlockedURLs(blockedUrls) - } - - /** - This method is called by the provider to cause the app (if it is registered) to display a prompt to the user asking - for a decision about a connection. - */ - func promptUser(aboutFlow flowInfo: [String: String], responseHandler:@escaping (Bool) -> Void) -> Bool { - - guard let connection = currentConnection else { - os_log("[SC] 🔍] Cannot prompt user because the app isn't registered") - return false - } - - guard let appProxy = connection.remoteObjectProxyWithErrorHandler({ promptError in - os_log("[SC] 🔍] Failed to prompt the user: %{public}@", promptError.localizedDescription) - self.currentConnection = nil - responseHandler(true) - }) as? ExtensionToApp else { - os_log("Failed to create a remote object proxy for the app") - return false - } - - appProxy.promptUser(aboutFlow: flowInfo, responseHandler: responseHandler) - - return true - } -} - -extension IPCConnection: NSXPCListenerDelegate { - - // MARK: NSXPCListenerDelegate - - func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { - - // The exported object is this IPCConnection instance. - newConnection.exportedInterface = NSXPCInterface(with: AppToExtensionExtension.self) - newConnection.exportedObject = self - - // The remote object is the delegate of the app's IPCConnection instance. - newConnection.remoteObjectInterface = NSXPCInterface(with: ExtensionToApp.self) - - newConnection.invalidationHandler = { - self.currentConnection = nil - } - - newConnection.interruptionHandler = { - self.currentConnection = nil - } - - currentConnection = newConnection - newConnection.resume() - - return true - } - - func enableURLBlocking(_ urls: [String]) { - os_log("[SC] 🔍] Enabling URL blocking") - guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) - }) as? AppToExtensionExtension else { - os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") - return - } - providerProxy.setBlockedURLs(urls) - } - - func enableIPAddressesBlocking(_ urls: [String]) { - os_log("[SC] 🔍] Enabling URL blocking") - guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) - }) as? AppToExtensionExtension else { - os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") - return - } - providerProxy.setBlockedIPAddresses(urls) - } - - func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) -> Bool { - os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension:\(extensionTypeRawValue), state:\(state)") - guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension: %{public}@", registerError.localizedDescription) - }) as? AppToExtensionExtension else { - os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") - return false - } - providerProxy.setActiveBrowserExtension(extensionTypeRawValue, state: state) - return true - } - - func sendMessageToEnableNetworkExtension(_enable: Bool) -> Bool { - os_log("[SC] 🔍] sendMessageToEnableNetworkExtension state:\(_enable)") - guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] sendMessageToEnableNetworkExtension: %{public}@", registerError.localizedDescription) - }) as? AppToExtensionExtension else { - os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") - return false - } - providerProxy.setEnableService(_enable) - return true - } -} - - -extension IPCConnection: AppToExtensionExtension { - func setEnableService(_ enable: Bool) { - self.isServiceActive = enable - } - - func setBlockedIPAddresses(_ ips: [String]) { - blockedIPAddresses = Set(ips) - os_log("[SC] 🔍] setBlockedIPAddresses: %{public}@", blockedIPAddresses) - } - - func setBlockedURLs(_ urls: [String]) { - os_log("[SC] 🔍] Extension Received Blocking: %{public}@",urls) - blockedUrls = urls -// delegate?.didSetUrls() - blockedList = BlockOrAllowList(items: blockedUrls) - - guard let connection = currentConnection else { - print("[SC] 🔍] Cannot update blocked urls, app isn't registered") - os_log("[SC] 🔍] Cannot update blocked urls, app isn't registered") - - return - } - - guard let appProxy = connection.remoteObjectProxyWithErrorHandler({ promptError in - os_log("[SC] 🔍] Failed to prompt the user: %{public}@", promptError.localizedDescription) - os_log("Failed to create a remote object proxy for the app") -// self.currentConnection = nil -// responseHandler(true) - }) as? ExtensionToApp else { - os_log("[SC] 🔍] Cannot update blocked urls, app isn't registered") - os_log("Failed to create a remote object proxy for the app") - return - } - appProxy.didSetUrls() - } - - func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { - // Map the raw value to the Swift enum if possible - os_log("[SC] 🔍] Received extension state to %{public}@", extensionTypeRawValue) - if let mapped = ActiveBrowserExtensios(rawValue: extensionTypeRawValue) { - switch mapped { - case .safari: - self.isSafariExtensionEnable = state - case .chrome: - self.isGoogleChromeEnabled = state - } - os_log("[SC] 🔍] Set browser extension state to %{public}@", extensionTypeRawValue) - os_log("[SC] 🔍] Set browser state to %{public}d", state) - } else { - os_log("[SC] 🔍] Unknown browser extension type %{public}@", extensionTypeRawValue) - } - } - - // MARK: ProviderCommunication - - func register(_ completionHandler: @escaping (Bool) -> Void) { - - os_log("[SC] 🔍] App registered") - completionHandler(true) - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Info.plist b/Self-Control-Extension/SelfControl/SelfControlExtension/Info.plist deleted file mode 100644 index cf77feef..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/Info.plist +++ /dev/null @@ -1,16 +0,0 @@ - - - - - NetworkExtension - - NEMachServiceName - $(TeamIdentifierPrefix)com.application.SelfControl.corebits.network - NEProviderClasses - - com.apple.networkextension.filter-data - $(PRODUCT_MODULE_NAME).FilterDataProvider - - - - diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Binary.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Binary.swift deleted file mode 100644 index 3397339e..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Binary.swift +++ /dev/null @@ -1,175 +0,0 @@ -// -// Binary.swift -// SelfControl -// -// Created by Satendra Singh on 10/10/25. -// - -import Foundation -import AppKit -import OSLog -import Security -import CoreServices // for MDItem - -@objcMembers -class Binary: NSObject { - - // MARK: - Properties (mirroring Binary.h) - - dynamic var path: String - dynamic var name: String = "" - dynamic var icon: NSImage = NSImage() - dynamic var attributes: NSDictionary? - dynamic var metadata: NSDictionary? - dynamic var bundle: Bundle? - dynamic var csInfo: NSMutableDictionary = NSMutableDictionary() - dynamic var sha256: NSMutableString = NSMutableString() - - // MARK: - Init - - init(_ path: String) { - self.path = (path as NSString).resolvingSymlinksInPath - super.init() - - // Try load app bundle (nil for non-apps) - self.getBundle() - - // Get name - self.getName() - - // File attributes - self.getAttributes() - - // Spotlight metadata - self.getMetadata() - } - - // MARK: - Bundle - - // Try load app bundle; nil for non-apps - func getBundle() { - // First try direct path - if let b = Bundle(path: self.path) { - self.bundle = b - return - } - // Else find dynamically - self.bundle = Utilities().findAppBundle(self.path) - } - - // MARK: - Name - - // Figure out binary's name via bundle CFBundleName or lastPathComponent - func getName() { - if let bundle = self.bundle, - let bundleName = bundle.infoDictionary?["CFBundleName"] as? String { - self.name = bundleName - } else { - self.name = (self.path as NSString).lastPathComponent - } - } - - // MARK: - Attributes - - func getAttributes() { - self.attributes = try? FileManager.default.attributesOfItem(atPath: self.path) as NSDictionary - } - - // MARK: - Spotlight metadata - - func getMetadata() { - // MDItemCreate wants CFString path - let cfPath = self.path as CFString - guard let mdItem = MDItemCreate(kCFAllocatorDefault, cfPath) else { - return - } - defer { } - - guard let attributeNames = MDItemCopyAttributeNames(mdItem) else { - return - } - defer { } - - if let attrs = MDItemCopyAttributes(mdItem, attributeNames) { - self.metadata = attrs as NSDictionary - } - } - - // MARK: - Icon - - // Get an icon for the process: app’s icon or system one - func getIcon() { - // Skip short/non-absolute paths if no bundle (system logs errors otherwise) - if !self.path.hasPrefix("/"), self.bundle == nil { - return - } - - // For apps, try CFBundleIconFile - if let bundle = self.bundle { - if let iconFile = bundle.infoDictionary?["CFBundleIconFile"] as? String { - let iconExt = (iconFile as NSString).pathExtension.isEmpty ? "icns" : (iconFile as NSString).pathExtension - let iconBase = (iconFile as NSString).deletingPathExtension - if let iconPath = bundle.path(forResource: iconBase, ofType: iconExt) { - if let img = NSImage(contentsOfFile: iconPath) { - self.icon = img - } - } - } - } - - // Fallback to workspace icon - if self.bundle == nil || self.icon.size == .zero { - self.icon = NSWorkspace.shared.icon(forFile: self.path) - } - - // Standard size 128x128 - self.icon.size = NSSize(width: 128, height: 128) - } - - // MARK: - Code signing (static) - - // You likely have this helper already, but defining its expected signature for clarity: - func extractSigningInfo(_ code: SecStaticCode?, _ path: String, _ flags: SecCSFlags) -> [String: Any]? { - var staticCode: SecStaticCode? - let url = URL(fileURLWithPath: path) as CFURL - let status = SecStaticCodeCreateWithPath(url, SecCSFlags(), &staticCode) - guard status == errSecSuccess, let codeRef = staticCode else { - return nil - } - - var signingInfo: CFDictionary? - let infoStatus = SecCodeCopySigningInformation(codeRef, flags, &signingInfo) - guard infoStatus == errSecSuccess, let info = signingInfo as? [String: Any] else { - return nil - } - return info - } - - // Your corrected function: - func generateSigningInfo(_ flags: SecCSFlags) { - // These constants are C macros in Security framework headers, not imported automatically: - let kSecCodeInfoStatus = "Status" // corresponds to kSecCodeInfoStatus in Security framework - let KEY_CS_STATUS = kSecCodeInfoStatus // for compatibility with your existing code - - // Instead of 'nil', explicitly pass an optional SecStaticCode? = nil - if let extracted = extractSigningInfo(nil as SecStaticCode?, self.path, flags), - let statusNum = extracted[KEY_CS_STATUS] as? NSNumber, - statusNum.intValue == Int(noErr) { - // Bridge [String: Any] to NSMutableDictionary - self.csInfo = NSMutableDictionary(dictionary: extracted) - } else { - // logging commented out in original code - } - } - - // MARK: - Description - - override var description: String { - return String(format: "name: %@\npath: %@\nattributes: %@\nsigning info: %@\nmetadata: %@", - self.name, - self.path, - self.attributes ?? [:], - self.csInfo, - self.metadata ?? [:]) - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Process.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Process.swift deleted file mode 100644 index 823bb5b2..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Process.swift +++ /dev/null @@ -1,330 +0,0 @@ -// -// Process.swift -// SelfControl -// -// Created by Satendra Singh on 10/10/25. -// - -import Foundation -import OSLog -import Security -import Darwin -import NetworkExtension - -@objcMembers -class Process: NSObject { - - // MARK: - Properties (mirroring Process.h) - - dynamic var pid: pid_t = -1 - dynamic var uid: uid_t = UInt32(bitPattern: -1) - dynamic var type: UInt16 = 0 - dynamic var exit: UInt32 = UInt32(bitPattern: -1) - dynamic var deleted: Bool = false - - dynamic var name: String? - dynamic var path: String? - dynamic var arguments: NSMutableArray? = NSMutableArray() - dynamic var ancestors: NSMutableArray? = NSMutableArray() - dynamic var csInfo: NSMutableDictionary? - dynamic var key: String = "" - dynamic var bundleID: String = "" - dynamic var binary: Binary = Binary.init("") - dynamic var timestamp: Date = Date() - - // MARK: - Init - - override init() { - super.init() - self.arguments = NSMutableArray() - self.ancestors = NSMutableArray() - self.timestamp = Date() - self.pid = -1 - self.uid = UInt32(bitPattern: -1) - self.exit = UInt32(bitPattern: -1) - } - - // Init with audit token pointer - // Matches -(id)init:(audit_token_t*)token - convenience init?(_ token: UnsafePointer) { - self.init() - - // Save pid - let tokenVal = token.pointee - let procPID = audit_token_to_pid(tokenVal) - self.pid = procPID - if self.pid == 0 { -// os_log_error(logHandle, "ERROR: 'audit_token_to_pid' returned NULL") - return nil - } - - // Get path via SecCode (also sets deleted flag) - self.getPath(token) - if (self.path ?? "").isEmpty { -// os_log_error(logHandle, "ERROR: failed to find path for process %d", self.pid) - return nil - } - - // Set name - if self.deleted != true { - self.name = Utilities().getProcessName(0, self.path!) - } else { - self.name = Utilities().getProcessName(self.pid, self.path!) - } - - // Get user - self.uid = audit_token_to_euid(tokenVal) - self.bundleID = Utilities().getBundleID(self.path ?? "") ?? "" - // Generate (dynamic) code information - self.generateSigningInfo(token) - - // Generate key - self.key = self.generateKey() - - // Init binary - self.binary = Binary(self.path ?? "") - - // PID specific logic: args and ancestors - self.getArgs() - self.ancestors = Utilities().generateProcessHierarchy(self.pid) - - // Verify pid-version (avoid pid reuse) - if let currentTokenNSData = Utilities().tokenForPid(self.pid), - currentTokenNSData.length == MemoryLayout.size { - let currentTokenData = Data(referencing: currentTokenNSData) - currentTokenData.withUnsafeBytes { (rawPtr: UnsafeRawBufferPointer) in - if let curPtr = rawPtr.baseAddress?.assumingMemoryBound(to: audit_token_t.self) { - let origVersion = audit_token_to_pidversion(tokenVal) - let curVersion = audit_token_to_pidversion(curPtr.pointee) - if origVersion != curVersion { -// os_log_error(logHandle, "ERROR: audit token mismatch ...pid re-used?") - self.arguments = nil - self.ancestors = nil - } - } - } - } - - // Process alive check - if Utilities().isAlive(self.pid) != true { -// os_log_error(logHandle, "ERROR: process (%d)%{public}@ has already exited", self.pid, self.path ?? "") - return nil - } - } - - // MARK: - Key generation - - // Matches -(NSString*)generateKey - func generateKey() -> String { - var id: String = "" - var signer: Int = 0 - enum Signer: Int { - case None = 0 - case Apple - case AppStore - case DevID - case AdHoc - } - - if let cs = self.csInfo { - if let v = cs[Constants.KEY_CS_SIGNER] as? NSNumber { - signer = v.intValue - } - - // Apple/App Store: just use cs id - if signer == Signer.Apple.rawValue || signer == Signer.AppStore.rawValue { - if let csid = cs[Constants.KEY_CS_ID] as? String, !csid.isEmpty { - id = csid - } - } - // Dev ID: use cs id + leaf signer - else if signer == Int(Signer.DevID.rawValue) { - if let csid = cs[Constants.KEY_CS_ID] as? String, !csid.isEmpty, - let auths = cs[Constants.KEY_CS_AUTHS] as? [Any], let first = auths.first as? String, !first.isEmpty { - id = "\(csid):\(first)" - } - } - } - - if id.isEmpty { - id = self.path ?? "" - } - -// os_log_debug(logHandle, "generated process key: %{public}@", id) - return id - } - - // MARK: - Path - - // Matches -(void)getPath:(audit_token_t*)token - func getPath(_ token: UnsafePointer) { - var status: OSStatus = errSecParam - var code: SecCode? - var staticCode: SecStaticCode? - var pathURL: CFURL? - - // SecCodeCopyGuestWithAttributes using audit token - let attrs: [String: Any] = [kSecGuestAttributeAudit as String: Data(bytes: token, count: MemoryLayout.size)] - status = SecCodeCopyGuestWithAttributes(nil, attrs as CFDictionary, SecCSFlags(), &code) - if status == errSecSuccess, let code = code { - // Convert dynamic code (SecCode) to static code (SecStaticCode) - status = SecCodeCopyStaticCode(code, SecCSFlags(), &staticCode) - if status == errSecSuccess, let staticCode = staticCode { - status = SecCodeCopyPath(staticCode, SecCSFlags(), &pathURL) - if status == errSecSuccess, let url = pathURL as NSURL? { - self.path = (url as URL).path - } else { -// os_log_error(logHandle, "ERROR: 'SecCodeCopyPath' failed with': %#x", status) - } - } else { -// os_log_error(logHandle, "ERROR: 'SecCodeCopyStaticCode' failed with': %#x", status) - } - } else { -// os_log_error(logHandle, "ERROR: 'SecCodeCopyGuestWithAttributes' failed with': %#x", status) - } - - // Deleted binary? - if status == OSStatus(kPOSIXErrorENOENT) { -// os_log_debug(logHandle, "process %d's binary appears to be deleted", self.pid) - self.deleted = true - } - - // Fallback - if pathURL == nil { - self.path = Utilities().getProcessPath(self.pid) - } - - if let p = self.path { - self.path = (p as NSString).resolvingSymlinksInPath - } - - // No CFRelease in Swift ARC; CoreFoundation objects are memory-managed automatically. - } - - // MARK: - Args - - // Matches -(void)getArgs - func getArgs() { - var mib: [Int32] = [CTL_KERN, KERN_ARGMAX, 0] - var systemMaxArgs: Int32 = 0 - var size = MemoryLayout.size(ofValue: systemMaxArgs) - - // Get system arg max - if sysctl(&mib, 2, &systemMaxArgs, &size, nil, 0) == -1 { - return - } - - guard systemMaxArgs > 0 else { return } - let bufSize = Int(systemMaxArgs) - let processArgs = UnsafeMutablePointer.allocate(capacity: bufSize) - defer { processArgs.deallocate() } - - mib = [CTL_KERN, KERN_PROCARGS2, Int32(self.pid)] - size = bufSize - - if sysctl(&mib, 3, processArgs, &size, nil, 0) == -1 { - return - } - - if size <= MemoryLayout.size { - return - } - - // numberOfArgs at start - var numberOfArgs: Int32 = 0 - memcpy(&numberOfArgs, processArgs, MemoryLayout.size) - - // parser after numberOfArgs - var parser = processArgs.advanced(by: MemoryLayout.size) - let endPtr = processArgs.advanced(by: size) - - // Skip executable path (NULL-terminated) - while parser < endPtr { - if parser.pointee == 0 { break } - parser = parser.advanced(by: 1) - } - if parser == endPtr { return } - - // Skip trailing NULLs to argv[0] - while parser < endPtr { - if parser.pointee != 0 { break } - parser = parser.advanced(by: 1) - } - if parser == endPtr { return } - - // Now parse args until count reached - var argStart = parser - let argsArray = NSMutableArray() - while parser < endPtr { - if parser.pointee == 0 { - if let arg = String(validatingUTF8: UnsafePointer(argStart)) { - argsArray.add(arg) - } - parser = parser.advanced(by: 1) - argStart = parser - if argsArray.count == Int(numberOfArgs) { - break - } - continue - } - parser = parser.advanced(by: 1) - } - - self.arguments = argsArray - } - - // MARK: - Code signing - - // Local key mapping for Security's kSecCodeInfoStatus - private let KEY_CS_STATUS = "Status" - - // Helper to extract signing info from an audit token (dynamic code) - private func extractSigningInfo(_ token: UnsafeMutablePointer, - _ requirement: SecRequirement?, - _ flags: SecCSFlags) -> NSMutableDictionary? { - // Build attributes with audit token - let attrs: [String: Any] = [kSecGuestAttributeAudit as String: - Data(bytes: token, count: MemoryLayout.size)] - var code: SecCode? - var status = SecCodeCopyGuestWithAttributes(nil, attrs as CFDictionary, flags, &code) - guard status == errSecSuccess, let codeRef = code else { - return nil - } - - // Convert SecCode (dynamic) to SecStaticCode before querying signing info - var staticCode: SecStaticCode? - status = SecCodeCopyStaticCode(codeRef, flags, &staticCode) - guard status == errSecSuccess, let staticCodeRef = staticCode else { - return nil - } - - var info: CFDictionary? - status = SecCodeCopySigningInformation(staticCodeRef, flags, &info) - guard status == errSecSuccess, let dict = info as? [String: Any] else { - return nil - } - return NSMutableDictionary(dictionary: dict) - } - - // Matches -(void)generateSigningInfo:(audit_token_t*)token - func generateSigningInfo(_ token: UnsafePointer) { - // Pass typed nil for requirement to avoid "nil requires a contextual type" - let requirement: SecRequirement? = nil - if let extracted = extractSigningInfo(UnsafeMutablePointer(mutating: token), requirement, SecCSFlags()) { - if let statusNum = extracted[KEY_CS_STATUS] as? NSNumber, statusNum.intValue == Int(noErr) { - self.csInfo = extracted - } else { -// os_log_error(logHandle, "ERROR: invalid code signing information for %{public}@: %{public}@", self.path ?? "", extracted) - } - } else { -// os_log_error(logHandle, "ERROR: failed to extract code signing information for %{public}@", self.path ?? "") - } - } - - // MARK: - Description - - override var description: String { - return String(format: "pid: %d\npath: %@\nuser: %d\nargs: %@\nancestors: %@\n signing info: %@\n binary:\n%@", self.pid, self.path ?? "nil", self.uid, self.arguments ?? [], self.ancestors ?? [], self.csInfo ?? [:], self.binary.description) - } -} - diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/SourceAppAuditTokenBuilder.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/SourceAppAuditTokenBuilder.swift deleted file mode 100644 index 68b96414..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/SourceAppAuditTokenBuilder.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// SourceAppAuditTokenBuilder.swift -// SelfControlExtension -// -// Created by Satendra Singh on 10/10/25. -// - -import NetworkExtension -import Security -import Foundation -import AppKit - -struct SourceAppAuditTokenBuilder { - - static func getAppInfo(from flow: NEFilterFlow) -> (bundleID: String?, appName: String?) { - guard let auditTokenData = flow.sourceAppAuditToken else { - return (nil, nil) - } - - // Convert NSData → audit_token_t - let auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in - return ptr.load(as: audit_token_t.self) - } - - // MARK: 1. Extract PID from audit token - let pid = audit_token_to_pid(auditToken) - - // MARK: 2. Use SecTask to get bundle identifier - var bundleID: String? = nil - if let secTask = SecTaskCreateWithAuditToken(nil, auditToken) { - // macOS doesn’t define kSecEntitlementApplicationIdentifier constant, - // so use the raw entitlement string instead: - let entitlementKey = "application-identifier" as CFString - bundleID = SecTaskCopyValueForEntitlement(secTask, entitlementKey, nil) as? String - } - - // MARK: 3. Try to get app name from bundle ID - var appName: String? = nil - if let bundleID = bundleID, - let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID) { - appName = appURL.deletingPathExtension().lastPathComponent - } - - // MARK: 4. Fallback: get executable name via proc_pidpath() - if appName == nil { - var pathBuffer = [CChar](repeating: 0, count: Int(PATH_MAX)) - let result = proc_pidpath(pid, &pathBuffer, UInt32(pathBuffer.count)) - if result > 0 { - let path = String(cString: pathBuffer) - appName = URL(fileURLWithPath: path).lastPathComponent - } - } - - return (bundleID, appName) - } - - // Helper: extract PID from audit_token_t - private static func audit_token_to_pid(_ token: audit_token_t) -> pid_t { - return pid_t(token.val.0) - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Utilities.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Utilities.swift deleted file mode 100644 index 0ee82162..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/Process/Utilities.swift +++ /dev/null @@ -1,714 +0,0 @@ -// -// File.swift -// SelfControl -// -// Created by Satendra Singh on 10/10/25. -// - - -// Swift port of utilities.m -// -// Note: This file relies on C system APIs. Ensure your bridging header -// imports the needed headers: libproc.h, sys/sysctl.h, CommonCrypto/CommonCrypto.h, -// mach/mach.h, Security/Security.h, Carbon/Carbon.h, CoreServices/CoreServices.h, CFNetwork/CFHost.h -// - -import AppKit -import Foundation -import OSLog -import SystemConfiguration -import Darwin -import CoreServices // for MDItem -import Security -import os.log -import os - -// Define C macro from libproc.h (not imported automatically into Swift) -private let PROC_PIDPATHINFO_MAXSIZE: Int = 4096 - -// MARK: - Globals - -final class Utilities { - // Provided elsewhere in project - - let logger = Logger(subsystem: "com.example.myapp", category: "network") - - // MARK: - Version / Bundle - - @objc - public func getAppVersion() -> String? { - return Bundle.main.infoDictionary?["CFBundleVersion"] as? String - } - - @objc - public func getBundleExecutable(_ appPath: String) -> String? { - guard let bundle = Bundle(path: appPath) else { - logger.error("ERROR: failed to load app bundle for %{public}\(appPath)") - return nil - } - return (bundle.executablePath as NSString?)?.resolvingSymlinksInPath - } - - // MARK: - Parent process (Carbon/deprecated) - - @objc - public func getRealParent(_ pid: pid_t) -> NSDictionary? { - let PROC_PIDPATHINFO_MAXSIZE = 4096 - - // Step 1: Get parent PID - var info = kinfo_proc() - var size = MemoryLayout.stride - var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid] - - guard sysctl(&mib, u_int(mib.count), &info, &size, nil, 0) == 0 else { - return nil - } - - let ppid = info.kp_eproc.e_ppid - if ppid == 0 { return nil } - - // Step 2: Get parent process path - var pathBuffer = [CChar](repeating: 0, count: Int(PROC_PIDPATHINFO_MAXSIZE)) - let result = proc_pidpath(ppid, &pathBuffer, UInt32(pathBuffer.count)) - let path = (result > 0) ? String(cString: pathBuffer) : nil - - // Step 3: Try to get bundle info via NSRunningApplication - var appName: String? = nil - var bundleID: String? = nil - - if let app = NSRunningApplication(processIdentifier: ppid) { - appName = app.localizedName - bundleID = app.bundleIdentifier - } - - // Step 4: Construct NSDictionary for compatibility - let dict: NSDictionary = [ - "ParentPID": NSNumber(value: ppid), - "ParentPath": path ?? "", - "ParentAppName": appName ?? "", - "ParentBundleID": bundleID ?? "" - ] - - return dict - } - - - // MARK: - Process hierarchy - - @objc - public func generateProcessHierarchy(_ child: pid_t) -> NSMutableArray { - let ancestors = NSMutableArray() - typealias RPIDFunc = @convention(c) (pid_t) -> pid_t - let rpidSym = dlsym(dlopen(nil, RTLD_NOW), "responsibility_get_pid_responsible_for_pid") - let getRPID = rpidSym.map { unsafeBitCast($0, to: RPIDFunc.self) } - - var currentPID = child - - while true { - let currentPath = getProcessPath(currentPID) ?? NSLocalizedString("unknown", comment: "unknown") - let currentName = getProcessName(0, currentPath) ?? NSLocalizedString("unknown", comment: "unknown") - ancestors.insert([ - Constants.KEY_PROCESS_ID: NSNumber(value: currentPID), - Constants.KEY_PROCESS_PATH: currentPath, - Constants.KEY_PROCESS_NAME: currentName - ], at: 0) - - var parentPID: pid_t = 0 - - if getuid() != 0 { - if let parent = getRealParent(currentPID), let p = parent["pid"] as? NSNumber { - parentPID = pid_t(truncating: p) - } - } - - if parentPID == 0, let getRPID = getRPID { - parentPID = getRPID(currentPID) - } - - if parentPID <= 0 || parentPID == currentPID { - parentPID = getParent(Int32(currentPID)) - } - - if parentPID <= 0 || parentPID == currentPID { - break - } - - currentPID = parentPID - } - - // add KEY_INDEX for UI - for i in 0.. String? { - var uid: uid_t = 0 - var gid: gid_t = 0 - - // explicitly type `nil` as `SCDynamicStore?` - if let cfUser = SCDynamicStoreCopyConsoleUser(nil as SCDynamicStore?, &uid, &gid) { - return cfUser as String - } - return nil - } - - // MARK: - Process name - - @objc - public func getProcessName(_ pid: pid_t, _ path: String) -> String? { - - let PROC_PIDPATHINFO_MAXSIZE = 4096 // actual constant value from libproc.h - - if pid != 0 { - var nameBuf = [CChar](repeating: 0, count: Int(PROC_PIDPATHINFO_MAXSIZE)) - let status = proc_name(pid, &nameBuf, UInt32(nameBuf.count)) - if status >= 0 { - return String(cString: nameBuf) - } - } - - if let bundle = findAppBundle(path), - let name = bundle.infoDictionary?["CFBundleName"] as? String { - return name - } - - return (path as NSString).lastPathComponent - } - - @objc - public func getBundleID(_ path: String) -> String? { - if let bundle = findAppBundle(path), - let name = bundle.infoDictionary?["CFBundleIdentifier"] as? String { - return name - } - return nil - } - - // MARK: - Find app bundle - - @objc - public func findAppBundle(_ path: String) -> Bundle? { - let standardized = ((path as NSString).standardizingPath as NSString).resolvingSymlinksInPath - var appPath: NSString = standardized as NSString - - while true { - if let bundle = Bundle(path: appPath as String) { - if bundle.bundlePath == standardized { return bundle } - if bundle.executablePath == standardized { return bundle } - } - - let next = appPath.deletingLastPathComponent as NSString - if next.length == 0 || next as String == "/" { break } - appPath = next - } - - return nil - } - - // MARK: - Process path - - @objc - public func getProcessPath(_ pid: pid_t) -> String? { - var buf = [CChar](repeating: 0, count: Int(PROC_PIDPATHINFO_MAXSIZE)) - let status = proc_pidpath(pid, &buf, UInt32(buf.count)) - if status > 0 { - return String(cString: buf) - } else { -// os_log_error(logHandle, "ERROR: for process %d, 'proc_pidpath' failed with %d (errno: %d)", pid, status, errno) - - var mib = [CTL_KERN, KERN_ARGMAX, 0] - var argMax: Int = 0 - var size = MemoryLayout.size(ofValue: argMax) - if sysctl(&mib, 2, &argMax, &size, nil, 0) == -1 { return nil } - - let argBuf = UnsafeMutablePointer.allocate(capacity: argMax) - defer { argBuf.deallocate() } - - mib = [CTL_KERN, KERN_PROCARGS2, Int32(pid)] - size = argMax - if sysctl(&mib, 3, argBuf, &size, nil, 0) == -1 { return nil } - if size <= MemoryLayout.size { return nil } - - // argv0 path follows int argc - let pathPtr = argBuf.advanced(by: MemoryLayout.size) - let p = String(cString: pathPtr) - - if p.hasPrefix("./") { - let trimmed = String(p.dropFirst(2)) - if let cwd = getProcessCWD(pid) { - return (cwd as NSString).appendingPathComponent(trimmed) - } - } - - return p - } - } - - // MARK: - CWD - - @objc - public func getProcessCWD(_ pid: pid_t) -> String? { - var vpi = proc_vnodepathinfo() - let status = withUnsafeMutablePointer(to: &vpi) { ptr -> Int32 in - return proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, ptr, Int32(MemoryLayout.size)) - } - if status > 0 { - return withUnsafePointer(to: vpi.pvi_cdir.vip_path) { ptr in - return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self)) - } - } - return nil - } - - // MARK: - PIDs for path/user - - @objc - public func getProcessIDs(_ processPath: String, _ userID: Int32) -> NSMutableArray { - let result = NSMutableArray() - - let count = proc_listallpids(nil, 0) - guard count > 0 else { return result } - - let pids = UnsafeMutablePointer.allocate(capacity: Int(count)) - defer { pids.deallocate() } - - let status = proc_listallpids(pids, count * Int32(MemoryLayout.size)) - guard status >= 0 else { return result } - - var mib = [CTL_KERN, KERN_PROC, KERN_PROC_PID, 0] - var kproc = kinfo_proc() - let kprocSize = MemoryLayout.size - - for i in 0.. NSImage? { - // invalid path -> generic application icon - guard FileManager.default.fileExists(atPath: path) else { - let icon = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kGenericApplicationIcon))) - icon.size = NSSize(width: 128, height: 128) - return icon - } - - if let bundle = findAppBundle(path) { - if let icon = NSWorkspace.shared.icon(forFile: bundle.bundlePath) as NSImage? { - return icon - } - - if let iconFile = bundle.infoDictionary?["CFBundleIconFile"] as? NSString { - var ext = iconFile.pathExtension - if ext.isEmpty { ext = "icns" } - if let iconPath = bundle.path(forResource: iconFile.deletingPathExtension, ofType: ext) { - return NSImage(contentsOfFile: iconPath) - } - } - } - - var icon = NSWorkspace.shared.icon(forFile: path) - // replace generic document icon with application icon - let documentIcon = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kGenericDocumentIcon))) - if icon == documentIcon { - icon = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kGenericApplicationIcon))) - } - icon.size = NSSize(width: 128, height: 128) - return icon - } - - // MARK: - Make modal - - @objc - public func makeModal(_ controller: NSWindowController) { - var window: NSWindow? - - for _ in 0..<20 { - DispatchQueue.main.sync { - window = controller.window - } - if window == nil { - Thread.sleep(forTimeInterval: 0.05) - continue - } - DispatchQueue.main.sync { - NSApplication.shared.runModal(for: controller.window!) - } - break - } - } - - // MARK: - Find processes by name - - @objc - public func findProcesses(_ processName: String) -> NSMutableArray { - let processes = NSMutableArray() - let count = proc_listpids(UInt32(PROC_ALL_PIDS), 0, nil, 0) - guard count > 0 else { return processes } - - let pids = UnsafeMutablePointer.allocate(capacity: Int(count)) - defer { pids.deallocate() } - - let status = proc_listpids(UInt32(PROC_ALL_PIDS), 0, pids, count * Int32(MemoryLayout.size)) - guard status >= 0 else { return processes } - - for i in 0.. Date? { -// os_log_debug(logHandle, "extracting 'kMDItemDateAdded' for %{public}@", file) -// -// let url: URL -// if let bundle = findAppBundle(file) { -// url = bundle.bundleURL -// } else { -// url = URL(fileURLWithPath: file) -// } -// -// guard let item = MDItemCreateWithURL(nil, url as CFURL) else { return nil } -// defer { CFRelease(item) } -// -// if let date = MDItemCopyAttribute(item, kMDItemDateAdded)?.takeRetainedValue() as? Date { -// os_log_debug(logHandle, "extacted date, %{public}@, for %{public}@", String(describing: date), file) -// return date -// } else { -// os_log_debug(logHandle, "'kMDItemDateAdded' is nil ...falling back to 'kMDItemFSCreationDate'") -// if let date = MDItemCopyAttribute(item, kMDItemFSCreationDate)?.takeRetainedValue() as? Date { -// os_log_debug(logHandle, "extacted date, %{public}@, for %{public}@", String(describing: date), file) -// return date -// } -// } -// return nil -// } - - // MARK: - Parent PID - - @objc - public func getParent(_ pid: Int32) -> pid_t { - var kproc = kinfo_proc() - var size = MemoryLayout.size - var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid] // 👈 consistent Int32s - let res = sysctl(&mib, u_int(mib.count), &kproc, &size, nil, 0) - if res == 0 && size != 0 { - let ppid = kproc.kp_eproc.e_ppid -// os_log_debug(logHandle, "extracted parent ID %d for process: %d", ppid, pid) - return ppid - } - return -1 - } - - // MARK: - Dark mode - - @objc - public func isDarkMode() -> Bool { - return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" - } - - // MARK: - String default - - @objc - public func valueForStringItem(_ item: String?) -> String { - return item ?? NSLocalizedString("unknown", comment: "unknown") - } - - // MARK: - Alerts - - @objc - public func showAlert(_ style: NSAlert.Style, _ messageText: String, _ informativeText: String?, _ buttons: [String]) -> NSApplication.ModalResponse { - let alert = NSAlert() - alert.alertStyle = style - alert.messageText = messageText - if let info = informativeText { - alert.informativeText = info - } - for title in buttons { - alert.addButton(withTitle: title) - } - if let first = alert.buttons.first { - first.keyEquivalent = "\r" - } - - NSApp.setActivationPolicy(.regular) - if #available(macOS 14.0, *) { - NSApp.activate() - } else { - NSApp.activate(ignoringOtherApps: true) - } - NSRunningApplication.current.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) - - alert.window.makeKeyAndOrderFront(nil) - alert.window.center() - - let response = alert.runModal() -// (NSApp.delegate as? AppDelegate)?.setActivationPolicy() - return response - } - - // MARK: - Audit token for pid - - // Swift doesn't expose this C macro — we define it manually. - let TASK_AUDIT_TOKEN_COUNT = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) - - @objc - public func tokenForPid(_ pid: pid_t) -> NSData? { - var task: mach_port_t = 0 - var token = audit_token_t() - var size = TASK_AUDIT_TOKEN_COUNT - -// os_log_debug(logHandle, "retrieving audit token for %d", pid) - - var kr = task_name_for_pid(mach_task_self_, pid, &task) - guard kr == KERN_SUCCESS else { -// os_log_error(logHandle, "ERROR: 'task_name_for_pid' failed with %x", kr) - return nil - } - defer { mach_port_deallocate(mach_task_self_, task) } - - kr = withUnsafeMutablePointer(to: &token) { ptr in - ptr.withMemoryRebound(to: integer_t.self, capacity: Int(size)) { intPtr in - return task_info(task, task_flavor_t(TASK_AUDIT_TOKEN), intPtr, &size) - } - } - - guard kr == KERN_SUCCESS else { -// os_log_error(logHandle, "ERROR: 'task_info' failed with %x", kr) - return nil - } - -// os_log_debug(logHandle, "retrieved audit token") - return NSData(bytes: &token, length: MemoryLayout.size) - } - - // MARK: - Reverse DNS resolve - - @objc - public func resolveAddress(_ ipAddr: String) -> NSArray? { -// os_log_debug(logHandle, "(attempting to) reverse resolve %{public}@", ipAddr) - - var hints = addrinfo(ai_flags: AI_NUMERICHOST, ai_family: PF_UNSPEC, ai_socktype: SOCK_STREAM, ai_protocol: 0, ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil) - var res: UnsafeMutablePointer? - guard getaddrinfo(ipAddr, nil, &hints, &res) == 0, let result = res else { - return nil - } - defer { freeaddrinfo(result) } - - guard let addrData = CFDataCreate(nil, UnsafePointer(OpaquePointer(result.pointee.ai_addr)), CFIndex(result.pointee.ai_addrlen)) else { - return nil - } - defer { /*addrData*/ } - - let host = CFHostCreateWithAddress(kCFAllocatorDefault, addrData).takeRetainedValue() - var streamErr = CFStreamError() - guard CFHostStartInfoResolution(host, .names, &streamErr) else { - return nil - } - guard let names = CFHostGetNames(host, nil)?.takeUnretainedValue() as NSArray? else { - return nil - } - return names - } - - // MARK: - Process alive - - @objc - public func isAlive(_ processID: pid_t) -> Bool { - errno = 0 - _ = kill(processID, 0) - if errno == ESRCH { return false } - - var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, Int32(processID)] - var info = kinfo_proc() - var size = MemoryLayout.size - if sysctl(&mib, 4, &info, &size, nil, 0) == 0 { - // SZOMB check - if (UInt8(info.kp_proc.p_stat) & UInt8(SZOMB)) == UInt8(SZOMB) { - return false - } - } - return true - } - - // MARK: - Simulator app? - - @objc - public func isSimulatorApp(_ path: String) -> Bool { -// os_log_debug(logHandle, "checking if %{public}@ is a simulator application", path) - guard let bundle = findAppBundle(path) else { return false } - guard let platforms = bundle.infoDictionary?["CFBundleSupportedPlatforms"] as? [String], !platforms.isEmpty else { - return false - } -// os_log_debug(logHandle, "supported platforms: %{public}@", String(describing: platforms)) - let set = Set(platforms) - return set.isSubset(of: Set(["iPhoneSimulator", "AppleTVSimulator"])) - } - - // MARK: - Launched by user? - - @objc - public func launchedByUser() -> Bool { - guard let parent = getRealParent(getpid()) else { return false } - let bid = parent["CFBundleIdentifier"] as? NSString - if bid == "com.apple.dock" || bid == "com.apple.finder" || bid == "com.apple.Terminal" { - return true - } - return false - } - - // MARK: - Fade out - - @objc - public func fadeOut(_ window: NSWindow, _ duration: Float) { - NSAnimationContext.runAnimationGroup { ctx in - ctx.duration = TimeInterval(duration) - window.animator().alphaValue = 0.0 - } completionHandler: { - window.close() - } - } - - // MARK: - Code-signing info match - -// @objc -// public func matchesCSInfo(_ csInfo1: NSDictionary?, _ csInfo2: NSDictionary?) -> Bool { -// var status1 = -1, status2 = -1 -// var signer1 = -1, signer2 = -1 -// var id1: String?, id2: String? -// var auths1: [Any]?, auths2: [Any]? -// -// if let n = csInfo1?[KEY_CS_STATUS] as? NSNumber { status1 = n.intValue } -// if let n = csInfo2?[KEY_CS_STATUS] as? NSNumber { status2 = n.intValue } -// if status1 != status2 { -// os_log_error(logHandle, "ERROR: code signing mismatch (signing status): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) -// return false -// } -// -// if let n = csInfo1?[KEY_CS_SIGNER] as? NSNumber { signer1 = n.intValue } -// if let n = csInfo2?[KEY_CS_SIGNER] as? NSNumber { signer2 = n.intValue } -// if signer1 != signer2 { -// if (signer1 == Apple && signer2 == AppStore) || (signer1 == AppStore && signer2 == Apple) { -// os_log_error(logHandle, "ignoring case where Apple App moved to/from Mac App Store: %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) -// } else { -// os_log_error(logHandle, "ERROR: code signing mismatch (signer): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) -// return false -// } -// } -// -// if let s = csInfo1?[KEY_CS_ID] as? String { id1 = s } -// if let s = csInfo2?[KEY_CS_ID] as? String { id2 = s } -// if (id1 != nil || id2 != nil), id1 != id2 { -// os_log_error(logHandle, "ERROR: code signing mismatch (signing ID): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) -// return false -// } -// -// if let a = csInfo1?[KEY_CS_AUTHS] as? [Any] { auths1 = a } -// if let a = csInfo2?[KEY_CS_AUTHS] as? [Any] { auths2 = a } -// if (auths1 != nil || auths2 != nil) && !(auths1 as NSArray? ?? []).isEqual(to: auths2 ?? []) { -// os_log_error(logHandle, "ERROR: code signing mismatch (signing auths): %{public}@ / %{public}@", String(describing: csInfo1), String(describing: csInfo2)) -// return false -// } -// -// return true -// } - - // MARK: - Escape JSON - - @objc - public func toEscapedJSON(_ input: String) -> String? { - do { - let data = try JSONSerialization.data(withJSONObject: input, options: .fragmentsAllowed) - return String(data: data, encoding: .utf8) - } catch { -// os_log_error(logHandle, "ERROR: failed to convert/escape %{public}@ to JSON (error: %{public}@)", input, error.localizedDescription) - return nil - } - } - - // MARK: - Absolute date from HH:mm (next 24h) - - @objc - public func absoluteDate(_ date: Date) -> Date { -// os_log_debug(logHandle, "function '%{public}s' invoked with %{public}@", #function, String(describing: date)) - - let now = Date() - let cal = Calendar.current - - let comps = cal.dateComponents([.hour, .minute], from: date) - var nowComps = cal.dateComponents([.year, .month, .day, .hour, .minute], from: now) - nowComps.hour = comps.hour - nowComps.minute = comps.minute - - var absDate = cal.date(from: nowComps) ?? now - if absDate < now { - absDate = cal.date(byAdding: .day, value: 1, to: absDate) ?? absDate - } - return absDate - } - - // MARK: - Internal volume? - - @objc - public func isInternalProcess(_ path: String) -> Bool { - var isInternal: AnyObject? - do { - var url = URL(fileURLWithPath: path) - try (url as NSURL).getResourceValue(&isInternal, forKey: URLResourceKey.volumeIsInternalKey) - return (isInternal as? NSNumber)?.boolValue ?? false - } catch { -// os_log_error(logHandle, "ERROR: 'getResourceValue'/'NSURLVolumeIsInternalKey' failed with %@", error.localizedDescription) - return false - } - } - - -} - diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/README.md b/Self-Control-Extension/SelfControl/SelfControlExtension/README.md deleted file mode 100644 index 234977c0..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# SelfControlExtension - -This directory contains the NetworkExtension implementation for SelfControl, responsible for URL and domain filtering. - -## Structure - -- `FilterProvider/` - NetworkExtension filter provider implementation -- `Configuration/` - Extension configuration code \ No newline at end of file diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift deleted file mode 100644 index 3ef45685..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/RequestSourceAppValidator.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// HostAppValidator.swift -// SelfControl -// -// Created by Satendra Singh on 29/11/25. -// - -import NetworkExtension -import OSLog - -struct RequestSourceAppValidator { - - static func isAllowedHost(flow: NEFilterFlow) -> Bool { - let process = processFromflow(flow: flow) -// os_log(" FilterDataProvider: appIdAndName %{public}@, %{public}@", log: OSLog.default, type: .debug, process?.name ?? "", process?.path ?? "") - os_log(" FilterDataProvider: app %{public}@", log: OSLog.default, type: .debug, process?.description ?? "") - let isSafariExtensionEnabled = IPCConnection.shared.isSafariExtensionEnable - let isChromeExtensionEnabled = IPCConnection.shared.isGoogleChromeEnabled - - if let process = process, AllowedProcess.isAllowedProcess(process, isSafariExtensionActive: isSafariExtensionEnabled, isChromeExtensionActive: isChromeExtensionEnabled) { -// os_log("[SC] 🔍] handleNewFlow: allowing as it is in allowed list.") - return true - } - return false - } - - static func processFromflow(flow: NEFilterFlow) -> Process? { - guard let auditTokenData = flow.sourceAppAuditToken else { - return nil - } - - // Convert NSData → audit_token_t - var auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in - return ptr.load(as: audit_token_t.self) - } - return Process.init(&auditToken) - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/ReverseDomainMapper.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/ReverseDomainMapper.swift deleted file mode 100644 index 0610f54e..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/ReverseDomainMapper.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// ReverseDomainMapper.swift -// SelfControlExtension -// -// Created by Satendra Singh on 14/10/25. -// - -import Foundation - -final class ReverseDomainMapper { - static func reverseDNSUsingGetNameInfo(ipAddress: String) -> String? { - var addr = sockaddr_in() - addr.sin_len = UInt8(MemoryLayout.size) - addr.sin_family = sa_family_t(AF_INET) - inet_pton(AF_INET, ipAddress, &addr.sin_addr) - - var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - - // Precompute length so we don't read from `addr` inside the closure. - let addrLen = socklen_t(addr.sin_len) - - let result: Int32 = withUnsafePointer(to: &addr) { ptr in - ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in - getnameinfo(saPtr, - addrLen, - &hostname, socklen_t(hostname.count), - nil, 0, - NI_NAMEREQD) - } - } - - guard result == 0 else { return nil } - return String(cString: hostname) - } -} - -enum Regex { - static let ipAddress = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" - static let hostname = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$" -} - -extension String { - var isValidIpAddress: Bool { - return self.matches(pattern: Regex.ipAddress) - } - - var isValidHostname: Bool { - return self.matches(pattern: Regex.hostname) - } - - private func matches(pattern: String) -> Bool { - return self.range(of: pattern, - options: .regularExpression, - range: nil, - locale: nil) != nil - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/String+URL.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/String+URL.swift deleted file mode 100644 index b2758df0..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/String+URL.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// String+URL.swift -// SelfControlExtension -// -// Created by Satendra Singh on 05/08/25. -// - -import Foundation - -extension String { - var domainString: String? { - Self.extractDomain(from: self) - } - - static func extractDomain(from urlString: String) -> String? { - var formattedURLString = urlString - if !formattedURLString.lowercased().hasPrefix("http://") && - !formattedURLString.lowercased().hasPrefix("https://") { - formattedURLString = "https://" + formattedURLString - } - - guard let url = URL(string: formattedURLString), - let host = url.host else { - return nil - } - - // Optional: extract root domain (e.g., "facebook.com" from "sub.facebook.com") - let components = host.components(separatedBy: ".") - if components.count >= 2 { - return components.suffix(2).joined(separator: ".") - } else { - return host - } - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/TLDURLToDomain.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/TLDURLToDomain.swift deleted file mode 100644 index f652a950..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/TLDURLToDomain.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// TLDURLToDomain.swift -// SelfControlExtension -// -// Created by Satendra Singh on 15/10/25. -// - -import Foundation - -final class TLDURLToDomain { - func domain(for tldURL: URL) -> String? { - return tldURL.host - } - - private func extractDomain(from url: URL) -> String? { - return url.host - } - - - func getDomain(from urlString: String) -> String? { - guard let url = URL(string: urlString) else { - print("Invalid URL string.") - return nil - } - - // Using URL.host - if let host = url.host { - return host - } - return nil - } - - static func getURLDomain(from input: String) -> String? { - // Normalize input to ensure it has a scheme - let formatted = input.contains("://") ? input : "https://\(input)" - guard let host = URL(string: formatted)?.host else { return nil } - - let parts = host.split(separator: ".") - guard parts.count >= 2 else { return host } - - // Return last two parts (e.g., facebook.com, google.co.uk) - if parts.count >= 3 && parts[parts.count - 2] == "co" { - return parts.suffix(3).joined(separator: ".") // handles e.g. google.co.uk - } else { - return parts.suffix(2).joined(separator: ".") - } - } - -} diff --git a/Self-Control-Extension/SelfControl/SelfControlExtension/main.swift b/Self-Control-Extension/SelfControl/SelfControlExtension/main.swift deleted file mode 100644 index ffadae74..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlExtension/main.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// main.swift -// SelfControlExtension -// -// Created by Egzon Arifi on 02/04/2025. -// - -import Foundation -import NetworkExtension -import os.log - -autoreleasepool { - os_log("[SC] 🔍] first light") - NEProvider.startSystemExtensionMode() - IPCConnection.shared.startListener() -} - -dispatchMain() From e34d5ef06e2260cd3de9517575bb53ced6ec2020 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Fri, 22 May 2026 22:30:37 +0530 Subject: [PATCH 44/64] Implemented Chrome extension handling with new background blocking feature --- .../SelfControl.xcodeproj/project.pbxproj | 4 +- .../SelfControl/SelfControl/Info.plist | 8 +- .../HelperConnection+ReceivedMessages.swift | 22 +++ ...erConnection+SafariExtensionMessages.swift | 21 ++- .../HelperConnection.swift | 31 ++-- .../LaunchedInstallManager/CheckProcess.swift | 55 ++++++ .../LaunchAgentManager.swift | 5 +- .../Main/SelfControlUI/NewContentView.swift | 57 +++++-- .../{ => CoreService}/AppDelegate.swift | 2 +- .../CoreService/AppStateManager.swift | 52 ++++++ ...elperService+SafariExtensionMessages.swift | 19 ++- .../HelperService+SendMessages.swift | 16 ++ .../CoreService/HelperService.swift | 82 ++++++++- .../Helpers/DelayTimerHandler.swift | 159 ++++++++++++++++++ .../ChromeExtensionRequestListner.swift | 92 +++++----- .../EventScheduler/BlockContentStore.swift | 111 ++++++++++++ .../Shared/AppToExtensionExtension.swift | 4 +- .../Shared/HelperServiceProtocol.swift | 12 ++ 18 files changed, 661 insertions(+), 91 deletions(-) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/CheckProcess.swift rename Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/{ => LaunchedInstallManager}/LaunchAgentManager.swift (97%) rename Self-Control-Extension/SelfControl/SelfControlBGService/{ => CoreService}/AppDelegate.swift (97%) create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift rename Self-Control-Extension/SelfControl/{SelfControl/Main/ChromeExtensionSupport => SelfControlBGService/Services/Chrome+SafariExtensionSupport}/ChromeExtensionRequestListner.swift (56%) create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/BlockContentStore.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index 5496cb33..15826e95 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -724,7 +724,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; @@ -759,7 +759,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; diff --git a/Self-Control-Extension/SelfControl/SelfControl/Info.plist b/Self-Control-Extension/SelfControl/SelfControl/Info.plist index 539358cf..14adde33 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Info.plist +++ b/Self-Control-Extension/SelfControl/SelfControl/Info.plist @@ -17,13 +17,13 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.14 + 1.15 CFBundleVersion - 14 + 15 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright - Copyright © 2025 SelfControl. All rights reserved. + Copyright © 2026 SelfControl. All rights reserved. NSLocalNetworkUsageDescription DNS lookup NSMainStoryboardFile @@ -36,6 +36,8 @@ SMPrivilegedExecutables + SelfControlBGLaunchService.app + identifier "org.eyebeam.selfcontrold" and anchor apple generic and certificate leaf[subject.OU] = "X6FQ433AWK" org.eyebeam.selfcontrold identifier "org.eyebeam.selfcontrold" and anchor apple generic and certificate leaf[subject.OU] = "X6FQ433AWK" diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift new file mode 100644 index 00000000..21b74682 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift @@ -0,0 +1,22 @@ +// +// HelperConnection+ReceivedMessages.swift +// SelfControl +// +// Created by Satendra Singh on 19/05/26. +// + +extension HelperConnection { + func didEnableWebExtension(_ extensionTypeRawValue: String, state: Bool) { +// Task { @MainActor in + if let ext = WEBExtension(rawValue: extensionTypeRawValue) { + onExtensionStateChange?(ext, state) +// switch ext { +// case .chrome: +// NetworkExtensionState.shared.isChromeExtensionEnabled = state +// case .safari: +// NetworkExtensionState.shared.isSafariExtensionEnabled = state +// } + } +// } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift index 9ab0a56b..4e04dd0d 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift @@ -29,5 +29,24 @@ extension HelperConnection: AppToExtensionExtension { func register(completionHandler: @escaping (Bool) -> Void) { bgProxyServiceConnection()?.register(completionHandler: completionHandler) } - + + func send_startNetwrokBlocking(minutes: Int) { + bgProxyServiceConnection()?.startNetwrokBlocking(minutes: minutes) + } + + func send_stopNetworkBlocking() { + bgProxyServiceConnection()?.stopNetworkBlocking() + } + + func send_getBlockedStates() { + bgProxyServiceConnection()?.getBlockedStates { state, endDate in + if state == true, let endDate = endDate { + let minutes: Double = Double(endDate.timeIntervalSinceNow / 60) + self.blockedStateHandler?(minutes) + } + print("REceived Blocked State: \(String(describing: state)), End Date: \(String(describing: endDate))") + } + } } +//TODO: create extension on protocol with shared variable to send these message with static + diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift index f8a9974e..601a2b10 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift @@ -9,16 +9,21 @@ import Cocoa import ServiceManagement +enum HelperServiceConstants: String { + case bundleID = "com.application.SelfControl.corebits.bgservice" // HelperApp’s bundle identifier + case processName = "SelfControlBGService" + case machServiceName = "com.application.SelfControl.corebits.bgservice.xpc" +} + final class HelperConnection: NSObject, HelperClientProtocol, NSSecureCoding { static let shared = HelperConnection() static var supportsSecureCoding: Bool = true - - private let helperBundleID = "com.application.SelfControl.corebits.bgservice" // HelperApp’s bundle identifier - private let machServiceName = "com.application.SelfControl.corebits.bgservice.xpc" - + var onExtensionStateChange: ((WEBExtension, Bool) -> Void)? + var blockedStateHandler: ((Double) -> Void)? private var connection: NSXPCConnection? private var exportedConnection: NSXPCListenerEndpoint? - + private let checkProcess = CheckProcess(bundleID: HelperServiceConstants.bundleID.rawValue, processName: HelperServiceConstants.processName.rawValue) + private let stateQueue = DispatchQueue(label: "com.application.SelfControl.corebits.bgservice.connection", attributes: .concurrent) override init() { } @@ -33,15 +38,19 @@ final class HelperConnection: NSObject, HelperClientProtocol, NSSecureCoding { // MARK: - Public API func installLoginItemIfNeeded() async throws { - do { - try LaunchAgentManager.install() - } catch { - throw error + if !checkProcess.isRunning() { + do { + try LaunchAgentManager.install() + } catch { + throw error + } + } else { + print("BG App is already running") } } func uninstallLoginItem() throws { - let loginItem = SMAppService.loginItem(identifier: helperBundleID) + let loginItem = SMAppService.loginItem(identifier: HelperServiceConstants.bundleID.rawValue) try loginItem.unregister() } @@ -49,7 +58,7 @@ final class HelperConnection: NSObject, HelperClientProtocol, NSSecureCoding { stateQueue.sync(flags: .barrier) { guard self.connection == nil else { return } - let conn = NSXPCConnection(machServiceName: machServiceName, options: []) + let conn = NSXPCConnection(machServiceName: HelperServiceConstants.machServiceName.rawValue, options: []) // Remote interface: the helper’s service protocol conn.remoteObjectInterface = NSXPCInterface(with: HelperServiceProtocol.self) // Exported interface: our client protocol to receive callbacks diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/CheckProcess.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/CheckProcess.swift new file mode 100644 index 00000000..8c7a7298 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/CheckProcess.swift @@ -0,0 +1,55 @@ +// +// CheckProcess.swift +// SelfControl +// +// Created by Satendra Singh on 21/05/26. +// + + +import AppKit + +struct CheckProcess { + + var bundleID: String + var processName: String + + func isRunning() -> Bool { + // Use the bundle identifier if possible (most reliable). + if isAppRunning(bundleIdentifier: bundleID) { + fputs("Installation aborted: \(bundleID) is currently running.\n", stderr) + return true + } + + // Optional: also guard by process name as a fallback. + if isProcessRunningByName(processName) { + fputs("Installation aborted: process '\(processName)' is currently running.\n", stderr) + return true + } + + // OK to proceed + return false + } + + private func isAppRunning(bundleIdentifier: String) -> Bool { + !NSRunningApplication.runningApplications(withBundleIdentifier: bundleIdentifier).isEmpty + } + + private func isProcessRunningByName(_ name: String) -> Bool { + // Uses /usr/bin/pgrep -x for exact match + let task = Process() + task.launchPath = "/usr/bin/pgrep" + task.arguments = ["-x", name] + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = Pipe() + + do { + try task.run() + task.waitUntilExit() + return task.terminationStatus == 0 + } catch { + return false + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchAgentManager.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/LaunchAgentManager.swift similarity index 97% rename from Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchAgentManager.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/LaunchAgentManager.swift index 4cd90a18..ee91b74c 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchAgentManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/LaunchAgentManager.swift @@ -11,8 +11,10 @@ import Foundation final class LaunchAgentManager { static let label = "com.application.SelfControl.corebits.bgservice" - static let plistName = "com.application.SelfControl.corebits.bgservice" + static let processName = "SelfControlBGService" + static let plistName = "com.application.SelfControl.corebits.bgservice" + static func install() throws { let fm = FileManager.default @@ -103,3 +105,4 @@ final class LaunchAgentManager { return String(data: data, encoding: .utf8) ?? "" } } + diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewContentView.swift index f9591761..69e44fe2 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewContentView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewContentView.swift @@ -1334,7 +1334,15 @@ struct NewContentView: View { let minutes = Double(eventreq.endTime.minutes(from: eventreq.startTime, wrapAroundMidnight: true)) await startBlocking(minutes: minutes) } - + HelperConnection.shared.blockedStateHandler = { minutes in + print("Already in blocked state, show remaining minutes:\(minutes)") + Task { @MainActor in + print("Already in blocked state, show remaining minutes:\(minutes)") + await showBlockingStateForReminingMinutes(minutes: minutes) + } + } + + HelperConnection.shared.send_getBlockedStates() // Don't focus slider on startup - let tips modal show first if needed // Focus will be set after tips are dismissed } @@ -1349,33 +1357,37 @@ struct NewContentView: View { } private func startBlocking() { - startBlocking(minutes: minutes) - } - - private func startBlocking(minutes: Double) { if AppPreferences.showVerifyNetworkAlertBeforeBlock { if SCUIUtility.checkNetworkAndShowNetworkAlert() == false { return } } + showBlockingStateForReminingMinutes(minutes: minutes) + startBlocking(minutes: minutes) + } + + private func showBlockingStateForReminingMinutes(minutes: Double) { withAnimation(DesignSystem.animationNormal) { isBlocking = true - viewModel.updateBlockList(newBlockedDomains: blockedURLs, time: minutes) +// viewModel.updateBlockList(newBlockedDomains: blockedURLs, time: minutes) // Calculate total time including days if easter egg is unlocked remainingTime = minutes * 60 - } - - // Start countdown timer - timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in - if remainingTime > 0 { - remainingTime -= 1 - } else { - stopBlocking() + // Start countdown timer + timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in + if remainingTime > 0 { + remainingTime -= 1 + } else { + showNonBlockingState() + } } } + } - - private func stopBlocking() { + + private func showNonBlockingState() { + withAnimation(DesignSystem.animationNormal) { + isBlocking = true + } withAnimation(DesignSystem.animationNormal) { isBlocking = false } @@ -1385,6 +1397,15 @@ struct NewContentView: View { cancelStopCountdown() } + private func startBlocking(minutes: Double) { + HelperConnection.shared.send_startNetwrokBlocking(minutes: Int(minutes)) + } + + private func stopBlocking() { + showNonBlockingState() + HelperConnection.shared.send_stopNetworkBlocking() + } + private func showStopConfirmation() { stopCountdownTime = 600 showingStopConfirmation = true @@ -1406,7 +1427,9 @@ struct NewContentView: View { stopCountdownTimer = nil stopCountdownTime = 600 showingStopConfirmation = false - viewModel.deactivateNetworkBlocking() + +// viewModel.deactivateNetworkBlocking() +// HelperConnection.shared.send_stopNetworkBlocking() } private func resumeBlocking() { diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/AppDelegate.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppDelegate.swift similarity index 97% rename from Self-Control-Extension/SelfControl/SelfControlBGService/AppDelegate.swift rename to Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppDelegate.swift index 0acc0534..45d1dd18 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/AppDelegate.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppDelegate.swift @@ -6,7 +6,7 @@ import OSLog final class AppDelegate: NSObject, NSApplicationDelegate, NSXPCListenerDelegate { // Use a reverse-DNS mach service name. This must match your helper’s Info.plist. private let listener = NSXPCListener(machServiceName: "com.application.SelfControl.corebits.bgservice.xpc") - private let service = HelperService() + let service = HelperService() static let shared = AppDelegate() override init() { print("AppDelegate: init") diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift new file mode 100644 index 00000000..04ca4edb --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift @@ -0,0 +1,52 @@ +// +// AppStateManager.swift +// SelfControlBGService +// +// Created by Satendra Singh on 18/05/26. +// + +import Foundation + +actor AppStateManager { + static let shared = AppStateManager() + var isBlockingEnabled: Bool = false + private var safariLastApiUpdateReceivedTime = Date() + private var isSafariExtensionRunning: Bool = false + private var isChromeExtensionRunning: Bool = false + + func handleApiRequest(path: ServicePath) { + switch path { + case .safari: + receivedSafariApiRequest() + case .chrome: + receivedChromeApiRequest() + } + } + + private func receivedSafariApiRequest() { + safariLastApiUpdateReceivedTime = Date() + guard !isChromeExtensionRunning else { return } + isChromeExtensionRunning = true + HelperService.send_didEnableWebExtension(WEBExtension.chrome.rawValue, state: true) + } + + private func receivedChromeApiRequest() { + guard !isChromeExtensionRunning else { return } + isChromeExtensionRunning = true + HelperService.send_didEnableWebExtension(WEBExtension.chrome.rawValue, state: true) + } + + func activateContentBlocking() { + isBlockingEnabled = true + } + + func deactivateContentBlocking() { + isBlockingEnabled = false + } + + func reset() { + safariLastApiUpdateReceivedTime = Date() + isSafariExtensionRunning = false + isChromeExtensionRunning = false + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift index f9a32b0f..7e8f560a 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift @@ -16,8 +16,10 @@ extension HelperService: AppToExtensionExtension { } func setBlockedURLs(_ urls: [String]) { - queue.async { + queue.async(flags: .barrier) { IPCConnection.shared.enableURLBlocking(urls) + self.blockedUrls = urls + BlockContentStore.saveBlockedUrls(blockedUrls: urls) } } @@ -27,10 +29,17 @@ extension HelperService: AppToExtensionExtension { } } - func setEnableService(_ enable: Bool) { - queue.async { - _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(enable) - } + func setEnableService(_ enable: Bool) { //TODO: REmove // Cleanup +// queue.async { [weak self] in +// _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(enable) +// Task { +// if enable { +// await AppStateManager.shared.activateContentBlocking() +// } else { +// await AppStateManager.shared.deactivateContentBlocking() +// } +// } +// } } func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift new file mode 100644 index 00000000..1061a539 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift @@ -0,0 +1,16 @@ +// +// HelperService+Messages.swift +// SelfControl +// +// Created by Satendra Singh on 18/05/26. +// + +extension HelperService { + static var proxyConnectionService: HelperClientProtocol? { + AppDelegate.shared.service.proxyConnectionService() + } + + static func send_didEnableWebExtension(_ extensionTypeRawValue: String, state: Bool) { + proxyConnectionService?.didEnableWebExtension(extensionTypeRawValue, state: state) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift index 3d9a976a..e31f5bf9 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift @@ -12,20 +12,26 @@ final class HelperService: NSObject, HelperServiceProtocol { private var clients = NSHashTable.weakObjects() let queue = DispatchQueue(label: "com.application.SelfControl.corebits.bgservice.queue") var clientConnection: NSXPCConnection? - + let chromeService = ChromeExtensionRequestListner() + var blockedUrls: [String] = [] + private var timer: DelayTimerHandler? + func registerClient() { os_log("[SC] 🔍] BG registerClient") queue.async { // self.clients.add(client) // client.didUpdateStatus(self.isMonitoring ? "running" : "stopped") print("Register Client received") - + self.blockedUrls = BlockContentStore.loadlockedUrls() ?? [] } self.sendPing() - + self.chromeService.startListening() + self.chromeService.blockeddomainFetcher = { [weak self] in + return self?.blockedUrls ?? [] + } } - private func proxyConnectionService() -> HelperClientProtocol? { + func proxyConnectionService() -> HelperClientProtocol? { var proxy: AnyObject? guard let conn = clientConnection else { os_log("[SC] 🔍] BG No client connection") @@ -39,7 +45,7 @@ final class HelperService: NSObject, HelperServiceProtocol { func sendPing() { os_log("[SC] 🔍] BG HELPER sendPing") - let schedules = EventSchedulerStore.loadSchedules() + let schedules = BlockContentStore.loadSchedules() var details :String = "" for schedule in schedules { os_log("[SC] 🔍] BG Schedule: \(schedule.summaryString)") @@ -101,7 +107,7 @@ final class HelperService: NSObject, HelperServiceProtocol { func saveSchedules(schedules: Data, reply: @escaping (Bool) -> Void) { queue.async { - EventSchedulerStore.saveSchedulesData(schedules: schedules) + BlockContentStore.saveSchedulesData(schedules: schedules) self.broadcastEvent("Schedules saved") self.sendPing() reply(true) @@ -114,4 +120,68 @@ final class HelperService: NSObject, HelperServiceProtocol { reply(Data()) } } + + func saveBlockedUrls(blockedURLS: [String]) { + queue.async(flags: .barrier) { + self.blockedUrls = blockedURLS + } + } + + func startNetwrokBlocking(minutes: Int) { + os_log("[SC] 🔍] BG startNetwrokBlocking:\(minutes)") + + queue.async { [weak self] in + self?.startNetworkBlocking() + self?.timer = DelayTimerHandler(delay: Double(minutes), completionHandler: { [weak self] in + self?.stopNetworkBlocking() + }, cancelHandler: { [weak self] in + self?.stopNetworkBlocking() + }) + if let queue = self?.queue { + self?.timer?.timerQueue = queue + } + self?.timer?.startTimerWithSelectedDelay() + } + + } + + func stopNetworkBlocking() { + timer?.cancelTimer() + queue.async { + Task { + _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(false) + await AppStateManager.shared.deactivateContentBlocking() + } + } + } + + func getBlockedStates(reply: @escaping (_ state: Bool, _ endDate: Date?) -> Void) { + queue.async { [weak self] in + Task { [weak self] in + let state = await AppStateManager.shared.isBlockingEnabled + if state { + if let endDate = self?.timer?.timerFireDate { + os_log("[SC] 🔍] BG getBlockedStates Timer active, firing at: \(endDate)") + reply(true, endDate) + } else { + os_log("[SC] 🔍] BG getBlockedStates Timer active, firing NIL") + reply(false, nil) + } + + } else { + os_log("[SC] 🔍] BG getBlockedStates Timer not running") + reply(false, nil) + } + } + } + } + + private func startNetworkBlocking() { + queue.async { + Task { + _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(true) + await AppStateManager.shared.activateContentBlocking() + } + } + } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift new file mode 100644 index 00000000..a5d544cb --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift @@ -0,0 +1,159 @@ +// +// DelayTimerHandler.swift +// SelfControl +// +// Created by Satendra Singh on 20/05/26. +// + + +import Foundation +import os.log + +//final class DelayTimerHandler { +// +// private var blockTimer: Timer? +// private(set) var timerFireDate: Date? +// private(set) var isActiveBlocking: Bool = false +// +// var delay: Double // delay in minutes +// +// // Handlers +// var completionHandler: (() -> Void)? +// var cancelHandler: (() -> Void)? +// +// init(delay: Double, +// completionHandler: (() -> Void)? = nil, +// cancelHandler: (() -> Void)? = nil) { +// self.delay = delay +// self.completionHandler = completionHandler +// self.cancelHandler = cancelHandler +// } +// +// @discardableResult +// func startTimerWithSelectedDelay() -> Bool { +// cancelTimer() +// +// let seconds = delay * 60.0 +// guard seconds > 30 else { +// os_log("[SC] 🔍 startTimerWithSelectedDelay called with non-positive delay: %f", seconds) +// return false +// } +// +// timerFireDate = Date().addingTimeInterval(seconds) +// os_log("[SC] 🔍 Scheduling timer to fire in %.0f seconds (%.2f minutes)", seconds, delay) +// +// blockTimer = Timer.scheduledTimer(withTimeInterval: seconds, repeats: false) { [weak self] _ in +// guard let self = self else { +// os_log("[SC] 🔍 Timer fired,self is nil.") +// return +// } +// os_log("[SC] 🔍 Timer fired.") +// self.completionHandler?() +// self.cancelTimer() +// } +// +// isActiveBlocking = true +// return true +// } +// +// func cancelTimer() { +// if blockTimer != nil { +// os_log("[SC] 🔍 Cancelling timer.") +// cancelHandler?() +// } +// blockTimer?.invalidate() +// blockTimer = nil +// isActiveBlocking = false +// timerFireDate = nil +// } +// +// var blockingEndTime: Date? { +// timerFireDate +// } +// +// var blockingStartTime: Date? { +// timerFireDate?.addingTimeInterval(delay * 60.0) +// } +//} + + +final class DelayTimerHandler { + + private var blockTimer: DispatchSourceTimer? + var timerQueue = DispatchQueue(label: "com.example.delaytimerhandler.timer", qos: .utility) + private(set) var timerFireDate: Date? + private(set) var isActiveBlocking: Bool = false + + var delay: Double // delay in minutes + + // Handlers + var completionHandler: (() -> Void)? + var cancelHandler: (() -> Void)? + + init(delay: Double, + completionHandler: (() -> Void)? = nil, + cancelHandler: (() -> Void)? = nil) { + self.delay = delay + self.completionHandler = completionHandler + self.cancelHandler = cancelHandler + } + + deinit { + cancelTimer() + } + + @discardableResult + func startTimerWithSelectedDelay() -> Bool { + cancelTimer() + + let seconds = delay * 60.0 + guard seconds > 30 else { + os_log("[SC] 🔍 startTimerWithSelectedDelay called with non-positive delay: %f", seconds) + return false + } + + timerFireDate = Date().addingTimeInterval(seconds) + os_log("[SC] 🔍 Scheduling dispatch timer to fire in %.0f seconds (%.2f minutes)", seconds, delay) + + let t = DispatchSource.makeTimerSource(queue: timerQueue) + // One-shot: schedule once and cancel in handler + t.schedule(deadline: .now() + seconds, repeating: .never, leeway: .seconds(1)) + t.setEventHandler { [weak self] in + guard let self = self else { + os_log("[SC] 🔍 Dispatch timer fired, self is nil.") + return + } + os_log("[SC] 🔍 Dispatch timer fired.") + self.completionHandler?() + self.completionHandler = nil + self.cancelHandler = nil + } + t.resume() + blockTimer = t + + isActiveBlocking = true + return true + } + + func cancelTimer() { + if let blockTimer { + os_log("[SC] 🔍 Cancelling dispatch timer.") + cancelHandler?() + blockTimer.setEventHandler {} // Break potential retain cycles + blockTimer.cancel() + } + blockTimer = nil + isActiveBlocking = false + timerFireDate = nil + self.completionHandler = nil + self.cancelHandler = nil + } + + var blockingEndTime: Date? { + timerFireDate + } + + var blockingStartTime: Date? { + timerFireDate?.addingTimeInterval(-delay * 60.0) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/ChromeExtensionSupport/ChromeExtensionRequestListner.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift similarity index 56% rename from Self-Control-Extension/SelfControl/SelfControl/Main/ChromeExtensionSupport/ChromeExtensionRequestListner.swift rename to Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift index 55c4b0d0..51fada01 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/ChromeExtensionSupport/ChromeExtensionRequestListner.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift @@ -14,19 +14,21 @@ enum ServicePath: String { case safari = "/safari" } -final class ChromeExtensionRequestListner: NSObject, ObservableObject { +final class ChromeExtensionRequestListner: NSObject { private var isChromeStatusSetInExtension: Bool = false var listener: NWListener? var blockeddomainFetcher: (() -> [String])? - var isBlockingEnabled: Bool = false +// private var isBlockingEnabled: Bool = false var onExtensionStateChange: (() -> Void)? - + static let servicePort: UInt16 = 8532 +// private var lastUpdateReceivedTime = Date() + func startListening() { os_log("[SC] 🔍] PlistListner startListening") // Safely convert Int port to NWEndpoint.Port - guard let port = NWEndpoint.Port(rawValue: SafariConst.servicePort) else { - os_log("[SC] 🔍] Invalid service port: %d", SafariConst.servicePort) + guard let port = NWEndpoint.Port(rawValue: ChromeExtensionRequestListner.servicePort) else { + os_log("[SC] 🔍] Invalid service port: %d", ChromeExtensionRequestListner.servicePort) return } do { @@ -43,19 +45,24 @@ final class ChromeExtensionRequestListner: NSObject, ObservableObject { let req = String(data: data, encoding: .utf8) { print("Raw Request:", req) if let service = req.httpPathFromConnection() { + Task { + await AppStateManager.shared.handleApiRequest(path: service) + } switch service { case .chrome: print("Chrome request received") - os_log("[SC] 🔍] Chrome request received") +// os_log("[SC] 🔍] Chrome request received") - self.sendChromeBlockedUrls(connection: conn) - self.updateChromeStatus() + Task { + await self.sendChromeBlockedUrls(connection: conn) + } +// self.updateChromeStatus() case .safari: // Handle Safari service path if needed conn.cancel() print("Safari request received") - os_log("[SC] 🔍] Safari request received") - self.updateSafariStatus() +// os_log("[SC] 🔍] Safari request received") +// self.updateSafariStatus() break } } @@ -66,10 +73,10 @@ final class ChromeExtensionRequestListner: NSObject, ObservableObject { conn.stateUpdateHandler = { state in if state == .ready { - os_log("[SC] 🔍] PlistListner stateUpdateHandler ready") +// os_log("[SC] 🔍] PlistListner stateUpdateHandler ready") } if state == .cancelled { - os_log("[SC] 🔍] PlistListner stateUpdateHandler cancelled") +// os_log("[SC] 🔍] PlistListner stateUpdateHandler cancelled") } } } @@ -77,27 +84,26 @@ final class ChromeExtensionRequestListner: NSObject, ObservableObject { listener?.start(queue: DispatchQueue.global(qos: .userInitiated)) } - func activateSafariBlocking() { - isBlockingEnabled = true - } - - func deactivateSafariBlocking() { - isBlockingEnabled = false - } +// func activateSafariBlocking() { +// isBlockingEnabled = true +// } +// +// func deactivateSafariBlocking() { +// isBlockingEnabled = false +// } - private func sendChromeBlockedUrls(connection: NWConnection) { + private func sendChromeBlockedUrls(connection: NWConnection) async { print("sendChromeBlockedUrls") var blockedDomainList: [String] = self.blockeddomainFetcher?() ?? [] - if self.isBlockingEnabled == false { + if await AppStateManager.shared.isBlockingEnabled == false { blockedDomainList = [] } let blockedUrls = ["blocked": blockedDomainList] let jsonData = try! JSONSerialization.data(withJSONObject: blockedUrls, options: []) let jsonString = String(data: jsonData, encoding: .utf8)! - os_log("[SC] 🔍] PlistListner newConnectionHandler") - os_log("[SC] 🔍] PlistListner conn.receiveMessage") + // os_log("[SC] 🔍] PlistListner newConnectionHandler") let response = """ HTTP/1.1 200 OK\r Content-Type: application/json\r @@ -107,30 +113,32 @@ final class ChromeExtensionRequestListner: NSObject, ObservableObject { """ connection.send(content: response.data(using: .utf8), contentContext: .finalMessage , completion: .contentProcessed { error in if let error = error { - os_log("[SC] 🔍] PlistListner Sent response error: %{public}@", "\(error)") +// os_log("[SC] 🔍] PlistListner Sent response error: %{public}@", "\(error)") } else { - os_log("[SC] 🔍] PlistListner Sent response successfully") +// os_log("[SC] 🔍] PlistListner Sent response successfully") } }) } - private func updateSafariStatus() { - SafariExtensionManager.shared.lastUpdateReceivedTime = Date() - } - - private func updateChromeStatus() { - Task { @MainActor in - print("newConnectionHandler isEnabled: \(NetworkExtensionState.shared.isEnabled)") - if NetworkExtensionState.shared.isEnabled == true && NetworkExtensionState.shared.isChromeExtensionEnabled == false { - NetworkExtensionState.shared.isChromeExtensionEnabled = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.chrome.rawValue, state: true) - NetworkExtensionState.shared.printAll() - } - if isChromeStatusSetInExtension == false { - isChromeStatusSetInExtension = true - self.onExtensionStateChange?() - } - } - } +// private func updateSafariStatus() { +// lastUpdateReceivedTime = Date() +// } +// +// private func updateChromeStatus() { +// Task { +// AppStateManager.shared.handleApiRequest(path: "safari-extension-status)") +// print("newConnectionHandler isEnabled: \(NetworkExtensionState.shared.isEnabled)") +// if NetworkExtensionState.shared.isEnabled == true && NetworkExtensionState.shared.isChromeExtensionEnabled == false { +//// NetworkExtensionState.shared.isChromeExtensionEnabled = IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.chrome.rawValue, state: true) +// NetworkExtensionState.shared.isChromeExtensionEnabled = true +// NetworkExtensionState.shared.printAll() +// } +// if isChromeStatusSetInExtension == false { +// isChromeStatusSetInExtension = true +// self.onExtensionStateChange?() +// } +// } +// } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/BlockContentStore.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/BlockContentStore.swift new file mode 100644 index 00000000..dc425997 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/BlockContentStore.swift @@ -0,0 +1,111 @@ +// +// EventSchedulerStore.swift +// SelfControl +// +// Created by Satendra Singh on 25/02/26. +// + +import Foundation + +struct BlockContentStore { + // Load schedules from UserDefaults + static func loadSchedules() -> [Schedule] { + if let data = UserDefaults.standard.data(forKey: "schedules"), + let decoded = try? JSONDecoder().decode([Schedule].self, from: data) { + return decoded + } else { + // Initialize with one empty schedule if no saved data + return [Schedule(timeSlots: [], enabledDays: [])] + } + } + + // Save schedules to UserDefaults + static func saveSchedules(schedules: [Schedule]) { + if let encoded = try? JSONEncoder().encode(schedules) { + saveSchedulesData(schedules: encoded) + } + } + + static func saveSchedulesData(schedules: Data) { + UserDefaults.standard.set(schedules, forKey: "schedules") + } + + static func saveBlockedUrls(blockedUrls: [String]) { + UserDefaults.standard.set(blockedUrls, forKey: "blockedUrls") + } + + static func loadlockedUrls() -> [String]? { + UserDefaults.standard.value(forKey: "blockedUrls") as? [String] + } +} + +// MARK: - Schedule +struct Schedule: Identifiable, Codable, Equatable { + let id: UUID + var timeSlots: [TimeSlot] + var enabledDays: Set + var isExpanded: Bool + var isEnabled: Bool + + init(id: UUID = UUID(), timeSlots: [TimeSlot] = [], enabledDays: Set = [], isExpanded: Bool = false, isEnabled: Bool = false) { + self.id = id + self.timeSlots = timeSlots + self.enabledDays = enabledDays + self.isExpanded = isExpanded + self.isEnabled = isEnabled + } + + var summaryString: String { + timeSlots.map { $0.displayString }.joined(separator: " ") + } + + var enabledDaysString: String { + SCWeekday.allCases.compactMap { enabledDays.contains($0) ? $0.letter : nil }.joined() + } +} + +// MARK: - Schedule Models +struct TimeSlot: Identifiable, Codable, Equatable { + let id: UUID + var startHour: Int + var startMinute: Int + var endHour: Int + var endMinute: Int + + init(id: UUID = UUID(), startHour: Int, startMinute: Int, endHour: Int, endMinute: Int) { + self.id = id + self.startHour = startHour + self.startMinute = startMinute + self.endHour = endHour + self.endMinute = endMinute + } + + // Helper to format hour in 12-hour format + private func formatHour12(_ hour24: Int) -> (hour: Int, isPM: Bool) { + if hour24 == 0 { + return (12, false) + } else if hour24 < 12 { + return (hour24, false) + } else if hour24 == 12 { + return (12, true) + } else { + return (hour24 - 12, true) + } + } + + var startTimeString: String { + let (hour12, isPM) = formatHour12(startHour) + let amPm = isPM ? "PM" : "AM" + return String(format: "%d:%02d %@", hour12, startMinute, amPm) + } + + var endTimeString: String { + let (hour12, isPM) = formatHour12(endHour) + let amPm = isPM ? "PM" : "AM" + return String(format: "%d:%02d %@", hour12, endMinute, amPm) + } + + var displayString: String { + "\(startTimeString)-\(endTimeString)" + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift index 487f689b..05eebca7 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift @@ -23,8 +23,8 @@ enum FlowInfoKey: String { func register(completionHandler: @escaping (Bool) -> Void) func register(_ completionHandler: @escaping (Bool) -> Void) func setBlockedURLs(_ urls: [String]) - func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) - func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) + func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) //TODO: Remove once code is exported + func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) //TODO: Remove once code is exported func setEnableService(_ enable: Bool) } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift index 9e0f8969..46e62b42 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift @@ -5,6 +5,7 @@ import Foundation @objc public protocol HelperClientProtocol { func didUpdateStatus(_ status: String) func didEmitEvent(_ message: String) + func didEnableWebExtension(_ extensionTypeRawValue: String, state: Bool) } // The service protocol defines the operations the UI app can call on the helper. @@ -22,4 +23,15 @@ import Foundation //Schedule Save/load func saveSchedules(schedules: Data, reply: @escaping (Bool) -> Void) func loadSchedules(reply: @escaping (_ schedules: Data) -> Void) + + //Start blocking network + func startNetwrokBlocking(minutes: Int) + func stopNetworkBlocking() + func getBlockedStates(reply: @escaping (_ state: Bool, _ endDate: Date?) -> Void) + //Save block url list +} + +enum WEBExtension: String { + case chrome + case safari } From 198614decd27e06f7f87c4bff5d8a1263cbfa8cc Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 24 May 2026 22:12:39 +0530 Subject: [PATCH 45/64] Timer handlings and other basic handlings for Safari extension --- .../SafariExtensionHandler.swift | 28 +-- .../ServerResponse.swift | 20 +- .../SelfControl/SelfControl/Info.plist | 4 +- .../CoreService/AppStateManager.swift | 2 + .../CoreService/HelperService.swift | 2 +- .../Helpers/DelayTimerHandler.swift | 192 ++++++++---------- .../ChromeExtensionRequestListner.swift | 18 +- .../EventScheduler/EventSchedulerStore.swift | 104 ---------- 8 files changed, 122 insertions(+), 248 deletions(-) delete mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerStore.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift index be30f489..9e1fe67b 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift @@ -13,7 +13,7 @@ typealias Const = SafariExtensionConstants class SafariExtensionHandler: SFSafariExtensionHandler { private let ping = ServerPing() private let defaults = UserDefaults(suiteName: Const.appGroup) - var blockedPatterns: [String] = [ + var blockedPatterns: Set = [ "facebook.com/friends", "facebook.com/marketplace", "instagram.com/explore", @@ -39,7 +39,8 @@ class SafariExtensionHandler: SFSafariExtensionHandler { override func beginRequest(with context: NSExtensionContext) { let request = context.inputItems.first as? NSExtensionItem - refreshBlockedURLs() +// refreshBlockedURLs() + blockedPatterns = ping.blockedURL let profile: UUID? if #available(iOS 17.0, macOS 14.0, *) { profile = request?.userInfo?[SFExtensionProfileKey] as? UUID @@ -55,7 +56,8 @@ class SafariExtensionHandler: SFSafariExtensionHandler { typealias MSG = SafariExtensionConstants.MessagesName os_log("[SC] 🔍 The extension received a message: %{public}@", messageName) if messageName == MSG.reloadList.rawValue { - refreshBlockedURLs() +// refreshBlockedURLs() + blockedPatterns = ping.blockedURL } // if messageName == "ready" { // pingHostApp() @@ -68,14 +70,14 @@ class SafariExtensionHandler: SFSafariExtensionHandler { // self.isEanbled = false // } - guard isEanbled else { return } +// guard isEanbled else { return } guard messageName == MSG.pageVisit.rawValue, let urlString = userInfo?["url"] as? String else { return } os_log("[SC] 🔍 Received URL: %{public}@", urlString) // Match against blocked URLs - if blockedPatterns.contains(where: { urlString.contains($0) }) { + if ping.blockedURL.contains(where: { urlString.contains($0) }) { os_log("[SC] 🚫 Blocking and redirecting: %{public}@", urlString) page.dispatchMessageToScript(withName: "REDIRECT_BLOCKED_URL", userInfo: ["redirect": redirectURL]) } @@ -95,13 +97,13 @@ class SafariExtensionHandler: SFSafariExtensionHandler { - private func refreshBlockedURLs() { - let res = ContentBlockerExtensionRequestHandler.handleRequestList(groupIdentifier: Const.appGroup) - os_log(.default, "[SC] 🔍] List of blocked URLs: %{public}@", res ?? "No data") - let domains: [String] = res?.map({ $0.trigger.urlFilter }) ?? [] - os_log(.default, "[SC] 🔍] List of blocked URLs: %{public}@", domains) - blockedPatterns = domains - } +// private func refreshBlockedURLs() { +// let res = ContentBlockerExtensionRequestHandler.handleRequestList(groupIdentifier: Const.appGroup) +// os_log(.default, "[SC] 🔍] List of blocked URLs: %{public}@", res ?? "No data") +// let domains: [String] = res?.map({ $0.trigger.urlFilter }) ?? [] +// os_log(.default, "[SC] 🔍] List of blocked URLs: %{public}@", domains) +// blockedPatterns = domains +// } func messagexReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) { os_log(.default, "[SC] 🔍] The extension messageReceived: %{public}@", messageName) @@ -172,7 +174,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler { override func validateToolbarItem(in window: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) { os_log(.default, "[SC] 🔍] validateToolbarItem") validationHandler(true, "") - updateExtensionState() +// updateExtensionState() pingHostApp() } diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift index 7619a664..64bd29e0 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift @@ -11,6 +11,7 @@ import os.log final class ServerPing { var timer: Timer? + var blockedURL: Set = [] init() { } @@ -29,7 +30,7 @@ final class ServerPing { URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { - os_log(("[SC] Fetch failed: \(error.localizedDescription)")) + os_log("[SC] Fetch failed: %{public}@", error.localizedDescription) return } @@ -38,14 +39,15 @@ final class ServerPing { // completion([]) return } - os_log(("[SC] Fetch data successful: \(String(decoding: data, as: UTF8.self))")) - // do { - // let decoded = try JSONDecoder().decode(ServerResponse.self, from: data) - // completion(decoded.blocked) - // } catch { - // os_log(("[SC] Fetch failed: \(error.localizedDescription)")) - // completion([]) - // } + os_log("[SC] Fetch data successful: %{public}@", String(decoding: data, as: UTF8.self)) + do { + let decoded = try JSONDecoder().decode(ServerResponse.self, from: data) + self.blockedURL = .init(decoded.blocked) +// completion(decoded.blocked) + } catch { + os_log(("[SC] Fetch failed: \(error.localizedDescription)")) +// completion([]) + } }.resume() } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Info.plist b/Self-Control-Extension/SelfControl/SelfControl/Info.plist index 14adde33..67fd65df 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Info.plist +++ b/Self-Control-Extension/SelfControl/SelfControl/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.15 + 1.16 CFBundleVersion - 15 + 16 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift index 04ca4edb..067faa2f 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift @@ -6,6 +6,7 @@ // import Foundation +import os.log actor AppStateManager { static let shared = AppStateManager() @@ -42,6 +43,7 @@ actor AppStateManager { func deactivateContentBlocking() { isBlockingEnabled = false + os_log("[SC] 🔍] BG deactivateContentBlocking: %{public}%d",isBlockingEnabled) } func reset() { diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift index e31f5bf9..55dbe408 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift @@ -142,10 +142,10 @@ final class HelperService: NSObject, HelperServiceProtocol { } self?.timer?.startTimerWithSelectedDelay() } - } func stopNetworkBlocking() { + os_log("[SC] 🔍] BG stopNetworkBlocking") timer?.cancelTimer() queue.async { Task { diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift index a5d544cb..3b112cb8 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift @@ -9,151 +9,121 @@ import Foundation import os.log -//final class DelayTimerHandler { -// -// private var blockTimer: Timer? -// private(set) var timerFireDate: Date? -// private(set) var isActiveBlocking: Bool = false -// -// var delay: Double // delay in minutes -// -// // Handlers -// var completionHandler: (() -> Void)? -// var cancelHandler: (() -> Void)? -// -// init(delay: Double, -// completionHandler: (() -> Void)? = nil, -// cancelHandler: (() -> Void)? = nil) { -// self.delay = delay -// self.completionHandler = completionHandler -// self.cancelHandler = cancelHandler -// } -// -// @discardableResult -// func startTimerWithSelectedDelay() -> Bool { -// cancelTimer() -// -// let seconds = delay * 60.0 -// guard seconds > 30 else { -// os_log("[SC] 🔍 startTimerWithSelectedDelay called with non-positive delay: %f", seconds) -// return false -// } -// -// timerFireDate = Date().addingTimeInterval(seconds) -// os_log("[SC] 🔍 Scheduling timer to fire in %.0f seconds (%.2f minutes)", seconds, delay) -// -// blockTimer = Timer.scheduledTimer(withTimeInterval: seconds, repeats: false) { [weak self] _ in -// guard let self = self else { -// os_log("[SC] 🔍 Timer fired,self is nil.") -// return -// } -// os_log("[SC] 🔍 Timer fired.") -// self.completionHandler?() -// self.cancelTimer() -// } -// -// isActiveBlocking = true -// return true -// } -// -// func cancelTimer() { -// if blockTimer != nil { -// os_log("[SC] 🔍 Cancelling timer.") -// cancelHandler?() -// } -// blockTimer?.invalidate() -// blockTimer = nil -// isActiveBlocking = false -// timerFireDate = nil -// } -// -// var blockingEndTime: Date? { -// timerFireDate -// } -// -// var blockingStartTime: Date? { -// timerFireDate?.addingTimeInterval(delay * 60.0) -// } -//} - - final class DelayTimerHandler { - + private var blockTimer: DispatchSourceTimer? - var timerQueue = DispatchQueue(label: "com.example.delaytimerhandler.timer", qos: .utility) + var timerQueue = DispatchQueue( + label: "com.example.delaytimerhandler.timer", + qos: .utility + ) + private(set) var timerFireDate: Date? private(set) var isActiveBlocking: Bool = false - - var delay: Double // delay in minutes - - // Handlers + + var delay: Double // minutes + var completionHandler: (() -> Void)? var cancelHandler: (() -> Void)? - - init(delay: Double, - completionHandler: (() -> Void)? = nil, - cancelHandler: (() -> Void)? = nil) { + + init( + delay: Double, + completionHandler: (() -> Void)? = nil, + cancelHandler: (() -> Void)? = nil + ) { self.delay = delay self.completionHandler = completionHandler self.cancelHandler = cancelHandler } - + deinit { cancelTimer() } - + @discardableResult func startTimerWithSelectedDelay() -> Bool { - cancelTimer() - + + cancelEventHandler() // IMPORTANT + let seconds = delay * 60.0 + guard seconds > 30 else { - os_log("[SC] 🔍 startTimerWithSelectedDelay called with non-positive delay: %f", seconds) + os_log("[SC] Invalid delay: %f", seconds) return false } - + timerFireDate = Date().addingTimeInterval(seconds) - os_log("[SC] 🔍 Scheduling dispatch timer to fire in %.0f seconds (%.2f minutes)", seconds, delay) - - let t = DispatchSource.makeTimerSource(queue: timerQueue) - // One-shot: schedule once and cancel in handler - t.schedule(deadline: .now() + seconds, repeating: .never, leeway: .seconds(1)) - t.setEventHandler { [weak self] in - guard let self = self else { - os_log("[SC] 🔍 Dispatch timer fired, self is nil.") - return - } - os_log("[SC] 🔍 Dispatch timer fired.") - self.completionHandler?() + isActiveBlocking = true + + let timer = DispatchSource.makeTimerSource(queue: timerQueue) + + timer.schedule( + deadline: .now() + seconds, + repeating: .never, + leeway: .seconds(1) + ) + + timer.setEventHandler { [weak self] in + guard let self else { return } + + os_log("[SC] Timer fired") + + // Prevent future usage immediately + let completion = self.completionHandler + + self.blockTimer?.setEventHandler {} + self.blockTimer?.cancel() + self.blockTimer = nil + + self.isActiveBlocking = false + self.timerFireDate = nil + self.completionHandler = nil self.cancelHandler = nil + + // Call once + completion?() } - t.resume() - blockTimer = t - - isActiveBlocking = true + + blockTimer = timer + timer.resume() + return true } + + private func cancelEventHandler() { + guard let timer = blockTimer else { return } + + os_log("[SC] Cancelling timer") + + timer.setEventHandler {} + timer.cancel() + } func cancelTimer() { - if let blockTimer { - os_log("[SC] 🔍 Cancelling dispatch timer.") - cancelHandler?() - blockTimer.setEventHandler {} // Break potential retain cycles - blockTimer.cancel() - } + + guard let timer = blockTimer else { return } + + os_log("[SC] Cancelling timer") + + timer.setEventHandler {} + timer.cancel() + blockTimer = nil + isActiveBlocking = false timerFireDate = nil - self.completionHandler = nil - self.cancelHandler = nil + + cancelHandler?() + + completionHandler = nil + cancelHandler = nil } - + var blockingEndTime: Date? { timerFireDate } - + var blockingStartTime: Date? { - timerFireDate?.addingTimeInterval(-delay * 60.0) + timerFireDate?.addingTimeInterval(-(delay * 60.0)) } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift index 51fada01..3fb78508 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift @@ -28,7 +28,7 @@ final class ChromeExtensionRequestListner: NSObject { // Safely convert Int port to NWEndpoint.Port guard let port = NWEndpoint.Port(rawValue: ChromeExtensionRequestListner.servicePort) else { - os_log("[SC] 🔍] Invalid service port: %d", ChromeExtensionRequestListner.servicePort) + os_log("[SC] 🔍] BG Invalid service port: %d", ChromeExtensionRequestListner.servicePort) return } do { @@ -50,8 +50,7 @@ final class ChromeExtensionRequestListner: NSObject { } switch service { case .chrome: - print("Chrome request received") -// os_log("[SC] 🔍] Chrome request received") +// os_log("[SC] 🔍] BG Chrome request received") Task { await self.sendChromeBlockedUrls(connection: conn) @@ -59,9 +58,11 @@ final class ChromeExtensionRequestListner: NSObject { // self.updateChromeStatus() case .safari: // Handle Safari service path if needed - conn.cancel() - print("Safari request received") -// os_log("[SC] 🔍] Safari request received") + Task { + await self.sendChromeBlockedUrls(connection: conn) + } +// conn.cancel() +// os_log("[SC] 🔍] BG Safari request received") // self.updateSafariStatus() break } @@ -93,10 +94,11 @@ final class ChromeExtensionRequestListner: NSObject { // } private func sendChromeBlockedUrls(connection: NWConnection) async { + let isBlockEnabled = await AppStateManager.shared.isBlockingEnabled print("sendChromeBlockedUrls") - +// os_log("[SC] 🔍] BG sendChromeBlockedUrls %{public}d", isBlockEnabled) var blockedDomainList: [String] = self.blockeddomainFetcher?() ?? [] - if await AppStateManager.shared.isBlockingEnabled == false { + if isBlockEnabled == false { blockedDomainList = [] } let blockedUrls = ["blocked": blockedDomainList] diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerStore.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerStore.swift deleted file mode 100644 index 2c52910c..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerStore.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// EventSchedulerStore.swift -// SelfControl -// -// Created by Satendra Singh on 25/02/26. -// - -import Foundation - -struct EventSchedulerStore { - // Load schedules from UserDefaults - static func loadSchedules() -> [Schedule] { - if let data = UserDefaults.standard.data(forKey: "schedules"), - let decoded = try? JSONDecoder().decode([Schedule].self, from: data) { - return decoded - } else { - // Initialize with one empty schedule if no saved data - return [Schedule(timeSlots: [], enabledDays: [])] - } - } - - // Save schedules to UserDefaults - static func saveSchedules(schedules: [Schedule]) { - if let encoded = try? JSONEncoder().encode(schedules) { - saveSchedulesData(schedules: encoded) - } - } - - static func saveSchedulesData(schedules: Data) { - UserDefaults.standard.set(schedules, forKey: "schedules") - } - -} - -// MARK: - Schedule -struct Schedule: Identifiable, Codable, Equatable { - let id: UUID - var timeSlots: [TimeSlot] - var enabledDays: Set - var isExpanded: Bool - var isEnabled: Bool - - init(id: UUID = UUID(), timeSlots: [TimeSlot] = [], enabledDays: Set = [], isExpanded: Bool = false, isEnabled: Bool = false) { - self.id = id - self.timeSlots = timeSlots - self.enabledDays = enabledDays - self.isExpanded = isExpanded - self.isEnabled = isEnabled - } - - var summaryString: String { - timeSlots.map { $0.displayString }.joined(separator: " ") - } - - var enabledDaysString: String { - SCWeekday.allCases.compactMap { enabledDays.contains($0) ? $0.letter : nil }.joined() - } -} - -// MARK: - Schedule Models -struct TimeSlot: Identifiable, Codable, Equatable { - let id: UUID - var startHour: Int - var startMinute: Int - var endHour: Int - var endMinute: Int - - init(id: UUID = UUID(), startHour: Int, startMinute: Int, endHour: Int, endMinute: Int) { - self.id = id - self.startHour = startHour - self.startMinute = startMinute - self.endHour = endHour - self.endMinute = endMinute - } - - // Helper to format hour in 12-hour format - private func formatHour12(_ hour24: Int) -> (hour: Int, isPM: Bool) { - if hour24 == 0 { - return (12, false) - } else if hour24 < 12 { - return (hour24, false) - } else if hour24 == 12 { - return (12, true) - } else { - return (hour24 - 12, true) - } - } - - var startTimeString: String { - let (hour12, isPM) = formatHour12(startHour) - let amPm = isPM ? "PM" : "AM" - return String(format: "%d:%02d %@", hour12, startMinute, amPm) - } - - var endTimeString: String { - let (hour12, isPM) = formatHour12(endHour) - let amPm = isPM ? "PM" : "AM" - return String(format: "%d:%02d %@", hour12, endMinute, amPm) - } - - var displayString: String { - "\(startTimeString)-\(endTimeString)" - } -} From 09f3896b9e8278c4f2733b62ec4a8c5ea331f448 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 24 May 2026 22:16:34 +0530 Subject: [PATCH 46/64] commented out filter view model code to handle extension state and send message to service --- .../SelfControl/Main/FilterViewModel.swift | 67 ++++++++++++------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index 338310f6..1771d93c 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -44,6 +44,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } } } + @Published var viewState: SelfControlViewState = .installNetworkExtension @Published var isNetworkExtensionSkipped: Bool = false { @@ -57,7 +58,6 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques var eventRunner: EventSchedulerRunner? = nil var eventRunnerHandler: EventSchedulerRunner.EventHandler? @State private var domains = AppPreferences.getBlockedDomains() - private let chromeService = ChromeExtensionRequestListner() @State var blockedURLs: [BlockedURL] = [] var blockerStorage: BlockedURLStore? @Published var delay: Double = 5.0 @@ -105,29 +105,49 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques super.init() // ProxyPreferences.reset() //TODO: remove onInit() - SafariExtensionManager.shared.onExtensionStateChange = { - print("SafariExtensionManager.shared.onChange++") - self.isSafariExtensionInstalled = true - AppPreferences.setSafariExtensionInstalled() - Task { @MainActor in - self.updateSafariExtensionViewStatus() - } - } - self.chromeService.onExtensionStateChange = { - print("Chrome.shared.onChange++") - self.isChromeExtensionInstalled = true - AppPreferences.setChromeExtensionInstalled() - Task { @MainActor in - self.updateChromeExtensionViewStatus() +// SafariExtensionManager.shared.onExtensionStateChange = { +// print("SafariExtensionManager.shared.onChange++") +// self.isSafariExtensionInstalled = true +// AppPreferences.setSafariExtensionInstalled() +// Task { @MainActor in +// self.updateSafariExtensionViewStatus() +// } +// } +// self.chromeService.onExtensionStateChange = { +// print("Chrome.shared.onChange++") +// self.isChromeExtensionInstalled = true +// AppPreferences.setChromeExtensionInstalled() +// Task { @MainActor in +// self.updateChromeExtensionViewStatus() +// } +// } + HelperConnection.shared.onExtensionStateChange = { (ext, state) in + if state == true { + switch ext { + case .safari: + print("SafariExtensionManager.shared.onChange++") + self.isSafariExtensionInstalled = true + AppPreferences.setSafariExtensionInstalled() + Task { @MainActor in + self.updateSafariExtensionViewStatus() + } + case .chrome: + print("Chrome.shared.onChange++") + self.isChromeExtensionInstalled = true + AppPreferences.setChromeExtensionInstalled() + Task { @MainActor in + self.updateChromeExtensionViewStatus() + } + } } } - SafariExtensionManager.shared.resetExtensionState() +// SafariExtensionManager.shared.resetExtensionState() self.extensionIdentifier = extensionBundle.bundleIdentifier - self.chromeService.blockeddomainFetcher = { - return AppPreferences.getBlockedDomains() - } +// self.chromeService.blockeddomainFetcher = { +// return AppPreferences.getBlockedDomains() +// } - self.chromeService.startListening() + //self.chromeService.startListening() // Print status whenever it changes $status @@ -472,7 +492,6 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques print("activateNetworkBlocking+++++") IPCConnectionProxy().setEnableService(true) BlockListManager.activateSafariBlocking() - chromeService.activateSafariBlocking() startShowingCountDownInDock() } @@ -480,7 +499,6 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques print("deactivateNetworkBlocking+++++") IPCConnectionProxy().setEnableService(false) BlockListManager.deactivateSafariBlocking() - chromeService.deactivateSafariBlocking() if AppPreferences.playSoundOnCompletion { NSSound.playDefaultSound() } @@ -493,7 +511,9 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques // MARK: - Timer management func startTimerWithSelectedDelay() -> Bool { - cancelTimer() + HelperConnection.shared.send_startNetwrokBlocking(minutes: Int(delay)) + return true +// cancelTimer() let seconds = delay * 60.0 guard seconds > 30 else { os_log("[SC] 🔍] startTimerWithSelectedDelay called with non-positive delay: %f", seconds) @@ -508,6 +528,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques self.cancelTimer() } isActiveBlocking = true + // RunLoop.main.add(blockTimer!, forMode: .common) return true } From fbf9092e72745dc6a167bcc891df4525c7e9f0b8 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 24 May 2026 22:17:01 +0530 Subject: [PATCH 47/64] commented public key --- .../SelfControl/Sparkle/dsa_pub.pem | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Self-Control-Extension/SelfControl/Sparkle/dsa_pub.pem diff --git a/Self-Control-Extension/SelfControl/Sparkle/dsa_pub.pem b/Self-Control-Extension/SelfControl/Sparkle/dsa_pub.pem new file mode 100644 index 00000000..950143dc --- /dev/null +++ b/Self-Control-Extension/SelfControl/Sparkle/dsa_pub.pem @@ -0,0 +1,36 @@ +-----BEGIN PUBLIC KEY----- +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 +CDx3nE+2CbIFoCzJUlEdTgwaHp/vmUKxMmG8 +-----END PUBLIC KEY----- From e3e82dbbb16e439b44a23f50a66f91651b720d7f Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 24 May 2026 22:17:20 +0530 Subject: [PATCH 48/64] partial code in script for safari --- .../Resources/script.js | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js index 07bd8f66..11153a12 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js @@ -254,7 +254,7 @@ safari.extension.dispatchMessage("ready"); (function () { - + // Listen for messages from the Swift extension safari.self.addEventListener("message", (event) => { if (event.name === "REDIRECT_BLOCKED_URL") { @@ -293,17 +293,37 @@ safari.extension.dispatchMessage("ready"); function notifySwiftDetails(details) { // main frame only if (details.frameId !== 0) return; + safari.extension.dispatchMessage("NAVIGATION", { + url: details.url, + time: Date.now(), + }); - browser.runtime.sendMessage({ - type: "navigation", - url: details.url - }); +// browser.runtime.sendMessage({ +// type: "navigation", +// url: details.url +// }); } - + + const browser = window.browser || window.chrome || window.safari; window.addEventListener("load", notifySwift); window.addEventListener("popstate", notifySwift); document.addEventListener("click", () => setTimeout(notifySwift, 10), true); // browser.webNavigation.onCommitted.addListener(notifySwiftDetails); +// browser.webNavigation.onBeforeNavigate.addListener((details) => { +// +// // main frame only +// if (details.frameId !== 0) return; +// console.log("[SC] onBeforeNavigate Blocking:", details.url); +// }); + +// browser.webRequest.onBeforeRequest.addListener( +// (details) => { +// console.log("[SC] webRequest onBeforeRequest Blocking:", details.url); +// }, +// { urls: [""] }, +// ["blocking"] +// ); +// browser.webNavigation.onBeforeNavigate.addListener(notifySwiftDetails); })(); //TODO: https://chatgpt.com/c/6963cb8a-349c-8320-a036-7f6b9828043a?ref=mini From c060fc5a28115edff7751848dfd20916613e579d Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 27 May 2026 23:30:22 +0530 Subject: [PATCH 49/64] fixed issues --- .../SelfControl.xcodeproj/project.pbxproj | 7 +-- .../SelfControl/SelfControl/Info.plist | 4 +- .../SelfControl/Main/FilterViewModel.swift | 7 +-- ...erConnection+SafariExtensionMessages.swift | 2 +- .../Views/BlocklistEditorView.swift | 1 + .../CoreService/FilterController.swift | 47 +++++++++++++++++++ ...elperService+SafariExtensionMessages.swift | 8 +--- .../CoreService/HelperService.swift | 9 +++- .../IPCConnection.swift | 9 +++- .../Shared/AppToExtensionExtension.swift | 1 - .../FilterDataProvider.swift | 15 +++--- .../IPCConnection.swift | 2 +- 12 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/FilterController.swift rename Self-Control-Extension/SelfControl/SelfControlBGService/Services/{SafariExtensionService => NetworkExtensionService}/IPCConnection.swift (95%) diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index 15826e95..e987c126 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -724,7 +724,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.2; + MARKETING_VERSION = 1.7; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; @@ -759,7 +759,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.2; + MARKETING_VERSION = 1.7; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; @@ -1014,6 +1014,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_ENTITLEMENTS = "SelfControl Rules Extension/SelfControl Rules Extension.entitlements"; + CODE_SIGN_IDENTITY = "Developer ID Application"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 26210; @@ -1049,7 +1050,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "com.application.SelfControl.corebits.SelfControl-Safari-Extension"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "SelfControl Dev Net Ext"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "SelfControl Dev Net Ext"; REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; diff --git a/Self-Control-Extension/SelfControl/SelfControl/Info.plist b/Self-Control-Extension/SelfControl/SelfControl/Info.plist index 67fd65df..15a8b3c3 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Info.plist +++ b/Self-Control-Extension/SelfControl/SelfControl/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.16 + 1.18 CFBundleVersion - 16 + 18 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index 1771d93c..edf0fbbc 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -161,6 +161,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques .store(in: &cancellables) Task { @MainActor in self.blockerStorage = BlockedURLStore() + self.blockerStorage?.load() } self.eventRunnerHandler = { event in @@ -304,7 +305,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func enableFilterConfiguration() { let filterManager = NEFilterManager.shared() guard !filterManager.isEnabled else { - registerWithProvider() +// registerWithProvider() return } Self.loadFilterConfiguration { success in @@ -332,7 +333,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques self.refreshExtensionState() return } - self.registerWithProvider() +// self.registerWithProvider() } } } @@ -575,7 +576,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } @MainActor func saveAndupdateBlockList(_ newBlockedDomains: [BlockedURL]) { - blockerStorage?.set(blockedURLs) + blockerStorage?.set(newBlockedDomains) let urls = newBlockedDomains.compactMap(\.urls) let flattened: [String] = urls.flatMap { $0 } AppPreferences.setBlockedDomains(flattened) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift index 4e04dd0d..9bb9307d 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift @@ -27,7 +27,7 @@ extension HelperConnection: AppToExtensionExtension { } func register(completionHandler: @escaping (Bool) -> Void) { - bgProxyServiceConnection()?.register(completionHandler: completionHandler) + bgProxyServiceConnection()?.register(completionHandler) } func send_startNetwrokBlocking(minutes: Int) { diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift index 43318161..6690f900 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift @@ -419,6 +419,7 @@ struct BlocklistEditorView: View { } private func saveChanges() { + viewModel.blockedURLs = blockedURLs viewModel.saveAndupdateBlockList(blockedURLs) } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/FilterController.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/FilterController.swift new file mode 100644 index 00000000..573191d6 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/FilterController.swift @@ -0,0 +1,47 @@ +// +// FilterController.swift +// SelfControl +// +// Created by Satendra Singh on 26/05/26. +// + + +import NetworkExtension +import os.log + +final class FilterController { + static func restartFilter(completion: @escaping (Error?) -> Void) { + os_log("[SC] 🔍] BG restartFilter:") + NEFilterManager.shared().loadFromPreferences { error in + if let error = error { + completion(error) + return + } + + let manager = NEFilterManager.shared() + // If it’s not enabled, just enable it once. + guard manager.isEnabled else { + manager.isEnabled = true + manager.saveToPreferences { saveError in + completion(saveError) + } + return + } + + // Disable first + manager.isEnabled = false + manager.saveToPreferences { disableError in + if let disableError = disableError { + completion(disableError) + return + } + + // Re-enable + manager.isEnabled = true + manager.saveToPreferences { enableError in + completion(enableError) + } + } + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift index 7e8f560a..3e927a20 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift @@ -11,7 +11,7 @@ extension HelperService: AppToExtensionExtension { func register(_ completionHandler: @escaping (Bool) -> Void) { queue.async { - IPCConnection.shared.register(completionHandler) + IPCConnection.shared.register(completionHandler: completionHandler) } } @@ -48,9 +48,5 @@ extension HelperService: AppToExtensionExtension { } } - func register(completionHandler: @escaping (Bool) -> Void) { - queue.async { - IPCConnection.shared.register(completionHandler: completionHandler) - } - } + func register(completionHandler: @escaping (Bool) -> Void) { } //TODO Remove } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift index 55dbe408..70322dda 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift @@ -25,6 +25,7 @@ final class HelperService: NSObject, HelperServiceProtocol { self.blockedUrls = BlockContentStore.loadlockedUrls() ?? [] } self.sendPing() + self.chromeService.startListening() self.chromeService.blockeddomainFetcher = { [weak self] in return self?.blockedUrls ?? [] @@ -129,7 +130,9 @@ final class HelperService: NSObject, HelperServiceProtocol { func startNetwrokBlocking(minutes: Int) { os_log("[SC] 🔍] BG startNetwrokBlocking:\(minutes)") - + self.register({ status in + os_log("[SC] 🔍] BG register NE status: \(status)") + }) queue.async { [weak self] in self?.startNetworkBlocking() self?.timer = DelayTimerHandler(delay: Double(minutes), completionHandler: { [weak self] in @@ -181,6 +184,10 @@ final class HelperService: NSObject, HelperServiceProtocol { Task { _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(true) await AppStateManager.shared.activateContentBlocking() + FilterController.restartFilter { result in + os_log("[SC] 🔍] BG FilterController.restartFilter: \(result)") + } + } } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/SafariExtensionService/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift similarity index 95% rename from Self-Control-Extension/SelfControl/SelfControlBGService/Services/SafariExtensionService/IPCConnection.swift rename to Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift index 0a81d5fb..f0f8f75e 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/SafariExtensionService/IPCConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift @@ -52,7 +52,12 @@ class IPCConnection: NSObject, NSSecureCoding { // The exported object is the delegate. newConnection.exportedInterface = NSXPCInterface(with: ExtensionToApp.self) newConnection.exportedObject = self - + newConnection.invalidationHandler = { [weak self] in + self?.currentConnection = nil + } + newConnection.interruptionHandler = { [weak self] in + self?.currentConnection = nil + } // The remote object is the provider's IPCConnection instance. newConnection.remoteObjectInterface = NSXPCInterface(with: AppToExtensionExtension.self) @@ -65,7 +70,7 @@ class IPCConnection: NSObject, NSSecureCoding { self.currentConnection = nil completionHandler(false) }) as? AppToExtensionExtension else { - os_log("Failed to create a remote object proxy for the provider") + os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") return } providerProxy.register(completionHandler) diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift index 05eebca7..c381459a 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift @@ -20,7 +20,6 @@ enum FlowInfoKey: String { /// App --> Provider IPC, to be implememted in Extension /// // Use Objective-C bridgeable type for XPC surface @objc public protocol AppToExtensionExtension { - func register(completionHandler: @escaping (Bool) -> Void) func register(_ completionHandler: @escaping (Bool) -> Void) func setBlockedURLs(_ urls: [String]) func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) //TODO: Remove once code is exported diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift index f2fb7929..335d04da 100644 --- a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift @@ -21,13 +21,16 @@ class FilterDataProvider: NEFilterDataProvider { static let localPort = "8888" override func startFilter(completionHandler: @escaping (Error?) -> Void) { os_log("[SC] 🔍 NE FilterDataProvider: Starting filter", log: OSLog.default, type: .info) - let blockedHosts = IPCConnection.shared.blockedUrls + var blockedHosts = IPCConnection.shared.blockedUrls +// if blockedHosts.count == 0 { +// blockedHosts = ["https://www.corebitss.com"] +// } os_log("[SC] 🔍 NE Blocked blockedUrls: %{public}@", log: OSLog.default, type: .info, String(describing: blockedHosts)) // Ensure blockedHosts is an array of String var filterRules: [NEFilterRule] = blockedHosts.compactMap { address in - let host = address + guard let host = address as? String else { return nil } // For hostname-based rules, you must use NENetworkRule with nil networks (matches all) let networkRule = NENetworkRule( remoteNetwork: nil, @@ -41,7 +44,7 @@ class FilterDataProvider: NEFilterDataProvider { } // Ensure at least one rule exists as required by NEFilterSettings - if filterRules.isEmpty { + if filterRules.count == 0 { // Add a default rule (matches all traffic, for example, and allows it) let defaultNetworkRule = NENetworkRule( remoteNetwork: nil, @@ -51,7 +54,7 @@ class FilterDataProvider: NEFilterDataProvider { protocol: .any, direction: .any ) - let defaultRule = NEFilterRule(networkRule: defaultNetworkRule, action: .allow) + let defaultRule = NEFilterRule(networkRule: defaultNetworkRule, action: .filterData) filterRules = [defaultRule] } @@ -238,12 +241,12 @@ class FilterDataProvider: NEFilterDataProvider { // Called for each new flow. override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict { - os_log("[SC] 🔍 NE] FilterDataProvider: handleNewFlow invoked", log: OSLog.default, type: .debug) +// os_log("[SC] 🔍 NE] FilterDataProvider: handleNewFlow invoked", log: OSLog.default, type: .debug) guard IPCConnection.shared.isServiceActive else { return .allow() } // Provide peek sizes required by this overload - os_log("[SC] 🔍 NE] FilterDataProvider: handleNewFlow invoked", log: OSLog.default, type: .debug) +// os_log("[SC] 🔍 NE] FilterDataProvider: handleNewFlow invoked", log: OSLog.default, type: .debug) guard IPCConnection.shared.blockedUrls.count > 0 else { //No signinficant urls to block return .allow() } diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift index c88693a2..0dbd62fb 100644 --- a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift @@ -109,7 +109,7 @@ extension IPCConnection: NSXPCListenerDelegate { os_log("[SC] 🔍] NE interruptionHandler:" ) self.currentConnection = nil } - + currentConnection?.suspend() currentConnection = newConnection newConnection.resume() From 7f64862b3190819333a91854447a3701ff2f69c7 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Tue, 2 Jun 2026 22:14:01 +0530 Subject: [PATCH 50/64] Fixed issues and code migration --- ...wModel+Dock.swift => AppDockManager.swift} | 25 ++- .../Main/EventScheduler/EventScheduler.swift | 158 -------------- .../EventScheduler/EventSchedulerRunner.swift | 195 ------------------ .../SelfControl/Main/FilterViewModel.swift | 124 ++--------- .../Main/Preferences/AppPreferences.swift | 2 +- .../Main/SCDaemonSupport/SCDaemonHelper.swift | 4 +- .../HelperConnection+ReceivedMessages.swift | 8 + ...erConnection+SafariExtensionMessages.swift | 18 -- .../HelperConnection+SendMessages.swift | 34 +++ .../HelperConnection.swift | 28 --- .../Main/SelfControlUI/NewContentView.swift | 19 +- .../Views/AdvancedSettingsView.swift | 124 +++++------ .../Views/BlockScheduleView.swift | 1 - .../Views/BlocklistEditorView.swift | 1 - .../Views/DomainDetailView.swift | 2 +- .../CoreService/AppStateManager.swift | 2 +- .../EventSchedulerRunnerController.swift} | 16 +- ...elperService+SafariExtensionMessages.swift | 2 +- .../HelperService+SendMessages.swift | 6 + .../CoreService/HelperService.swift | 72 +++---- .../Helpers/DelayTimerHandler.swift | 35 +++- .../Helpers}/Sound.swift | 12 +- .../HelperAppPreferences.swift} | 18 +- .../EventScheduler/EventSchedulerRunner.swift | 121 ++++++----- .../Shared/HelperServiceProtocol.swift | 8 +- 25 files changed, 339 insertions(+), 696 deletions(-) rename Self-Control-Extension/SelfControl/SelfControl/Main/{FilterViewModel+Dock.swift => AppDockManager.swift} (75%) delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerRunner.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift rename Self-Control-Extension/SelfControl/{SelfControl/Main/FilterViewModel+EventScheduler.swift => SelfControlBGService/CoreService/EventSchedulerRunnerController.swift} (72%) rename Self-Control-Extension/SelfControl/{SelfControl/Main/Preferences => SelfControlBGService/Helpers}/Sound.swift (70%) rename Self-Control-Extension/SelfControl/SelfControlBGService/{Services/EventScheduler/BlockContentStore.swift => Preferences/HelperAppPreferences.swift} (85%) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+Dock.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/AppDockManager.swift similarity index 75% rename from Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+Dock.swift rename to Self-Control-Extension/SelfControl/SelfControl/Main/AppDockManager.swift index bc811a3c..6d37f182 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+Dock.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/AppDockManager.swift @@ -1,17 +1,23 @@ // -// FilterViewModel+Dock.swift +// AppDockManager.swift // SelfControl // -// Created by Satendra Singh on 12/04/26. +// Created by Satendra Singh on 31/05/26. // -import Foundation -import AppKit +import Cocoa -extension FilterViewModel { +final class AppDockManager { + var dockTimer: Timer? + var timerFireDate: Date? - func startShowingCountDownInDock() { - dockTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateDockCountDown), userInfo: nil, repeats: true) + func checAndStartShowDockTimer(endTime: Date) { + timerFireDate = endTime + if UserDefaults.standard.bool(forKey: "BadgeApplicationIcon") { + dockTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateDockCountDown), userInfo: nil, repeats: true) + } else { + stopShowingCountDownInDock() + } } func stopShowingCountDownInDock() { @@ -19,7 +25,7 @@ extension FilterViewModel { NSApp?.dockTile.badgeLabel = nil } - @objc func updateDockCountDown() { + @objc private func updateDockCountDown() { let blockEndingDate: Date = timerFireDate ?? .now let blockingSecond: Int = Int(blockEndingDate.timeIntervalSinceNow) var numSeconds: Int = Int(blockEndingDate.timeIntervalSinceNow) @@ -30,8 +36,7 @@ extension FilterViewModel { numSeconds %= 3600 numMinutes = numSeconds / 60 numSeconds %= 60 - if UserDefaults.standard.bool(forKey: "BadgeApplicationIcon"), - blockingSecond > 0 { + if blockingSecond > 0 { // Round up minutes when showing mm:ss style without seconds var minutes = numMinutes if numSeconds > 0 && minutes != 59 { minutes += 1 } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventScheduler.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventScheduler.swift index c16efccd..128270a5 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventScheduler.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventScheduler.swift @@ -7,7 +7,6 @@ import Foundation - // A simple, concurrency-safe scheduler for recurring events by SCSCWeekday and time. // It validates time ranges and prevents overlapping events on the same day. @@ -42,18 +41,6 @@ public enum SCWeekday: Int, CaseIterable, Codable, Hashable, Sendable { case saturday = 7 case sunday = 1 -// public var displayName: String { -// switch self { -// case .monday: return "Monday" -// case .tuesday: return "Tuesday" -// case .wednesday: return "Wednesday" -// case .thursday: return "Thursday" -// case .friday: return "Friday" -// case .saturday: return "Saturday" -// case .sunday: return "Sunday" -// } -// } - var letter: String { switch self { case .monday: return "M" @@ -195,148 +182,3 @@ public struct Event: Identifiable, Codable, Hashable, Sendable { return aStart < bEnd && aEnd > bStart } } - -/// A concurrency-safe scheduler to add, update, and query recurring events by SCWeekday and time. -public actor EventScheduler { - public init(calendar: Calendar = .current) { - self.calendar = calendar - } - - public let calendar: Calendar - private var eventsByID: [UUID: Event] = [:] - - // MARK: - CRUD - - /// Schedules a new event. Throws if the time range is invalid or conflicts with existing events. - @discardableResult - public func schedule( - title: String = "", - days: Set, - startTime: TimeOfDay, - endTime: TimeOfDay, - userInfo: [String: String]? = nil - ) throws -> Event { - let newEvent = try Event(title: title, days: days, startTime: startTime, endTime: endTime, userInfo: userInfo) - try assertNoConflicts(with: newEvent, excludingID: nil) - eventsByID[newEvent.id] = newEvent - return newEvent - } - - @discardableResult - public func schedule( - newEvent: Event - ) throws -> Event { - try assertNoConflicts(with: newEvent, excludingID: nil) - eventsByID[newEvent.id] = newEvent - return newEvent - } - - /// Updates an existing event. Throws if not found or if the updated event conflicts. - public func update( - id: UUID, - title: String? = nil, - days: Set? = nil, - startTime: TimeOfDay? = nil, - endTime: TimeOfDay? = nil, - userInfo: [String: String]? = nil - ) throws -> Event { - guard var existing = eventsByID[id] else { - throw SchedulerError.eventNotFound - } - - let newTitle = title ?? existing.title - let newDays = days ?? existing.days - let newStart = startTime ?? existing.startTime - let newEnd = endTime ?? existing.endTime - let newUserInfo = userInfo ?? existing.userInfo - - let updated = try Event(id: id, title: newTitle, days: newDays, startTime: newStart, endTime: newEnd, userInfo: newUserInfo) - try assertNoConflicts(with: updated, excludingID: id) - eventsByID[id] = updated - return updated - } - - /// Removes an event by ID. Returns the removed event or throws if not found. - @discardableResult - public func remove(id: UUID) throws -> Event { - guard let removed = eventsByID.removeValue(forKey: id) else { - throw SchedulerError.eventNotFound - } - return removed - } - - /// Returns all scheduled events sorted by title. - public func allEvents() -> [Event] { - eventsByID.values.sorted { $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedAscending } - } - - /// Returns events that occur on the specified SCWeekday, sorted by start time. - public func events(on day: SCWeekday) -> [Event] { - eventsByID.values - .filter { $0.occurs(on: day) } - .sorted { lhs, rhs in - if lhs.startTime == rhs.startTime { - return lhs.title.localizedCaseInsensitiveCompare(rhs.title) == .orderedAscending - } - return lhs.startTime < rhs.startTime - } - } - - // MARK: - Conflict Checking - - /// Returns any events that would conflict with the provided event. - public func conflicts(for event: Event, excludingID: UUID? = nil) -> [Event] { - eventsByID.values - .filter { existing in - if let exclude = excludingID, existing.id == exclude { return false } - // Only check days that overlap - let sharedDays = existing.days.intersection(event.days) - guard !sharedDays.isEmpty else { return false } - return sharedDays.contains { event.overlaps(with: existing, on: $0) } - } - .sorted { $0.startTime < $1.startTime } - } - - private func assertNoConflicts(with event: Event, excludingID: UUID?) throws { - let conflicts = conflicts(for: event, excludingID: excludingID) - if !conflicts.isEmpty { - throw SchedulerError.conflict(conflictingEvents: conflicts) - } - } - - // MARK: - Query Helpers - - /// Returns the next DateInterval this event will occur after the given date, if any. - /// This uses the scheduler's calendar to resolve the next occurrence in the current week or later. - public func nextOccurrence(of eventID: UUID, after date: Date = Date()) -> DateInterval? { - guard let event = eventsByID[eventID] else { return nil } - return nextOccurrence(of: event, after: date) - } - - /// Returns the next DateInterval an event will occur after the given date, if any. - public func nextOccurrence(of event: Event, after date: Date = Date()) -> DateInterval? { - // Search up to 8 weeks ahead to be safe for unusual calendars - for dayOffset in 0..<(7 * 8) { - guard let candidateDay = calendar.date(byAdding: .day, value: dayOffset, to: date) else { continue } - let SCWeekday = SCWeekday.from(date: candidateDay, calendar: calendar) - guard event.occurs(on: SCWeekday) else { continue } - - var comps = calendar.dateComponents([.year, .month, .day], from: candidateDay) - comps.hour = event.startTime.hour - comps.minute = event.startTime.minute - comps.second = 0 - let start = calendar.date(from: comps) - - comps.hour = event.endTime.hour - comps.minute = event.endTime.minute - let end = calendar.date(from: comps) - - if let start, let end, end > date { - // If the start is in the past but end is in the future, return the remaining portion. - let actualStart = max(start, date) - return DateInterval(start: actualStart, end: end) - } - } - return nil - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerRunner.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerRunner.swift deleted file mode 100644 index b10998bd..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/EventScheduler/EventSchedulerRunner.swift +++ /dev/null @@ -1,195 +0,0 @@ -// -// EventSchedulerRunner.swift -// SelfControl -// -// Created by Satendra Singh on 23/02/26. -// - -import Foundation - -// A firing engine that uses EventScheduler as storage and fires events at their scheduled times. -// This actor observes the scheduler and invokes registered handlers when events start. -// -public actor EventSchedulerRunner { - public typealias EventHandler = @Sendable (Event) async -> Void - - public init(scheduler: EventScheduler, fireTolerance: TimeInterval = 1.0) { - self.scheduler = scheduler - self.fireTolerance = fireTolerance - } - - private let scheduler: EventScheduler - private let fireTolerance: TimeInterval - - private var timer: Timer? - private var perEventHandlers: [UUID: EventHandler] = [:] - private var globalHandlers: [UUID: EventHandler] = [:] // keyed by token - private var scheduledEvent: Event? - - deinit { - timer?.invalidate() - timer = nil - } - - // MARK: - Control - - public func start() async { - guard timer == nil else { return } - await scheduleNextTimer() - } - - public func stop() { - timer?.invalidate() - timer = nil - scheduledEvent = nil - } - - /// Restart timer to quickly reflect changes (handlers or events). - private func rescheduleLoop() async { - await scheduleNextTimer() - } - - // MARK: - Handlers - - public func setHandler(for eventID: UUID, handler: EventHandler?) async { - if let handler { - perEventHandlers[eventID] = handler - } else { - perEventHandlers.removeValue(forKey: eventID) - } - await rescheduleLoop() - } - - @discardableResult - public func addGlobalHandler(_ handler: @escaping EventHandler) async -> UUID { - let token = UUID() - globalHandlers[token] = handler - // await rescheduleLoop() - return token - } - - public func removeGlobalHandler(token: UUID) async { - globalHandlers.removeValue(forKey: token) - await rescheduleLoop() - } - - // MARK: - Timer scheduling - - private func scheduleNextTimer() async { - // Cancel existing timer, if any - timer?.invalidate() - timer = nil - - let now = Date() - let calendar = await scheduler.calendar - let events = await scheduler.allEvents() - - // Compute earliest next start among all events - let next = earliestNextStart(after: now, events: events, calendar: calendar) - - // If there are no upcoming events, poll again in a short while - let interval: TimeInterval - if let nextDate = next.0 { - interval = max(0.0, nextDate.timeIntervalSinceNow) - } else { - interval = 0.3 - } - - // Create a one-shot timer to fire at the next event (or retry interval) - let newTimer = Timer(timeInterval: interval, repeats: false) { [weak self] _ in - guard let self else { return } - // Hop back to the actor to handle firing and rescheduling - Task { - await self.timerDidFire() - } - } - scheduledEvent = next.1 - // Ensure the timer fires during UI interactions as well - newTimer.tolerance = min(fireTolerance, max(0.0, interval * 0.90)) - RunLoop.main.add(newTimer, forMode: .common) - self.timer = newTimer - } - - private func timerDidFire() async { - let fireDate = Date() - let calendar = await scheduler.calendar - let events = await scheduler.allEvents() - - await fireEventsStarting(around: fireDate, events: events, calendar: calendar, tolerance: fireTolerance) - - // Schedule the next timer based on the latest data - await scheduleNextTimer() - } - - // MARK: - Event computations - - private func earliestNextStart(after date: Date, events: [Event], calendar: Calendar) -> (Date?, Event?) { - var best: Date? - var bestEvent: Event? - for event in events { - if let start = nextStart(of: event, after: date, calendar: calendar) { - if let b = best { - if start < b { - best = start - bestEvent = event - } - } else { - best = start - bestEvent = event - } - } - } - return (best, bestEvent) - } - - private func nextStart(of event: Event, after date: Date, calendar: Calendar) -> Date? { - // Search up to 8 weeks ahead - for dayOffset in 0..<(7 * 8) { - guard let candidateDay = calendar.date(byAdding: .day, value: dayOffset, to: date) else { continue } - let weekday = SCWeekday.from(date: candidateDay, calendar: calendar) - guard event.occurs(on: weekday) else { continue } - - var comps = calendar.dateComponents([.year, .month, .day], from: candidateDay) - comps.hour = event.startTime.hour - comps.minute = event.startTime.minute - comps.second = 0 - if let start = calendar.date(from: comps), start > date { - return start - } - } - return nil - } - - private func fireEventsStarting(around date: Date, events: [Event], calendar: Calendar, tolerance: TimeInterval) async { - // Determine which events start at this time within tolerance -// let starting = events.compactMap { event -> Event? in -// let weekday = SCWeekday.from(date: date, calendar: calendar) -// guard event.occurs(on: weekday) else { return nil } -// var comps = calendar.dateComponents([.year, .month, .day], from: date) -// comps.hour = event.startTime.hour -// comps.minute = event.startTime.minute -// comps.second = 0 -// guard let start = calendar.date(from: comps) else { return nil } -// return abs(start.timeIntervalSince(date)) <= tolerance ? event : nil -// } - - guard let firedEvent = scheduledEvent else { return } - - // Capture handlers to call outside the actor's critical path - let global = globalHandlers.values -// for event in starting { - var handlers: [EventHandler] = [] - if let h = perEventHandlers[firedEvent.id] { - handlers.append(h) - } - handlers.append(contentsOf: global) -// if handlers.isEmpty { continue } - - for handler in handlers { - Task.detached(priority: .userInitiated) { - await handler(firedEvent) - } - } -// } - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index edf0fbbc..48da04a4 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -37,14 +37,8 @@ enum SelfControlViewState: Equatable { } final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionRequestDelegate, ExtensionToApp { - @Published var status: Status = .stopped { - didSet { - if self.status == .running { - updateScheduledEvents() - } - } - } - + @Published var status: Status = .stopped + let dockManager = AppDockManager() @Published var viewState: SelfControlViewState = .installNetworkExtension @Published var isNetworkExtensionSkipped: Bool = false { @@ -55,13 +49,8 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques private var isSafariExtensionInstalled: Bool = AppPreferences.isSafariExtensionInstalled private var isChromeExtensionInstalled: Bool = AppPreferences.isChromeExtensionInstalled - var eventRunner: EventSchedulerRunner? = nil - var eventRunnerHandler: EventSchedulerRunner.EventHandler? @State private var domains = AppPreferences.getBlockedDomains() - @State var blockedURLs: [BlockedURL] = [] - var blockerStorage: BlockedURLStore? - @Published var delay: Double = 5.0 - var blockedIPAddressed: [String] = [] + private(set) var blockerStorage: BlockedURLStore? private var cancellables = Set() @Published var isActiveBlocking: Bool = false lazy var selfControlDaemon = SSCDaemonHelper() @@ -163,9 +152,6 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques self.blockerStorage = BlockedURLStore() self.blockerStorage?.load() } - self.eventRunnerHandler = { event in - - } } deinit { @@ -453,85 +439,27 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } // MARK: - App Communication (Prompting the User) - - @objc func promptUser(aboutFlow flowInfo: [String: String], responseHandler: @escaping (Bool) -> Void) { - guard let localPort = flowInfo[FlowInfoKey.localPort.rawValue], - let remoteAddress = flowInfo[FlowInfoKey.remoteAddress.rawValue] else { - os_log("[SC] 🔍] Got a promptUser call without valid flow info: %@", flowInfo) - responseHandler(true) - return - } - let connectionDate = Date() - DispatchQueue.main.async { - // For SwiftUI on macOS, use NSAlert via the shared NSApplication window. - if let window = NSApplication.shared.windows.first { - let alert = NSAlert() - alert.alertStyle = .informational - alert.messageText = "New incoming connection" - alert.informativeText = "A new connection on port \(localPort) has been received from \(remoteAddress)." - alert.addButton(withTitle: "Allow") - alert.addButton(withTitle: "Deny") - alert.beginSheetModal(for: window) { response in - let userAllowed = (response == .alertFirstButtonReturn) - self.logFlow(flowInfo, at: connectionDate, userAllowed: userAllowed) - responseHandler(userAllowed) - } - } else { - // Fallback if no window is available. - self.logFlow(flowInfo, at: connectionDate, userAllowed: true) - responseHandler(true) - } - } - } - - func didSetUrls() { - print("didSetUrls+++++") - // URLs updated; refresh extension state in case Safari side changed. - } + + //TODO: Cleanup + @objc func promptUser(aboutFlow flowInfo: [String: String], responseHandler: @escaping (Bool) -> Void) { } + func didSetUrls() { } - func activateNetworkBlocking() { + func startBlocking(endDate: Date) { print("activateNetworkBlocking+++++") - IPCConnectionProxy().setEnableService(true) - BlockListManager.activateSafariBlocking() - startShowingCountDownInDock() + isActiveBlocking = true + dockManager.checAndStartShowDockTimer(endTime: endDate) } - func deactivateNetworkBlocking() { + func stopBlocking() { print("deactivateNetworkBlocking+++++") - IPCConnectionProxy().setEnableService(false) - BlockListManager.deactivateSafariBlocking() - if AppPreferences.playSoundOnCompletion { - NSSound.playDefaultSound() - } +// if AppPreferences.playSoundOnCompletion { +// NSSound.playDefaultSound() +// } if AppPreferences.showNotificationOnCompletion { LocalNotificationManager.scheduleNotification() } - stopShowingCountDownInDock() - } - - // MARK: - Timer management - - func startTimerWithSelectedDelay() -> Bool { - HelperConnection.shared.send_startNetwrokBlocking(minutes: Int(delay)) - return true -// cancelTimer() - let seconds = delay * 60.0 - guard seconds > 30 else { - os_log("[SC] 🔍] startTimerWithSelectedDelay called with non-positive delay: %f", seconds) - return false - } - timerFireDate = Date().addingTimeInterval(seconds) - os_log("[SC] 🔍] Scheduling timer to fire in %.0f seconds (%.2f minutes)", seconds, delay) - blockTimer = Timer.scheduledTimer(withTimeInterval: seconds, repeats: false) { [weak self] _ in - guard let self = self else { return } - os_log("[SC] 🔍] Timer fired. Deactivating network blocking.") - self.deactivateNetworkBlocking() - self.cancelTimer() - } - isActiveBlocking = true - -// RunLoop.main.add(blockTimer!, forMode: .common) - return true + dockManager.stopShowingCountDownInDock() + isActiveBlocking = false } func extendBlockTimer(by minutes: Int) { @@ -540,6 +468,8 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques timer.fireDate = timer.fireDate.addingTimeInterval(delta) // Optionally also track a blockEndDate if you show a countdown timerFireDate = timer.fireDate + dockManager.dockTimer?.fireDate = timer.fireDate + dockManager.timerFireDate = timer.fireDate os_log("[SC] 🔍] Timer fire date updated to %{public}@", timerFireDate?.description ?? "Empty time") } @@ -561,20 +491,6 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques selfControlDaemon.updateBlocklist(newBlockedDomains) } - @MainActor func updateBlockList(newBlockedDomains: [BlockedURL], time: Double ) { - self.delay = time - if status == .stopped { - installLegacyLaunched(futureDuration: Date.now.addingTimeInterval(delay*60)) - } else { - if startTimerWithSelectedDelay() == false { - return - } - saveAndupdateBlockList(newBlockedDomains) - if startTimerWithSelectedDelay() == false { return } - activateNetworkBlocking() - } - } - @MainActor func saveAndupdateBlockList(_ newBlockedDomains: [BlockedURL]) { blockerStorage?.set(newBlockedDomains) let urls = newBlockedDomains.compactMap(\.urls) @@ -582,8 +498,4 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques AppPreferences.setBlockedDomains(flattened) setBlockedUrls(urls: flattened) } - - func updateScheduledEvents() { - startEventScheduler() - } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/AppPreferences.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/AppPreferences.swift index 99fee2e3..f1c2b31f 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/AppPreferences.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/AppPreferences.swift @@ -15,7 +15,7 @@ struct AppPreferences { private static let isChromeExtensionKey: String = "isChromeExtensionKey" private static let isSafariExtInstallKey: String = "isSafariExtInstallKey" private static let isChromeExtInstallKey: String = "isChromeExtInstallKey" - private static let playSoundOnCompletionkey: String = "playSoundOnCompletion" + static let playSoundOnCompletionkey: String = "playSoundOnCompletion" private static let showNotificationOnCompletionkey: String = "showNotificationOnCompletion" private static let verifyNetworkBeforeBlock: String = "VerifyInternetConnection" diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/SCDaemonHelper.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/SCDaemonHelper.swift index b43d50c5..305d1640 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/SCDaemonHelper.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SCDaemonSupport/SCDaemonHelper.swift @@ -10,9 +10,7 @@ import Foundation final class SSCDaemonHelper { let xpc = SCXPCClient() - init() { - - } + init() { } func install(blockedDomains: [String], time: Date) { xpc.installDaemon { (error: Error?) in diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift index 21b74682..2272badd 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift @@ -19,4 +19,12 @@ extension HelperConnection { } // } } + + func didStartedBlocking(_ state: Bool, _ endDate: Date) -> Void { + if state == true { + let minutes: Double = Double(endDate.timeIntervalSinceNow / 60) + self.blockedStateHandler?(minutes) + } + print("REceived Blocked State: \(String(describing: state)), End Date: \(String(describing: endDate))") + } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift index 9bb9307d..e3ff1dd2 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift @@ -29,24 +29,6 @@ extension HelperConnection: AppToExtensionExtension { func register(completionHandler: @escaping (Bool) -> Void) { bgProxyServiceConnection()?.register(completionHandler) } - - func send_startNetwrokBlocking(minutes: Int) { - bgProxyServiceConnection()?.startNetwrokBlocking(minutes: minutes) - } - - func send_stopNetworkBlocking() { - bgProxyServiceConnection()?.stopNetworkBlocking() - } - - func send_getBlockedStates() { - bgProxyServiceConnection()?.getBlockedStates { state, endDate in - if state == true, let endDate = endDate { - let minutes: Double = Double(endDate.timeIntervalSinceNow / 60) - self.blockedStateHandler?(minutes) - } - print("REceived Blocked State: \(String(describing: state)), End Date: \(String(describing: endDate))") - } - } } //TODO: create extension on protocol with shared variable to send these message with static diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift new file mode 100644 index 00000000..6b70b5f3 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift @@ -0,0 +1,34 @@ +// +// HelperConnection+SendMessages.swift +// SelfControl +// +// Created by Satendra Singh on 31/05/26. +// + +extension HelperConnection { + func send_startNetwrokBlocking(minutes: Int) { + bgProxyServiceConnection()?.startNetwrokBlocking(minutes: minutes) + } + + func send_stopNetworkBlocking() { + bgProxyServiceConnection()?.stopNetworkBlocking() + } + + func send_getBlockedStates() { + bgProxyServiceConnection()?.getBlockedStates { state, endDate in + if state == true, let endDate = endDate { + let minutes: Double = Double(endDate.timeIntervalSinceNow / 60) + self.blockedStateHandler?(minutes) + } + print("REceived Blocked State: \(String(describing: state)), End Date: \(String(describing: endDate))") + } + } + + func send_extendBlocking(minutes: Int) { + bgProxyServiceConnection()?.extendBlocking(minutes: minutes) + } + + func send_setPreference(key: String, value: Bool) { + bgProxyServiceConnection()?.setPreference(key: key, value: value) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift index 601a2b10..c7c348d0 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift @@ -91,9 +91,6 @@ final class HelperConnection: NSObject, HelperClientProtocol, NSSecureCoding { self.connection = conn // Register for callbacks bgProxyServiceConnection()?.registerClient() - bgProxyServiceConnection()?.startMonitoring { status in - print("startMonitoring: Status Received: \(status)") - } } } @@ -114,23 +111,6 @@ final class HelperConnection: NSObject, HelperClientProtocol, NSSecureCoding { //TODO: } - // Convenience wrappers using async/await - func startMonitoring() async throws { - try await withCheckedThrowingContinuation { cont in - self.withProxy(error: { cont.resume(throwing: $0) }) { proxy in - proxy.startMonitoring { _ in cont.resume() } - } - } - } - - func stopMonitoring() async throws { - try await withCheckedThrowingContinuation { cont in - self.withProxy(error: { cont.resume(throwing: $0) }) { proxy in - proxy.stopMonitoring { _ in cont.resume() } - } - } - } - func currentStatus() async throws -> String { try await withCheckedThrowingContinuation { cont in self.withProxy(error: { cont.resume(throwing: $0) }) { proxy in @@ -139,14 +119,6 @@ final class HelperConnection: NSObject, HelperClientProtocol, NSSecureCoding { } } - func performWork(_ input: String) async throws { - try await withCheckedThrowingContinuation { cont in - self.withProxy(error: { cont.resume(throwing: $0) }) { proxy in - proxy.performWork(input) { _ in cont.resume() } - } - } - } - // MARK: - HelperClientProtocol (callbacks from helper) func didUpdateStatus(_ status: String) { diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewContentView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewContentView.swift index 69e44fe2..f7df8706 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewContentView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/NewContentView.swift @@ -176,6 +176,7 @@ struct NewContentView: View { extensionMinutes: $extensionMinutes, onConfirm: { minutes in extendTimer(by: minutes) + HelperConnection.shared.send_extendBlocking(minutes: Int(minutes)) viewModel.extendBlockTimer(by: Int(minutes)) withAnimation(DesignSystem.animationNormal) { showingExtendTimer = false @@ -1328,12 +1329,7 @@ struct NewContentView: View { } } .onAppear { - print("onAppear viewModel.eventRunnerHandler") - viewModel.eventRunnerHandler? = { eventreq in - print("New Event schedule started: \(eventreq.startTime) - \(eventreq.endTime)") - let minutes = Double(eventreq.endTime.minutes(from: eventreq.startTime, wrapAroundMidnight: true)) - await startBlocking(minutes: minutes) - } + print("onAppear main view") HelperConnection.shared.blockedStateHandler = { minutes in print("Already in blocked state, show remaining minutes:\(minutes)") Task { @MainActor in @@ -1362,8 +1358,8 @@ struct NewContentView: View { return } } - showBlockingStateForReminingMinutes(minutes: minutes) startBlocking(minutes: minutes) + viewModel.startBlocking(endDate: Date().addingTimeInterval(minutes * 60)) } private func showBlockingStateForReminingMinutes(minutes: Double) { @@ -1373,6 +1369,10 @@ struct NewContentView: View { // Calculate total time including days if easter egg is unlocked remainingTime = minutes * 60 // Start countdown timer + if timer != nil { + timer?.invalidate() + timer = nil + } timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in if remainingTime > 0 { remainingTime -= 1 @@ -1385,6 +1385,7 @@ struct NewContentView: View { } private func showNonBlockingState() { + viewModel.stopBlocking() withAnimation(DesignSystem.animationNormal) { isBlocking = true } @@ -1404,6 +1405,7 @@ struct NewContentView: View { private func stopBlocking() { showNonBlockingState() HelperConnection.shared.send_stopNetworkBlocking() + viewModel.stopBlocking() } private func showStopConfirmation() { @@ -1427,9 +1429,6 @@ struct NewContentView: View { stopCountdownTimer = nil stopCountdownTime = 600 showingStopConfirmation = false - -// viewModel.deactivateNetworkBlocking() -// HelperConnection.shared.send_stopNetworkBlocking() } private func resumeBlocking() { diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/AdvancedSettingsView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/AdvancedSettingsView.swift index 996df35f..2505f162 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/AdvancedSettingsView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/AdvancedSettingsView.swift @@ -110,7 +110,9 @@ struct AdvancedSettingsView: View { VStack(spacing: 0) { settingRow(title: Strings.AdvancedSettings.autoCheckUpdates, isOn: $autoCheckUpdates, showDivider: true) // settingRow(title: Strings.AdvancedSettings.autoSendErrorReports, isOn: $sendErrorReports, showDivider: true) - settingRow(title: Strings.AdvancedSettings.playSoundOnCompletion, isOn: $playSoundOnCompletion, showDivider: false) + settingRow(title: Strings.AdvancedSettings.playSoundOnCompletion, isOn: $playSoundOnCompletion, showDivider: false) {value in + HelperConnection.shared.send_setPreference(key: AppPreferences.playSoundOnCompletionkey, value: value) + } settingRow(title: Strings.AdvancedSettings.showNotificationOnCompletion, isOn: $showNotificationOnCompletion, showDivider: false) } } @@ -129,7 +131,7 @@ struct AdvancedSettingsView: View { VStack(spacing: 0) { settingRow(title: Strings.AdvancedSettings.verifyInternetConnection, isOn: $verifyConnection, showDivider: true) - settingRow(title: Strings.AdvancedSettings.clearBrowserCache, isOn: $clearCache, showDivider: true) +// settingRow(title: Strings.AdvancedSettings.clearBrowserCache, isOn: $clearCache, showDivider: true) settingRow(title: Strings.AdvancedSettings.allowLocalNetworks, isOn: $allowLocalNetworks, showDivider: false) } } @@ -147,9 +149,9 @@ struct AdvancedSettingsView: View { .tracking(1.2) VStack(spacing: 0) { - settingRow(title: Strings.AdvancedSettings.timerFloatsOnTop, isOn: $timerFloatsOnTop, showDivider: true) +// settingRow(title: Strings.AdvancedSettings.timerFloatsOnTop, isOn: $timerFloatsOnTop, showDivider: true) settingRow(title: Strings.AdvancedSettings.showCountdownInDock, isOn: $showCountdownInDock, showDivider: true) - settingRow(title: Strings.AdvancedSettings.hideSecondsInCountdown, isOn: $hideSeconds, showDivider: false) +// settingRow(title: Strings.AdvancedSettings.hideSecondsInCountdown, isOn: $hideSeconds, showDivider: false) } } .padding(DesignSystem.spacingMedium) @@ -159,61 +161,61 @@ struct AdvancedSettingsView: View { ) // Mode Settings - VStack(alignment: .leading, spacing: 8) { - Text(Strings.AdvancedSettings.mode) - .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) - .foregroundColor(DesignSystem.textTertiary) - .tracking(1.2) - - // Allowlist mode checkbox with warning - VStack(alignment: .leading, spacing: 8) { - Toggle(isOn: Binding( - get: { blockingMode == .allowlist }, - set: { newValue in - if !isBlockingMode { - if newValue { - showingAllowlistWarning = true - } else { - withAnimation(DesignSystem.animationFast) { - blockingMode = .blocklist - } - } - } - } - )) { - HStack(spacing: 8) { - Image(systemName: "exclamationmark.shield.fill") - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) - .foregroundColor(blockingMode == .allowlist ? Color.white : DesignSystem.textSecondary) - VStack(alignment: .leading, spacing: 2) { - Text(Strings.AdvancedSettings.allowlistMode) - .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) - .foregroundColor(DesignSystem.textPrimary) - Text(Strings.AdvancedSettings.allowlistModeDescription) - .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) - .foregroundColor(DesignSystem.textSecondary) - } - } - } - .toggleStyle(SwitchToggleStyle(tint: DesignSystem.toggleTint)) - .disabled(isBlockingMode) - .opacity(isBlockingMode ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) - .padding(DesignSystem.spacingMedium) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .fill(blockingMode == .allowlist ? DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium) : DesignSystem.backgroundTertiary) - .overlay( - RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) - .stroke(blockingMode == .allowlist ? DesignSystem.hoverBorder.opacity(DesignSystem.opacityHigher) : DesignSystem.borderPrimary, lineWidth: 1) - ) - ) - } - } - .padding(DesignSystem.spacingMedium) - .background( - RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) - .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) - ) +// VStack(alignment: .leading, spacing: 8) { +// Text(Strings.AdvancedSettings.mode) +// .font(DesignSystem.font(size: DesignSystem.fontSizeSmall, weight: DesignSystem.fontWeightSemibold)) +// .foregroundColor(DesignSystem.textTertiary) +// .tracking(1.2) +// +// // Allowlist mode checkbox with warning +// VStack(alignment: .leading, spacing: 8) { +// Toggle(isOn: Binding( +// get: { blockingMode == .allowlist }, +// set: { newValue in +// if !isBlockingMode { +// if newValue { +// showingAllowlistWarning = true +// } else { +// withAnimation(DesignSystem.animationFast) { +// blockingMode = .blocklist +// } +// } +// } +// } +// )) { +// HStack(spacing: 8) { +// Image(systemName: "exclamationmark.shield.fill") +// .font(DesignSystem.font(size: DesignSystem.fontSizeLarge)) +// .foregroundColor(blockingMode == .allowlist ? Color.white : DesignSystem.textSecondary) +// VStack(alignment: .leading, spacing: 2) { +// Text(Strings.AdvancedSettings.allowlistMode) +// .font(DesignSystem.font(size: DesignSystem.fontSizeLarge, weight: DesignSystem.fontWeightMedium)) +// .foregroundColor(DesignSystem.textPrimary) +// Text(Strings.AdvancedSettings.allowlistModeDescription) +// .font(DesignSystem.font(size: DesignSystem.fontSizeSmall)) +// .foregroundColor(DesignSystem.textSecondary) +// } +// } +// } +// .toggleStyle(SwitchToggleStyle(tint: DesignSystem.toggleTint)) +// .disabled(isBlockingMode) +// .opacity(isBlockingMode ? DesignSystem.disabledOpacity : DesignSystem.opacityFull) +// .padding(DesignSystem.spacingMedium) +// .background( +// RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) +// .fill(blockingMode == .allowlist ? DesignSystem.textPrimary.opacity(DesignSystem.opacityMedium) : DesignSystem.backgroundTertiary) +// .overlay( +// RoundedRectangle(cornerRadius: DesignSystem.radiusMedium) +// .stroke(blockingMode == .allowlist ? DesignSystem.hoverBorder.opacity(DesignSystem.opacityHigher) : DesignSystem.borderPrimary, lineWidth: 1) +// ) +// ) +// } +// } +// .padding(DesignSystem.spacingMedium) +// .background( +// RoundedRectangle(cornerRadius: DesignSystem.radiusSmall) +// .fill(DesignSystem.backgroundPrimary.opacity(DesignSystem.opacityHigher)) +// ) } .padding(DesignSystem.spacingLarge) } @@ -233,7 +235,7 @@ struct AdvancedSettingsView: View { } } - private func settingRow(title: String, isOn: Binding, showDivider: Bool) -> some View { + private func settingRow(title: String, isOn: Binding, showDivider: Bool, onChange:( (Bool) -> Void)? = nil) -> some View { VStack(spacing: 0) { HStack(alignment: .center) { Toggle(isOn: isOn) { @@ -241,6 +243,8 @@ struct AdvancedSettingsView: View { .font(DesignSystem.font(size: DesignSystem.fontSizeMedium)) .foregroundColor(DesignSystem.textPrimary) .multilineTextAlignment(.leading) + }.onChange(of: isOn.wrappedValue) { _ in + onChange?(isOn.wrappedValue) } .toggleStyle(CheckboxToggleStyle()) Spacer() diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlockScheduleView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlockScheduleView.swift index f896b325..80179ab1 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlockScheduleView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlockScheduleView.swift @@ -132,7 +132,6 @@ struct BlockScheduleView: View { } .onDisappear { print("Schedule off-screen") - viewModel.updateScheduledEvents() } } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift index 6690f900..43318161 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift @@ -419,7 +419,6 @@ struct BlocklistEditorView: View { } private func saveChanges() { - viewModel.blockedURLs = blockedURLs viewModel.saveAndupdateBlockList(blockedURLs) } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/DomainDetailView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/DomainDetailView.swift index ced1f32e..e6e6a3c1 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/DomainDetailView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/DomainDetailView.swift @@ -392,7 +392,7 @@ struct DomainDetailView: View { } private func saveChanges() { - viewModel.blockerStorage?.set(blockedURLs) + viewModel.saveAndupdateBlockList(blockedURLs) } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift index 067faa2f..de820455 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift @@ -43,7 +43,7 @@ actor AppStateManager { func deactivateContentBlocking() { isBlockingEnabled = false - os_log("[SC] 🔍] BG deactivateContentBlocking: %{public}%d",isBlockingEnabled) + os_log("[SC] 🔍] BG deactivateContentBlocking: %{public}d",isBlockingEnabled) } func reset() { diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+EventScheduler.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/EventSchedulerRunnerController.swift similarity index 72% rename from Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+EventScheduler.swift rename to Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/EventSchedulerRunnerController.swift index 74fbe5fe..15f5a191 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel+EventScheduler.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/EventSchedulerRunnerController.swift @@ -1,17 +1,24 @@ // -// FilterViewModel+EventScheduler.swift +// EventSchedulerRunnerContainer.swift // SelfControl // -// Created by Satendra Singh on 25/02/26. +// Created by Satendra Singh on 28/05/26. // -extension FilterViewModel { +final class EventSchedulerRunnerController { + private var eventRunner: EventSchedulerRunner? = nil + var eventRunnerHandler: EventSchedulerRunner.EventHandler? + + init(eventRunnerHandler: EventSchedulerRunner.EventHandler? = nil) { + self.eventRunnerHandler = eventRunnerHandler + startEventScheduler() + } func startEventScheduler() { Task { await self.eventRunner?.stop() let scheduler = EventScheduler() - let events = EventSchedulerStore.loadSchedules() + let events = HelperAppPreferences.loadSchedules() var eventObjs: [Event] = [] for event in events { eventObjs.append(contentsOf: event.scheduledEvents) @@ -26,7 +33,6 @@ extension FilterViewModel { } await eventRunner?.start() } -// eventRunner = EventRunner(schedules: self.storedValue.schedules.scheduledEvents) } func stopEventScheduler() { diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift index 3e927a20..0cb01c29 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift @@ -19,7 +19,7 @@ extension HelperService: AppToExtensionExtension { queue.async(flags: .barrier) { IPCConnection.shared.enableURLBlocking(urls) self.blockedUrls = urls - BlockContentStore.saveBlockedUrls(blockedUrls: urls) + HelperAppPreferences.saveBlockedUrls(blockedUrls: urls) } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift index 1061a539..b116a3da 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift @@ -5,6 +5,8 @@ // Created by Satendra Singh on 18/05/26. // +import Foundation + extension HelperService { static var proxyConnectionService: HelperClientProtocol? { AppDelegate.shared.service.proxyConnectionService() @@ -13,4 +15,8 @@ extension HelperService { static func send_didEnableWebExtension(_ extensionTypeRawValue: String, state: Bool) { proxyConnectionService?.didEnableWebExtension(extensionTypeRawValue, state: state) } + + static func send_didStartedBlocking(_ state: Bool, _ endDate: Date) { + proxyConnectionService?.didStartedBlocking(state, endDate) + } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift index 70322dda..623e0667 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift @@ -15,6 +15,18 @@ final class HelperService: NSObject, HelperServiceProtocol { let chromeService = ChromeExtensionRequestListner() var blockedUrls: [String] = [] private var timer: DelayTimerHandler? + private var eventSchedulerController: EventSchedulerRunnerController? + + override init() { + super.init() + + eventSchedulerController = EventSchedulerRunnerController(eventRunnerHandler: { [weak self] event in + os_log("New Event schedule started: \(event.startTime.formatted()) - \(event.endTime.formatted())") + + let minutes = Double(event.endTime.minutes(from: event.startTime, wrapAroundMidnight: true)) + self?.startNetwrokBlocking(minutes: Int(minutes)) + }) + } func registerClient() { os_log("[SC] 🔍] BG registerClient") @@ -22,7 +34,7 @@ final class HelperService: NSObject, HelperServiceProtocol { // self.clients.add(client) // client.didUpdateStatus(self.isMonitoring ? "running" : "stopped") print("Register Client received") - self.blockedUrls = BlockContentStore.loadlockedUrls() ?? [] + self.blockedUrls = HelperAppPreferences.loadlockedUrls() ?? [] } self.sendPing() @@ -46,7 +58,7 @@ final class HelperService: NSObject, HelperServiceProtocol { func sendPing() { os_log("[SC] 🔍] BG HELPER sendPing") - let schedules = BlockContentStore.loadSchedules() + let schedules = HelperAppPreferences.loadSchedules() var details :String = "" for schedule in schedules { os_log("[SC] 🔍] BG Schedule: \(schedule.summaryString)") @@ -55,44 +67,12 @@ final class HelperService: NSObject, HelperServiceProtocol { proxyConnectionService()?.didEmitEvent("Hello from helper: \(schedules.count), details: \(details)") } - - func startMonitoring(reply: @escaping (Bool) -> Void) { - os_log("[SC] 🔍] BG startMonitoring") - queue.async { - guard !self.isMonitoring else { reply(true); return } - self.isMonitoring = true - self.broadcastStatus() - // Start your actual background work here (timers, file watchers, etc.) - reply(true) - } - } - - func stopMonitoring(reply: @escaping (Bool) -> Void) { - os_log("[SC] 🔍] BG stopMonitoring") - - queue.async { - guard self.isMonitoring else { reply(true); return } - self.isMonitoring = false - // Stop your background work - self.broadcastStatus() - reply(true) - } - } - func currentStatus(reply: @escaping (String) -> Void) { queue.async { reply(self.isMonitoring ? "running" : "stopped") } } - func performWork(_ input: String, reply: @escaping (Bool) -> Void) { - queue.async { - // Do some work… - self.broadcastEvent("Processed: \(input)") - reply(true) - } - } - private func broadcastStatus() { let status = self.isMonitoring ? "running" : "stopped" for client in self.clients.allObjects { @@ -107,10 +87,11 @@ final class HelperService: NSObject, HelperServiceProtocol { } func saveSchedules(schedules: Data, reply: @escaping (Bool) -> Void) { - queue.async { - BlockContentStore.saveSchedulesData(schedules: schedules) - self.broadcastEvent("Schedules saved") - self.sendPing() + queue.async { [weak self] in + HelperAppPreferences.saveSchedulesData(schedules: schedules) + self?.broadcastEvent("Schedules saved") + self?.sendPing() + self?.eventSchedulerController?.startEventScheduler() reply(true) } } @@ -144,6 +125,10 @@ final class HelperService: NSObject, HelperServiceProtocol { self?.timer?.timerQueue = queue } self?.timer?.startTimerWithSelectedDelay() + if let fireDate = self?.timer?.timerFireDate { + os_log("[SC] 🔍] BG register NE timer active, firing at: \(fireDate)") + HelperService.send_didStartedBlocking(true, fireDate) + } } } @@ -154,10 +139,21 @@ final class HelperService: NSObject, HelperServiceProtocol { Task { _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(false) await AppStateManager.shared.deactivateContentBlocking() + Sound.checkAndPlay() } } } + func extendBlocking(minutes: Int) { + os_log("[SC] 🔍] BG extendBlocking: %{public}d", minutes) + timer?.extendBlocking(minutes: minutes) + } + + func setPreference(key: String, value: Bool) { + os_log("[SC] 🔍] BG extendBlocking: %{public}@:, %{public}d", key, value) + HelperAppPreferences.savePreference(key: key, value: value) + } + func getBlockedStates(reply: @escaping (_ state: Bool, _ endDate: Date?) -> Void) { queue.async { [weak self] in Task { [weak self] in diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift index 3b112cb8..cf9ee2a4 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift @@ -47,7 +47,7 @@ final class DelayTimerHandler { let seconds = delay * 60.0 guard seconds > 30 else { - os_log("[SC] Invalid delay: %f", seconds) + os_log("[SC] 🔍] BG Invalid delay: %f", seconds) return false } @@ -65,7 +65,7 @@ final class DelayTimerHandler { timer.setEventHandler { [weak self] in guard let self else { return } - os_log("[SC] Timer fired") + os_log("[SC] 🔍] BG Timer fired") // Prevent future usage immediately let completion = self.completionHandler @@ -89,11 +89,36 @@ final class DelayTimerHandler { return true } - + + func extendBlocking(minutes: Int) { + guard minutes > 0 else { return } + timerQueue.async { [weak self] in + guard let self = self else { return } + guard self.isActiveBlocking, let timer = self.blockTimer else { + os_log("[SC] 🔍] BG extendBlocking ignored: no active timer") + return + } + + let additionalSeconds = Double(minutes) * 60.0 + let now = Date() + let currentFire = self.timerFireDate ?? now + let newFire = currentFire.addingTimeInterval(additionalSeconds) + + // Update model + self.timerFireDate = newFire + self.delay += Double(minutes) // keep start time consistent + + // Reschedule timer to the new deadline + let remaining = max(0, newFire.timeIntervalSince(now)) + os_log("[SC] 🔍] BG Extending blocking by %{public}d minutes (%{public}.0f s). New remaining: %.0f s", minutes, additionalSeconds, remaining) + timer.schedule(deadline: .now() + remaining, repeating: .never, leeway: .seconds(1)) + } + } + private func cancelEventHandler() { guard let timer = blockTimer else { return } - os_log("[SC] Cancelling timer") + os_log("[SC] 🔍] BG Cancelling timer") timer.setEventHandler {} timer.cancel() @@ -103,7 +128,7 @@ final class DelayTimerHandler { guard let timer = blockTimer else { return } - os_log("[SC] Cancelling timer") + os_log("[SC] 🔍] BG Cancelling timer") timer.setEventHandler {} timer.cancel() diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/Sound.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/Sound.swift similarity index 70% rename from Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/Sound.swift rename to Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/Sound.swift index ee6a3536..68e2b622 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/Sound.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/Sound.swift @@ -2,10 +2,11 @@ // Sound.swift // SelfControl // -// Created by Satendra Singh on 02/04/26. +// Created by Satendra Singh on 31/05/26. // import AppKit +import os.log public extension NSSound { @@ -38,3 +39,12 @@ public extension NSSound { play(.basso) } } + +struct Sound { + static func checkAndPlay() { + os_log("[SC] 🔍] BG checkAndPlay: %{public}d",HelperAppPreferences.playSoundOnCompletion) + if HelperAppPreferences.playSoundOnCompletion { + NSSound.playDefaultSound() + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/BlockContentStore.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Preferences/HelperAppPreferences.swift similarity index 85% rename from Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/BlockContentStore.swift rename to Self-Control-Extension/SelfControl/SelfControlBGService/Preferences/HelperAppPreferences.swift index dc425997..c04930d7 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/BlockContentStore.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Preferences/HelperAppPreferences.swift @@ -7,7 +7,11 @@ import Foundation -struct BlockContentStore { +struct HelperAppPreferences { + enum Keys: String { + case playSoundOnCompletionkey = "playSoundOnCompletion" + } + // Load schedules from UserDefaults static func loadSchedules() -> [Schedule] { if let data = UserDefaults.standard.data(forKey: "schedules"), @@ -37,6 +41,18 @@ struct BlockContentStore { static func loadlockedUrls() -> [String]? { UserDefaults.standard.value(forKey: "blockedUrls") as? [String] } + + static var playSoundOnCompletion: Bool { + return UserDefaults.standard.bool(forKey: Keys.playSoundOnCompletionkey.rawValue) + } + + static func setPlaySoundOnCompletion(_ value: Bool) { + UserDefaults.standard.set(value, forKey: Keys.playSoundOnCompletionkey.rawValue) + } + + static func savePreference(key: String, value: Bool) { + UserDefaults.standard.set(value, forKey: key) + } } // MARK: - Schedule diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift index b10998bd..11953726 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift @@ -6,6 +6,7 @@ // import Foundation +import os.log // A firing engine that uses EventScheduler as storage and fires events at their scheduled times. // This actor observes the scheduler and invokes registered handlers when events start. @@ -21,26 +22,40 @@ public actor EventSchedulerRunner { private let scheduler: EventScheduler private let fireTolerance: TimeInterval - private var timer: Timer? +// private var timer: Timer? + private var timer: DispatchSourceTimer? + private let timerQueue = DispatchQueue(label: "com.yourcompany.EventSchedulerRunner.timer") + private var perEventHandlers: [UUID: EventHandler] = [:] private var globalHandlers: [UUID: EventHandler] = [:] // keyed by token private var scheduledEvent: Event? deinit { - timer?.invalidate() - timer = nil + if let t = timer { + t.setEventHandler {} // break potential retain cycles + t.cancel() + timer = nil + } + scheduledEvent = nil } // MARK: - Control public func start() async { + os_log("[SC] 🔍] BG Event Scheduler is starting") + guard timer == nil else { return } await scheduleNextTimer() } public func stop() { - timer?.invalidate() - timer = nil + // Your existing stop logic (e.g., cancel reschedule task, clear state) stays the same + + if let t = timer { + t.setEventHandler {} // break potential retain cycles + t.cancel() + timer = nil + } scheduledEvent = nil } @@ -64,6 +79,7 @@ public actor EventSchedulerRunner { public func addGlobalHandler(_ handler: @escaping EventHandler) async -> UUID { let token = UUID() globalHandlers[token] = handler + // await rescheduleLoop() return token } @@ -74,55 +90,76 @@ public actor EventSchedulerRunner { } // MARK: - Timer scheduling - private func scheduleNextTimer() async { - // Cancel existing timer, if any - timer?.invalidate() - timer = nil + // Cancel any existing timer first + if let t = timer { + t.setEventHandler { } + t.cancel() + timer = nil + } + // Your existing logic to compute the next fire date and select the event: + // (Keep this unchanged if you already have it) let now = Date() - let calendar = await scheduler.calendar - let events = await scheduler.allEvents() + let calendar = Calendar.autoupdatingCurrent + let events = await scheduler.allEvents() - // Compute earliest next start among all events - let next = earliestNextStart(after: now, events: events, calendar: calendar) + // If you already obtain events differently, keep your existing code here. + // Example (keep your own): + let (maybeNextDate, maybeEvent) = earliestNextStart(after: now, events: events, calendar: calendar) + os_log("[SC] 🔍] BG scheduleNextTimer: %{public}@ ",maybeNextDate?.debugDescription ?? "nil") - // If there are no upcoming events, poll again in a short while - let interval: TimeInterval - if let nextDate = next.0 { - interval = max(0.0, nextDate.timeIntervalSinceNow) - } else { - interval = 0.3 + guard let fireDate = maybeNextDate, let event = maybeEvent else { + scheduledEvent = nil + return } + scheduledEvent = event + + // New: Create a one-shot DispatchSourceTimer + let t = DispatchSource.makeTimerSource(queue: timerQueue) + + // Convert your tolerance (seconds) into Dispatch leeway + let leeway = DispatchTimeInterval.nanoseconds(Int(fireTolerance * 1_000_000_000)) + + // Compute the deadline relative to now + let delta = fireDate.timeIntervalSinceNow + let deadline: DispatchTime = delta > 0 ? .now() + delta : .now() - // Create a one-shot timer to fire at the next event (or retry interval) - let newTimer = Timer(timeInterval: interval, repeats: false) { [weak self] _ in + // One-shot timer: schedule once with leeway + t.schedule(deadline: deadline, leeway: leeway) + + // Hop back into the actor when it fires + t.setEventHandler { [weak self] in guard let self else { return } - // Hop back to the actor to handle firing and rescheduling - Task { - await self.timerDidFire() - } + Task { await self.timerDidFire() } } - scheduledEvent = next.1 - // Ensure the timer fires during UI interactions as well - newTimer.tolerance = min(fireTolerance, max(0.0, interval * 0.90)) - RunLoop.main.add(newTimer, forMode: .common) - self.timer = newTimer + timer = t + t.resume() } - + private func timerDidFire() async { + os_log("[SC] 🔍] BG timerDidFire: %{public}%@ ",scheduledEvent.debugDescription) + + // Clear scheduled event if you do that today + + + // Cancel and clear the GCD timer (one-shot) + if let t = timer { + t.setEventHandler {} + t.cancel() + timer = nil + } + let fireDate = Date() - let calendar = await scheduler.calendar + let calendar = scheduler.calendar let events = await scheduler.allEvents() - await fireEventsStarting(around: fireDate, events: events, calendar: calendar, tolerance: fireTolerance) - + scheduledEvent = nil // Schedule the next timer based on the latest data await scheduleNextTimer() } - + // MARK: - Event computations - private func earliestNextStart(after date: Date, events: [Event], calendar: Calendar) -> (Date?, Event?) { var best: Date? var bestEvent: Event? @@ -161,18 +198,6 @@ public actor EventSchedulerRunner { } private func fireEventsStarting(around date: Date, events: [Event], calendar: Calendar, tolerance: TimeInterval) async { - // Determine which events start at this time within tolerance -// let starting = events.compactMap { event -> Event? in -// let weekday = SCWeekday.from(date: date, calendar: calendar) -// guard event.occurs(on: weekday) else { return nil } -// var comps = calendar.dateComponents([.year, .month, .day], from: date) -// comps.hour = event.startTime.hour -// comps.minute = event.startTime.minute -// comps.second = 0 -// guard let start = calendar.date(from: comps) else { return nil } -// return abs(start.timeIntervalSince(date)) <= tolerance ? event : nil -// } - guard let firedEvent = scheduledEvent else { return } // Capture handlers to call outside the actor's critical path diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift index 46e62b42..58566b35 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift @@ -6,19 +6,16 @@ import Foundation func didUpdateStatus(_ status: String) func didEmitEvent(_ message: String) func didEnableWebExtension(_ extensionTypeRawValue: String, state: Bool) + func didStartedBlocking(_ state: Bool, _ endDate: Date) } // The service protocol defines the operations the UI app can call on the helper. @objc public protocol HelperServiceProtocol: AppToExtensionExtension { // One-time registration so the helper can call back into the UI app. - // func registerClient() func registerClient() - func startMonitoring(reply: @escaping (Bool) -> Void) - func stopMonitoring(reply: @escaping (Bool) -> Void) func currentStatus(reply: @escaping (String) -> Void) // Example command that produces an async result pushed back to the client - func performWork(_ input: String, reply: @escaping (Bool) -> Void) //Schedule Save/load func saveSchedules(schedules: Data, reply: @escaping (Bool) -> Void) @@ -27,6 +24,9 @@ import Foundation //Start blocking network func startNetwrokBlocking(minutes: Int) func stopNetworkBlocking() + func extendBlocking(minutes: Int) + func setPreference(key: String, value: Bool) + func getBlockedStates(reply: @escaping (_ state: Bool, _ endDate: Date?) -> Void) //Save block url list } From 4e27c0e34d8e9559c9fd36bc5c6bb66a71c8eee2 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 3 Jun 2026 17:11:38 +0530 Subject: [PATCH 51/64] fixed extension install update issue --- .../HelperConnection+ReceivedMessages.swift | 2 ++ .../SelfControlBGService/CoreService/AppStateManager.swift | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift index 2272badd..51ec5c7d 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift @@ -8,6 +8,8 @@ extension HelperConnection { func didEnableWebExtension(_ extensionTypeRawValue: String, state: Bool) { // Task { @MainActor in + print("REceived didEnableWebExtension: \(extensionTypeRawValue), state: \(state)") + if let ext = WEBExtension(rawValue: extensionTypeRawValue) { onExtensionStateChange?(ext, state) // switch ext { diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift index de820455..f7887cfa 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift @@ -26,9 +26,9 @@ actor AppStateManager { private func receivedSafariApiRequest() { safariLastApiUpdateReceivedTime = Date() - guard !isChromeExtensionRunning else { return } - isChromeExtensionRunning = true - HelperService.send_didEnableWebExtension(WEBExtension.chrome.rawValue, state: true) + guard !isSafariExtensionRunning else { return } + isSafariExtensionRunning = true + HelperService.send_didEnableWebExtension(WEBExtension.safari.rawValue, state: true) } private func receivedChromeApiRequest() { From cc37400d61685877356af13efbc914e16f865472 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Wed, 3 Jun 2026 23:05:12 +0530 Subject: [PATCH 52/64] code cleanup, removed unused code --- .../SafariExtensionHandler.swift | 4 +- .../ServerResponse.swift | 3 +- .../SelfControl.xcodeproj/project.pbxproj | 4 +- .../SelfControl/Main/FilterViewModel.swift | 40 +------------------ .../HelperConnection+ReceivedMessages.swift | 8 ---- 5 files changed, 8 insertions(+), 51 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift index 9e1fe67b..991f6635 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift @@ -11,7 +11,7 @@ import os.log typealias Const = SafariExtensionConstants class SafariExtensionHandler: SFSafariExtensionHandler { - private let ping = ServerPing() + private let ping = ServerPing.shared private let defaults = UserDefaults(suiteName: Const.appGroup) var blockedPatterns: Set = [ "facebook.com/friends", @@ -80,6 +80,8 @@ class SafariExtensionHandler: SFSafariExtensionHandler { if ping.blockedURL.contains(where: { urlString.contains($0) }) { os_log("[SC] 🚫 Blocking and redirecting: %{public}@", urlString) page.dispatchMessageToScript(withName: "REDIRECT_BLOCKED_URL", userInfo: ["redirect": redirectURL]) + } else { + os_log("[SC] 🔍 Blocked URL: %{public}@", ping.blockedURL) } } diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift index 64bd29e0..9abdcd65 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/ServerResponse.swift @@ -12,8 +12,9 @@ import os.log final class ServerPing { var timer: Timer? var blockedURL: Set = [] + static let shared = ServerPing() - init() { } + private init() {} func start() { ping() diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index e987c126..aeb6e837 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -724,7 +724,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.7; + MARKETING_VERSION = 1.8; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; @@ -759,7 +759,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.7; + MARKETING_VERSION = 1.8; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index 48da04a4..bef83937 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -28,7 +28,6 @@ enum SelfControlViewState: Equatable { } } - case installNetworkExtension case installSafariExtension case installChromeExtension @@ -92,24 +91,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques override init() { super.init() -// ProxyPreferences.reset() //TODO: remove onInit() -// SafariExtensionManager.shared.onExtensionStateChange = { -// print("SafariExtensionManager.shared.onChange++") -// self.isSafariExtensionInstalled = true -// AppPreferences.setSafariExtensionInstalled() -// Task { @MainActor in -// self.updateSafariExtensionViewStatus() -// } -// } -// self.chromeService.onExtensionStateChange = { -// print("Chrome.shared.onChange++") -// self.isChromeExtensionInstalled = true -// AppPreferences.setChromeExtensionInstalled() -// Task { @MainActor in -// self.updateChromeExtensionViewStatus() -// } -// } HelperConnection.shared.onExtensionStateChange = { (ext, state) in if state == true { switch ext { @@ -130,14 +112,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } } } -// SafariExtensionManager.shared.resetExtensionState() self.extensionIdentifier = extensionBundle.bundleIdentifier -// self.chromeService.blockeddomainFetcher = { -// return AppPreferences.getBlockedDomains() -// } - - //self.chromeService.startListening() - // Print status whenever it changes $status .sink { [weak self] newValue in @@ -244,9 +219,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques // MARK: - UI and Filter Management func setBlockedUrls(urls: [String]) { - IPCConnectionProxy().setBlockedURLs(urls) - if status == .stopped { //If legacy blocking if isActiveBlocking { //if is active blocking updateLegacyBlockedList(newBlockedDomains: urls) @@ -326,16 +299,8 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques } func registerWithProvider() { + self.refreshExtensionState() print("+registerWithProvider") - // Assuming an IPCConnection singleton similar to the AppKit sample - IPCConnectionProxy().register(completionHandler: { success in - DispatchQueue.main.async { - self.status = success ? .running : .stopped - self.setBlockedUrls(urls: AppPreferences.getBlockedDomains()) - self.refreshExtensionState() - } -// setBlockedURLs([]) - }) } func activateExtension() { @@ -452,9 +417,6 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques func stopBlocking() { print("deactivateNetworkBlocking+++++") -// if AppPreferences.playSoundOnCompletion { -// NSSound.playDefaultSound() -// } if AppPreferences.showNotificationOnCompletion { LocalNotificationManager.scheduleNotification() } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift index 51ec5c7d..53eb9dd0 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift @@ -9,17 +9,9 @@ extension HelperConnection { func didEnableWebExtension(_ extensionTypeRawValue: String, state: Bool) { // Task { @MainActor in print("REceived didEnableWebExtension: \(extensionTypeRawValue), state: \(state)") - if let ext = WEBExtension(rawValue: extensionTypeRawValue) { onExtensionStateChange?(ext, state) -// switch ext { -// case .chrome: -// NetworkExtensionState.shared.isChromeExtensionEnabled = state -// case .safari: -// NetworkExtensionState.shared.isSafariExtensionEnabled = state -// } } -// } } func didStartedBlocking(_ state: Bool, _ endDate: Date) -> Void { From e6fadb758d89d40f032ecefecc61353662974281 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Thu, 4 Jun 2026 09:50:45 +0530 Subject: [PATCH 53/64] removed un necessary code for protocol confirmation --- .../SelfControl.xcodeproj/project.pbxproj | 2 -- .../SelfControl/Main/FilterViewModel.swift | 2 +- .../Main/SafariExtensionWebView.swift | 1 - ...erConnection+SafariExtensionMessages.swift | 34 ------------------ .../HelperConnection+SendMessages.swift | 4 +++ .../BlockListManager.swift | 7 +--- .../Shared/HelperServiceProtocol.swift | 2 +- .../IPCConnectionProxy.swift | 36 ------------------- 8 files changed, 7 insertions(+), 81 deletions(-) delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnectionProxy.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index aeb6e837..f18f29e9 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -125,7 +125,6 @@ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Info.plist, - IPCConnectionProxy.swift, ); target = 77BA53052D9D5E2A00863476 /* SelfControlNetworkExtension */; }; @@ -156,7 +155,6 @@ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( BlockOrAllowList.swift, - IPCConnectionProxy.swift, "String+URL.swift", ); target = 77BA52D52D9D5D7700863476 /* SelfControl */; diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index bef83937..d9eccdef 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -219,7 +219,7 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques // MARK: - UI and Filter Management func setBlockedUrls(urls: [String]) { - IPCConnectionProxy().setBlockedURLs(urls) + HelperConnection.shared.send_setBlockedURLs(urls) if status == .stopped { //If legacy blocking if isActiveBlocking { //if is active blocking updateLegacyBlockedList(newBlockedDomains: urls) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift index de49e78f..416e546f 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SafariExtensionWebView.swift @@ -88,7 +88,6 @@ final class WebPageViewModel: NSObject, WKNavigationDelegate, WKScriptMessageHan private func updateSafariExtensionState(state: Bool) { AppPreferences.setSafariExtensionState(state) - IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: state) if state == true { updateBlocker() } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift deleted file mode 100644 index e3ff1dd2..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SafariExtensionMessages.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// File.swift -// SelfControl -// -// Created by Satendra Singh on 17/05/26. -// - -extension HelperConnection: AppToExtensionExtension { - func register(_ completionHandler: @escaping (Bool) -> Void) { - bgProxyServiceConnection()?.register(completionHandler) - } - - func setBlockedURLs(_ urls: [String]) { - bgProxyServiceConnection()?.setBlockedURLs(urls) - } - - func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { - bgProxyServiceConnection()?.setActiveBrowserExtension(extensionTypeRawValue, state: state) - } - - func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool){ - bgProxyServiceConnection()?.sendMessageToSetActiveBrowserExtension(extensionTypeRawValue, state: state) - } - - func setEnableService(_ enable: Bool) { - bgProxyServiceConnection()?.setEnableService(enable) - } - - func register(completionHandler: @escaping (Bool) -> Void) { - bgProxyServiceConnection()?.register(completionHandler) - } -} -//TODO: create extension on protocol with shared variable to send these message with static - diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift index 6b70b5f3..800a96d4 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift @@ -31,4 +31,8 @@ extension HelperConnection { func send_setPreference(key: String, value: Bool) { bgProxyServiceConnection()?.setPreference(key: key, value: value) } + + func send_setBlockedURLs(_ urls: [String]) { + bgProxyServiceConnection()?.setBlockedURLs(urls) + } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift index f040506c..a5082142 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SafariExtensionSupport/BlockListManager.swift @@ -77,7 +77,6 @@ enum BlockListManager { print("Safari Extension State :\(state?.isEnabled ?? false)") Task { @MainActor in if NetworkExtensionState.shared.isEnabled == true { - _ = IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: state?.isEnabled ?? false) NetworkExtensionState.shared.isSafariExtensionEnabled = state?.isEnabled ?? false NetworkExtensionState.shared.printAll() } @@ -93,7 +92,6 @@ enum BlockListManager { if error == nil { Task { @MainActor in if NetworkExtensionState.shared.isEnabled == true && NetworkExtensionState.shared.isSafariExtensionEnabled == false { //possibly extension is ready now - _ = IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: true) NetworkExtensionState.shared.isSafariExtensionEnabled = true NetworkExtensionState.shared.printAll() } @@ -107,9 +105,7 @@ enum BlockListManager { static func updateExtensionState() { Task { @MainActor in - if NetworkExtensionState.shared.isEnabled == true { - IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: SafariExtensionManager.shared.isExtensionReady) - } + if NetworkExtensionState.shared.isEnabled == true { } } } @@ -122,7 +118,6 @@ enum BlockListManager { static func deactivateSafariBlocking() { updateExtensionState() SafariExtensionManager.shared.disableExtension() - _ = IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.safari.rawValue, state: SafariExtensionManager.shared.isExtensionReady) os_log("Safari Message toExtensionWithIdentifier deactivateSafariBlocking called:") } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift index 58566b35..5136790d 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift @@ -26,7 +26,7 @@ import Foundation func stopNetworkBlocking() func extendBlocking(minutes: Int) func setPreference(key: String, value: Bool) - + func setBlockedURLs(_ urls: [String]) func getBlockedStates(reply: @escaping (_ state: Bool, _ endDate: Date?) -> Void) //Save block url list } diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnectionProxy.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnectionProxy.swift deleted file mode 100644 index 1bbece35..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnectionProxy.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// IPCConnectionProxy.swift -// SelfControl -// -// Created by Satendra Singh on 17/05/26. -// - -import Foundation - -class IPCConnectionProxy: NSObject, AppToExtensionExtension { - - func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { - HelperConnection.shared.sendMessageToSetActiveBrowserExtension(extensionTypeRawValue, state: state) - } - - - func register(completionHandler: @escaping (Bool) -> Void) { - HelperConnection.shared.register(completionHandler: completionHandler) - } - - func register(_ completionHandler: @escaping (Bool) -> Void) { - HelperConnection.shared.register(completionHandler) - } - - func setBlockedURLs(_ urls: [String]) { - HelperConnection.shared.setBlockedURLs(urls) - } - - func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { - HelperConnection.shared.setActiveBrowserExtension(extensionTypeRawValue, state: state) - } - - func setEnableService(_ enable: Bool) { - HelperConnection.shared.setEnableService(enable) - } -} From 14a67888916114d990f13a4070e39c0ae6c2e9d2 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 14 Jun 2026 09:18:15 +0530 Subject: [PATCH 54/64] Fixed issues, updated logging, other issues debugging --- .../SelfControl.xcodeproj/project.pbxproj | 6 +- .../SelfControl/SelfControl/Info.plist | 4 +- .../SelfControl/Main/AppDockManager.swift | 45 ++-- .../SelfControl/Main/AppMover.swift | 227 ++++++++++++------ .../SelfControl/Main/FilterViewModel.swift | 9 +- .../Main/Preferences/AppPreferences.swift | 5 +- .../HelperConnection+ReceivedMessages.swift | 24 ++ .../HelperConnection+SendMessages.swift | 38 +++ .../HelperConnection.swift | 167 +++++++++++++ .../LaunchedInstallManager/CheckProcess.swift | 55 +++++ .../LaunchAgentManager.swift | 108 +++++++++ .../SelfControlServiceSupport.swift | 39 +++ .../Onboarding/ClickableLinkButton.swift | 5 +- .../OnBoardingInstallChromeExt.swift | 10 +- .../OnBoardingInstallSafariExt.swift | 8 +- .../Views/BlocklistEditorView.swift | 1 + .../ChromeExtensionRequestListner.swift | 14 +- .../EventScheduler/EventSchedulerRunner.swift | 12 +- .../IPCConnection.swift | 47 ++-- .../Shared/FileLogger.swift | 193 +++++++++++++++ .../Shared/FileLoggerConsts.swift | 8 + .../SelfControlBGService/main.swift | 5 +- .../Process/Utilities.swift | 2 +- .../RequestSourceAppValidator.swift | 2 +- 24 files changed, 895 insertions(+), 139 deletions(-) create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection+ReceivedMessages.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection+SendMessages.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/LaunchedInstallManager/CheckProcess.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/LaunchedInstallManager/LaunchAgentManager.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/SelfControlServiceSupport.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLogger.swift create mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLoggerConsts.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj index f18f29e9..bd8a0824 100644 --- a/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj +++ b/Self-Control-Extension/SelfControl/SelfControl.xcodeproj/project.pbxproj @@ -140,7 +140,7 @@ 9A33B1F52FAFA3FE002AAA0F /* Exceptions for "SelfControl" folder in "SelfControl" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - Main/SelfControlServiceSupport/SelfControlServiceSupport.swift, + Main/SelfControlBGServiceSupport/SelfControlServiceSupport.swift, ); target = 77BA52D52D9D5D7700863476 /* SelfControl */; }; @@ -722,7 +722,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.8; + MARKETING_VERSION = 1.9; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; @@ -757,7 +757,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = 1.8; + MARKETING_VERSION = 1.9; PRODUCT_BUNDLE_IDENTIFIER = com.application.SelfControl.corebits; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Self Control app dev"; diff --git a/Self-Control-Extension/SelfControl/SelfControl/Info.plist b/Self-Control-Extension/SelfControl/SelfControl/Info.plist index 15a8b3c3..f0daf04d 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Info.plist +++ b/Self-Control-Extension/SelfControl/SelfControl/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.18 + 1.20 CFBundleVersion - 18 + 20 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/AppDockManager.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/AppDockManager.swift index 6d37f182..325c602e 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/AppDockManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/AppDockManager.swift @@ -25,22 +25,22 @@ final class AppDockManager { NSApp?.dockTile.badgeLabel = nil } - @objc private func updateDockCountDown() { + @objc private func updateDockCountDown() { let blockEndingDate: Date = timerFireDate ?? .now let blockingSecond: Int = Int(blockEndingDate.timeIntervalSinceNow) var numSeconds: Int = Int(blockEndingDate.timeIntervalSinceNow) - var numMinutes: Int = blockingSecond - var numHours: Int = 0 - + var numMinutes: Int = blockingSecond + var numHours: Int = 0 + numHours = numSeconds / 3600 numSeconds %= 3600 numMinutes = numSeconds / 60 numSeconds %= 60 - if blockingSecond > 0 { + if blockingSecond > 0 { // Round up minutes when showing mm:ss style without seconds var minutes = numMinutes if numSeconds > 0 && minutes != 59 { minutes += 1 } - + let badgeString = String(format: "%02d:%02d", numHours, minutes) print("Badge: \(badgeString)") Task { @MainActor in @@ -51,24 +51,25 @@ final class AppDockManager { // Clear the badge when not using badging Task { @MainActor in setDockBadge(nil) + } } - } - - @MainActor - func ensureDockIconVisible() { - let app = NSApplication.shared - if app.activationPolicy() != .regular { - _ = app.setActivationPolicy(.regular) + + @MainActor + func ensureDockIconVisible() { + let app = NSApplication.shared + if app.activationPolicy() != .regular { + _ = app.setActivationPolicy(.regular) + } + } + + + @MainActor + func setDockBadge(_ text: String?) { + // text: use nil to clear, non-empty string to show + let app = NSApplication.shared + app.dockTile.badgeLabel = (text?.isEmpty == false) ? text : nil + app.dockTile.display() } - } - - - @MainActor - func setDockBadge(_ text: String?) { - // text: use nil to clear, non-empty string to show - let app = NSApplication.shared - app.dockTile.badgeLabel = (text?.isEmpty == false) ? text : nil - app.dockTile.display() } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/AppMover.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/AppMover.swift index f8cbae6a..95c7e499 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/AppMover.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/AppMover.swift @@ -7,120 +7,209 @@ import Cocoa import os.log +import Darwin final class AppMover { - + static func moveIfNeeded() { - // 1. Check current app path - let bundleURL = Bundle.main.bundleURL let fm = FileManager.default - - // Prefer /Applications, fallback to ~/Applications - let appName = bundleURL.lastPathComponent - let systemApplicationsDir = URL(fileURLWithPath: "/Applications", isDirectory: true) - let userApplicationsDir = fm.homeDirectoryForCurrentUser.appendingPathComponent("Applications", isDirectory: true) - - let destinations = [ systemApplicationsDir, userApplicationsDir ] - - // 3. If already inside one of those destinations (and not in DMG, etc), skip - if destinations.contains(where: { bundleURL.deletingLastPathComponent() == $0 }) { - return // already correct place + let originalBundleURL = Bundle.main.bundleURL + let bundleURL = (try? originalBundleURL.resolvingSymlinksInPath()) ?? originalBundleURL + + os_log("[SC] 🔍 AppMover.moveIfNeeded bundle: %@", log: OSLog.default, type: .info, bundleURL.path) + + // 1. If already in Applications (system or user), do nothing. + if isInApplicationsFolder(bundleURL) { + os_log("[SC] ✅ App is already in Applications, skipping move.", log: OSLog.default, type: .info) + return + } + + // 2. Determine preferred Applications destination. + guard let destinationAppDir = preferredApplicationsDirectory() else { + os_log("[SC] ⚠️ Could not determine Applications directory, skipping move.", log: OSLog.default, type: .error) + return } - - // 4. Ask user via alert + + // Ensure ~/Applications exists when chosen as destination. + if destinationAppDir.path.hasPrefix(fm.homeDirectoryForCurrentUser.appendingPathComponent("Applications").path) { + do { + try fm.createDirectory(at: destinationAppDir, withIntermediateDirectories: true, attributes: nil) + } catch { + os_log("[SC] ❌ Could not create user Applications directory: %@", log: OSLog.default, type: .error, error.localizedDescription) + return + } + } + + // 3. Prompt the user to move. DispatchQueue.main.async { let alert = NSAlert() alert.messageText = "Move to Applications Folder?" alert.informativeText = "Would you like to move this app to your Applications folder? It must quit and relaunch." alert.addButton(withTitle: "Move to Applications Folder") alert.addButton(withTitle: "Do Not Move") + let response = alert.runModal() if response == .alertFirstButtonReturn { - performMove(to: destinations.first!, bundleURL: bundleURL) + performMove(to: destinationAppDir, bundleURL: bundleURL) + } else { + os_log("[SC] 🚫 User chose not to move the app.", log: OSLog.default, type: .info) } } } - + private static func performMove(to appDir: URL, bundleURL: URL) { let fm = FileManager.default - - // Ensure destination directory exists - do { - try fm.createDirectory(at: appDir, withIntermediateDirectories: true, attributes: nil) - } catch { - os_log("[SC] 🔍] Could not create Applications directory: %@", error.localizedDescription) - print("Could not create Applications directory: \(error)") - } - + os_log("[SC] 🔧 AppMover.performMove from: %@ → %@", log: OSLog.default, type: .info, bundleURL.path, appDir.path) + // Destination path for the app - let destURL = appDir.appendingPathComponent(bundleURL.lastPathComponent) - - // If exists, maybe prompt to overwrite + let destURL = appDir.appendingPathComponent(bundleURL.lastPathComponent, isDirectory: true) + + // If a copy already exists at destination, try to trash it to avoid conflicts. if fm.fileExists(atPath: destURL.path) { - // For simplicity: remove existing do { - try fm.removeItem(at: destURL) + var resultingURL: NSURL? + try fm.trashItem(at: destURL, resultingItemURL: &resultingURL) + os_log("[SC] 🗑️ Trashed existing app at destination.", log: OSLog.default, type: .info) } catch { - os_log("[SC] 🔍] Could not remove existing app at destination: %@", error.localizedDescription) - print("Could not remove existing app at destination: \(error)") - // Handle error (maybe ask user, or abort) + os_log("[SC] ⚠️ Could not trash existing app. Attempting removal: %@", log: OSLog.default, type: .error, error.localizedDescription) + do { + try fm.removeItem(at: destURL) + } catch { + os_log("[SC] ❌ Could not remove existing app at destination: %@", log: OSLog.default, type: .error, error.localizedDescription) + return + } } } - + + // Copy (not move) the running app bundle. Moving a running app may fail (e.g., from a read-only DMG). do { - // Copy the bundle - try fm.moveItem(at: bundleURL, to: destURL) - - // Clear quarantine attribute (so first launch doesn’t ask gatekeeper) - removeQuarantineRecursively(at: destURL) - // Relaunch the copied app + try fm.copyItem(at: bundleURL, to: destURL) + os_log("[SC] ✅ Copied app to Applications.", log: OSLog.default, type: .info) + } catch { + os_log("[SC] ❌ Error copying app to Applications: %@", log: OSLog.default, type: .error, error.localizedDescription) + return + } + + // Clear quarantine attribute on the copied app so Gatekeeper doesn't warn again. + removeQuarantineRecursively(at: destURL) + + // Attempt to remove (trash) the original copy now that the app has been copied. + attemptToRemoveOriginal(at: bundleURL) + + // Relaunch the copied app. + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: { let config = NSWorkspace.OpenConfiguration() - NSWorkspace.shared.openApplication(at: destURL, configuration: config) { (app, err) in + NSWorkspace.shared.openApplication(at: destURL, configuration: config) { _, err in if let err = err { - os_log("[SC] 🔍] Failed to relaunch at dest: %@", err.localizedDescription) - print("Failed to relaunch at dest: \(err)") + os_log("[SC] ❌ Failed to relaunch moved app: %@", log: OSLog.default, type: .error, err.localizedDescription) + } else { + os_log("[SC] 🚀 Relaunched moved app successfully.", log: OSLog.default, type: .info) + } + + // Terminate current instance regardless; the copy succeeded. + if NSApp != nil { + NSApp.terminate(nil) + } else { + exit(0) } - // After launching new instance, quit old one - exit(0) } - - } catch { - os_log("[SC] 🔍] Error copying app to Applications: %@", error.localizedDescription) - print("Error copying app to Applications: \(error)") - // Optionally ask for authorization and retry - } + }) } private static func removeQuarantineAttribute(from url: URL) { let path = url.path - - // Check if attribute exists let attrName = "com.apple.quarantine" - let result = getxattr(path, attrName, nil, 0, 0, 0) - if result >= 0 { - // The attribute exists → remove it - let removeResult = removexattr(path, attrName, 0) - if removeResult == 0 { - os_log("[SC] 🔍] Successfully removed quarantine attribute") - print("Successfully removed quarantine attribute") + // Check if attribute exists + let hasAttr = getxattr(path, attrName, nil, 0, 0, 0) >= 0 + if hasAttr { + // Attempt to remove the attribute; ignore failure. + let result = removexattr(path, attrName, 0) + if result == 0 { + os_log("[SC] 🧹 Removed quarantine attribute: %@", log: OSLog.default, type: .debug, path) } else { - os_log("[SC] 🔍] Failed to remove quarantine attribute") - print("Failed to remove quarantine attribute") + os_log("[SC] ⚠️ Failed to remove quarantine attribute: %@", log: OSLog.default, type: .debug, path) } - } else { - os_log("[SC] 🔍] No quarantine attribute found") - print("No quarantine attribute found") } } private static func removeQuarantineRecursively(at url: URL) { let fm = FileManager.default - if let enumerator = fm.enumerator(at: url, includingPropertiesForKeys: nil) { + + // Walk the bundle contents + if let enumerator = fm.enumerator(at: url, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles]) { for case let fileURL as URL in enumerator { removeQuarantineAttribute(from: fileURL) } } - removeQuarantineAttribute(from: url) // root bundle + // Also clear on the root bundle directory + removeQuarantineAttribute(from: url) + } + + private static func attemptToRemoveOriginal(at sourceBundleURL: URL) { + let fm = FileManager.default + + // Never try to remove if already in Applications (safety) + if isInApplicationsFolder(sourceBundleURL) { + return + } + + // Prefer moving to Trash so user can recover; if that fails, try direct removal. + do { + var trashedURL: NSURL? + try fm.trashItem(at: sourceBundleURL, resultingItemURL: &trashedURL) + os_log("[SC] 🗑️ Trashed original app at: %@", log: OSLog.default, type: .info, sourceBundleURL.path) + } catch { + os_log("[SC] ⚠️ Could not trash original app (%@). Attempting direct removal.", log: OSLog.default, type: .error, error.localizedDescription) + do { + try fm.removeItem(at: sourceBundleURL) + os_log("[SC] ✅ Removed original app at: %@", log: OSLog.default, type: .info, sourceBundleURL.path) + } catch { + // This commonly fails if the app is on a read-only volume (e.g., a DMG). + os_log("[SC] ❌ Failed to remove original app: %@", log: OSLog.default, type: .error, error.localizedDescription) + } + } + } +} + +// MARK: - Helpers + +private extension AppMover { + static func preferredApplicationsDirectory() -> URL? { + let fm = FileManager.default + let systemApplications = URL(fileURLWithPath: "/Applications", isDirectory: true) + let userApplications = fm.homeDirectoryForCurrentUser.appendingPathComponent("Applications", isDirectory: true) + + // Prefer /Applications if writable, otherwise fallback to ~/Applications. + if fm.isWritableFile(atPath: systemApplications.path) { + return systemApplications + } else { + return userApplications + } + } + + static func isInApplicationsFolder(_ bundleURL: URL) -> Bool { + let fm = FileManager.default + let parent = bundleURL.deletingLastPathComponent() + + let systemApplications = URL(fileURLWithPath: "/Applications", isDirectory: true) + let userApplications = fm.homeDirectoryForCurrentUser.appendingPathComponent("Applications", isDirectory: true) + + // Compare after resolving symlinks for reliability + let resolvedParent = (try? parent.resolvingSymlinksInPath()) ?? parent + let resolvedSystem = (try? systemApplications.resolvingSymlinksInPath()) ?? systemApplications + let resolvedUser = (try? userApplications.resolvingSymlinksInPath()) ?? userApplications + + if resolvedParent == resolvedSystem || resolvedParent == resolvedUser { + return true + } + + // Additional guard: some volumes may present Applications differently; fall back to path prefix check. + let path = resolvedParent.path + if path == "/Applications" || path.hasSuffix("/Applications") { + return true + } + + return false } } diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift index d9eccdef..20c42633 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/FilterViewModel.swift @@ -45,9 +45,10 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques self.viewState = .filter } } - - private var isSafariExtensionInstalled: Bool = AppPreferences.isSafariExtensionInstalled - private var isChromeExtensionInstalled: Bool = AppPreferences.isChromeExtensionInstalled + + private var isSafariExtensionInstalled: Bool { AppPreferences.isSafariExtensionInstalled } + private var isChromeExtensionInstalled: Bool { AppPreferences.isChromeExtensionInstalled } + @State private var domains = AppPreferences.getBlockedDomains() private(set) var blockerStorage: BlockedURLStore? private var cancellables = Set() @@ -97,14 +98,12 @@ final class FilterViewModel: NSObject, ObservableObject, OSSystemExtensionReques switch ext { case .safari: print("SafariExtensionManager.shared.onChange++") - self.isSafariExtensionInstalled = true AppPreferences.setSafariExtensionInstalled() Task { @MainActor in self.updateSafariExtensionViewStatus() } case .chrome: print("Chrome.shared.onChange++") - self.isChromeExtensionInstalled = true AppPreferences.setChromeExtensionInstalled() Task { @MainActor in self.updateChromeExtensionViewStatus() diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/AppPreferences.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/AppPreferences.swift index f1c2b31f..cf58cefd 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/AppPreferences.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/Preferences/AppPreferences.swift @@ -70,7 +70,10 @@ struct AppPreferences { } static var showVerifyNetworkAlertBeforeBlock: Bool { - return UserDefaults.standard.bool(forKey: verifyNetworkBeforeBlock) + if let value = defaults.value(forKey: verifyNetworkBeforeBlock) as? Bool { + return value + } + return true } static func setShowVerifyNetworkAlertBeforeBlock(_ isEnabled: Bool) { diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection+ReceivedMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection+ReceivedMessages.swift new file mode 100644 index 00000000..53eb9dd0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection+ReceivedMessages.swift @@ -0,0 +1,24 @@ +// +// HelperConnection+ReceivedMessages.swift +// SelfControl +// +// Created by Satendra Singh on 19/05/26. +// + +extension HelperConnection { + func didEnableWebExtension(_ extensionTypeRawValue: String, state: Bool) { +// Task { @MainActor in + print("REceived didEnableWebExtension: \(extensionTypeRawValue), state: \(state)") + if let ext = WEBExtension(rawValue: extensionTypeRawValue) { + onExtensionStateChange?(ext, state) + } + } + + func didStartedBlocking(_ state: Bool, _ endDate: Date) -> Void { + if state == true { + let minutes: Double = Double(endDate.timeIntervalSinceNow / 60) + self.blockedStateHandler?(minutes) + } + print("REceived Blocked State: \(String(describing: state)), End Date: \(String(describing: endDate))") + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection+SendMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection+SendMessages.swift new file mode 100644 index 00000000..800a96d4 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection+SendMessages.swift @@ -0,0 +1,38 @@ +// +// HelperConnection+SendMessages.swift +// SelfControl +// +// Created by Satendra Singh on 31/05/26. +// + +extension HelperConnection { + func send_startNetwrokBlocking(minutes: Int) { + bgProxyServiceConnection()?.startNetwrokBlocking(minutes: minutes) + } + + func send_stopNetworkBlocking() { + bgProxyServiceConnection()?.stopNetworkBlocking() + } + + func send_getBlockedStates() { + bgProxyServiceConnection()?.getBlockedStates { state, endDate in + if state == true, let endDate = endDate { + let minutes: Double = Double(endDate.timeIntervalSinceNow / 60) + self.blockedStateHandler?(minutes) + } + print("REceived Blocked State: \(String(describing: state)), End Date: \(String(describing: endDate))") + } + } + + func send_extendBlocking(minutes: Int) { + bgProxyServiceConnection()?.extendBlocking(minutes: minutes) + } + + func send_setPreference(key: String, value: Bool) { + bgProxyServiceConnection()?.setPreference(key: key, value: value) + } + + func send_setBlockedURLs(_ urls: [String]) { + bgProxyServiceConnection()?.setBlockedURLs(urls) + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection.swift new file mode 100644 index 00000000..c7c348d0 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/HelperConnection.swift @@ -0,0 +1,167 @@ +// +// HelperConnection.swift +// SelfControl +// +// Created by Satendra Singh on 08/05/26. +// + +// MainApp/HelperConnection.swift +import Cocoa +import ServiceManagement + +enum HelperServiceConstants: String { + case bundleID = "com.application.SelfControl.corebits.bgservice" // HelperApp’s bundle identifier + case processName = "SelfControlBGService" + case machServiceName = "com.application.SelfControl.corebits.bgservice.xpc" +} + +final class HelperConnection: NSObject, HelperClientProtocol, NSSecureCoding { + static let shared = HelperConnection() + static var supportsSecureCoding: Bool = true + var onExtensionStateChange: ((WEBExtension, Bool) -> Void)? + var blockedStateHandler: ((Double) -> Void)? + private var connection: NSXPCConnection? + private var exportedConnection: NSXPCListenerEndpoint? + private let checkProcess = CheckProcess(bundleID: HelperServiceConstants.bundleID.rawValue, processName: HelperServiceConstants.processName.rawValue) + + private let stateQueue = DispatchQueue(label: "com.application.SelfControl.corebits.bgservice.connection", attributes: .concurrent) + + override init() { } + + required init?(coder: NSCoder) { + return nil + } + + func encode(with coder: NSCoder) { + + } + // MARK: - Public API + + func installLoginItemIfNeeded() async throws { + if !checkProcess.isRunning() { + do { + try LaunchAgentManager.install() + } catch { + throw error + } + } else { + print("BG App is already running") + } + } + + func uninstallLoginItem() throws { + let loginItem = SMAppService.loginItem(identifier: HelperServiceConstants.bundleID.rawValue) + try loginItem.unregister() + } + + func connect() { + stateQueue.sync(flags: .barrier) { + guard self.connection == nil else { return } + + let conn = NSXPCConnection(machServiceName: HelperServiceConstants.machServiceName.rawValue, options: []) + // Remote interface: the helper’s service protocol + conn.remoteObjectInterface = NSXPCInterface(with: HelperServiceProtocol.self) + // Exported interface: our client protocol to receive callbacks + let clientInterface = NSXPCInterface(with: HelperClientProtocol.self) + conn.exportedInterface = clientInterface + conn.exportedObject = self + // exportedConnection = listener.endpoint + + conn.interruptionHandler = { [weak self] in + print("Conn interruptionHandler") + // Temporary loss of connection; try to reconnect + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self?.reconnect() + } + } + + conn.invalidationHandler = { [weak self] in + print("Conn invalidationHandler") + + // Connection invalidated; tear down and reconnect + self?.stateQueue.sync(flags: .barrier) { + self?.connection = nil + } + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self?.connect() + } + } + + conn.resume() + self.connection = conn + // Register for callbacks + bgProxyServiceConnection()?.registerClient() + } + } + + func disconnect() { + stateQueue.sync(flags: .barrier) { + self.connection?.invalidate() + self.connection = nil + } + } + + func saveSchedules(schedules: Data, reply: @escaping (Bool) -> Void) { + stateQueue.async(flags: .barrier) { + self.bgProxyServiceConnection()?.saveSchedules(schedules: schedules, reply: reply) + } + } + + func loadSchedules(reply: @escaping (_ schedules: Data) -> Void) { + //TODO: + } + + func currentStatus() async throws -> String { + try await withCheckedThrowingContinuation { cont in + self.withProxy(error: { cont.resume(throwing: $0) }) { proxy in + proxy.currentStatus { status in cont.resume(returning: status) } + } + } + } + + // MARK: - HelperClientProtocol (callbacks from helper) + + func didUpdateStatus(_ status: String) { + print("didUpdateStatus: \(status)") + // Update UI or post notifications as needed + NotificationCenter.default.post(name: .helperStatusChanged, object: status) + } + + func didEmitEvent(_ message: String) { + print("didEmitEvent: \(message)") + NotificationCenter.default.post(name: .helperEvent, object: message) + } + + // MARK: - Internal + + private func reconnect() { + disconnect() + connect() + } + + private func withProxy(error: ((Error) -> Void)? = nil, _ body: (HelperServiceProtocol) -> Void) { + var proxy: AnyObject? + stateQueue.sync { + proxy = self.connection?.remoteObjectProxyWithErrorHandler { err in + error?(err) + } as AnyObject? + } + if let proxy = proxy as? HelperServiceProtocol { + body(proxy) + } else { + error?(NSError(domain: "HelperConnection", code: 1, userInfo: [NSLocalizedDescriptionKey: "No XPC connection"])) + } + } + + func bgProxyServiceConnection() -> HelperServiceProtocol? { + var proxy: AnyObject? + proxy = self.connection?.remoteObjectProxyWithErrorHandler { err in + print("Conn Error:\(err)") } as AnyObject? + return proxy as? HelperServiceProtocol + } +} + +extension Notification.Name { + static let helperStatusChanged = Notification.Name("HelperStatusChanged") + static let helperEvent = Notification.Name("HelperEvent") +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/LaunchedInstallManager/CheckProcess.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/LaunchedInstallManager/CheckProcess.swift new file mode 100644 index 00000000..8c7a7298 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/LaunchedInstallManager/CheckProcess.swift @@ -0,0 +1,55 @@ +// +// CheckProcess.swift +// SelfControl +// +// Created by Satendra Singh on 21/05/26. +// + + +import AppKit + +struct CheckProcess { + + var bundleID: String + var processName: String + + func isRunning() -> Bool { + // Use the bundle identifier if possible (most reliable). + if isAppRunning(bundleIdentifier: bundleID) { + fputs("Installation aborted: \(bundleID) is currently running.\n", stderr) + return true + } + + // Optional: also guard by process name as a fallback. + if isProcessRunningByName(processName) { + fputs("Installation aborted: process '\(processName)' is currently running.\n", stderr) + return true + } + + // OK to proceed + return false + } + + private func isAppRunning(bundleIdentifier: String) -> Bool { + !NSRunningApplication.runningApplications(withBundleIdentifier: bundleIdentifier).isEmpty + } + + private func isProcessRunningByName(_ name: String) -> Bool { + // Uses /usr/bin/pgrep -x for exact match + let task = Process() + task.launchPath = "/usr/bin/pgrep" + task.arguments = ["-x", name] + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = Pipe() + + do { + try task.run() + task.waitUntilExit() + return task.terminationStatus == 0 + } catch { + return false + } + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/LaunchedInstallManager/LaunchAgentManager.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/LaunchedInstallManager/LaunchAgentManager.swift new file mode 100644 index 00000000..ee91b74c --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/LaunchedInstallManager/LaunchAgentManager.swift @@ -0,0 +1,108 @@ +// +// LaunchAgentManager.swift +// SelfControl +// +// Created by Satendra Singh on 13/05/26. +// + + +import Foundation + +final class LaunchAgentManager { + + static let label = "com.application.SelfControl.corebits.bgservice" + static let processName = "SelfControlBGService" + + static let plistName = "com.application.SelfControl.corebits.bgservice" + + static func install() throws { + + let fm = FileManager.default + + let sourcePlist = Bundle.main.url( + forResource: plistName, + withExtension: "plist" + )! + + let launchAgentsDir = fm.homeDirectoryForCurrentUser + .appendingPathComponent("Library/LaunchAgents") + + let destinationPlist = launchAgentsDir + .appendingPathComponent("\(plistName).plist") + + // Create LaunchAgents directory if needed + try fm.createDirectory( + at: launchAgentsDir, + withIntermediateDirectories: true + ) + + // Replace old plist + if fm.fileExists(atPath: destinationPlist.path) { + try fm.removeItem(at: destinationPlist) + } + + try fm.copyItem( + at: sourcePlist, + to: destinationPlist + ) + + // unload existing + try runLaunchctl([ + "unload", + destinationPlist.path + ]) + + // load new + try runLaunchctl([ + "load", + destinationPlist.path + ]) + + print("LaunchAgent installed") + } + + static func uninstall() throws { + + let fm = FileManager.default + + let plist = fm.homeDirectoryForCurrentUser + .appendingPathComponent( + "Library/LaunchAgents/\(plistName).plist" + ) + + try runLaunchctl([ + "unload", + plist.path + ]) + + try fm.removeItem(at: plist) + + print("LaunchAgent removed") + } + + @discardableResult + static func runLaunchctl(_ args: [String]) throws -> String { + + let process = Process() + + process.executableURL = URL( + fileURLWithPath: "/bin/launchctl" + ) + + process.arguments = args + + let pipe = Pipe() + + process.standardOutput = pipe + process.standardError = pipe + + try process.run() + + process.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + + return String(data: data, encoding: .utf8) ?? "" + } +} + diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/SelfControlServiceSupport.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/SelfControlServiceSupport.swift new file mode 100644 index 00000000..7155d55e --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlBGServiceSupport/SelfControlServiceSupport.swift @@ -0,0 +1,39 @@ +// +// SelfControlServiceSupport.swift +// SelfControl +// +// Created by Satendra Singh on 03/05/26. +// + +import Cocoa + +final class SelfControlServiceSupport: NSObject, NSXPCListenerDelegate { + // Use a reverse-DNS mach service name. This must match your helper’s Info.plist. + private let listener = NSXPCListener(machServiceName: AppToServiceMessagesConstants.bundleID) +// private let service = HelperService() + + func applicationDidFinishLaunching(_ notification: Notification) { + listener.delegate = self + listener.resume() + // Agent app has no UI; it just keeps running. + } + + func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { + // Configure interfaces for bidirectional communication. + let exportedInterface = NSXPCInterface(with: AppToServiceMessages.self) + let clientInterface = NSXPCInterface(with: AppToServiceMessages.self) +// exportedInterface.setInterface(clientInterface, for: #selector(SelfControlServiceProtocol.registerClient(_:)), argumentIndex: 0, ofReply: false) + + newConnection.exportedInterface = exportedInterface +// newConnection.exportedObject = service + + newConnection.invalidationHandler = { [weak self] in + // Optionally clean up client references if you track them per-connection + _ = self // keep capture + } + newConnection.interruptionHandler = { /* handle temporary interruptions */ } + + newConnection.resume() + return true + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/ClickableLinkButton.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/ClickableLinkButton.swift index 54268fea..5ce56a88 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/ClickableLinkButton.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/ClickableLinkButton.swift @@ -13,10 +13,11 @@ struct ClickableLinkButton: View { var body: some View { Text("[\(message)](https://localhost)") .environment(\.openURL, OpenURLAction { url in - handleURL(url) - return .handled + handleURL(url) + return .handled }) } + func handleURL(_ url: URL) { print("Handled URL: \(url)") onTap() diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallChromeExt.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallChromeExt.swift index 193cf702..5c0236fb 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallChromeExt.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallChromeExt.swift @@ -9,6 +9,8 @@ import SwiftUI struct OnBoardingInstallChromeExt: View { + @EnvironmentObject var viewModel: FilterViewModel + var body: some View { VStack { Text("Welcome to SelfControl!") @@ -21,13 +23,13 @@ struct OnBoardingInstallChromeExt: View { Spacer() - Text("To get started, we'll need t o prepare your computer so our blocks work properly.") + Text("To get started, we'll need to prepare your computer so our blocks work properly.") .font(.title2) Spacer() OnboardingStepView(step: 2) - Text("Install the SelfControl Chrome Extension so we can provide better blocking in Chrome.. We never store, share, or analyze your data - this is used or blocking.") + Text("Install the SelfControl Chrome Extension so we can provide better blocking in Chrome. We never store, share, or analyze your data - this is used or blocking.") .font(.title2) Spacer() @@ -39,13 +41,15 @@ struct OnBoardingInstallChromeExt: View { // } let task = Process() task.launchPath = "/usr/bin/open" - task.arguments = ["-a", "Google Chrome", "chrome://extensions"] + task.arguments = ["-a", "Google Chrome", "https://chromewebstore.google.com/detail/selfcontrol-blocker/lmpnckgcpefbmipnbfcickkaakpgbdhj"] task.launch() } Spacer() ClickableLinkButton(message: "Skip and accept subpar blocking", onTap: { print("Handle callback") + AppPreferences.setChromeExtensionInstalled() + viewModel.updateChromeExtensionViewStatus() }) Spacer() // .tint(.blue) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallSafariExt.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallSafariExt.swift index ce19cfc8..295480f8 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallSafariExt.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Onboarding/OnBoardingInstallSafariExt.swift @@ -9,6 +9,8 @@ import SwiftUI import SafariServices struct OnBoardingInstallSafariExt: View { + @EnvironmentObject var viewModel: FilterViewModel + var body: some View { VStack { Text("Welcome to SelfControl!") @@ -21,13 +23,13 @@ struct OnBoardingInstallSafariExt: View { Spacer() - Text("To get started, we'll need t o prepare your computer so our blocks work properly.") + Text("To get started, we'll need to prepare your computer so our blocks work properly.") .font(.title2) Spacer() OnboardingStepView(step: 3) - Text("Install the SelfControl Saari Extension so we can provide better blocking in Safari. We never store, share,o r analyze your data - this i s used o r blocking only.") + Text("Install the SelfControl Safari Extension so we can provide better blocking in Safari. We never store, share, or analyze your data — this is used for blocking only.") .font(.title2) Spacer() @@ -40,6 +42,8 @@ struct OnBoardingInstallSafariExt: View { Spacer() ClickableLinkButton(message: "Skip and accept subpar blocking", onTap: { print("Handle callback") + AppPreferences.setSafariExtensionInstalled() + viewModel.updateSafariExtensionViewStatus() }) Spacer() // .tint(.blue) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift index 43318161..38ba80c0 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlUI/Views/BlocklistEditorView.swift @@ -798,6 +798,7 @@ private struct DomainListSection: View { } .buttonStyle(.plain) .focusable(false) + .disabled(isBlockingMode) .onHover { hovering in withAnimation(DesignSystem.animationFast) { isHoveringBulkEdit = hovering diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift index 3fb78508..dd8b2729 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift @@ -24,17 +24,19 @@ final class ChromeExtensionRequestListner: NSObject { // private var lastUpdateReceivedTime = Date() func startListening() { - os_log("[SC] 🔍] PlistListner startListening") +// os_log("[SC] 🔍] PlistListner startListening") // Safely convert Int port to NWEndpoint.Port guard let port = NWEndpoint.Port(rawValue: ChromeExtensionRequestListner.servicePort) else { - os_log("[SC] 🔍] BG Invalid service port: %d", ChromeExtensionRequestListner.servicePort) +// os_log("[SC] 🔍] BG Invalid service port: %d", ChromeExtensionRequestListner.servicePort) + BGFileLogger.error("\(#function) invalid service port: : \(ChromeExtensionRequestListner.servicePort)") return } do { listener = try NWListener(using: .tcp, on: port) } catch { - os_log("[SC] 🔍] Failed to create NWListener: %{public}@", error.localizedDescription) + BGFileLogger.error("\(#function) error: \(error.localizedDescription)") +// os_log("[SC] 🔍] BG Failed to create NWListener: %{public}@", error.localizedDescription) return } @@ -73,11 +75,12 @@ final class ChromeExtensionRequestListner: NSObject { conn.start(queue: DispatchQueue.global(qos: .userInitiated)) conn.stateUpdateHandler = { state in + BGFileLogger.info("\(#function) Conn stateUpdateHandler: : \(state)") if state == .ready { -// os_log("[SC] 🔍] PlistListner stateUpdateHandler ready") + os_log("[SC] 🔍] PlistListner stateUpdateHandler ready") } if state == .cancelled { -// os_log("[SC] 🔍] PlistListner stateUpdateHandler cancelled") + os_log("[SC] 🔍] BG PlistListner stateUpdateHandler cancelled") } } } @@ -115,6 +118,7 @@ final class ChromeExtensionRequestListner: NSObject { """ connection.send(content: response.data(using: .utf8), contentContext: .finalMessage , completion: .contentProcessed { error in if let error = error { + BGFileLogger.error("\(#function) error: \(error.localizedDescription)") // os_log("[SC] 🔍] PlistListner Sent response error: %{public}@", "\(error)") } else { // os_log("[SC] 🔍] PlistListner Sent response successfully") diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift index 11953726..d9c5c4c4 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/EventScheduler/EventSchedulerRunner.swift @@ -42,8 +42,8 @@ public actor EventSchedulerRunner { // MARK: - Control public func start() async { - os_log("[SC] 🔍] BG Event Scheduler is starting") - +// os_log("[SC] 🔍] BG Event Scheduler is starting") + BGFileLogger.info("\(#function) Event Scheduler is starting") guard timer == nil else { return } await scheduleNextTimer() } @@ -107,8 +107,8 @@ public actor EventSchedulerRunner { // If you already obtain events differently, keep your existing code here. // Example (keep your own): let (maybeNextDate, maybeEvent) = earliestNextStart(after: now, events: events, calendar: calendar) - os_log("[SC] 🔍] BG scheduleNextTimer: %{public}@ ",maybeNextDate?.debugDescription ?? "nil") - +// os_log("[SC] 🔍] BG scheduleNextTimer: %{public}@ ",maybeNextDate?.debugDescription ?? "nil") + BGFileLogger.info("\(#function) scheduleNextTimer:\(maybeNextDate?.debugDescription ?? "nil")") guard let fireDate = maybeNextDate, let event = maybeEvent else { scheduledEvent = nil return @@ -138,8 +138,8 @@ public actor EventSchedulerRunner { } private func timerDidFire() async { - os_log("[SC] 🔍] BG timerDidFire: %{public}%@ ",scheduledEvent.debugDescription) - +// os_log("[SC] 🔍] BG timerDidFire: %{public}%@ ",scheduledEvent.debugDescription) + BGFileLogger.info("\(#function) timerDidFire:\(scheduledEvent.debugDescription)") // Clear scheduled event if you do that today diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift index f0f8f75e..4b53a58f 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift @@ -39,10 +39,12 @@ class IPCConnection: NSObject, NSSecureCoding { func register(completionHandler: @escaping (Bool) -> Void) { // self.delegate = delegate - os_log("[SC] 🔍] register(withExtension") +// os_log("[SC] 🔍] register(withExtension") + BGFileLogger.error("\(#function) ") guard currentConnection == nil else { - os_log("[SC] 🔍] Already registered with the provider") +// os_log("[SC] 🔍] Already registered with the provider") + BGFileLogger.error("\(#function) Already registered with the provider") completionHandler(true) return } @@ -53,9 +55,11 @@ class IPCConnection: NSObject, NSSecureCoding { newConnection.exportedInterface = NSXPCInterface(with: ExtensionToApp.self) newConnection.exportedObject = self newConnection.invalidationHandler = { [weak self] in + BGFileLogger.error("\(#function) invalidationHandler") self?.currentConnection = nil } newConnection.interruptionHandler = { [weak self] in + BGFileLogger.error("\(#function) interruptionHandler") self?.currentConnection = nil } // The remote object is the provider's IPCConnection instance. @@ -65,12 +69,14 @@ class IPCConnection: NSObject, NSSecureCoding { newConnection.resume() guard let providerProxy = newConnection.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) +// os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) + BGFileLogger.error("\(#function) Failed to register with the provider: \(registerError.localizedDescription) ") self.currentConnection?.invalidate() self.currentConnection = nil completionHandler(false) }) as? AppToExtensionExtension else { - os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") + BGFileLogger.error("\(#function) Failed to create a remote object proxy ") +// os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") return } providerProxy.register(completionHandler) @@ -84,16 +90,19 @@ class IPCConnection: NSObject, NSSecureCoding { func promptUser(aboutFlow flowInfo: [String: String], responseHandler:@escaping (Bool) -> Void) -> Bool { guard let connection = currentConnection else { - os_log("[SC] 🔍] Cannot prompt user because the app isn't registered") + BGFileLogger.error("\(#function) Cannot prompt user because the app isn't registered") +// os_log("[SC] 🔍] Cannot prompt user because the app isn't registered") return false } guard let appProxy = connection.remoteObjectProxyWithErrorHandler({ promptError in - os_log("[SC] 🔍] Failed to prompt the user: %{public}@", promptError.localizedDescription) +// os_log("[SC] 🔍] Failed to prompt the user: %{public}@", promptError.localizedDescription) + BGFileLogger.error("\(#function) Failed to prompt the user: \(promptError.localizedDescription) ") self.currentConnection = nil responseHandler(true) }) as? ExtensionToApp else { - os_log("Failed to create a remote object proxy for the app") + BGFileLogger.error("\(#function) Failed to create a remote object proxy ") +// os_log("Failed to create a remote object proxy for the app") return false } @@ -131,33 +140,41 @@ extension IPCConnection: NSXPCListenerDelegate { } func enableURLBlocking(_ urls: [String]) { - os_log("[SC] 🔍] Enabling URL blocking") +// os_log("[SC] 🔍] Enabling URL blocking") + BGFileLogger.info("\(#function) Enabling URL blocking") guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) }) as? AppToExtensionExtension else { - os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") +// os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") + BGFileLogger.error("\(#function) Failed to create a remote object proxy ") return } providerProxy.setBlockedURLs(urls) } func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { - os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension:\(extensionTypeRawValue), state:\(state)") +// os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension:\(extensionTypeRawValue), state:\(state)") + BGFileLogger.error("\(#function) Failed to create a remote object proxy \(extensionTypeRawValue), state:\(state)") guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension: %{public}@", registerError.localizedDescription) + BGFileLogger.error("\(#function) \(registerError.localizedDescription)") +// os_log("[SC] 🔍] sendMessageToSetActiveBrowserExtension: %{public}@", registerError.localizedDescription) }) as? AppToExtensionExtension else { - os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") + BGFileLogger.error("\(#function) Failed to create a remote object proxy ") +// os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") return } providerProxy.setActiveBrowserExtension(extensionTypeRawValue, state: state) } func sendMessageToEnableNetworkExtension(_ enable: Bool) -> Bool { - os_log("[SC] 🔍] sendMessageToEnableNetworkExtension state %{public}d", enable) +// os_log("[SC] 🔍] sendMessageToEnableNetworkExtension state %{public}d", enable) + BGFileLogger.error("\(#function) \(enable)") guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] sendMessageToEnableNetworkExtension: %{public}@", registerError.localizedDescription) +// os_log("[SC] 🔍] sendMessageToEnableNetworkExtension: %{public}@", registerError.localizedDescription) + BGFileLogger.error("\(#function) \(enable)") }) as? AppToExtensionExtension else { - os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") + BGFileLogger.error("\(#function) Failed to create a remote object proxy") +// os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") return false } providerProxy.setEnableService(enable) diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLogger.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLogger.swift new file mode 100644 index 00000000..03c9ec1a --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLogger.swift @@ -0,0 +1,193 @@ +// +// FileLogger.swift +// SelfControl +// +// Created by Satendra Singh on 12/06/26. +// + +import Foundation +import OSLog + +//struct FileLogger { +// static let logger = Logger(subsystem: "com.application.SelfControl.corebits.bgservice", category: "service"); +//} + +public enum LogLevel: String { + case debug = "DEBUG" + case info = "INFO" + case notice = "NOTICE" + case warning = "WARNING" + case error = "ERROR" + case fault = "FAULT" +} + +private actor FileLogger { + public static let shared = FileLogger() + + private let logDirectoryURL: URL + private let logFileURL: URL + private let fileManager = FileManager.default + private var fileHandle: FileHandle? + private let dateFormatter: DateFormatter + private let maxFileSizeBytes: Int64 = 5 * 1024 * 1024 // 5 MB + private let maxRotatedFiles = 5 + + public init(directoryName: String = "Logs", fileName: String = "app.log") { + // Default to Application Support//Logs/app.log + let baseDir: URL + if let appSupport = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first { + baseDir = appSupport + } else { + baseDir = URL(fileURLWithPath: NSTemporaryDirectory()) + } + + let bundleID = appName + (Bundle.main.bundleIdentifier ?? Bundle.main.bundleURL.lastPathComponent) + let appDir = baseDir.appendingPathComponent(bundleID, isDirectory: true) + + self.logDirectoryURL = appDir.appendingPathComponent(directoryName, isDirectory: true) + self.logFileURL = logDirectoryURL.appendingPathComponent(fileName) + os_log("[SC] 🔍] BG LOG Dir: %{public}@", log: OSLog.default, type: .debug, logDirectoryURL.path) + + self.dateFormatter = DateFormatter() + self.dateFormatter.locale = Locale(identifier: "en_US_POSIX") + self.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSZ" + } + + // Call once at app start or before first log + public func start() async { + do { + try fileManager.createDirectory(at: logDirectoryURL, withIntermediateDirectories: true, attributes: nil) + if !fileManager.fileExists(atPath: logFileURL.path) { + fileManager.createFile(atPath: logFileURL.path, contents: nil, attributes: nil) + } + fileHandle = try FileHandle(forWritingTo: logFileURL) + try fileHandle?.seekToEnd() + } catch { + // As a fallback, ignore failures silently to avoid crashing logging + fileHandle = nil + } + } + + public func stop() async { + try? fileHandle?.close() + fileHandle = nil + } + + public func debug(_ message: @autoclosure () -> String) async { + await write(level: .debug, message()) + } + + public func info(_ message: @autoclosure () -> String) async { + await write(level: .info, message()) + } + + public func notice(_ message: @autoclosure () -> String) async { + await write(level: .notice, message()) + } + + public func warning(_ message: @autoclosure () -> String) async { + await write(level: .warning, message()) + } + + public func error(_ message: @autoclosure () -> String) async { + await write(level: .error, message()) + } + + public func fault(_ message: @autoclosure () -> String) async { + await write(level: .fault, message()) + } + + public func log(_ level: LogLevel, _ message: @autoclosure () -> String) async { + await write(level: level, message()) + } + + private func write(level: LogLevel, _ message: @autoclosure () -> String) async { + let timestamp = dateFormatter.string(from: Date()) + let line = "[\(timestamp)] [\(level.rawValue)] \(message())\n" + + // Ensure file is ready + if fileHandle == nil { + await start() + } + + // Attempt rotation when file too large + await rotateIfNeeded() + + guard let data = line.data(using: .utf8) else { return } + + do { + try fileHandle?.seekToEnd() + try fileHandle?.write(contentsOf: data) + } catch { + // If writing fails, try reopening once + do { + try fileHandle?.close() + fileHandle = try FileHandle(forWritingTo: logFileURL) + try fileHandle?.seekToEnd() + try fileHandle?.write(contentsOf: data) + } catch { + // Give up silently to avoid crashing the app + } + } + } + + private func rotateIfNeeded() async { + guard let attrs = try? fileManager.attributesOfItem(atPath: logFileURL.path), + let size = attrs[.size] as? NSNumber else { return } + + if size.int64Value < maxFileSizeBytes { return } + + // Close current file first + try? fileHandle?.close() + fileHandle = nil + + // Rotate existing files: app.log.(n) -> app.log.(n+1) + for index in stride(from: maxRotatedFiles - 1, through: 1, by: -1) { + let src = logFileURL.appendingPathExtension("\(index)") + let dst = logFileURL.appendingPathExtension("\(index + 1)") + if fileManager.fileExists(atPath: src.path) { + try? fileManager.removeItem(at: dst) + try? fileManager.moveItem(at: src, to: dst) + } + } + + // Move current app.log -> app.log.1 + let firstRotation = logFileURL.appendingPathExtension("1") + try? fileManager.removeItem(at: firstRotation) + try? fileManager.moveItem(at: logFileURL, to: firstRotation) + + // Create a new empty log file + fileManager.createFile(atPath: logFileURL.path, contents: nil, attributes: nil) + do { + fileHandle = try FileHandle(forWritingTo: logFileURL) + try fileHandle?.seekToEnd() + } catch { + fileHandle = nil + } + } + + // Convenience to get the current log file URL (for sharing or debug) + public func currentLogFileURL() -> URL { + logFileURL + } + + public func allLogFiles() -> [URL] { + var urls: [URL] = [logFileURL] + for i in 1...maxRotatedFiles { + let rotated = logFileURL.appendingPathExtension("\(i)") + if fileManager.fileExists(atPath: rotated.path) { + urls.append(rotated) + } + } + return urls + } +} + +struct BGFileLogger { + static func debug(_ message: String) { Task { await FileLogger.shared.debug(message) }} + static func info(_ message: String) { Task { await FileLogger.shared.info(message) } } + static func notice(_ message: String) { Task { await FileLogger.shared.notice(message) } } + static func warning(_ message: String) { Task { await FileLogger.shared.warning(message) } } + static func error(_ message: String) { Task { await FileLogger.shared.error(message)} } + static func fault(_ message: String) { Task { await FileLogger.shared.fault(message) } } +} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLoggerConsts.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLoggerConsts.swift new file mode 100644 index 00000000..50587a48 --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLoggerConsts.swift @@ -0,0 +1,8 @@ +// +// FileLoggerConsts.swift +// SelfControl +// +// Created by Satendra Singh on 14/06/26. +// + +let appName = "SelfControlBGService" diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/main.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/main.swift index 0a9e6c8f..ab22b9b6 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/main.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/main.swift @@ -8,7 +8,8 @@ import Foundation import OSLog -os_log("[SC] 🔍] BG AshouldAcceptNewConnection") -print("Hello, World!") +//os_log("[SC] 🔍] BG AshouldAcceptNewConnection") +BGFileLogger.info("\(#function) Hello, World!") +//print("Hello, World!") let deletegate = AppDelegate.shared RunLoop.current.run() diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Utilities.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Utilities.swift index 0ee82162..05e8cd3c 100644 --- a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Utilities.swift +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/Process/Utilities.swift @@ -31,7 +31,7 @@ private let PROC_PIDPATHINFO_MAXSIZE: Int = 4096 final class Utilities { // Provided elsewhere in project - let logger = Logger(subsystem: "com.example.myapp", category: "network") + let logger = Logger(subsystem: "com.application.SelfControl.corebits.SelfControl-Safari-Extension", category: "network") // MARK: - Version / Bundle diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/RequestSourceAppValidator.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/RequestSourceAppValidator.swift index 3ef45685..9e74863d 100644 --- a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/RequestSourceAppValidator.swift +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/RequestSourceAppValidator.swift @@ -29,7 +29,7 @@ struct RequestSourceAppValidator { return nil } - // Convert NSData → audit_token_t + // Convert NSData → audit_token_tdf var auditToken = auditTokenData.withUnsafeBytes { ptr -> audit_token_t in return ptr.load(as: audit_token_t.self) } From 81a407e5e04323aecf7342c97869ea24d3bb83b0 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sun, 14 Jun 2026 09:18:32 +0530 Subject: [PATCH 55/64] reverted file and relocated --- .../HelperConnection+ReceivedMessages.swift | 24 --- .../HelperConnection+SendMessages.swift | 38 ---- .../HelperConnection.swift | 167 ------------------ .../LaunchedInstallManager/CheckProcess.swift | 55 ------ .../LaunchAgentManager.swift | 108 ----------- .../SelfControlServiceSupport.swift | 39 ---- 6 files changed, 431 deletions(-) delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/CheckProcess.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/LaunchAgentManager.swift delete mode 100644 Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/SelfControlServiceSupport.swift diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift deleted file mode 100644 index 53eb9dd0..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+ReceivedMessages.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// HelperConnection+ReceivedMessages.swift -// SelfControl -// -// Created by Satendra Singh on 19/05/26. -// - -extension HelperConnection { - func didEnableWebExtension(_ extensionTypeRawValue: String, state: Bool) { -// Task { @MainActor in - print("REceived didEnableWebExtension: \(extensionTypeRawValue), state: \(state)") - if let ext = WEBExtension(rawValue: extensionTypeRawValue) { - onExtensionStateChange?(ext, state) - } - } - - func didStartedBlocking(_ state: Bool, _ endDate: Date) -> Void { - if state == true { - let minutes: Double = Double(endDate.timeIntervalSinceNow / 60) - self.blockedStateHandler?(minutes) - } - print("REceived Blocked State: \(String(describing: state)), End Date: \(String(describing: endDate))") - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift deleted file mode 100644 index 800a96d4..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection+SendMessages.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// HelperConnection+SendMessages.swift -// SelfControl -// -// Created by Satendra Singh on 31/05/26. -// - -extension HelperConnection { - func send_startNetwrokBlocking(minutes: Int) { - bgProxyServiceConnection()?.startNetwrokBlocking(minutes: minutes) - } - - func send_stopNetworkBlocking() { - bgProxyServiceConnection()?.stopNetworkBlocking() - } - - func send_getBlockedStates() { - bgProxyServiceConnection()?.getBlockedStates { state, endDate in - if state == true, let endDate = endDate { - let minutes: Double = Double(endDate.timeIntervalSinceNow / 60) - self.blockedStateHandler?(minutes) - } - print("REceived Blocked State: \(String(describing: state)), End Date: \(String(describing: endDate))") - } - } - - func send_extendBlocking(minutes: Int) { - bgProxyServiceConnection()?.extendBlocking(minutes: minutes) - } - - func send_setPreference(key: String, value: Bool) { - bgProxyServiceConnection()?.setPreference(key: key, value: value) - } - - func send_setBlockedURLs(_ urls: [String]) { - bgProxyServiceConnection()?.setBlockedURLs(urls) - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift deleted file mode 100644 index c7c348d0..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/HelperConnection.swift +++ /dev/null @@ -1,167 +0,0 @@ -// -// HelperConnection.swift -// SelfControl -// -// Created by Satendra Singh on 08/05/26. -// - -// MainApp/HelperConnection.swift -import Cocoa -import ServiceManagement - -enum HelperServiceConstants: String { - case bundleID = "com.application.SelfControl.corebits.bgservice" // HelperApp’s bundle identifier - case processName = "SelfControlBGService" - case machServiceName = "com.application.SelfControl.corebits.bgservice.xpc" -} - -final class HelperConnection: NSObject, HelperClientProtocol, NSSecureCoding { - static let shared = HelperConnection() - static var supportsSecureCoding: Bool = true - var onExtensionStateChange: ((WEBExtension, Bool) -> Void)? - var blockedStateHandler: ((Double) -> Void)? - private var connection: NSXPCConnection? - private var exportedConnection: NSXPCListenerEndpoint? - private let checkProcess = CheckProcess(bundleID: HelperServiceConstants.bundleID.rawValue, processName: HelperServiceConstants.processName.rawValue) - - private let stateQueue = DispatchQueue(label: "com.application.SelfControl.corebits.bgservice.connection", attributes: .concurrent) - - override init() { } - - required init?(coder: NSCoder) { - return nil - } - - func encode(with coder: NSCoder) { - - } - // MARK: - Public API - - func installLoginItemIfNeeded() async throws { - if !checkProcess.isRunning() { - do { - try LaunchAgentManager.install() - } catch { - throw error - } - } else { - print("BG App is already running") - } - } - - func uninstallLoginItem() throws { - let loginItem = SMAppService.loginItem(identifier: HelperServiceConstants.bundleID.rawValue) - try loginItem.unregister() - } - - func connect() { - stateQueue.sync(flags: .barrier) { - guard self.connection == nil else { return } - - let conn = NSXPCConnection(machServiceName: HelperServiceConstants.machServiceName.rawValue, options: []) - // Remote interface: the helper’s service protocol - conn.remoteObjectInterface = NSXPCInterface(with: HelperServiceProtocol.self) - // Exported interface: our client protocol to receive callbacks - let clientInterface = NSXPCInterface(with: HelperClientProtocol.self) - conn.exportedInterface = clientInterface - conn.exportedObject = self - // exportedConnection = listener.endpoint - - conn.interruptionHandler = { [weak self] in - print("Conn interruptionHandler") - // Temporary loss of connection; try to reconnect - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - self?.reconnect() - } - } - - conn.invalidationHandler = { [weak self] in - print("Conn invalidationHandler") - - // Connection invalidated; tear down and reconnect - self?.stateQueue.sync(flags: .barrier) { - self?.connection = nil - } - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - self?.connect() - } - } - - conn.resume() - self.connection = conn - // Register for callbacks - bgProxyServiceConnection()?.registerClient() - } - } - - func disconnect() { - stateQueue.sync(flags: .barrier) { - self.connection?.invalidate() - self.connection = nil - } - } - - func saveSchedules(schedules: Data, reply: @escaping (Bool) -> Void) { - stateQueue.async(flags: .barrier) { - self.bgProxyServiceConnection()?.saveSchedules(schedules: schedules, reply: reply) - } - } - - func loadSchedules(reply: @escaping (_ schedules: Data) -> Void) { - //TODO: - } - - func currentStatus() async throws -> String { - try await withCheckedThrowingContinuation { cont in - self.withProxy(error: { cont.resume(throwing: $0) }) { proxy in - proxy.currentStatus { status in cont.resume(returning: status) } - } - } - } - - // MARK: - HelperClientProtocol (callbacks from helper) - - func didUpdateStatus(_ status: String) { - print("didUpdateStatus: \(status)") - // Update UI or post notifications as needed - NotificationCenter.default.post(name: .helperStatusChanged, object: status) - } - - func didEmitEvent(_ message: String) { - print("didEmitEvent: \(message)") - NotificationCenter.default.post(name: .helperEvent, object: message) - } - - // MARK: - Internal - - private func reconnect() { - disconnect() - connect() - } - - private func withProxy(error: ((Error) -> Void)? = nil, _ body: (HelperServiceProtocol) -> Void) { - var proxy: AnyObject? - stateQueue.sync { - proxy = self.connection?.remoteObjectProxyWithErrorHandler { err in - error?(err) - } as AnyObject? - } - if let proxy = proxy as? HelperServiceProtocol { - body(proxy) - } else { - error?(NSError(domain: "HelperConnection", code: 1, userInfo: [NSLocalizedDescriptionKey: "No XPC connection"])) - } - } - - func bgProxyServiceConnection() -> HelperServiceProtocol? { - var proxy: AnyObject? - proxy = self.connection?.remoteObjectProxyWithErrorHandler { err in - print("Conn Error:\(err)") } as AnyObject? - return proxy as? HelperServiceProtocol - } -} - -extension Notification.Name { - static let helperStatusChanged = Notification.Name("HelperStatusChanged") - static let helperEvent = Notification.Name("HelperEvent") -} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/CheckProcess.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/CheckProcess.swift deleted file mode 100644 index 8c7a7298..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/CheckProcess.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// CheckProcess.swift -// SelfControl -// -// Created by Satendra Singh on 21/05/26. -// - - -import AppKit - -struct CheckProcess { - - var bundleID: String - var processName: String - - func isRunning() -> Bool { - // Use the bundle identifier if possible (most reliable). - if isAppRunning(bundleIdentifier: bundleID) { - fputs("Installation aborted: \(bundleID) is currently running.\n", stderr) - return true - } - - // Optional: also guard by process name as a fallback. - if isProcessRunningByName(processName) { - fputs("Installation aborted: process '\(processName)' is currently running.\n", stderr) - return true - } - - // OK to proceed - return false - } - - private func isAppRunning(bundleIdentifier: String) -> Bool { - !NSRunningApplication.runningApplications(withBundleIdentifier: bundleIdentifier).isEmpty - } - - private func isProcessRunningByName(_ name: String) -> Bool { - // Uses /usr/bin/pgrep -x for exact match - let task = Process() - task.launchPath = "/usr/bin/pgrep" - task.arguments = ["-x", name] - - let pipe = Pipe() - task.standardOutput = pipe - task.standardError = Pipe() - - do { - try task.run() - task.waitUntilExit() - return task.terminationStatus == 0 - } catch { - return false - } - } -} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/LaunchAgentManager.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/LaunchAgentManager.swift deleted file mode 100644 index ee91b74c..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/LaunchedInstallManager/LaunchAgentManager.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// LaunchAgentManager.swift -// SelfControl -// -// Created by Satendra Singh on 13/05/26. -// - - -import Foundation - -final class LaunchAgentManager { - - static let label = "com.application.SelfControl.corebits.bgservice" - static let processName = "SelfControlBGService" - - static let plistName = "com.application.SelfControl.corebits.bgservice" - - static func install() throws { - - let fm = FileManager.default - - let sourcePlist = Bundle.main.url( - forResource: plistName, - withExtension: "plist" - )! - - let launchAgentsDir = fm.homeDirectoryForCurrentUser - .appendingPathComponent("Library/LaunchAgents") - - let destinationPlist = launchAgentsDir - .appendingPathComponent("\(plistName).plist") - - // Create LaunchAgents directory if needed - try fm.createDirectory( - at: launchAgentsDir, - withIntermediateDirectories: true - ) - - // Replace old plist - if fm.fileExists(atPath: destinationPlist.path) { - try fm.removeItem(at: destinationPlist) - } - - try fm.copyItem( - at: sourcePlist, - to: destinationPlist - ) - - // unload existing - try runLaunchctl([ - "unload", - destinationPlist.path - ]) - - // load new - try runLaunchctl([ - "load", - destinationPlist.path - ]) - - print("LaunchAgent installed") - } - - static func uninstall() throws { - - let fm = FileManager.default - - let plist = fm.homeDirectoryForCurrentUser - .appendingPathComponent( - "Library/LaunchAgents/\(plistName).plist" - ) - - try runLaunchctl([ - "unload", - plist.path - ]) - - try fm.removeItem(at: plist) - - print("LaunchAgent removed") - } - - @discardableResult - static func runLaunchctl(_ args: [String]) throws -> String { - - let process = Process() - - process.executableURL = URL( - fileURLWithPath: "/bin/launchctl" - ) - - process.arguments = args - - let pipe = Pipe() - - process.standardOutput = pipe - process.standardError = pipe - - try process.run() - - process.waitUntilExit() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - - return String(data: data, encoding: .utf8) ?? "" - } -} - diff --git a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/SelfControlServiceSupport.swift b/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/SelfControlServiceSupport.swift deleted file mode 100644 index 7155d55e..00000000 --- a/Self-Control-Extension/SelfControl/SelfControl/Main/SelfControlServiceSupport/SelfControlServiceSupport.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// SelfControlServiceSupport.swift -// SelfControl -// -// Created by Satendra Singh on 03/05/26. -// - -import Cocoa - -final class SelfControlServiceSupport: NSObject, NSXPCListenerDelegate { - // Use a reverse-DNS mach service name. This must match your helper’s Info.plist. - private let listener = NSXPCListener(machServiceName: AppToServiceMessagesConstants.bundleID) -// private let service = HelperService() - - func applicationDidFinishLaunching(_ notification: Notification) { - listener.delegate = self - listener.resume() - // Agent app has no UI; it just keeps running. - } - - func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { - // Configure interfaces for bidirectional communication. - let exportedInterface = NSXPCInterface(with: AppToServiceMessages.self) - let clientInterface = NSXPCInterface(with: AppToServiceMessages.self) -// exportedInterface.setInterface(clientInterface, for: #selector(SelfControlServiceProtocol.registerClient(_:)), argumentIndex: 0, ofReply: false) - - newConnection.exportedInterface = exportedInterface -// newConnection.exportedObject = service - - newConnection.invalidationHandler = { [weak self] in - // Optionally clean up client references if you track them per-connection - _ = self // keep capture - } - newConnection.interruptionHandler = { /* handle temporary interruptions */ } - - newConnection.resume() - return true - } -} From a50e41271a37714164c91d51db2036c9dcade964 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sat, 20 Jun 2026 10:48:53 +0530 Subject: [PATCH 56/64] updated version number --- Self-Control-Extension/SelfControl/SelfControl/Info.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/Info.plist b/Self-Control-Extension/SelfControl/SelfControl/Info.plist index f0daf04d..346d80fc 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/Info.plist +++ b/Self-Control-Extension/SelfControl/SelfControl/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.20 + 1.21 CFBundleVersion - 20 + 21 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright From e893ace7c329619cad800d7420277d0f898eb052 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sat, 20 Jun 2026 10:49:25 +0530 Subject: [PATCH 57/64] shifted okay button in case of check internet connection state --- .../SelfControl/SelfControl/SCUIUtility.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl/SCUIUtility.swift b/Self-Control-Extension/SelfControl/SelfControl/SCUIUtility.swift index a9d353d0..cb80b5c4 100644 --- a/Self-Control-Extension/SelfControl/SelfControl/SCUIUtility.swift +++ b/Self-Control-Extension/SelfControl/SelfControl/SCUIUtility.swift @@ -13,8 +13,8 @@ final class SCUIUtility { let alert = NSAlert() alert.messageText = NSLocalizedString("No network connection detected", comment: "No network connection detected message") alert.informativeText = NSLocalizedString("A block cannot be started without a working network connection. You can override this setting in Preferences.", comment: "Message when network connection is unavailable") - alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK button")) alert.runModal() + alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK button")) return false } return true From 287581a8539896f63f35f881ecf1e639f56ce53f Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sat, 20 Jun 2026 10:51:38 +0530 Subject: [PATCH 58/64] added advance logging --- .../SelfControlBGService/CoreService/AppDelegate.swift | 4 +++- .../SelfControlBGService/CoreService/HelperService.swift | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppDelegate.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppDelegate.swift index 45d1dd18..508b554f 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppDelegate.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppDelegate.swift @@ -30,7 +30,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSXPCListenerDelegate print("shouldAcceptNewConnection") os_log("[SC] 🔍] BG shouldAcceptNewConnection") - + BGFileLogger.info("shouldAcceptNewConnection") let exportedInterface = NSXPCInterface(with: HelperServiceProtocol.self) @@ -39,10 +39,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSXPCListenerDelegate newConnection.remoteObjectInterface = NSXPCInterface(with: HelperClientProtocol.self) newConnection.invalidationHandler = { os_log("[SC] 🔍] BG newConnection.invalidationHandler") + BGFileLogger.error("newConnection.invalidationHandler") } newConnection.interruptionHandler = { os_log("[SC] 🔍] BG newConnection.interruptionHandler") + BGFileLogger.error("newConnection.interruptionHandler") } service.clientConnection = newConnection newConnection.resume() diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift index 623e0667..bfebc0d3 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift @@ -48,10 +48,12 @@ final class HelperService: NSObject, HelperServiceProtocol { var proxy: AnyObject? guard let conn = clientConnection else { os_log("[SC] 🔍] BG No client connection") + BGFileLogger.error("BG No client connection") return nil } proxy = conn.remoteObjectProxyWithErrorHandler { err in os_log("[SC] 🔍] BG Client proxy error:\(err)") + BGFileLogger.error("HelperService: proxyConnectionService: Error: \(err)") } as AnyObject? return proxy as? HelperClientProtocol } From 3bb9b215ca1a99adc3e37d6ed73f0637dc395fb6 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sat, 20 Jun 2026 10:52:06 +0530 Subject: [PATCH 59/64] updated logging --- .../SelfControlBGService/Helpers/DelayTimerHandler.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift index cf9ee2a4..1b6cc4a6 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Helpers/DelayTimerHandler.swift @@ -111,6 +111,7 @@ final class DelayTimerHandler { // Reschedule timer to the new deadline let remaining = max(0, newFire.timeIntervalSince(now)) os_log("[SC] 🔍] BG Extending blocking by %{public}d minutes (%{public}.0f s). New remaining: %.0f s", minutes, additionalSeconds, remaining) + BGFileLogger.info("[SC] 🔍] BG Extending blocking by \(minutes) minutes \(additionalSeconds). New remaining: \(remaining)") timer.schedule(deadline: .now() + remaining, repeating: .never, leeway: .seconds(1)) } } From 66db2a8e6fc82532f21dfbfb356b898cbb6eb89c Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sat, 20 Jun 2026 10:52:23 +0530 Subject: [PATCH 60/64] updated logging to write in files --- .../Services/NetworkExtensionService/IPCConnection.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift index 4b53a58f..531a8d43 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift @@ -144,6 +144,7 @@ extension IPCConnection: NSXPCListenerDelegate { BGFileLogger.info("\(#function) Enabling URL blocking") guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) + BGFileLogger.error("\(#function) Failed to register with the provider: \(registerError.localizedDescription)") }) as? AppToExtensionExtension else { // os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") BGFileLogger.error("\(#function) Failed to create a remote object proxy ") From 22dd6f93071081b79ff8f87397cd40f8e7ef35fb Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Sat, 20 Jun 2026 11:25:13 +0530 Subject: [PATCH 61/64] updated logic to handle the connection request on queue and updated logging --- .../ChromeExtensionRequestListner.swift | 394 +++++++++++++----- .../Shared/FileLogger.swift | 1 + 2 files changed, 293 insertions(+), 102 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift index dd8b2729..180df1df 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/Chrome+SafariExtensionSupport/ChromeExtensionRequestListner.swift @@ -16,136 +16,326 @@ enum ServicePath: String { final class ChromeExtensionRequestListner: NSObject { private var isChromeStatusSetInExtension: Bool = false - var listener: NWListener? + private let queue = DispatchQueue(label: "com.yourcompany.ChromeExtensionRequestListner", qos: .userInitiated) + private(set) var listener: NWListener? var blockeddomainFetcher: (() -> [String])? -// private var isBlockingEnabled: Bool = false var onExtensionStateChange: (() -> Void)? static let servicePort: UInt16 = 8532 -// private var lastUpdateReceivedTime = Date() - - func startListening() { -// os_log("[SC] 🔍] PlistListner startListening") - // Safely convert Int port to NWEndpoint.Port + // MARK: - Public API + + func startListening() { + // Convert Int port to NWEndpoint.Port guard let port = NWEndpoint.Port(rawValue: ChromeExtensionRequestListner.servicePort) else { -// os_log("[SC] 🔍] BG Invalid service port: %d", ChromeExtensionRequestListner.servicePort) BGFileLogger.error("\(#function) invalid service port: : \(ChromeExtensionRequestListner.servicePort)") return } + do { - listener = try NWListener(using: .tcp, on: port) + let newListener = try NWListener(using: .tcp, on: port) + self.listener = newListener + + // Configure service parameters if needed (e.g., fast open, local interface, etc.) + newListener.newConnectionHandler = { [weak self] connection in + self?.handleNewConnection(connection) + } + + newListener.stateUpdateHandler = { [weak self] state in + self?.handleListenerStateUpdate(state) + } + + newListener.start(queue: queue) } catch { - BGFileLogger.error("\(#function) error: \(error.localizedDescription)") -// os_log("[SC] 🔍] BG Failed to create NWListener: %{public}@", error.localizedDescription) - return + BGFileLogger.error("\(#function) failed to create NWListener: \(error.localizedDescription)") } - - listener?.newConnectionHandler = { conn in - print("Path: \(conn.endpoint.debugDescription)") - conn.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, _, _, _ in - if let data = data, - let req = String(data: data, encoding: .utf8) { - print("Raw Request:", req) - if let service = req.httpPathFromConnection() { - Task { - await AppStateManager.shared.handleApiRequest(path: service) - } - switch service { - case .chrome: -// os_log("[SC] 🔍] BG Chrome request received") - - Task { - await self.sendChromeBlockedUrls(connection: conn) - } -// self.updateChromeStatus() - case .safari: - // Handle Safari service path if needed - Task { - await self.sendChromeBlockedUrls(connection: conn) - } -// conn.cancel() -// os_log("[SC] 🔍] BG Safari request received") -// self.updateSafariStatus() - break - } - } - } + } + + func stopListening() { + listener?.cancel() + listener = nil + } + + deinit { + stopListening() + } + + // MARK: - Listener State Handling + + private func handleListenerStateUpdate(_ state: NWListener.State) { + switch state { + case .setup: + os_log("[SC] 🔍] Listener setup") + BGFileLogger.info("Listener state: setup") + case .waiting(let error): + os_log("[SC] 🔍] Listener waiting: %{public}@", error.localizedDescription) + BGFileLogger.error("Listener waiting: \(error.localizedDescription)") + + // Optional: attempt a delayed restart if appropriate + scheduleRestartIfNeeded() + case .ready: + os_log("[SC] 🔍] Listener ready on port %{public}d", ChromeExtensionRequestListner.servicePort) + BGFileLogger.info("Listener state: ready on port \(ChromeExtensionRequestListner.servicePort)") + case .failed(let error): + os_log("[SC] 🔍] Listener failed: %{public}@", error.localizedDescription) + BGFileLogger.error("Listener failed: \(error.localizedDescription)") + + // Cancel and clear; optionally try to restart after a delay + listener?.cancel() + listener = nil + scheduleRestartIfNeeded() + case .cancelled: + os_log("[SC] 🔍] Listener cancelled") + BGFileLogger.info("Listener state: cancelled") + @unknown default: + os_log("[SC] 🔍] Listener unknown state") + BGFileLogger.error("Listener state: unknown") + } + } + + private func scheduleRestartIfNeeded() { + // If you want to throttle restarts, add a backoff strategy here. + // For now, try a simple delayed restart if listener is nil. + guard listener == nil else { return } + queue.asyncAfter(deadline: .now() + 2.0) { [weak self] in + guard let self else { return } + if self.listener == nil { + self.startListening() } + } + } - conn.start(queue: DispatchQueue.global(qos: .userInitiated)) + // MARK: - Connection Handling - conn.stateUpdateHandler = { state in - BGFileLogger.info("\(#function) Conn stateUpdateHandler: : \(state)") - if state == .ready { - os_log("[SC] 🔍] PlistListner stateUpdateHandler ready") - } - if state == .cancelled { - os_log("[SC] 🔍] BG PlistListner stateUpdateHandler cancelled") + private func handleNewConnection(_ connection: NWConnection) { +// BGFileLogger.info("New connection from: \(connection.endpoint.debugDescription)") + os_log("[SC] 🔍] BG New connection from: %{public}@", connection.endpoint.debugDescription) + + connection.stateUpdateHandler = { [weak self] state in + self?.handleConnectionStateUpdate(connection, state: state) + } + + // Start the connection + connection.start(queue: queue) + + // Receive a single HTTP request (simple, non-streaming) + receiveHTTPRequest(on: connection) + } + + private func handleConnectionStateUpdate(_ connection: NWConnection, state: NWConnection.State) { + switch state { + case .setup: + BGFileLogger.info("Conn state: setup") + case .waiting(let error): + BGFileLogger.error("Conn state: waiting - \(error.localizedDescription)") + case .ready: + print("[SC] 🔍] BG Connection ready") +// os_log("[SC] 🔍] BG Connection ready") +// BGFileLogger.info("Conn state: ready") + case .failed(let error): + BGFileLogger.error("Conn state: failed - \(error.localizedDescription)") + connection.cancel() + case .cancelled: + print("[SC] 🔍] BG Connection cancelled") +// os_log("[SC] 🔍] BG Connection cancelled") +// BGFileLogger.info("Conn state: cancelled") + case .preparing: + print("[SC] 🔍] BG Connection preparing") +// os_log("[SC] 🔍] BG Connection preparing") + + @unknown default: + os_log("[SC] 🔍] BG Conn state: unknown") + + BGFileLogger.error("Conn state: unknown") + } + } + + private func receiveHTTPRequest(on connection: NWConnection) { + // Read up to some reasonable maximum for a small HTTP request + connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] data, _, isComplete, error in + guard let self else { return } + + if let error = error { + BGFileLogger.error("Receive error: \(error.localizedDescription)") + self.sendHTTPError(.internalServerError, to: connection) + connection.cancel() + return + } + + guard let data = data, !data.isEmpty else { + BGFileLogger.error("Received empty data") + self.sendHTTPError(.badRequest, to: connection) + connection.cancel() + return + } + + guard let requestString = String(data: data, encoding: .utf8) else { + BGFileLogger.error("Invalid UTF-8 in request") + self.sendHTTPError(.badRequest, to: connection) + connection.cancel() + return + } + + // Basic request parsing: we expect HTTP-like requests + // Example: "GET /chrome HTTP/1.1\r\n..." + let path = self.extractPath(from: requestString) + + // If the original helper exists, try to use it first + if let service = requestString.httpPathFromConnection() { + Task { + await AppStateManager.shared.handleApiRequest(path: service) } + self.handleService(service, on: connection) + return } + + // Fallback: handle based on parsed path +// if let path { +// Task { +// await AppStateManager.shared.handleApiRequest(path: path) +// } +// self.handlePath(path, on: connection) +// } + else { + BGFileLogger.error("Unable to parse path from request \(path)") + self.sendHTTPError(.notFound, to: connection) + } + + // If the peer indicated end-of-stream, we can clean up + if isComplete { + connection.cancel() + } + } + } + + // MARK: - Routing + + // If you have a specific enum for service paths, keep using it. + // Here we just switch on known strings as a fallback. + private func handleService(_ service: Any, on connection: NWConnection) { + // The original code suggests `service` is an enum with cases .chrome and .safari. + // We'll try to handle those names via String conversion for safety. + let serviceString = String(describing: service).lowercased() + switch serviceString { + case "chrome": + Task { [weak self] in + await self?.sendChromeBlockedUrls(connection: connection) + } + case "safari": + Task { [weak self] in + await self?.sendChromeBlockedUrls(connection: connection) + } + default: + // Unknown path + sendHTTPError(.notFound, to: connection) } - - listener?.start(queue: DispatchQueue.global(qos: .userInitiated)) - } - -// func activateSafariBlocking() { -// isBlockingEnabled = true -// } -// -// func deactivateSafariBlocking() { -// isBlockingEnabled = false -// } - + } + + private func handlePath(_ path: String, on connection: NWConnection) { + switch path.lowercased() { + case "/chrome", "chrome": + Task { [weak self] in + await self?.sendChromeBlockedUrls(connection: connection) + } + case "/safari", "safari": + Task { [weak self] in + await self?.sendChromeBlockedUrls(connection: connection) + } + default: + sendHTTPError(.notFound, to: connection) + } + } + + // MARK: - Response Builders + private func sendChromeBlockedUrls(connection: NWConnection) async { let isBlockEnabled = await AppStateManager.shared.isBlockingEnabled - print("sendChromeBlockedUrls") -// os_log("[SC] 🔍] BG sendChromeBlockedUrls %{public}d", isBlockEnabled) var blockedDomainList: [String] = self.blockeddomainFetcher?() ?? [] + if isBlockEnabled == false { blockedDomainList = [] } - let blockedUrls = ["blocked": blockedDomainList] - let jsonData = try! JSONSerialization.data(withJSONObject: blockedUrls, options: []) - let jsonString = String(data: jsonData, encoding: .utf8)! - - // os_log("[SC] 🔍] PlistListner newConnectionHandler") - let response = """ - HTTP/1.1 200 OK\r - Content-Type: application/json\r - Access-Control-Allow-Origin: *\r - \r - \(jsonString) - """ - connection.send(content: response.data(using: .utf8), contentContext: .finalMessage , completion: .contentProcessed { error in + + let payload = ["blocked": blockedDomainList] + + do { + let jsonData = try JSONSerialization.data(withJSONObject: payload, options: []) + sendHTTPResponse(status: .ok, contentType: "application/json", body: jsonData, to: connection) + } catch { + BGFileLogger.error("\(#function) JSON encode error: \(error.localizedDescription)") + sendHTTPError(.internalServerError, to: connection) + } + } + + private func sendHTTPResponse(status: HTTPStatus, contentType: String, body: Data, to connection: NWConnection) { + let headers = [ + "Content-Type": contentType, + "Access-Control-Allow-Origin": "*", + "Content-Length": "\(body.count)" + ] + + let headerString = headers + .map { "\($0): \($1)" } + .joined(separator: "\r\n") + + let responseHead = "HTTP/1.1 \(status.code) \(status.reason)\r\n\(headerString)\r\n\r\n" + var responseData = Data(responseHead.utf8) + responseData.append(body) + + connection.send(content: responseData, contentContext: .finalMessage, isComplete: true, completion: .contentProcessed { error in if let error = error { - BGFileLogger.error("\(#function) error: \(error.localizedDescription)") -// os_log("[SC] 🔍] PlistListner Sent response error: %{public}@", "\(error)") - } else { -// os_log("[SC] 🔍] PlistListner Sent response successfully") + BGFileLogger.error("Send response error: \(error.localizedDescription)") } + // Close the connection after sending the response + connection.cancel() }) } - -// private func updateSafariStatus() { -// lastUpdateReceivedTime = Date() -// } -// -// private func updateChromeStatus() { -// Task { -// AppStateManager.shared.handleApiRequest(path: "safari-extension-status)") -// print("newConnectionHandler isEnabled: \(NetworkExtensionState.shared.isEnabled)") -// if NetworkExtensionState.shared.isEnabled == true && NetworkExtensionState.shared.isChromeExtensionEnabled == false { -//// NetworkExtensionState.shared.isChromeExtensionEnabled = IPCConnectionProxy().sendMessageToSetActiveBrowserExtension(ActiveBrowserExtensios.chrome.rawValue, state: true) -// NetworkExtensionState.shared.isChromeExtensionEnabled = true -// NetworkExtensionState.shared.printAll() -// } -// if isChromeStatusSetInExtension == false { -// isChromeStatusSetInExtension = true -// self.onExtensionStateChange?() -// } -// } -// } + private func sendHTTPError(_ status: HTTPStatus, to connection: NWConnection) { + let bodyDict = ["error": status.reason] + let bodyData = (try? JSONSerialization.data(withJSONObject: bodyDict, options: [])) ?? Data() + sendHTTPResponse(status: status, contentType: "application/json", body: bodyData, to: connection) + } + + // MARK: - Helpers + + private func extractPath(from request: String) -> String? { + // Very simple parser: first line "METHOD /path HTTP/1.1" + guard let firstLine = request.components(separatedBy: "\r\n").first else { + return nil + } + let components = firstLine.split(separator: " ") + guard components.count >= 2 else { + return nil + } + return String(components[1]) + } +} + +// MARK: - HTTPStatus + +private enum HTTPStatus { + case ok + case badRequest + case notFound + case internalServerError + + var code: Int { + switch self { + case .ok: return 200 + case .badRequest: return 400 + case .notFound: return 404 + case .internalServerError: return 500 + } + } + + var reason: String { + switch self { + case .ok: return "OK" + case .badRequest: return "Bad Request" + case .notFound: return "Not Found" + case .internalServerError: return "Internal Server Error" + } + } } extension String { diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLogger.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLogger.swift index 03c9ec1a..ca0247e7 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLogger.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/FileLogger.swift @@ -126,6 +126,7 @@ private actor FileLogger { try fileHandle?.seekToEnd() try fileHandle?.write(contentsOf: data) } catch { + os_log("[SC] 🔍] File handler error: %{public}@", error.localizedDescription) // Give up silently to avoid crashing the app } } From b8cf9beae8e966f517d2fa9eb9169f279e10cb97 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Mon, 22 Jun 2026 21:42:19 +0530 Subject: [PATCH 62/64] removed unused code, code cleanup and other logic fixed and changed --- ...elperService+SafariExtensionMessages.swift | 52 --------------- .../CoreService/HelperService.swift | 28 +++++--- .../IPCConnection.swift | 30 +-------- .../Shared/AppToExtensionExtension.swift | 1 - .../Shared/HelperServiceProtocol.swift | 2 +- .../FilterDataProvider.swift | 4 +- .../IPCConnection.swift | 64 +------------------ 7 files changed, 27 insertions(+), 154 deletions(-) delete mode 100644 Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift deleted file mode 100644 index 0cb01c29..00000000 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SafariExtensionMessages.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// HelperService+AppToExtensionExtension.swift -// SelfControl -// -// Created by Satendra Singh on 17/05/26. -// -import OSLog -import Foundation - -extension HelperService: AppToExtensionExtension { - - func register(_ completionHandler: @escaping (Bool) -> Void) { - queue.async { - IPCConnection.shared.register(completionHandler: completionHandler) - } - } - - func setBlockedURLs(_ urls: [String]) { - queue.async(flags: .barrier) { - IPCConnection.shared.enableURLBlocking(urls) - self.blockedUrls = urls - HelperAppPreferences.saveBlockedUrls(blockedUrls: urls) - } - } - - func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { - queue.async { - IPCConnection.shared.setActiveBrowserExtension(extensionTypeRawValue, state: state) - } - } - - func setEnableService(_ enable: Bool) { //TODO: REmove // Cleanup -// queue.async { [weak self] in -// _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(enable) -// Task { -// if enable { -// await AppStateManager.shared.activateContentBlocking() -// } else { -// await AppStateManager.shared.deactivateContentBlocking() -// } -// } -// } - } - - func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { - queue.async { - _ = IPCConnection.shared.sendMessageToSetActiveBrowserExtension(extensionTypeRawValue, state: state) - } - } - - func register(completionHandler: @escaping (Bool) -> Void) { } //TODO Remove -} diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift index bfebc0d3..e6347c4d 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift @@ -13,10 +13,11 @@ final class HelperService: NSObject, HelperServiceProtocol { let queue = DispatchQueue(label: "com.application.SelfControl.corebits.bgservice.queue") var clientConnection: NSXPCConnection? let chromeService = ChromeExtensionRequestListner() - var blockedUrls: [String] = [] + var blockedUrls: [String] = HelperAppPreferences.loadlockedUrls() ?? [] private var timer: DelayTimerHandler? private var eventSchedulerController: EventSchedulerRunnerController? - + let networkExtension = IPCConnection.shared + override init() { super.init() @@ -35,6 +36,10 @@ final class HelperService: NSObject, HelperServiceProtocol { // client.didUpdateStatus(self.isMonitoring ? "running" : "stopped") print("Register Client received") self.blockedUrls = HelperAppPreferences.loadlockedUrls() ?? [] + self.networkExtension.register(completionHandler: { [weak self] status in + os_log("[SC] 🔍] BG register NE status: \(status)") + self?.networkExtension.sendMessageToSetBlockingURLs(self?.blockedUrls ?? []) + }) } self.sendPing() @@ -105,17 +110,23 @@ final class HelperService: NSObject, HelperServiceProtocol { } } - func saveBlockedUrls(blockedURLS: [String]) { + func setBlockedURLs(_ urls: [String]) { + os_log("[SC] 🔍] BG setBlockedURLs: %{public}@", urls) queue.async(flags: .barrier) { - self.blockedUrls = blockedURLS + self.networkExtension.sendMessageToSetBlockingURLs(urls) + self.blockedUrls = urls + HelperAppPreferences.saveBlockedUrls(blockedUrls: urls) } } func startNetwrokBlocking(minutes: Int) { - os_log("[SC] 🔍] BG startNetwrokBlocking:\(minutes)") - self.register({ status in + os_log("[SC] 🔍] BG startNetwrokBlocking: %{public}d", minutes) + self.networkExtension.register(completionHandler: { [weak self] status in os_log("[SC] 🔍] BG register NE status: \(status)") + self?.networkExtension.sendMessageToSetBlockingURLs(self?.blockedUrls ?? []) }) + os_log("[SC] 🔍] BG startNetwrokBlocking: %{public}@", blockedUrls) + queue.async { [weak self] in self?.startNetworkBlocking() self?.timer = DelayTimerHandler(delay: Double(minutes), completionHandler: { [weak self] in @@ -139,7 +150,7 @@ final class HelperService: NSObject, HelperServiceProtocol { timer?.cancelTimer() queue.async { Task { - _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(false) + _ = self.networkExtension.sendMessageToEnableNetworkExtension(false) await AppStateManager.shared.deactivateContentBlocking() Sound.checkAndPlay() } @@ -180,12 +191,11 @@ final class HelperService: NSObject, HelperServiceProtocol { private func startNetworkBlocking() { queue.async { Task { - _ = IPCConnection.shared.sendMessageToEnableNetworkExtension(true) + _ = self.networkExtension.sendMessageToEnableNetworkExtension(true) await AppStateManager.shared.activateContentBlocking() FilterController.restartFilter { result in os_log("[SC] 🔍] BG FilterController.restartFilter: \(result)") } - } } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift index 531a8d43..0aee30fe 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift @@ -29,8 +29,6 @@ class IPCConnection: NSObject, NSSecureCoding { var currentConnection: NSXPCConnection? weak var delegate: ExtensionToApp? static let shared = IPCConnection() -// var blockedUrls: [String] = ProxyPreferences.getBlockedDomains() - var blockedUrls: [String] = [String]() // Published extension state for UI/observers // MARK: Methods @@ -80,7 +78,6 @@ class IPCConnection: NSObject, NSSecureCoding { return } providerProxy.register(completionHandler) - providerProxy.setBlockedURLs(blockedUrls) } /** @@ -139,8 +136,7 @@ extension IPCConnection: NSXPCListenerDelegate { return true } - func enableURLBlocking(_ urls: [String]) { -// os_log("[SC] 🔍] Enabling URL blocking") + func sendMessageToSetBlockingURLs(_ urls: [String]) { // os_log("[SC] 🔍] Enabling URL blocking") BGFileLogger.info("\(#function) Enabling URL blocking") guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) @@ -183,30 +179,6 @@ extension IPCConnection: NSXPCListenerDelegate { } } - -extension IPCConnection: AppToExtensionExtension { - func setEnableService(_ enable: Bool) { - os_log("[SC] 🔍] setEnableService") - - } //Unused cleanup later - - func setBlockedURLs(_ urls: [String]) { - os_log("[SC] 🔍] setBlockedURLs") - } //Unused cleanup later - - func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { - os_log("[SC] 🔍] setActiveBrowserExtension") - } //Unused cleanup later - - // MARK: ProviderCommunication - - func register(_ completionHandler: @escaping (Bool) -> Void) { - - os_log("[SC] 🔍] App registered") - completionHandler(true) - } -} - extension IPCConnection: ExtensionToApp { func promptUser(aboutFlow flowInfo: [String: String], responseHandler: @escaping (Bool) -> Void) { diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift index c381459a..db484935 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/AppToExtensionExtension.swift @@ -23,7 +23,6 @@ enum FlowInfoKey: String { func register(_ completionHandler: @escaping (Bool) -> Void) func setBlockedURLs(_ urls: [String]) func setActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) //TODO: Remove once code is exported - func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) //TODO: Remove once code is exported func setEnableService(_ enable: Bool) } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift index 5136790d..3b0c3c64 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Shared/HelperServiceProtocol.swift @@ -10,7 +10,7 @@ import Foundation } // The service protocol defines the operations the UI app can call on the helper. -@objc public protocol HelperServiceProtocol: AppToExtensionExtension { +@objc public protocol HelperServiceProtocol { // One-time registration so the helper can call back into the UI app. func registerClient() diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift index 335d04da..ed86cdcf 100644 --- a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/FilterDataProvider.swift @@ -438,7 +438,7 @@ class FilterDataProvider: NEFilterDataProvider { // completionHandler(.allow()) // } - /// Processes the flow and returns a verdict. + ///ss Processes the flow and returns a verdict. /// This is a simplified test rule that blocks flows destined for "example.com". private func processEvent(for flow: NEFilterSocketFlow) -> NEFilterNewFlowVerdict { guard let endpoint = flow.remoteEndpoint as? NWHostEndpoint else { @@ -471,6 +471,8 @@ class FilterDataProvider: NEFilterDataProvider { private func addRelatedFlow(forKey key: String, flow: NEFilterSocketFlow) { os_log("[SC] 🔍 NE] Adding related flow for key: %@", log: OSLog.default, type: .debug, key) if relatedFlows[key] == nil { + + relatedFlows[key] = [] } relatedFlows[key]?.append(flow) diff --git a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift index 0dbd62fb..fe7890e9 100644 --- a/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControlNetworkExtension/IPCConnection.swift @@ -18,7 +18,6 @@ class IPCConnection: NSObject { var currentConnection: NSXPCConnection? weak var delegate: ExtensionToApp? static let shared = IPCConnection() -// var blockedUrls: [String] = ProxyPreferences.getBlockedDomains() var blockedUrls: [String] = [String]() var blockedList = BlockOrAllowList(items: []) var blockedIPAddresses: Set = [] @@ -46,6 +45,7 @@ class IPCConnection: NSObject { return machServiceName } + //This method is called to start listing for the connections to communicate with app func startListener() { let machServiceName = extensionMachServiceName(from: Bundle.main) @@ -60,32 +60,8 @@ class IPCConnection: NSObject { /// This method is called by the app to register with the provider running in the system extension. func register(completionHandler: @escaping (Bool) -> Void) { } - - /** - This method is called by the provider to cause the app (if it is registered) to display a prompt to the user asking - for a decision about a connection. - */ - func promptUser(aboutFlow flowInfo: [String: String], responseHandler:@escaping (Bool) -> Void) -> Bool { - - guard let connection = currentConnection else { - os_log("[SC] 🔍] Cannot prompt user because the app isn't registered") - return false - } - - guard let appProxy = connection.remoteObjectProxyWithErrorHandler({ promptError in - os_log("[SC] 🔍] Failed to prompt the user: %{public}@", promptError.localizedDescription) - self.currentConnection = nil - responseHandler(true) - }) as? ExtensionToApp else { - os_log("Failed to create a remote object proxy for the app") - return false - } - - appProxy.promptUser(aboutFlow: flowInfo, responseHandler: responseHandler) - - return true + } -} extension IPCConnection: NSXPCListenerDelegate { @@ -110,45 +86,11 @@ extension IPCConnection: NSXPCListenerDelegate { self.currentConnection = nil } currentConnection?.suspend() + currentConnection?.invalidate() currentConnection = newConnection newConnection.resume() - return true } - -// func enableURLBlocking(_ urls: [String]) { -// os_log("[SC] 🔍] Enabling URL blocking") -// guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in -// os_log("[SC] 🔍] Failed to register with the provider: %{public}@", registerError.localizedDescription) -// }) as? AppToExtensionExtension else { -// os_log("[SC] 🔍] Failed to create a remote object proxy for the provider") -// return -// } -// providerProxy.setBlockedURLs(urls) -// } - - func sendMessageToSetActiveBrowserExtension(_ extensionTypeRawValue: String, state: Bool) { - os_log("[SC] 🔍] NE sendMessageToSetActiveBrowserExtension:\(extensionTypeRawValue), state:\(state)") - guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] NE sendMessageToSetActiveBrowserExtension: %{public}@", registerError.localizedDescription) - }) as? AppToExtensionExtension else { - os_log("[SC] 🔍] NE Failed to create a remote object proxy for the provider") - return - } - providerProxy.setActiveBrowserExtension(extensionTypeRawValue, state: state) - } - - func sendMessageToEnableNetworkExtension(_enable: Bool) -> Bool { - os_log("[SC] 🔍] NE sendMessageToEnableNetworkExtension state:\(_enable)") - guard let providerProxy = currentConnection?.remoteObjectProxyWithErrorHandler({ registerError in - os_log("[SC] 🔍] NE sendMessageToEnableNetworkExtension: %{public}@", registerError.localizedDescription) - }) as? AppToExtensionExtension else { - os_log("[SC] 🔍] NE Failed to create a remote object proxy for the provider") - return false - } - providerProxy.setEnableService(_enable) - return true - } } From c2ca56581de19efe350d3d51ce629610bb7e9d6a Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Fri, 26 Jun 2026 22:38:50 +0530 Subject: [PATCH 63/64] implemented services and code cleanup --- .../Resources/blocked.html | 2 +- .../Resources/script.js | 307 +----------------- .../SafariExtensionHandler.swift | 20 ++ .../CoreService/AppStateManager.swift | 5 + .../HelperService+SendMessages.swift | 1 + .../CoreService/HelperService.swift | 30 +- .../IPCConnection.swift | 6 +- .../org.eyebeam.selfcontrold/DaemonMain.m | 7 + 8 files changed, 65 insertions(+), 313 deletions(-) diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/blocked.html b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/blocked.html index d44aa0c0..569d4915 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/blocked.html +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/blocked.html @@ -25,7 +25,7 @@ -

    🚫 Site Blocked

    +

    🚫 SelfControl Blocked

    This website has been blocked by Self Control content blocker as per policy.

    diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js index 11153a12..30bf6b53 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/Resources/script.js @@ -1,279 +1,22 @@ -//safari.extension.dispatchMessage("load") -//document.addEventListener("DOMContentLoaded", function(event) { -// console.log("DOMContentLoaded"); -// safari.extension.dispatchMessage("[SC] 🔍] Hello World!"); -//}); - -// -//window.addEventListener("load", function () { -// console.log("Page fully loaded"); -// safari.extension.dispatchMessage("[SC] 🔍] Hello World! Load"); -//}); -//document.addEventListener("click", function (event) { -// safari.extension.dispatchMessage("PAGE_CLICKED", { -// url: window.location.href, -// x: event.clientX, -// y: event.clientY -// }); -//}); -//function notifySwift(data) { -// safari.extension.dispatchMessage("REQUEST_DETECTED", data); -//} -// -//// Example usage: -//notifySwift({ url: location.href, time: Date.now() }); -//safari.self.addEventListener("message", function(event) { -// if (event.name === "SWIFT_ACK") { -// console.log("[SC] 🔍] Swift acknowledged:", event.message); -// } -//}); -//(function() { -// -// function checkURL() { -// console.log("[SC] 🔍] Swift acknowledged:", location.href); -// -// if (location.href.match(/facebook\.com\/friends/)) { -// // Stop page loading immediately -// window.stop(); -// -// // Notify Swift -// safari.extension.dispatchMessage("BLOCK_FRIENDS_PAGE", { -// url: location.href -// }); -// } -// } -// -// // Run on load + on history navigation -// checkURL(); -// document.addEventListener("DOMContentLoaded", checkURL); -// -// // For SPA router (Facebook uses React navigation) -// const pushState = history.pushState; -// history.pushState = function() { -// pushState.apply(history, arguments); -// checkURL(); -// }; -// -//})(); -// Injected script for Safari App Extension -// Blocks navigation to facebook.com/friends by stopping load and notifying the extension. -//(function() { -// -// function isFriendsPath(href) { -// try { -// // Normalize and check path+hash+query -// const url = new URL(href, location.origin); -// // Check hostname contains facebook.com and path begins with /friends -// return /(^|\.)facebook\.com$/.test(url.hostname) && url.pathname.startsWith('/friends'); -// } catch (e) { -// return false; -// } -// } -// -// function notifySwift() { -// console.log("[SC] 🔍] notifySwift:"); -// -// try { -// // Stop network activity for this page -// if (typeof window.stop === 'function') { -// window.stop(); -// } -// -// // Dispatch message to Safari App Extension -// if (window.safari && safari.extension && safari.extension.dispatchMessage) { -// console.log("[SC] 🔍] notifySwift:if (window.safari"); -// -// safari.extension.dispatchMessage("BLOCK_FRIENDS_PAGE", { -// url: location.href, -// time: Date.now() -// }); -// } else { -// console.warn("[SC] 🔍] Safari extension messaging API unavailable"); -// } -// } catch (err) { -// console.error("[SC] 🔍] notifySwift error:", err); -// } -// } -// -// function checkURL() { -// if (isFriendsPath(location.href)) { -// notifySwift(); -// } -// } -// -// // initial check -// checkURL(); -// -// // handle regular page load -// document.addEventListener("DOMContentLoaded", checkURL); -// -// // handle SPA navigation (monkey-patch history) -// (function(history) { -// const pushState = history.pushState; -// history.pushState = function() { -// const result = pushState.apply(this, arguments); -// setTimeout(checkURL, 10); -// return result; -// }; -// const replaceState = history.replaceState; -// history.replaceState = function() { -// const result = replaceState.apply(this, arguments); -// setTimeout(checkURL, 10); -// return result; -// }; -// })(window.history); -// -// // handle popstate (back/forward) -// window.addEventListener('popstate', function() { -// setTimeout(checkURL, 10); -// }); -// -// // observe clicks on anchors that may bypass history -// document.addEventListener('click', function(e) { -// setTimeout(checkURL, 10); -// }, true); -// -// // for good measure, observe mutations — some SPAs do their own routing -// const observer = new MutationObserver(function() { -// checkURL(); -// }); -// observer.observe(document.documentElement || document.body, { childList: true, subtree: true }); -// -//})(); - -//(function () { -// -// function notifySwift() { -// try { -// if (typeof window.stop === "function") { -// window.stop(); -// } -// -// if ( -// window.safari && -// safari.extension && -// typeof safari.extension.dispatchMessage === "function" -// ) { -// safari.extension.dispatchMessage("PAGE_VISIT", { -// url: location.href, -// time: Date.now(), -// }); -// } -// } catch (err) { -// console.error("[SC] notifySwift error:", err); -// } -// } -// -// function onURLChange() { -// console.log("[SC] 🔍 onURLChange:", location.href); -// notifySwift(); -// } -// -// // Initial load -// onURLChange(); -// -// // Observe SPA navigation (pushState, replaceState) -// (function (history) { -// ["pushState", "replaceState"].forEach(function (method) { -// const original = history[method]; -// history[method] = function () { -// const result = original.apply(this, arguments); -// setTimeout(onURLChange, 10); -// return result; -// }; -// }); -// })(window.history); -// -// window.addEventListener("popstate", () => setTimeout(onURLChange, 10)); -// document.addEventListener("click", () => setTimeout(onURLChange, 10), true); -// -// // ✅ Wait for DOM before attaching MutationObserver -// function setupObserver() { -// const target = document.body; -// if (!target) { -// // If body not yet available, retry shortly -// console.warn("[SC] document.body not ready, retrying..."); -// return setTimeout(setupObserver, 50); -// } -// -// try { -// const observer = new MutationObserver(() => onURLChange()); -// observer.observe(target, { childList: true, subtree: true }); -// console.log("[SC] ✅ MutationObserver attached"); -// } catch (err) { -// console.error("[SC] MutationObserver error:", err); -// } -// } -// -// if (document.readyState === "loading") { -// document.addEventListener("DOMContentLoaded", setupObserver); -// } else { -// setupObserver(); -// } -// -//})(); - -//if (safari.self && safari.self.addEventListener) { -// safari.self.addEventListener("message", function(event) { -// if (event.name === "REDIRECT_BLOCKED_URL" && event.message && event.message.redirect) { -// console.log("[SC] 🔍] REDIRECT_BLOCKED_URL:"); -// -// location.replace(event.message.redirect); -// } -// }); -//} -// Simple in-memory block list (replace with native messaging later) -//const BLOCKED_DOMAINS = [ ]; -//function isBlocked(url) { -// try { -// const u = new URL(url); -// return BLOCKED_DOMAINS.some(domain => -// u.hostname === domain || u.hostname.endsWith("." + domain) -// ); -// } catch (e) { -// return false; -// } -//} - - -// Fires for: -// - already open tabs -// - reloads -// - back/forward -// - SPA navigations -//browser.webNavigation.onCommitted.addListener((details) => { -// if (details.frameId !== 0) return; // main frame only -// -// if (isBlocked(details.url)) { -// browser.tabs.update(details.tabId, { -// url: "blocked.html" -// }); -// } -//}); - - +// Notify native extension that the content script is ready safari.extension.dispatchMessage("ready"); + (function () { - // Listen for messages from the Swift extension safari.self.addEventListener("message", (event) => { if (event.name === "REDIRECT_BLOCKED_URL") { - const redirectURL = event.message.redirect; - console.log("[SC] 🚫 Redirecting to:", redirectURL); - try { - window.stop(); // stop current load -// location.href = redirectURL; // redirect + // Attempt to stop loading immediately + if (document.readyState !== 'complete') { + window.stop(); + } + // Redirect to a local "blocked" page const blockedPage = safari.extension.baseURI + "blocked.html"; - window.location.href = blockedPage; + window.location.replace(blockedPage); } catch (err) { console.error("[SC] redirect error:", err); } } -// if (event.name === "updateBlockedList") { -// BLOCKED_DOMAINS.length = 0; -// BLOCKED_DOMAINS.push(...message.domains); -// } }); // Send PAGE_VISIT message @@ -289,41 +32,9 @@ safari.extension.dispatchMessage("ready"); console.error("[SC] notifySwift error:", err); } } - - function notifySwiftDetails(details) { - // main frame only - if (details.frameId !== 0) return; - safari.extension.dispatchMessage("NAVIGATION", { - url: details.url, - time: Date.now(), - }); -// browser.runtime.sendMessage({ -// type: "navigation", -// url: details.url -// }); - } - - - const browser = window.browser || window.chrome || window.safari; + // Notify on navigation events window.addEventListener("load", notifySwift); window.addEventListener("popstate", notifySwift); document.addEventListener("click", () => setTimeout(notifySwift, 10), true); -// browser.webNavigation.onCommitted.addListener(notifySwiftDetails); -// browser.webNavigation.onBeforeNavigate.addListener((details) => { -// -// // main frame only -// if (details.frameId !== 0) return; -// console.log("[SC] onBeforeNavigate Blocking:", details.url); -// }); - -// browser.webRequest.onBeforeRequest.addListener( -// (details) => { -// console.log("[SC] webRequest onBeforeRequest Blocking:", details.url); -// }, -// { urls: [""] }, -// ["blocking"] -// ); -// browser.webNavigation.onBeforeNavigate.addListener(notifySwiftDetails); })(); -//TODO: https://chatgpt.com/c/6963cb8a-349c-8320-a036-7f6b9828043a?ref=mini diff --git a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift index 991f6635..7e403372 100644 --- a/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift +++ b/Self-Control-Extension/SelfControl/SelfControl Rules Extension/SafariExtensionHandler.swift @@ -202,4 +202,24 @@ class SafariExtensionHandler: SFSafariExtensionHandler { // // try? (data as NSDictionary).write(to: url) // } + + override func additionalRequestHeaders(for url: URL) async -> [String : String]? { + os_log(.default, "[SC] 🔍] Safari additionalRequestHeaders %{public}@", url.absoluteString) + return await super.additionalRequestHeaders(for: url) + } + + override func page(_ page: SFSafariPage, willNavigateTo url: URL?) { + os_log(.default, "[SC] 🔍] Safari page(_ page: SFSafariPage, willNavigateTo: %{public}@", url?.absoluteString ?? url?.path() ?? url?.debugDescription ?? "nil") + + if let urlString = url?.absoluteString, ping.blockedURL.contains(where: { urlString.contains($0) }) { + os_log("[SC] 🚫 Blocking and redirecting: %{public}@", urlString) + + Task { + await page.containingTab().navigate(to: URL(string: redirectURL)!) + } +// page.dispatchMessageToScript(withName: "REDIRECT_BLOCKED_URL", userInfo: ["redirect": redirectURL]) + } else { + os_log("[SC] 🔍 Blocked URL: %{public}@", ping.blockedURL) + } + } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift index f7887cfa..6ca9bd12 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/AppStateManager.swift @@ -51,4 +51,9 @@ actor AppStateManager { isSafariExtensionRunning = false isChromeExtensionRunning = false } + + func sendLatestNetworkExtensionStatus() { + HelperService.send_didEnableWebExtension(WEBExtension.safari.rawValue, state: isSafariExtensionRunning) + HelperService.send_didEnableWebExtension(WEBExtension.chrome.rawValue, state: isChromeExtensionRunning) + } } diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift index b116a3da..de6cd4e9 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService+SendMessages.swift @@ -14,6 +14,7 @@ extension HelperService { static func send_didEnableWebExtension(_ extensionTypeRawValue: String, state: Bool) { proxyConnectionService?.didEnableWebExtension(extensionTypeRawValue, state: state) + AppDelegate.shared.service.networkExtension.sendMessageToSetActiveBrowserExtension(extensionTypeRawValue, state: state) } static func send_didStartedBlocking(_ state: Bool, _ endDate: Date) { diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift index e6347c4d..4b4bf206 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/CoreService/HelperService.swift @@ -31,21 +31,22 @@ final class HelperService: NSObject, HelperServiceProtocol { func registerClient() { os_log("[SC] 🔍] BG registerClient") - queue.async { -// self.clients.add(client) -// client.didUpdateStatus(self.isMonitoring ? "running" : "stopped") + queue.async { [weak self] in + // Fix: Use Task to ensure actor-isolated method is called on its actor +// Task { @MainActor in +// await AppStateManager.shared.reset() +// } + // Continue other non-actor work here print("Register Client received") - self.blockedUrls = HelperAppPreferences.loadlockedUrls() ?? [] - self.networkExtension.register(completionHandler: { [weak self] status in - os_log("[SC] 🔍] BG register NE status: \(status)") - self?.networkExtension.sendMessageToSetBlockingURLs(self?.blockedUrls ?? []) + self?.blockedUrls = HelperAppPreferences.loadlockedUrls() ?? [] + self?.networkExtension.register(completionHandler: { [weak self] status in + // ... rest of your code ... }) - } - self.sendPing() - - self.chromeService.startListening() - self.chromeService.blockeddomainFetcher = { [weak self] in - return self?.blockedUrls ?? [] + + self?.chromeService.startListening() + self?.chromeService.blockeddomainFetcher = { [weak self] in + return self?.blockedUrls ?? [] + } } } @@ -124,6 +125,9 @@ final class HelperService: NSObject, HelperServiceProtocol { self.networkExtension.register(completionHandler: { [weak self] status in os_log("[SC] 🔍] BG register NE status: \(status)") self?.networkExtension.sendMessageToSetBlockingURLs(self?.blockedUrls ?? []) + Task { @MainActor in + await AppStateManager.shared.sendLatestNetworkExtensionStatus() + } }) os_log("[SC] 🔍] BG startNetwrokBlocking: %{public}@", blockedUrls) diff --git a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift index 0aee30fe..d6da422d 100644 --- a/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift +++ b/Self-Control-Extension/SelfControl/SelfControlBGService/Services/NetworkExtensionService/IPCConnection.swift @@ -46,7 +46,11 @@ class IPCConnection: NSObject, NSSecureCoding { completionHandler(true) return } - + // Fix: Use Task to ensure actor-isolated method is called on its actor + Task { @MainActor in + await AppStateManager.shared.reset() + } + let newConnection = NSXPCConnection(machServiceName: "X6FQ433AWK.com.application.SelfControl.corebits.network", options: []) // The exported object is the delegate. diff --git a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/DaemonMain.m b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/DaemonMain.m index 89cec21b..ee0a60b4 100644 --- a/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/DaemonMain.m +++ b/Self-Control-Extension/SelfControl/org.eyebeam.selfcontrold/DaemonMain.m @@ -23,3 +23,10 @@ int main(int argc, const char *argv[]) { return 0; } + + + + + + + From 9e35d348f9ebdb9ba5cecd32157c048e87799715 Mon Sep 17 00:00:00 2001 From: Satendra Singh Date: Fri, 26 Jun 2026 22:41:49 +0530 Subject: [PATCH 64/64] added files for Chrome extension --- Self-Control-Extension/SelfControl-Blocker.zip | Bin 0 -> 6594 bytes .../background.js | 0 .../manifest.json | 5 ++--- .../popup.css | 0 .../popup.html | 2 +- .../popup.js | 0 6 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 Self-Control-Extension/SelfControl-Blocker.zip rename Self-Control-Extension/{dynamic-url-blocker => SelfControl-Blocker}/background.js (100%) rename Self-Control-Extension/{dynamic-url-blocker => SelfControl-Blocker}/manifest.json (83%) rename Self-Control-Extension/{dynamic-url-blocker => SelfControl-Blocker}/popup.css (100%) rename Self-Control-Extension/{dynamic-url-blocker => SelfControl-Blocker}/popup.html (92%) rename Self-Control-Extension/{dynamic-url-blocker => SelfControl-Blocker}/popup.js (100%) diff --git a/Self-Control-Extension/SelfControl-Blocker.zip b/Self-Control-Extension/SelfControl-Blocker.zip new file mode 100644 index 0000000000000000000000000000000000000000..a000c72c9fd53c38c1fda29ba824f12783bf01fc GIT binary patch literal 6594 zcmd5=cU05a)=lUHqzD4itAt(!=@6v%UKJ1s)r5|KNRcMJ1qP)_4@IOGL3)$k5wTE3 zkS+>?z$4BpFoR{@T5o+{vTlC)Cue8hbM8L-9!(W=3{n8 zP+2(4%?0kr1#yI1I#|2#xZlA805Nuf0Km5&>giqpV3%{D*`d~zJw|)YZxL|-IDd&q zdlJ#iOie~sL&uPZ+w7d+KZ>oy%VKY7e#z3t{FeEqnyUULp?;XAKUypWIx7(s(3ynj z{+Eh` l?lFPx@;;dQK;UG|$ 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 literal 0 HcmV?d00001 diff --git a/Self-Control-Extension/dynamic-url-blocker/background.js b/Self-Control-Extension/SelfControl-Blocker/background.js similarity index 100% rename from Self-Control-Extension/dynamic-url-blocker/background.js rename to Self-Control-Extension/SelfControl-Blocker/background.js diff --git a/Self-Control-Extension/dynamic-url-blocker/manifest.json b/Self-Control-Extension/SelfControl-Blocker/manifest.json similarity index 83% rename from Self-Control-Extension/dynamic-url-blocker/manifest.json rename to Self-Control-Extension/SelfControl-Blocker/manifest.json index c4fae9a6..be56f780 100644 --- a/Self-Control-Extension/dynamic-url-blocker/manifest.json +++ b/Self-Control-Extension/SelfControl-Blocker/manifest.json @@ -1,12 +1,11 @@ { "manifest_version": 3, - "name": "Dynamic URL Blocker", + "name": "SelfControl Blocker", "version": "1.0.0", "description": "Block websites by typing them into a popup. Uses dynamic Declarative Net Request rules.", "permissions": [ "declarativeNetRequest", "storage", - "scripting", "alarms" ], "host_permissions": [ @@ -19,6 +18,6 @@ }, "action": { "default_popup": "popup.html", - "default_title": "Dynamic URL Blocker" + "default_title": "SelfControl Blocker" } } \ No newline at end of file diff --git a/Self-Control-Extension/dynamic-url-blocker/popup.css b/Self-Control-Extension/SelfControl-Blocker/popup.css similarity index 100% rename from Self-Control-Extension/dynamic-url-blocker/popup.css rename to Self-Control-Extension/SelfControl-Blocker/popup.css diff --git a/Self-Control-Extension/dynamic-url-blocker/popup.html b/Self-Control-Extension/SelfControl-Blocker/popup.html similarity index 92% rename from Self-Control-Extension/dynamic-url-blocker/popup.html rename to Self-Control-Extension/SelfControl-Blocker/popup.html index abb3a07e..328a567b 100644 --- a/Self-Control-Extension/dynamic-url-blocker/popup.html +++ b/Self-Control-Extension/SelfControl-Blocker/popup.html @@ -3,7 +3,7 @@ - Dynamic URL Blocker + SelfControl Blocker diff --git a/Self-Control-Extension/dynamic-url-blocker/popup.js b/Self-Control-Extension/SelfControl-Blocker/popup.js similarity index 100% rename from Self-Control-Extension/dynamic-url-blocker/popup.js rename to Self-Control-Extension/SelfControl-Blocker/popup.js

    8=qPkLk4G`1IUi6~xhZLvtDfW@1z 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 literal 0 HcmV?d00001 diff --git a/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_64.png b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/AppIcon.appiconset/icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..05bd2d23533e1d7c1af06b8d5d4a03b1e7a3c5de GIT binary patch literal 4949 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 T{e{r>00000NkvXXu0mjfG7@8f literal 0 HcmV?d00001 diff --git a/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/Image.imageset/Contents.json b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/Image.imageset/Contents.json new file mode 100644 index 00000000..3dcbc3dd --- /dev/null +++ b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/Image.imageset/Contents.json @@ -0,0 +1,9 @@ +{ + "images" : [ + + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/SelfControlIcon.icns b/Self-Control-Extension/SelfControl/SelfControl/Assets.xcassets/SelfControlIcon.icns new file mode 100755 index 0000000000000000000000000000000000000000..d49e3035c2a1bbadaea93bddeecff17b125011c5 GIT binary patch literal 776926 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%0D!gtOs5~R(L`Tw|mugXO>bj+*gA@ir%_Q3TXHKrV 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 z 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 )}3)&POFXs;o zrV`H`l2;4DkfC5yd-sx8`%Y+bF7;g}k7NGG#qVdr${3Y-a(gS=q+KmsciyxkX;x+@ z6yJ7f2T{**B{}G>LuiXJ!=+NEzu1&oRj!Si&yB~TKX+^!V11!N?(w&rTuIN(h+9#Q zV&)6Fx9^2-C>v^n?=O^Aebqqh`k^N5>V|Juf2guu2BSRXV60Nj3(s>)$g_&>Jud-v^S%(k2*Dn~WEr<^L zgHwp&7XqOC_W{?J>GwdVT@K!;4?V9L+^ss77V){(LNRZ-0+g2^r}%v~OaJYq)i?i6 z{xWk=ge9S$aO0nFN^@C57_YBLmKHs-hCXi8%I0hop0_osD5S5ypJu6L z3Ysncv1bZ+Ze>Y#n(j0nHfhB@OU>)fg(4^8~Xt?Lg$3Q1+l_CF6 z_;T9I2M+_FU$Sd}vNJg^YEKERRqii$TuXjKa}-^K8PndXx4*g0n9E(;Fz_3_m-_}< z7n8-GUz0KPeOOiOKmM7K^S-2|&B?+zh7>lbHdpwBpU{g`vo|b$|87!Z2)Hi4yxN6S z5%t2C$R;+8JSce>Exx>s?;Xm3vlM~g&)~gJ$x?E&My-+Hi#VK#+md@K*>s&*bY0cF zxE@KN2bYF5O$6YisCw2(hf+G9)6=Q}WW8}Y*m(NO9-A9B^jilt*`^gK`q;U+bLx-$ z@~72L3?IGqAR|%hJm}2wgwedgjrWcxjSDSfrzn4@?+^FyI&Rt7biC1V*(>~6Vx^ff z;f{aB@7ILPfC~!1t7qX4S{~fYJa$qAq_5BqCadHr zQ18cODotMp(p&J2id5$VG;HAELe@U=F@yh z2Nb$#XDAS*a;LYQP(d;z(d%|99kptS*Yc1fvZQt#K66pJ40u>J5uU-{T}Leag6?T} zd}V3^&UlR3;h5ke?R6Xt);`tN1cn`6hmA@m%o#i)tl_r;7~(ss_{wGfY?Muz$+#~V z8u{UP9(v!SR0V?Ej|bch%C(fho$d!^ekuOF9K-U!SuAhgfrGB%!j&~IO?*&VqaB_R z4xP-1iy`bME0uR(^mY+j14Z3gZaLoxQ7YMSF=5i4i`s)?X7U2d{;dHenXRG()}knh z1u0gMJ9!v}E6vN&3^a4_&`f_{5*3baxc`y z+*n+6Y@F;8GBxkrCp*9*>_K0Qk&KX}LpA-9ij41zoRvC2Wpp^10NhBr$_ zj^h`(I}~8GNAjk+V)f>8dr+JcoS1&4I__Ua!p~=O@n6@9_#SQmMGo3LE)JR)H(ZgF z5Z>{(Y;YDY0Z^tFA$qn~eX@5x-^$#5bSFg5S~(@vOhMQ6vYSe1;=+FPbv2cYNAURE zEnqt-fM>jlYG}46_=>EyOQmbtIgqG;@Qka0T0lRHIQcM_0l+fuMA4=F3E9Cl?o~kR z-;BorJ4!}f+X-R<-3w#}-y*?pK4yMd`nT`;#Dxv~!KXV}|L%VEBzLgDIXKPjPFk(S z#(wxbIzeXFl7T(lKj^DWETW`lOxvxy>FHS%|5rdi>BvK1j}g}0_o_R0uUcacu66%r|Z!H zk(P?dycsfKLO(F_4fNIMxVXfl$00dWHmF82kA-`R`lGpCK8dz&huI`7CmNyKR`~h@ z`&>=OnX3J6fAde;{y8Z4`LwF;7<%-if$x%fm-M^yWh^(6m6-GCNwzS#m{t z`#kkq^<-~mzFpb$7iQs7kE4U=v$H5N8mg2nTq86-qD0fVFd81Jp{V(?Gx2%Y#COh=elLJ+>FyG2`<7oCDv zLzsJ?_r0x9TsG)=&?-N7Ai8?E9!ra;_fyEP7+aCSAwOkhfW=9&UKbA3d>!P*XkH~I zoNe5=UkKR{k_EW`nS583{2A?Q)B3t?8f4EMG(5rkZdO&IwFro42i*W~})ugXuY96d5l z58a}Xaz29izhO63-A}5|9veuFBF^P@E}ueVhP<86an3kTooO~B+X-D5{lCCVIX=^o zuHN!v#x`4ylSP*esZ79~P`f-$$Rtxx2sw?hV~GZzb88tm?5i}1r4cg_aOcOc0pTFJ-&s0lyLl4>iT8@h_! zS6#T&JN0tHF_aJJi)AH^Y~23Y=N2+e^=t946mxx% z9=-L!28#YeK0lD{XXoZ)LBhykRSVXQqmrAYDi$nni6W9q0+=yw02*1v5wIhK2i%FJ0bguQk zG*aADp3l5MnAez}AKp-gMC`7NazA3X`R(XJmv^Hzk-4h96<6i@L zAXtI3jAZz0I7S!EcX-Zi$`elV3RtXsVomH>>r;~o?W=l~#Y`w+8b1WP{S1s6fjptXqjBboE741%kF8y^?Ig;X$ zAFxJM9iOJu(3lJ=4W)ZC=}o2fOluFTtDE5H zTLYgalmS_h+hKht0*La+ZT z?gITxxGvJ1&xd@^$g^(fMjKZlJzlK~+iiAlz7tjySn}mdakRt60FS46A2C~~-wueo z$U`5&w;^W(A~|a0IklO&l=MY%S!=0YZ~cl~oxxr{$fkBrRJ!i?WIFR4jJX@e+>!lLuL!E*@_N}iu0fZ0&XVqlj|*Z z4omOk*LsBHe<;2*NJ0uNKQ0GtEHhe7MmNcB%x-VV4&rSV$#u>zq4W(Y!*%JnzU0Hd z`K4QR;*_vlOA#%nLQ(Fjil>!L-@oJP&ANaXZ2k<-g2^yQ9!Gio>U}mbm2v^`5j3q4 z6b%Vs{*;VFmu`p@6Z%aI`M=#tHWI##^+rn3riGxVcE~gM^J{%-J)cmE{7$#=A9hfB zw;ATJSekeh@*EjlwjN81I77)1 zkG6#AH?6{Ns3h5+*9KG?SWER<`8x^oiopH22a_fpjp_sA4_b$0)<5t(d~-eri)>4C zSZ0)LJIZg@ObOG=ZRtbA%BL#SLA=1}VTdgEvW648sduHa+_^w> zl#6xQuggs)e4|x`QHDWKjsN9mRCmUifE}K{^MnhU7oZ~^tnvXrF6Bm7aYK3V{<`*({Pz+C1Mx!k%9=qrx z6MObWq2KvGOf$Fv7QG^0C^*wQpc%esyU^f5{Ef}6_~da=yH)ARFe4?^u$cHyynC_b zL%;NDA`W@L^v$6VV-M`2%%2cl~oUT0Bq|S2-eVnZ3vASvIwb5aEB}EHT zUArCS;*4OhpUQjP4@-Yd)L1g73hue_e`Tm!0RONfYJn|napq~bO4~ftcgXVp3YV0@ zpiilmmLQW4Xan}KB=dO%jdvVaP0C!AsN@5)`}B9LMIP^G?OM`o_jFxVa@~mweM)2M zztQqelNbAk#6`ZTBN`pTqC+#_*IxTtZOIlN&QoIC3vaR}{P!2VPsfjhiKE~fvSXpk z-IF-vz}6qx$F#St>%lBFg>;iS73+=Dk0s@C*k8p~f3T0$E!eZ~-viPILRTtw!}zMF z-);%B@hjQ#|INwYx)t22jos$|yup3Ax9s*#qYpIKxdS^C+3)$Ud!R*o5Y0)3E6G2w z4DBwWukF7!T+?pjn_13dJ?K=ZnT?B0*RhFs^q?%g<4^q3rzCVey*WT#WssG54i`A- zM&B?()MUr3nMC*k)xgU4`#<@guy?rdp7c^Sc<3*iq|IXgcTje>3~~f}<_BxyWZ`-z z?w*f-UCGP3+akAP1iB90n^HPERTde}Qtxq4Z?z%1smd(sZ@PSkT7x$euK9ws)7w z;cUCkk(&aG0PE8-qRI1{Sb*pz4_{`-gQ{>7p1eObs`P8cV@WUAu8jU9lA@Mr@ngI2 z8?6pjWwO0TsnNGgUD-S>@XVed|F3F6(vmuqg6+V<|DVt?;_X8M-HejxS2YFZLueZgM_EHlvUc} zxE05fD>Q37fQu9S{BSd23W8_+fx#px5QZIjFZGa24|QkvJ2tCFkrN^66Dt7c4U3qY zl6|#kOa#TgbknmZ(guT~mIheB$1@O4n+_|Vz`L16A;c|YdevHrTfzA}~p1Qq@*>lj6%9?o7;(gHkU&iGw|x&wr9-Xc_TgJ7hiyOSXM{uCcO> z>^*rTn!7ytnm<`z8vn-fwy;vm%~`9dy(4q@m_p_`XmSB5;PDD~sb;khv)W9m2=bgko&X_7t@iDws19{pg-K*-lJNfl zVep84WClco(>@q4+E;P)Gruxi&} zLaIHS-3?6?;%x*UXLvI<AnI_fBgWj!P>^fbFmKF ze**RP{67mIV~4%nY28>NaCE>WizLaX0y)v5oo&H3YHbG#&+3&QSC=vS#yYe$bTk{X zxZo&5W@S13k)?0o?H5_*w}_GnN1VbUgn6k1yobYI8&DU&)i=m7iX!eZ-18*F!K2Se z5M19oukhSBM5%SkeskT@S`T|!r6OZqW~z}D{>s4;&OOEt1q(8a$LOSQp4upOfsSA7 z0Nhp5Hv~|jD+{+hHLZUBKG&{7T6WTAPcej;5C8yp*h+Xh2<9tEj8#1S!5d zNuI0@fG#geH(mwAn1^j|ZW38&#saLeE&4N7gEiS<623J@J05<@Vvt6X`((PDG~cfc zUheE78uvmfV#RkPJ@@s=+t|g2lP4_NW+Ea-#epxRAM8W#4`(AJSw>e;nT*aDUTVOB za-&y_`du?~tf~k#aB08-VLsh&NfFPk#4g3cXawAyXhn{QVJ3n@Du9NTC( z@SAM^jDZ4n^0dy3)_;t08`nCc9-sFh#{#%`lY1X;%lS10U9}ec#v()V6oM4;%OBFMj{rmy+#tR#PW!T@*mgo@z=k?&wJ5rXJ)5OcA50*v^0!dLZg zxQdpRZSb#CKNd#;F0i%{j+BAibWrS|M?Cv{UisRoGX;}OiAp| zJ3@IY3eySA?~G;=c8KlaI;RldFgW*Yr7`1pBjgu@*gXL=w{@4p!gX%kDgvEBUOAR& zG2@aNCAiq}K&KZMz;Eb*@ds0jk^@{@Eb~B3Vm8J#&OZCKV9N#WY z{jXZZ)B(ITPDx~9ly@Nt7xh{?Neq--UZiIP$B9eyProU9ddRnY!c$;2pU1dLYw-IGM!P5+?496`G_OV~!60s?~Jy4=< zV$Mm{P)BS(cDnCe+0}kM7POXRAkuISWRbxhRTK}>efT?aX*=aq_y+k@kyi(1%fmgl zD0Zrk*vN`|0APDOK3id%+`EBcz0|Ow{N&nhEDxpFp_xgSo~1!vq!7NTw7hiUQP7`= z<&o2my|Dk@HXMutlzCk_@2#_3yE>^Dm{m;z7C`LGl=s*wD#w|yi zhtR7wZR!cMRE0nro6Nu$LZm&-vU9Pr#%P0hF@b4^%aZXVo`U_Wx-k8iu z!Z=A}S`=(?YU@;(I|rGiosFow2KHY+Ju}tk{FqRph!cK^LJkx%EC|@Jj%nJHuegxR z;Xc6;sT?2@>FS`*Y0F}L#Yzt?-2G}_TO3jtXVJcE%?+oQ?BKEt#h#`>#rUy~~7R*ZrLOIvy^*Pr< zZZF2=Q+>hS^=#R0e#u%mY5fIjRG?tk&^D*)WnziXFhcK_DP=Qf$$Zp_sbH2&#> zPvw7BAJ;4!#E1J_%F_hdpd|RfsK`JPYET+P}*IoZD6jJEL?&g zn=9Qy)dh&8Nf1ICHA(5H6kVA|4h|=UAxNzv@}jH!b^}>7%35cL*JCB?7GySaoxxI% z4bz%N-n3b)BxdkRydVxnh#~ETwwFG-vb{G?1yN2bGT^sE!iGy_%Aze^Mc%K-Xb3Bq z_y^kE#z2_(_%BFJ{5{v3#H5R?!SE7r@{MP?9-m0>bRvwg^uS3%|I=3qv%^$P);jbO}va zFu|#=V=%7-OcgUH@;-ChL+AS~qn5_{afV@kPgBjQRj`Fj%#O1?@{UB!YTyEH*RR+! z7b?vqX^Ec>z{}E_&~Mp7wF%L9*%xO*IaXGEP^%vR}DKQq_(P9s=3^@)$`OXF} z1tflTlo~9A9TdiAE}u*#1U=V+bm%e09G(m_ei<0EZxJT~76ZmZ5gnSq&dN)=bZ>N@ zwvtdDe&0M1?Adb}-OD7Y02{JUPZzAni3+KXdE@W>Iw0|~1C7~+rcIaojenxUSMtN@ zx@8gWfBD=q`9MB(I$}G^cb)gI-aBNe}DhK(_ijvaLT}@u_*Y|yaK3AViAfk8{(VChaw{kcypLAOp zdN>W=lI*d;upo=A0B{>{{D6QKkG4QObm2n~4(B;!h{Z*2`pKL5IRa`8}P^teZa&Ow%?8x)1 zu{fRT9YEhuUJ5V@-D=(XcCE`sZRSsDP1e(v!py^5ab%>eMLgl~Rxpa6(wf=RKzoxz3uEJ`>PBz z&Y^0w;6jHpExQ7JwVgUi#cVfHuh8)}UkfNnSb(q@&^Lo5NI|8of<~fXA?bw%(Br&s zd5zX^8d7hH(AR(KyS#bi5Mm{P%E6v5e3A0tNhiZ@eK6s(z#y$8f#S#4R^B8a{2>D_2>=xx#&rEf1j7|^K3a3?hjpQblklZhx*B=fBQ|_S;vJn zUZ)`$OX40d(3(p)F$ z0!&Vin|IcNuFAQrm-8U5c`fj`He zpGs8l;o)=xDE*FsO%{7M6usfhzuQ@`58!g~uI+^Y38!&v5&NuhaPhgOro7jy``|~X zYFjU_VvPs8?GmI}&f)PGL+1Xq1r1d|htlWrE|^pX2+U*WP*w9-i9V{xda-}ehsx64 z!N;F7u8ZB(96K|Ru@aI37O&atSil`J?_fDQ=Mw602lNK zacE>NWA$bWMtaAyl%URQQB^DLp`p#*MHF=C_EV#&@`i!npb)9+XkW}c<*%bF2S<; zQ}jiXCBkdC^u{3Zr8$f@StL>8b#vNF>`Q|zn!~WcNbcuqrImEtZ%^0G`H!2Hml1Lq zQ2LEp;>qRFL%uvRB??*0YT(ullWt8R#4#RN7kkO~K>pu5^ps_!lO#$d|F>E@`9AGd z_U~GR*efe@sq(FlwNFG%nSU`uB_8%91M<%-?O&00(92*Pcc#175~Mh)fue0fVAX7Y6CJYx}{vKliIcn9@mvh~2+R`4!} z$0r-}wR*- zu@{JQNd`e?m6rlk{W9}Do;3ZXM*?tIhkh9yT+|2sN%J-d*mYPmgAxR=Givh{U;LJ# z)@6a-WDOH1JxUa9j_jW?F)+@+Trk*p<#O4C!`v^jUR95432%N)a1=L7pRqYv6jPR+ zLcIgC*imIVB)k<+4^2r2lJ;y5C#wiW#~j{O7nD@{#37e9RU+7kxtZq^ww4fae(MK2d15x|(3otV!iS_2@=V zi`KCBuNB3a2WP3jNKNY@xN!+y!UW1gSS3FGg7NCkKfzzrqYjX6)VOk%2LF$yv;J#( zfB(OV%JHC}2NB7s97F}3h{TA^b2zx82vax;NK8qkCe2`BA(9h88YUto-M!JHC(_+r z3kK_b_x{{&-+$os%d4*E^@zI@_U&PasMgk|?9TIjBN~qKAoL_^hTB3|;TkvNnXy;08wg*8-(PtvY!M;)l;5Vg7-`;Q{LCnfMCEN-aqPX1eV&Zh~jZVj`7It~~glu+?2Ywyv!^ z)ZJ4pP5&ZscO*}tO#L#rYOO$&A4C-vBoF$v+`v=n*=N=PHt84i{C%(cpyN#H?Cq^X zsf+pE2f$9`^Uj9ZLvzP23i3PP(T(#5MwIVu>7+>B{z5G0RsWR3%%QHFDgrY3mU_(u zy$(tMa3LDnTo|u^SD*VoK+Y4a%(&=Q`Q^rRC?s#0U3p{E?RDN}jYb{Nd1_U4wWUyh z{TP3%Yl&eMC&=UD`7J{1o^Rttis$aWr*p{_)}n@A+}>Fyz|G6;{c@s9y;(HLOt@pL z3&uj$jaT<#^vQ;zzc?fN)C}4HY4Z{Y%o<(We}~%_ES2NYObJH)Fl0XyQn&xwP2`ls zF}09w-Y2)med7mhrz2PuE_JsqB$NT~Y{dqg>|g3$AZ|6Sq`rQO2r6d(*@WjL;y|cg zJjaKXka(p-gw@;7E1@fKaDDGAZhPp=-wpGLvoWoNPpueS4fazjJxxg>c+HTWI%fhw z94(0+>S8}Mas5_g_paxf@*KZsEYijWm}T_$sS*8r@Q&<6XM0SD`8{exe#9CjM4QK}{A;tL?R=<(?k^KWh5 zz?&|;%tX#U_!Pyg|{H@3Idc}^Ru+kJEQl1ZVpv$ z6@||PdFBPsAYTb>^De4hPTJ^GaJnYkhnZZ=-|(Y-y4h8%@*iyB>SWs7)hG48-Z0+Y zgt48psp#iAu89QQgmp1J@=;{2dW3-|m|)sw2^JYkf0YAolSva(Hxt`?%axuT$yjulW^B=@z4b(e9n=PgxG-fXC(war)FA=)!H=&67K_47jRbvt@TNfRI0u4l84O zTk+T)7c_U#cJTtxWOTcvlx})z4NGEM`n?2}*(w1LoWPgZEd@EKFpSh$tH-|84y;xW zc1n7(U^AJ=@irN21%^M|&CqvbV(UVkWw=DTx$s9ME;J59;tW2RL=&jZT8(R5?pOim zgQD^u)TSzW+uL3JmXUXC(CjTEvz3OgFCd+xeW$s5MzAAeGn7Z61~iF$=}Q7SoEs6! z4oRG9aM-v|4N5gU2<05sMQ-iW!wiu%dOF9?Z|3h9Yb5`*8w+Ai_`KUt3!^^ZqYksoA$6o5lk3cD2?@=Ys1xqrcmZ)O%2j zqgOxpuby=fRP3bgepboG1Q1H_s(vQi8=;%c)~gj?U=1;?1#r2)J9fVDpa1lGxhnsq zfp0HqWbjkEBsv*bKT=pZl)YB=(#m}fah4bX*@SsA{U%Ky06FdGe zFS!fY9(bJM-+B-}OL!9b=U=+g2f)&&vn!>mr(R2NN+isErPdHbDvAKw&rtB*^)G5p z;~n{*I9VWPqK&IpW!#AQ>@EYY2$!b7>aVQ}o}jHMp;%@pzzz=hYiVh}ZSmffOjtkT z{jU&p7t27?m3X5WuC7<8=Q?Le%Nh0D!kbsWsIhBv4&xAb(phL8rCrnXK|lALd3I~R z_5o-=Sf3px10u~h{bm){_k*MjO{IQwl##| zhG*21-5AAguP}zpj?b+j%<`kvjM|PV@Jd*|>t+07G474on#lv#&@ht&82snd(R1I=uf&PjZ+;M61FL^qwcaqsW;UxwU8o$h-%MkdS>3Cbyrty7# zcMQ)4{bp7&o$|hw%tg=QcO#D^{|MD1K_N?G=4BWBYdd!1 zDiIzkCI(J=ZYuu;eGvSj_HAOf%^?g*354!)qtU7ioXedO-H~)Mb00R@TI#xorv&W( zHJf)ZRQFKN`+)JC&+y*>(lP*xW9D1qlQ0Y|R^op+!0MC+GiP{t!km=`Q}Z! zsd&fz8Y{$bmlcB!lX$I}N>V&bJs%}uy;{zStrofAmyn*(vH8?jnTSP)&A~YpupYt8 z&g^f$l_*lkulrkhdU>3{?zSy-+q%Nuv*KT&8+Ivw+Mlu-WzLD+Yfz ztcWvC6D~R zDu!J`8~sGjZUO8Mp^iNoPJ9H{<-YG8|0ByHFVd%$zvJ^cU-i}OtQiEvNEbQ*(S*|b zwYM15QNjQ8s62XZ_gd_AlvGU1r_sYWn~i&&sS33uXm&#`xP>;?ggwQ34;@iar}Z7l z2CX;Q%3|9-Vin~TdXmqO17i}RkpG6LMLXoC(tczlAhdbH>Qf?us!SV_8ZafM>zwJBJ`eJJV zHiUrvir5L$Ty#=ccI!w6!7yTz)G+AIZxO3qYOohSgS?m%K_WXq!7?!wbA}!yG1X|$ z5mYt}(njO~UDFKF^FrgoU>f>vN8bR1PZBUGeY4!YQ1nK+~eNz)`H@Ep4h8AVb9J&7BYFi=@qw_JRh@i4U$@H5> z3+XZGctzCi0cJN7ex_+e9l7`;W|ng(cdC5nA?&_)^aeI<*9WqW*>}WRfsjBym{rt|pSinNlHMY6804 z?0rs#f)p!af86g4H`-m!8obNj|F+cn$#e$2z<I!tg{Cbs@5N^?`4skteI=Q zVTpYa&D9N6VmNUTYRPqta~mNpW`-%lf~yfm4qI;+(#KK)DND0sAFfYFCfQIPiH`YD za~?p)_J3M{#<F6wkB1WW$!7WIA=^Lk(1FC*# zAFaO{RTx6#V~wtl&DLKn*N-_1tblyuv&oWLDknl-{i6Iywp4lOJo!##w*o41WLj6o zjS$H7@{(20ZVcR;xEM?F-&IAvz-{+ye=}G~?K`1g^96d@fe`V$@y&~rd!re>I(pnk zsf|S?(ktX+(s=f^&WZ!^#wK9Q$s&-+v!sSzQjdfzJ@145n5k7qIB@gyPoT{RWaSS| zFFiHk=%iqOnn(Gr00UlJjd-amgk6(kv*Cw2#SHn^_Tqv9l4qB*Nez@4tn3|7+`Ef? zOInx&ZTp8s|UAAeI@!s1p>U&qyZZTJfBdV}$ zGT(~O@R=8nHW*Rcd?RnQ%S)EuH5sXjt}O0R1d|7pi~n?elIb;J6z-`)`cKX`hthc& zeBQM~Q6Do|jdf}T^PVGG4XNg$!5kmRfsX`$MiS^yyvtch+^LiuCr3_Wfhnu49SP|@ zZ_eLe7>xDbN&oBkZnLHxUvAU7i^vLt=@$qDt3hzY=buSF!5NNwLq#U(pE$L*Mn58% zm-duv&$Z9?25*awgjTi^#E+?#(4k`*Cn6AT@-!bxVzNW3C1LJ4c&RUc6J zDGKxX@bE&9E9md^j{pPjy9fR>DDv(ji8R4REM{OAW&2@tT1cU$TGz@K3ii@&E+?T5 zXvxJ5*+skzW7Cy&dFv0fhy}@VcV|zi=34+V!ovdqQCCZ{ zo}tC#ieY93m1R@mpiJ$6g#^a{C;s16zywgBAHAqoG5Ge%SA3|;tds7*DBsNM;H=GhP9Kit|b2-b|x z@hVg?QE!q6ZSuoIZtpx|WTZ_$gZSXGGxgbd72(nyyG%q* zpeo5jPBUh{?TE`##N~4Az;NqlDWW&H-5UDZR#N>wA$`g}Q&9SE2Dz-aJZB%FsCN79V*0rktWzIARojqHl1z`)wpaBa)sd9jl-Pry zmnh%=ncT(6%y|HqJ0_fO=XtXNo&@pWfN%G=a#3UCv(9Mm4elr83yI+;x=z9AthAR~ z9H_@m&t}F>fcv}(I2LnLT(BtkBDCg(V)=gXfLz!yXP8eWsc!W{v0!4?Zlz~-$|3fL z3!;$M%8tiJ+R>j*c>slcu^{T&0rRCa_VJ|bHjEpNgE{=Q`L8=UG#QF(IF2cOju5t% z3q_a^p$f+mvq9hYZtd7`M;tfb-#@d3A*O$>OgX)sWgZYrSus@$T=kK-$ce_%5yEYV z8!?{Em42PN-wSru7L!k4roZ4pr(ClA-P=J-&oQi}I9`Q46sVU{PDj zFO3!1#6>Wc{s637b54=>F-1oc-m*C*^M_|T@rao@174(8w5U{k>GJtO$u`~1(l zjHPu#gW_b$c!#bha#L6w@{yf5nK0Gc)juVKw&C5zsNXkMaDUn=7u(UM7mEq``(r#Xbom;i}ie}c&E`NQ=PX@T`Ye*u#Ns>R?Kn$ zN!9%!IFhnXZ{gdX#xnkxjUFdPBa803>mg{Gu$MMg2~~GCEMY5@&TQMhMQJn9s@_&r zx2KMB3n_}r8kFQ>`2d~G+F|Ocl7k2oFrSr|{KRwH4FW}(X76^KR>f+1-j`@D5Mn=x zC#f%A6KNLw?s*LeU4!iTcJtU=9_;W>GB3i=aQew@P}vjBl?V&untsVFx!Dxb&x)c0 zi?so)6KbA+cn|*67AyG6Q~v8?m;Fl>tJZh3TCIA*E&?BSutK*!zEu#xQ!V~eOumTe zUyjHoe3OI~geAZm??A+f5BhdLJS*n1aGaXf|D{p^J6Hk7g1vZ5yEHc?*)=e>L+C=6 zTb;Pq?gj;SFECAM&uq(Q<5s2&dTi`|xKEfC=%ZV+?L_3$Ed?!~5h**G_6Pu@VY$FA z2=3N_$?F=yj%KVxYuxx+Ir@!ML`~98HxMFbe*aLEoQ2xXOa|EDnU;*E|q;Pq`LJ*VjYL9)rKHa4_ zzR#$t%`q3xQjH*R4VcXmV6IR~-5pigKGM)7%-v5&6ymLU8CDA>>9nl%w8lzg-ndDj zFL|1_B?O%-Y^(?nvqR%MpM$3y04dUHA1yBS6~_X*x9xAjzNI#-1$~68J^>2HSt*RY zH&b2wkDs!_RjQf`mxbKec;0@a5#{XzrV)p!0bY@fH1#J}{|Fv)@#G)M%j}3j&1LFS zpLyi-i;S@m@l*1?t@5e9z|jNR8?@Yd4=ay}R1f<0;aOKsv{3W~bsetmfs&qqooLmJ zh$(ZSJH>P(!e%9QH!il=(y`$;zJ*`|t^nqgLpTrdiMl~=@Y4IkB8d3Ekr$^LOHJ3f zs!ABNevf_wgAd>no%XsOX^Ud$99J+JSuq5`G`U4U(s^d6oKWn_6G z4&Be;1`}6<#qn(6<>!;gfp<3!yVyQNB5o%31ea$_r`J7M>iRjVRZlnA*>v!f*!P`P z^(b}7O$e|ZBct*rV+joF@{R(BnFjjhFO9vr;{6iZ`7ha@Dg&!&xiZL>5KPk50B!ecG5&oioC8SH>CaF85Lc z;-IX))S2I0qLz%)2dMMXgzXYbOYf7+_awa?JWZ$>-KQ&ZZA7?iV|UhJRm7o75VE;=ZQ(ef;H&! znEKD%yMK0et)4y3Nwbq)`GB)|C0mSH_>_i=rl<)e^7J|jSVEcoam)$3NlDf_CrhHV zzC)g#v@|@@4&np05;!Bvuwihp&7c&?rdbQ0+QI%>cLl3H)(QUzk%BZ$zXM72JtRZKVU>QLX}0} z=Z~v5&EbF34u{!A;S9Z-RR~IBiixCvo4~ zad`V0^C)diB-^=9orK{EKKn?*RMT_S3zntvKNq;U<{go`4G}PJM?Digf!ETp>=ku61at z`NCAfPYalv?77N&y;sh7*6Nv<*w+bq?1&qaE+yTa#QqbbM+DJQaLAQ#T>B?mn4sc} zApL(99Ko(|VbCg+wScYDdwUF_-%@nM?e*$li0Kod{fPjh^ixh8aHimIN)vTaF$QB9eo0GyJd zOda?$@Y3>6+2#tI1}x=9ko&W1cIge>PxO4A;4RZEm>oBGy?}&Z17w`Z7SuFJCFF0L zvqZEMNcI`XOhD<=Fa2l^^?n)NotP(*(qi`%@>Q~x0SSj}Q~K6snu^9KS*yRGu42ROzC(A!o&_Ec1k(iCf|Xf)eU25m+vE%|(8qiN-6@6?m_03P z_zF}r$k=$SY;dP4!C|u02C`QB=vEeVQ=9jtk7lGV$`@uvP~&P37T+kt&{tHRDCCv0 zO9Y!&>3fDL-%!HpdjT6x1}gvQDXh)+_*8pb_^O#-7N1_CqFv-it{4nQT+I~im9hUS z9vH;faG{#y8=mgx}t#_C1&zTe~?3)!5TgThNe^q>w&pDtlQq5q;6 zqqT0H#85?gdxF%<;-k_w!@G5;Z(jD2_vN%AGoLb)r%%QgXL5~0$q|%(g7L}s zyXRNnhfmavG_ma?JojrnqFng>&24}RuhoTWiM@sP0(R;1vyCTl65C%#TNX8W?Se>v z3h&t#n%^C<5Na7VL;u=*ve7SNGhFLOarBw`1&zHN&u0TL!47=!JHPKEX{^fPvavHC zbtDw-RC_;@pclgFeLV8G8;#$5j1_ry{cm2XB;11oJvcT~KwmZ3MG6~Fc6RMlylHz! z3&KJ|F0C$I?be6(=*RqB*?N2?qapX#LsS*~0nT?p&LP#WSrv4vBV1FRmeQ=t!rAvS z+lwPg!2_-Ik{+L~>;{K#*3#KMl)+svxK1|g-E5L^Ppm?XPLDN|+T9p+# zGJlyd2GrTw9IG=|E0g`@;jN`N8quQ2(0yHfiny_S!~|?kg>dXsdgxJW3=xqO}8pKl{$7~JQ{`< zi}ge3n=i3HUjoyH{ZCWpkvbtN0!eDj@`{A^_Mvcj;@(9q%FIna>?it%6Fko3Fll!S zcVhxRRjWlQYEO4nk`ncMkXDv|KhH_r&0IVSsyOCazMF;_D!=@{4YvfAHwfOQ6tMs4 zVT*YdF9#{T)>M<(uYG<4nxPpWsM*;O+kYo=gH;uYbUfe7o&eZ$tyo5ZTaf@vD6T-U zNyT)%)i8kX;*nU6Ej6*F?HwkR&{Gdgnu-PNVu%p+gjvrZY|Bc!m586@U}U7?Frb%P zi8Cu(y(dakA2){aRx@K8v*1hKn_|xPv@9 ztkss3R+h$%*`eMAybz${T<`-hwg2pVG)4Ig1pWKHBF(Ua@WWET8&yge`{<{8!As?P zF%fP*u;|M>lo0(}iJyD$`Bt_pO^kb3>l>KIL=I~U9j4+~A|j0748z|;WQg=hhY@zd zU?y*{v@^_MURTBbfs8&=s(!C)oZWn}dvX7?K3g_MdsjY*>cW#-P{Wki0{odqT2wfv z5?;?cBHLfwlWe`*958~lFn&U7AQO07USbz(jTGTQ2KCG+z zlKqdhinP?FO-Yri_Tazng!ZP@2;p}3W|=jv*4v-jv5DK??aE?|xdDWgC^gK4=loLw zZG%bw`|MQPV#=ec`S2TZ)Le%l-)B!77$IGZIr5#!r?>m-BERrY=ZgcsM{GElZMlLy z>gqawLAI#((uYP3A1t}FM9EPHRLGB1?)FH6ClOrf{anF0@_eoEE|QUlcwbT7 zo^g17v+n3YYVf=F?zF+<09f$Epg4Tr2_B!K=!x>q{BJ1b^_0Aq&%;};E;Yw#wa`7l-qU7`b-CI=@k!Iavwz*K;yOlb~Wuo zDt%q6tKCYu!p9~9AMQE7WMt9wD$jL)r4^Ycc>iM)sx&W^YDwC{`R#I^A->yRk+Tb4 z;Rdx^jH;~ho0R~_z*f20M085Zq#fYkHgz<4KT1)Tdy$;}19_YlHMyT2TzK`#%Ac5V z?SAA|Tjr+zWyMmV17;PQ%KagAkH{@9G))>1S9wcg3=Rk9xFrW*@7|4%?n~=aIz5P0 zVvzd6pHR9RF;^!4?&T#<{?eO*~SR2~V2gzbbI4Yli;sm2mw)-<4yK zy|#tKcSvr+bT}Nf)%1;orLQcZ*w%q}n5SobIbVU)>uxB{W zkUPQOB8RcxS-@k3*+|>JKaq7^BEMiy7At{4f3#*U=n0bWtfYqDR@$$N%&rFu*;+&Q z$ktPdj_jDwei57x7@0|>>Uus|J>@=9V7H~T=&Y?>Yb3L=cB0V)qIC?X!U5F(Is;EJ z5XU;5HaOcGe%LycV}?!CvEN;~#EmR2tXPy&QNJHA*IqV&-N^9|(#X~Rw>wcl6pjE2 zihN4ws?yr~Bwpx2#|yLVI1@eU#7$zi!$^7Yy)<8Jx(SFYj}Sh^;u`6et_&a?1Dp88L3nuo2iF0=y898_I@{`UJ}ca4NmfaRHr5_Knb% z$rtvhskDVJ+jQYdIIVE>I5;H$|0I0cSWng!IJq`OcySU9&vfj{DJ{t4c#pp^2cEV% zrAT##WONFh@JZDMm|I_!aPc?Pd^kso1qNXMwMF0ZSZToH)ngFOt<~n31n6=O5dD5< zxnJ-2`Do)s(wPtSd?gjkDf$rnec_-pLfhj;iz?2;poZToiIj%;D^Hent4Z_-Z>aXW z@=YAZum>Z~d*R^G?NA?qjfdDkZzd-v}w_eRV^*2Y;zK6R9 zEEtUjZu|Ugf}(a-r0Hpuc4ZuAujwSkc-`*Tw+|ykcQ$#<%_)g$aKk`SfTY01`Ghl^ zsBzj0nIMn1N#GAV@&vbuMOZtG&z41BNH5KpP{t)|pcekbxF0tu=yT6MG3zHxw5`J@ zf7*q%jUT}EORJAo{l@G#M-(Z>=6W4&0_u;wA;H*Tz(_YRl{(0@f@I62krtkRfT2l1 zni+l*-g9I*nQ&(#9o&-xf}`>LYJY7td~Zan8b*Qv}cIsH6Gp9Kzyo*CS-*g2-@M?_Er~uA<=J6 zdle|FO5T`nph7Vl9Tt#|0L*YM%REP9ypo+^*PBYDeJR#Qneu|#%Qu46D4K#_csagZ zLZ$RItySM+)6w2_{nUj;x$p+@S3OS%SNuDt^&Z}g(S|Smi5XE-9GrXZ+T?ORHn1-O zPBjKEZi*)Hg+as*_#r!=qQm*v5aV6;#OppFw*@E{0C4C3ma62VaaThpVbJS1N2-5kR6o79$LR zNPo`11&!!yIU(c1#dqc_?qTq%ZE!tR^^YLK+?XXbgJ z4d|jtou4T=O(%70L9?aUVWD*UO~{kBG^&N2ljJ{(_T}>O@SA7ITUR+(9-vxE5Wm(X5F9>W z8P0x*w^LP3Ru_U?W86H?cdzAOHgZ10l%jt_7FFJeYT&GOGIdpg@lA2}U|mD^N?Gv; zR(xyqFlmFk$^X9Jk@DuD=V!~%mZwA?ZlBi>R(%@c{p!!?*l!)fmr9SP*KYp4^<=(S zis(?Fqk*Az5#2H*YMa|WyhAEP6sKHSDK6SQHkY%?d@LR6HXjw^v&J_$XcOvoqx z?u!@h|FYu8B~~WJ^c*9FsTgI}AI5}+J~^x~nTy>jFex7RRUY4{GUAuL($@LRHo_{{ zL})J#Zo`S7sLhqAm(9D5VZLVwXhq@HZO;tFGEvlfz&$nHa3iExfBiAL9lXnKvlmA10E8mybHJtus>TJ7K60 z((XA=@sY8!nM{qqSO4Y|X)i#0 z)(c0u?hy3}s4o;Y$i$t^YdrMv`Z?`^y|kJ2w0-=Mny|8Z!hb~@a)eK3_r7`AQkYZ%rEpWI*jc?nfXzFP|`w?7jbO`S2p9yn=swH};hDT7&zkNRMFY=VkEc9?Aj;(~u@nEBksj||{cC3e^}kfbdY^3HS&a*9r zIsbh43|>LIYHmHDIY`l+6}9`V_OG}5Z@7L8dN|$|afGs5#7rh(`d;BMGoP+Vy8Irt zzVy4B>@e4WbO513!c_6qaXF1-(_gD#{)n}>Z_t|xDGbZ2?L&*39W@Vvk8 z1V!{kY?!=;Lf8YWz#SXyzyqK!Ex7)uHX(NM;~x>~4jb%SGDO;E#s8x6iHF=UKd|O^ z$*Hj40SazIT4tlSwA$RQ9dg7+^WFY zOsAG-%BAo?X+WVc!)~{xfhnOL(qI#5f6WkjPsLP);qLvjEMl$T4B>@GCtNsMWJ=hO=RuvY1^y=HcJGz=agvqB=85I}aGX17J zJI^>X`QSK#x7y~BN{DH8@kRMuT$QiqR(cFMn@heLl_GL?HsN5{FyiY-%%D+Va3%X6 zQ@`&W2I*}7MMr+fpjk3utEBBH&SZ;19`MTCOiWn8&6W*@YFdfXLIAaI2KI9??vW9@ z!osu3%evi+v%_a3F8fPBbWL8F8=wyx%X4K8BauscsuU><)6JyW!EosF)%>k+ih(Vw!sZ zKNx|I-Xnp1ryJ(T&pbMQ3PyM)Z`=zF=jUxFm2MLlqh6E}I5A{)3U4rTg|KD8jjpCa z&OneCiq(T5t8OWIY2kvicBUeeNzdjXQNo%V&<%TAA$QWdBpW;t@tF zw>Cx92XVS^^{j!@J6u8ovz;|2aN976Pc7afh>x_^Y8QY4j7eip{H?7IT=oqbuNQ3M zdD>ed(eDTttzzPk3L$E^bXFQ?Lb`9~6d`W)g6vhgKP!%dFrJkRu4>YOoH0*vGavSH z!^@y?gD8UiX%ftWvU?70flc#K!z;h)`+1cZ`D1W&^1j9F2POOSE@pvr#%%qVsQid+ zQzfReO+~y)^VdOi^}TMRzU9U8)wxWij!(}Q*~%D>XSG~&HYJkjnSw^~__<86MK2*Q(@U&=+q zDr<~%m*3XFa$m{Mzbg{;P1|gPW{b-XMmaASuJ3bP*liNI^30cTF}ujxyQ4v)S1O#k zU|%bIRWV(kFP5zsHV1!aps#V59_RJhNt`W@$)fOmMWMfK;f`SSOs%{*ywxWW2x z;R*6CQ5bN;;wN+f!8X78^zvb<@IEu~L zNQ8+#xdXtWKug@(fEpl_S(O8`b6 za~(~Botl=r*#*`0ynrF_rL~X=o@Pwm8@#pNo+ed8k@hmv6ck7N3|=^c`&WSaOAirg zqy54&y$<7w=f~T2SqOf6sKndl9}n%F8JyHDW7|p%n;OH zPCD)?{WXI-*C^#Xse_r$Zp2742LloST+gTBQv@3j%-G=g=sv*E*T@E*d&_daUH2j5 zIk)f6+{*s3;+a^Ea|KrxVdzn+e$n%|`h2sN;t&3Bv+TKj<{vxMXVQvz4-upOJJ(?1 zn^!=bFZw@8cL{6!*~4qi=6Lo{t2sLdC|0&?*uQwEKe23Rvg`_?Rdn$|g85|#8RQ-V z5qd=nY1KPRF}l7(v|IYK#IjO&azrao#6VtDeaS6(tbDh*cZ{hcg%v@7=Y!j-nb{*o z{6UMAkQ5eq>Zb1@*v(AuvO=Qw>=Fwd+%2d4a> zYzNSNoi!G7v|E9?(IL$wOEHcHT|2sNPQ{tlvB5r|GQ5T$zyEmd85T+g#~JpuGa|PB zL2b}~yQ_BN=NlQ`B(S4J4-aWYSGPU8XN%@sIZI4^jR{m=oGaNb3Ct0p zo0J>a)3z>{gP}711Xi%B#NWWk=RR%1eI)H?!g0=RD z2!Ojx2hhJO6z)OsGGIE*@)O~*kk{9%_)3=q{py-F&s=!TReeH@)dSsQOTA+Adw z#pYE%zpYed4Tw++5erU^ELpCY*bu8>) z%1oT}!OW)>=dcN#-+@vox?T^amR1#7e3{Wry`?4n z?r~zo&BG6lNW>kzW<1|~%`E=g_xs9HQdeVs{7d`XK!2F`I`Bu6$b5#`nUBVYAN_gb zaBusZT#b9vafgyqW3PK^n$F9K9FMF`jG!Ld`i|X36q3Om@;-%?WY9>G9`UxbKrmzN z#=F&F6I@(5YM1|axRI(C*T;5g5YRNVfcCzdxfzhEpCtHzNTbyAd%Y5%>^(?4*y>g~ z(iJR4!tWmX=#roYVH?~maZiBD2EchFGYE_xlv z-+K&kTwlTREClxjjenDU`t)Yok9^SYwXNFk?rCg=kJ_N2+c49Tx&oD{9lpxUt{6n@ zMXwsNN;ct$nHbX+qcfnhpjx%MOV^9`KaiPz6;SEB9ldF_vIuCPtuJ=$=!FuNtqIF3 zeF#i>ToA_3o*WKz0R;fpv8$LV($d{4oCZ=hZVW$+RbsWWkJtt+^H&@o3=iiWR&T9v zuz%X(ASP#nQaCU$7UacDMNu(yZY1{wG{HbtSNgcB2koTI!tztqAQ9<4(S?Mkt4XYF z7`*?Q`(ksTVpQgh(QLkh(k z%JG=p_mD!q7%3;R^NDN#aPqaVH=T*75!v!(#Gni`A!Lbg?G%fxCb!J#XbFf0oCjNN)Hx~fK^WJaMG z`S8LkLA9?=r?{ZfVp|t2p>@>uOOOLW0M9)pU>H`pgH0-8kGT!o;cMcZ#5ZunElJ24 zWMVPC_Fh9#0?@r|y8wqp6UE2B&ARIr6z7RqyP zrM}tOXh1FJ%-|~|tKfxp(N>ureMXoIYg2H11|@9N9PWdU?&iAWX-%^3VeQ)v_-!Q; zJ>PJ9$;mlDd|aR1fmc83uZUge`TD0duwF|a=M_JO^pW3tk~l|zV(8TNsF~`WVWI1827NdW)quXMbT2~sPSK8lsqR&S{Xf4vTyIWT|`hB zwAIn;+6dX3MK_Y}n}MHX))jMfc|msu)sXD5n!NgNAW3vTA-}!22{Ohi% zrFX5@dOYz&j_6{8<*Q^(jN1!-s#}=ojQaWxYi=Xll*y<~!^Dg0O0?3jhNJ)56i=Yf zyB^G*27EBn)&$e0gcUqQRdS9;gGCUN5(+`lJsA0dz-xQ{atPh~{c;zU4`SoABWF|F zQXsQ7%~sFA=(NF74UE^p`I$MJftS(0>!80j0Z%efr%Qk~Sy}uu3B`8bjTM<{*8PPM zWZvTD(=Cg%m3ytaPi&G`B>$K_Km4GG;xQi%fHVwY?}?cJ*H0A|)h;xuL_1q~``Nx2 z3++n=JZug(N4akx_U7(6jOZVXj8kmQY27W&aIxTcc+BJ2Dp5xMsB%d~?m+El2=9l) zK@%8cWbu~qIVl9f!OOPPV${>L%~-}jm%u^`P0IC7qP0L21itx0$VHKNvk&ErY^S#( zS86MuakEc^%?u59G+PGYQ>^b5`>gN(9TfB@Bd;aVVG!yub1k2aFTu^JW98AV7EM4O ze(?3a`d73;`kR?_K$I~n|Ld1=|JLHKc|KWyExu&gvm73T-$$&SU$t6d zwYwfqR}Y?s?wqh#cfQ-KOuXi0(5NlyDVz?z>&HS-w1+B*miq;kFS`d6pJz$Tj*)r+ ztXK=QSFX{B`N|JT7q6gF0FO?^eF3d4&NG=Y=K&V{T(IH_d@Wb-OSySk%<{?B4Q^tz zxoRZ^Rf;pxAVU2|L^Fi_ef`)veWBk}}F1$>ZMt-gDeL>sj*gb+-Ld^=)sa4xfHh`kqP>W5q>Z%prsq-#V zvY=h-H+ID>rZYr~SW)TyG(aB_m8Zmm?wS(b^c7FB&PztQRcRJuZHUv` zse*hyWL!DVY>xMELiwGrVD2l~i>uQ_%l{Tjk|<-I4Q+iKG5YF1EKLWZ&TOPeYd7?t zINtZHImtH+#{5|9zM_hqUx;2#E*c9ymIM_g$8sD zz^fn4oZgdgb}F~cf~HyCt84JbCU-zRgTA||O)Amsd6wI!+qb%vEE!>xc@UDs(4L+6 zA#_;th z*??-m3m$!!M8T@RSp zYMu01AZqFDK6W()vi^*uPr>Aa`3f?xu`OMi4XlLH&8h&gvJez{@}M@c$Pu^0Ygs8@--J{T+yw-`4g!IpZ*XS4_{x$Zez) zJHp()k$i+VJggebc7exhs&~8my}LdM?oEZr&V4p|+TjBH;WcN@Nn@quvnC$;Kc18K z9*79uOjf;Pk|$@{j|+i;8UN1It(f8al_TG+69U*I~cQ^LXjH0tY+_ADa@)WUPm=>!e{@jj~x6a+b?PfE947)!v=pP%>2vipr*v-7^y2_W{y_gF!+nM8g+}ga1t}~ z@7TyS)P8BVMdA{C{OZ@!vDP=*Ddq0S%4mGrs)*97{#blXyhE_U5wZ-oTlTw6)_txQ6^)u3hWu95QD$)rq=xhf-R)6lgWo36E!aJUUzMe3X!@!>Ipz6phK?h8JW8# ztuFq@LO-Nfw%&0KT)$*%IgsNhcxPsliHMj_r;H~vcDQ|+yEG!B%J?lHcP99 z0x0P@AeS?O&cGSDF7(@^7%SVPZ^pek$~bY&OemT$gx%<`27b0udadoW}1Sa%j~kb=a-X_q;QBg+J!Kj0DpPZ*R&v*n95x* z?j8uLmQBLT#{ptC4*|Io+f}etaxrHTtyv~#}j+An3n;hT=Ocg4UL8? zUCO!d&gl>|)VS&K0fw>V;msopO`j>dQlcv{E;+Ef)3tb(hhfhl&t>WeBZ@ST7xzIr zt;N1}^GNyJKPuSTvyd~r>dAmeQqEdF#K8q%-H2c5*i_tRjg@;3f697T>hqzO8qGyuhy7gX($XWHt_rMCJLX$0VC?4R`8kYtIaEC9@r!^Tu>|DhC5e*2$$weVx~t}S|Ns{xq_Akbix6rCqH`% zd!d@X6J~@}ENe-eaY@Vx_J2<+vj@DJ+O4d3c)~rMWxb^zP6iZp^yu3E?-)=D-180^ zTWr9aZkhvJi5S3`S3I&0&!6A05WT}M`z1h0e6$RB3LH*Zw)%-U@V9}n(&{CHGp^to zXvH1G`@@$W|IGI~Y!Lu-=fYF_eoEIPH|?bR36RNLY~%GmG`?1+4ae<3`y5h^{zgG} z8Pmq&^U?Ejlk0jZ=r5>>1A2{BY+}u0RMO^#^>-Arnj8~6I4+vJC4b7bA{>ECGJHKJ zgQ4<}41LTSswtlxSDD9Hgi%Y}7Y_tj2{b2nGx!xS3m+0PA575ruB?kzuJH zK4hykYsdj`N=+Hg6TaG5iC1G{XX9^I-4#~5!~P;LpEx`BtgCE&q##voG z1E*q_U?bLQ3{iXi?gSjZHG>cuS01`wM@-tTqgD*p zoJDj(W$9txi-O}(>q@A<(LQ!0)insOAHUo$wI9CfCZbl_NUdo8Sbvc zj^lo$`4>=Wp*XmH9hL`ANh6QcDa+r3PoT z>WtcWnv^H#4M6jIp|<+$>Bq+4cXNkL`F#!VUfpfzzVlV5JI6QPN$WM$<(O|lf}$E~ zrMEEoPI<-wMm*O{5asHtrx0^18H1173^1)#s|#WF)NY8UV#-U(J@7Tcwht+T0WAaK zJ-0jGb#m0)&3pCS%u%(!awj)Fe?d|L^HmO3(y@Y*p+YGLf6X?b&lwk9e1G{yK!hWyGXI=;|2I*Jz9R%n+tNpK36A~ugGG^2*#ZOA#-7k$iy_Aem0hfA5Pv2pIyn=Kxjxhe_;PJosWoP7 zr*~%~S^?l}|&@dcac ziTDy&-3}(rGMs9iUU0NwT+loc5sP}~Vxc~3j32mTTn$4oZfTl-3BUrswO-0KwpNMn zXs2dq^f!p1=U}nMXyM2(wz=4-b{^yD2ri_WFs?$p;uC>H8;Oldo2yrZy;=a}nwAsm z$@JM>=zG1`BlW#B=?>Vj&O5#}hy{wpGVSFqAI?v-xkp;WdQpXT)vK#K75W_1Hak>! zN*NgV6`o(w#jn8b^o+xgPo4zd!hzTscO;3X0I-KxEN02|P+w(T9T12OkN z&Za}#Z`Opmi)M6>5I9As68bo$)J!Wl7P1&~V!%_FGH><|*!-Lir@z>2I(l}__LSBg zf(y6pnc1hwxASY_sXM)X*3GdmrhlvCbkQZ|&vsYTZVKWv1wM+9X(`K6#m3IGH7mHp!Kt0KUQ}*_h*Wqz#VgDQ7??6r{5RNYkdR|!YTQR zoR>x4W3Qox0qD+=hy12@GZh~M#l(@`;2`tC9ps1~=s!BS>=X4d!RzdVK^Y?sUnDBR z36l6yRPClp{gO-UrZIYG=cVQUHTh`9ju)6VDRTWY;!jtH9%c{;6gMH83QE#W4YCgS zls^s$Q-VreY}S-~m<5CGjElg0PBk6(p%w>=1+;@!vOfrVv&AQLqy2Z53ew*B&RW$P zCvOzRIdXH}kzmILU32BzSF_iozOyPnd3gd&C1Vz(W1TfEj4;)wc>mLuksxtL)FJrcUFs`w-!q)+I zNK(WDT*f}StUckQT1kwH~X>*kVX(ugS#5!wIal%l6o_V zchJDm2Vs$gt$AG3*pb$g?p`0Oor+rnhF?FeH=F0zdZZp>qgXlfn$;^PV$G%LI-BiF$OvIF;1OQ62U0ER+rzWx|$PcxThm5A}!1cXw5bYE^vN|a@ad3=> zYt$HO`C^US;CgI5K^m@uzGdS7e-PVg#!F`jnLCwx=rJ zA+~dQYdZ%ZvkJ}QD<7LP*wl=aJ9!$h>02ZPD~om)KO zLLI*~AgVuEP%L6!{4{oVTIg^}oo696FN95}8>EgD_~hvsdWe@@+Nv`YXsJ+S_hc90 zE$k61y(Yi_yWkMHV7*L_Xd51|zIr#?xoZ>}w31Y%u~p3F>{bQJAh!qyH-E7b+esO5 z$<|{am+CU`*M^xa(Z&VAQU-cH28z^+0awB8#z92HcG2F%(YgnKZoN3aw77)K=<}G= zsp^j~#@rWTtZ8v?&d<|Xf)x9Xgy*h7J8|_!pGmZy{bhF5IK(Yx#!OcbKRa-? zX{-Iz%>#^18p51<=%rdn?T;}op-8IE>!{8bQi`No5C7E0b%!;c6;&d(KmHTUDTE0N zVNRPL_=lBIj{6~bZlzkAR*KcL)6U*jVpPFg$}7m3%+{p{PA%@0^pEXlC%+7JG0YA# zn(eu5HsTjb>IB~fQ?ljawZxi+z9;C7+9dm3v7RyMEZ-E8%I`v#tK8Cezi-W5y^i@% zWLZ9l++GK5v2dR8kabc#CF;3jul7KK2o_uF=UEvW z_q6aglJ;Ai!Ht}~^T^7WDm|6!!W}I-v%qDvxK%8&a8R^&k`(fSOYlp%I zwW_hn0|zvn$Q69oZa=6&f79x>ts_+(+k5a8i+f(sF-bjUka2 zh6Qp))&UWBT&F&d&a zD^2!!=#Pzv!0Zd4)U;(_-QDw)5C+4Qb%|n1^eOb0xQSz47yDO*Q0qZkO-mZr1|%@= zkgxvs!%$pjo`S|!0Lj?soYvYH>*G;i-gemd;RQrLK4F+eF23iw>Aqyb`1fwci7liv`s*#(pTO5{e7GRE7!vb} zvbQZPrSZ{Wua=@Oan}MR=kz zMBae}vj`>9U!?PNf!*y2l;etQ9Uz?ofJ^rjCVY*ojev>Nwk>P}#>nY;n-KSvlTium z4%t`cU50IU?tA)gsLEf?N36xD_4&jePHtgUh!WP^{ev#L%FQ`6!0$_fqg{AX574%- z2a0cBsx}-U<-X%4Ck6-ENwNTw0|oB$mu!r}1Z7~hgC6*@oM5C8ajI^17BV~Ae;mMQ zG)43j5+^Nt3y |rXXMso#Mb`HPhe$nAaQP6IvLDuDn@GpgyBQNpQblRubfoqZ| zPBGp3!LB|S_%Lor8v++?aUE<2P91qv(*tuRwcpk^&C%=VtEyfTrg^#;_=joD_xNFF zRFie&7o)4t_w3;r6|=60i^uiP2i{^(MN_)OkN$W4HZyQ2MJAEFF1yp4C$1AJ8;neO z>s93Z*aO11{|E*J6e=BmWMN{_uY{{8E^l1EYK)i;q4NC!`y3d;JjD&M!l#!gY-AW5!-q^Wtjd`u|3%#+spvs?B=Pc!jp4~VnCb2X z{G5iClf@q&G9DOSHUHzLVK5#}e{!}sZ7vRM4LV~?`?1(qaH*{F+dSxdF=^dL`U1!{VYSGHy7Ye9L=` z0{ksi#qa*)UtSAOgZG8~%h*AvodUe%Q`an8-#2aPsvYe9WS5uX{5;Eb1=hohb(3~t zY!JK4C#=#%Wu)FM6!py3H;!{5gXiW@d<~|SQ+}TF4aBb27tl8ENg8kt-mjIKe7^Rk zMKb#(Hh4}cea;{k(EQgG-f^JS_+ogzU?q*zB^>;sts$nkAcpVe;@^N8t4SHCPPgib zp1Y_p?QiUM7ZCY;W+Nx7gS4-rB1*a$Oc#^Z#H69KnenkntFvEo%2_F^Um0R0A&3|*T3Z5UeIFrsBUgcxUvU6|H*gOr(>)#_%1DTu4CyIr{h=o!mQbr zr}*`ysJGnaAGy2}X~Bdcv_K_|C{FcP`^9^jyX#f<%Ff$24QAJJYldtg#E&KQIZKAXeC0$COsU#UAnIe?ZXFz4vhN6w~6dq{? zh6m5-bgJR}m-0h)^}p;}m<5P{lGnfkX5!VJmNoK0$zLt6k}VhH%zr&)_SR+4a4z#S zI>^a{l8X$C-DZ!sI&W#{1kp8)JfwyhJLDeYpkhb_#dw3}itdF5b-~G>>sOzr%k?4_ z)om+C{i;jSss8{KAV$ALNsH)zeHmkM3Lp_MMcYhkdtS77e%kI+WKmm_w$JoztfBd< zUrvGmI!`z1jB+uJf*t#Hu)Oh8EDWqsNh)aQOnk79h#5`~e+6n;5M0~$k$X>qf#=%@ zE~`va9QZY#vuz@_mt^FSD)H#f@M3()YvKHC%c%?==MwMNHt_lrz+N)%A?q834H&Kw zHxNqNfzLqOe7(rA)Ea3_kd)+TyLQot?g1;GNo5efm?;9RmHTrC;W4A4gE+6)doK?J z3&lQ%cU{|-4vSlwvM~&JLR89~Yt1TUZ)OZ0C*_DS>A5wW>bo9&gpaJ+Gkrl#3`SWq z_4Y(<);8#J& znly3q$+iKDBbGqTBa}tR^Y1BZCb$^lDJM80#RQ(dW~A})ThZApU@^g$k^cR9^J zQbnPsPh-i(`QB8(gM7^_?l}VW6y(L!gX!~E>)U63^x}!AiKv0s<`hk{Yjpe+*rwfOml)#f)$n*_cIt5+qm$JA&&MmMt ze`CcyFD6r^!v`tvykzC85kF(BPi&5#sBk@#qew?x&X(;rt{QqW+uWIXT7B(cPbSFZ zic)UNqeua}7!qJckDUjXPyeE%UHP!CIq&~Ow60t;I2us*&dgWHjEUJlRQxWs-T4qQ z>&oc(iRE8RrDe9FreISD?BXIhEs~XBT5#z0((Ei^iyC^5snL3 zIB`W&B>d1u+~JT_*&P65*LlGQat57_{Od6xyC9|HWk1ra*q-M#k`6mEQ~Q)Y5|Yw; zqZNws56JTcy{~M0ME6Y28E8W`qkp58P`6?LyucG-1zN=6q;FUo1|bye&K=jbD1& z*`m{hf~Ca^76F3~5{D5BC8n37dWo(dz1obg^&xr$nW<+jp|=deLl8kIkH zJuQme1YlYJg(jJqT=S-!xDk8W20k63FGw1`EE$pCDR40gjb+m(2*Mtym9u|)PL}0k z|CP!Zz#95^(8e?zn4~+#!F8isp|5c6!sCBye-AW=+b4^QlWLGm8nSef6VT;jI)Czw z%T*8jdRyzI^N1utAtJjsAAnzzX-lm+@>CXHwaF79sIu**Cf5_y{9jIff1T}K0;%~M zfDcoH!0HDZq&KFbCb@JA%S86Kyi=?(YMEJ!gSWVf|2%v__K2f58ml8UK6t}vAq(!Y z^-k1keWB=ajyBb~zM7;MITI%Pq_U&2DMMVY`4YREmp~TUni(PlDGE(EEFa^>qI`x& zb#B!}bYleuR}A_*o=I!1dR)*wdtSw5q#?(Z3^U8fQ|2AX?={cvLnL5ey57oWxh#tnMxLCmNh$4J{#U0`*;04`{Z_=sxfc?{y+nM2p7sIEa z(h7QvYH2jNP|W}84^+lF7>e0lGwJ))aZ~9KctR9!HUL~z<{<<81Sp8*-)&BUc82eC z{#GG>cK6zlZ6!AoKwp&RAcV#x%|jdUyz%#*1M3ZEIvmK7F<@{cGnHvqnj!GTAtM{AC)}dZoe1fs=i8Dld{u}eM;J;)|l!C z%gF`oY7*2+w`1Q?f7q5MO>{#w;R4Vw^ehuwx4F)atmvH3yXw=bjWH>EG!8L&ti;Nu zRB=B=B3|fbRSkAWqIZB&7OtYn4EF#sqwL=0b4mA(r#OtbW-c-RJaXCN^hv?!3~$ERG(%;@M(H zcAVei2ZM1wHCEcSKEIQewPQZ{i3^3(;ZKN!%ok4yrb#tuDM0-AV$A19U(|+YT$}QD z;zme~r;L$ZST2{P+rvozcl8pv&|LUWioj8pD=1Cs|-)L1y%}P+^ zLRC};Yiv$yn;p7aW`M+y{zRrut=Ie@J6;vLmy9+mEyP`!?$TlCpy!X1ZehmYlGG=o zTL8FGmNsd43qZ6a$e~DzC8{YHNKWL!5Hb?LhshU7CYq~I0oP-@XK3M$rpA6YANVed zWfSc&S2&6a1Mfyc%+uwA@<2grNYE-eFGD3LEL0mxTIfp4KSz9kjrljucek@;38*n1 zH#*S{wM{3Gp-20$vFSbVVWltBLMc4B{ZJkEy+e-2rMx8jUwCPO;1oeH^fyRc>lQJL zfKU1@sCkLGba097Uqz%K-7X|A|G=%NV~ zA6qFLbCo~e5@&~suyHke=?pJYTPljo_iEe2Q=ky@w4e<=|LbQ z4Bw|e!3ij(#ZCvp+y!1WZ4LXY+S0hUtmbnNA(ZzI_6x?c#GNmRO8bW&ke1^S-#B`B zT#X=O%})Bo7nPbo1P-SbWp(fo~&c&fjLQT z&6GzVa5U`~0^0@Ee6S^eOU9ZGFBzpZ{`f9_^;}1!&*_FRKv4olPpW_Ltp_%^wPPWA;AqQCUsf>TAts486r@jH?X2>zRb zFVa1K{!w5RP~^O7pPwXF{?05A5QFi+0=b$cBoGwrn^e1NYK+`VHpwJa0w}U{@{txLo7Wi!cQHC=UAx%2JB@H z4wSidZk8~O-xq-B3$*4&;Nd8u&c$@>n1g)Q+03{>EjJa>&*q-*%7hCo3x zZ|^HS;l`U;(<%mHO&$Q4u*Y!-ab$ksVz$n=i1Yv|y++ZErj0TeNzD+daXIN}NZF5f zHSZ*|OOq2V0WFj7!HE#M?+kar2pHF`8~MH%u>S=m*(hiQN)jx*g}$x8i_Bx3 z0AB239p?Sf(=sp!KGWU$+{eGI8+(U_REFiK2BX3p95&VXa}LtGa0%sgXg(Ca*(ars;dz3yn`Fj>|jgPfG?H&K*VML5mv=d>f%AWiTyn<{%j{vAqe0SG2 zj~DcLta!O$ofwTCGiRSONLoEKZ)ABXD!({ac7^-8eS^?`_}Qo**hF1lBXr`+ zz2$zaDA7?1@gMVGR!e9#zqhRm_l8Lh~3igFn^xRgBxwUS-b1?PB* z!o_J_4}C0l&giPC$+VxyWNBFVzS7c7*9beArLi7DfAeQ|8d)^Z_I-t`R94Y>``26* z&L;eW=c9BW3pvxf`(;ApM!G^7=dCJ8=#{7b@at4f4otL36!Sa2$)0eUDtr@@eT4Gx zTY???o+a)s%3PdqA}mC|g2gQM3hzAn(Ww(0$Sk=@{ujSa^uvcvJA^Y+tY-m1Y&lT@ zS4r)X7I$Ih96?=sdUli)B3J*E#9Q=(W^(pfEdJSIx$|1g8x=J3U&DJP2QFd44MuyT zY;!UsQ=WY|XB_Ac$X-@46v-TxooNk_?s&W^v5_-n8C0TlJaPJ&td)z!ojv8=72cj2?TaR~Q^a;}-s95z?r4gO_KK#tuhT<^^=b7k(q2TcZ2ZYR^M;RV{SxO-Erzz%EA zE=iUe^nn_06$}pJYXjV8s+ACfI`CEC83<2fIPE0xhiUDUK4NuuRqq43NqpgE<9~Ny zHgPNBu($M)ximvlpnE}sMN?fy5=D(E&k*tH5lS+BNb|aPe+J~Y#XO|Hq78T$F81VB z`kd?SI@XLS3?_tR)TqVvtQXS{kaY*!m9nBoR|^%Pxxen|QdXJ<(EjX|U|~e)lB!#l zk7L-($!E=5)x}5Y4{_I@4&Mzmfz(AjkczBbOWGPg_VlzhdUMpv&kO#gC}LSuu_{(M z5QijJ7)3moq?B4wR>lT@-=DhGzId~#eFX~)1IlgCmM_jL!G7a!@kG6JIjrzwy$e?w zyu%sw-O2$?dYiJA01*}-VM+71-0)^OrK108K0{U&xyFZ4GdDlMEA}EPYe`>2KAIdB zFDa`%zm17nP@Ax9Bv8HS8i)`8zg=Vf@@~*3Sp30+yEPelau}53xde0A?p*bcF$5V} zDqb=%mV+($ipBpHL~^pat#rvM4+=C}5Y@6gx*)I5rd)sc={!d*XPq^R6!!R-)>+rI z=dTRD0N<-Uo4#ypEDpyq{y?mkNnGq`S)#{jl7WoIE?my@UgvStv%%1`!GrtDoTICz zI@FX<2A*x_AmOdCKulOpe3~lJmQZ1kGcqvnsY_j`j(XCm$wm^3-Aq&s#o6gXx5S() zDtKKI*$p46C{@y40`T)o?aHM?CI z#a8PI=EoXeiQ>%bc`;QI)d))*pJbT%y_j(B_P;}Jw>%%t{I^2jqm%$Ns2Z%)G4c!O zaC#nb@mt7XK4SR6ve$TTR%Jxf71Gn*+Puvc zd}rxcX$wmMhiB=l2lc4LtE^D|nmEt*dPhDg%NNAeQRc_qpy>ooy_V2<1*}Uj-^{T0 zShA0#p*ea*-o=-yrVeU?B2HBmT>GjmuKsqb z#;0_x_4w&NEvS}?TS zIW@GSu-M5h`A?~qEIwS=T(E1tYN62c)2wim<$l;oj9?jI1Ap%*g5Kzp1|ds1{V*wq z^Cr^BF^zZV2&io@ZTMkv1Xbk5`#FQ2Ee{@UVL}JKpa%Y}NlYY^E%Z25{=@OnvBzgF45828z$@=m5&EJw}dp%-p6S}Owsv`CR0%~iJwtkl66d? zxvk{rzy+D2XW%`3H$VNfjfLj31^j>F{jHp8M|I1GQXm$Unl`9_M;_D{k@;p`11A{; zBHFQntDMWM=eUjHeyiaxtxT33>MkgBD46r9I|gm6OV3VmS|6~hKiK{my+pO)D8)k> zr}wb5l6W(oQ-mM#v5p;8Q91xP2VXAdIVAFyY_iR18Ar8KQ!4tltJqnbMbGb?(t=CZ z3-VkHqnjf>KI`pK{B99{!|q~D=7NK8!e*1w5s4{CZc)gj86KR&;*?q(XAF^N2{qT|2e}qz)Wr5u% z!%b)zzc`p=RkQP>W4*1!oJ|xDvyhyQpM?C6MhhL#PV$TW@J%>xC;xX>;U^%mB@q8x zdxJ;^eCF#Y7s&k`AM&E>u<6!Y!RFcEd*pMwDP{hkp!gstZ*PT-yY1EDv#H)Op)SrzKa9*#@6;i&FrbdZnc z1mdZz&lw*^e0?;`@i_js_o7A6t{)IQQRA;K>#kJ;r~0@4?WsyUaNFnL9;cXBIDhV9`q7X~RGx~8=~&g`QL+=FY|BHTaa((GEy&^x@Egb5qH^lK zg|1e`oFjO)y+>`|U83Le4@I??{CFE_=bpO3Km*|o)ZZ@iwBz0Qz_1TTVQ%in`42du z**F8xpnlwvv~83UOri{F6dUE#c_>$9m(F;gS9BrV^qrs+=e zn9p!};6VcJJ?qe&u%^YZH@e3noB}c036XxqXm3i|Q{F+uUD9EEFAjOLG{*o|wdfK! z6S8|Y*3zd8y#63qKcZul43*qerx}zMbgyvrRyDq#%kLWyltc;nlx@ zx>iSO8KGJ40m{k+xzX*rvF_vHPGOx379-QQt})Pi#YpLSR+MeYTci-zf&iDAGOHkD z;}t(x9V$Sj#}kSB#qA6_TY%i3Z+uvak`<~6f8qG!>eIf)%6Ys-&;5slF~@fm2xe~t zh81GQyO%7EMrh1~d~sBpyj5d?GfxqkUrlVAxE`Q@V{Li9ZsKPUVrrpMIi2d!wX?!! zkbrbg|2u_-h|M~A0pK&U#5{KBD+yz}+1Zq44`;n*JWk7XSI1*m+6`;zz$=XNC6Gpv zuI=n?b!mw`sxrck7!<0K+j0jGCANml9y&NR)g`uo+_VHl(@YSfpVvl6z`sWkcoTQ$ zhH}}ibtryD4}YAn@9$as=Zm-%`4#iuX;!mG$y24e&IG;&oELMV zI#8uw{WiR3Lx~xE$>3u{JV<7qewWF)a^0Z#!r0WoaIMLWB%XSj8J;(*d?k43#kFrC zd0A&q?VXrAg1-cgKHPv&*>}z?8PR+@f;4q&oeOJ~Qdw#hsNuTMVVde^#N zqRIFF?86rhE9;q@Uc0rFtsUuvo_+(VIpb&&VclR>OooTv_-;&MdutF58~Du=EC zo)xDp1JhC->dWzHX_JzNymZ0<6IOxgOdsavHuxD1)K=hSQ!BqVg3&%lTL#XfLSMfc zxcI=dID%wp*=~OIT)dq3=(GU)<(0;vm!e`b=AzRwK*+!HxRttQ4Y$=JDy;|+=EHA= zO`ajUP8Br}qYV06R&CC0fYLM8oF#gxGk`hgG3WYD95^ zdsHN6AG!(tC-zEBg{DvZH!XMOr(MEZ26JA)_%oY@SEg0X7i?HY>+p&if#VsI&vg_P zeFU*MkY*kLhrS%^K1el+rOgWqbk7UAFDKP+vH|#888Gtx`hm}g8A=Riv`BZpyg+|! zkE&kGoJ(kse0~o**I8^V_~k6Bh~u-%0|Wh?ok<=L*NE48RbFx3AuWwEOus21#6t-) zbfe`e;a}mqRk9CPkZu2!4uZ7 z5e0nglW=vy_r*fGZ|`T6rX462p9kg9JdY_hm=j(o#uGisfFVkDh8!ghJhJJosz%t9 zU^3QZlJ8eDhvwWwOLFGiWL_@S95BHg6X>_`(Oqc^3SBKW<3KA=67t#YudJG3i^@E(K}34$({M3&IKq=wcQ`LH6+pX zbTav^T9~o=Jp3?OuvT(`Kq zHfmvWI^3{Ie**jSE;}W_fcd>T@{p;x{hY4FvwceaicVWqXObRBEtH&Z^1j}P87yzy z&jkc-S%$rDz@lfLXX7gJl$10G8cRHFH}{ zjXC^{3gP(g*G+I#4rS>6qc#2AuYxq#kO26`+6!r}hFi|hT1&@h_{b8^6+9|+VpwYS zI6%DP@x%MbepU^WD8ZDrlop&pp5Frqv~Jc1|Fxu$YZT2;Fw_88hH8aGFDOOn0Vfr~ ze>w$Ju{I!0)Abh1UN?@*l{5CXgx`lu<{ulT{croXYg-Dm94k&-@8+xlN+BJ68}L? z`LD2O;)kW^*QR0@?wqnK$XE;L6hQgdP-ytHlX=krS!9SvZYc(4g(PJVHHJVRHkU$j z5;)V|7FnBfMHT$Y3Wrd_V#B7IV!mJi-gh1nnq8CMtTb^|DcAa#_&l(N#O7<&ty5&O5cMf<%Eb$UU0sqf;ZLR`5To@mGya zbW#g(RGegurlucXI_pCgB;7$muD^8W&W6g?2`9r+G#rET0t(>Ak-PorgWzygfaVis z;ldlq*HNrj;bU;Ae#HCY2JPUPr+XE>KV0$t2g(Ih1a7Dm{un1j+%d%|@jxTdnW4-4 zJi2(}gFXp+h*HKs=+q+bAm~ckwE;YGhUg@y$}5Vehn8=Tv`3%v{yIWk9 zVH&1Pv6f{Ht6g^;Y(^*+Ve;2fQY*@Lu#|ISZ>WE@A-i`lzurQEIyKqCBp5)dJfs=l zO|*+J8YNQuY@LeD7Zz!k zSlQR4)&#K{S1I`iz6-k|DGCXH&e6Z zFO$U=kW4u=v!rP=R;S!(%Yk`ellg7qT-F7I{4OnuV*&HIWSh$@U9cb=-;~hgilgV+ zX`;X~CQjjqpY0~}d_0mTXVKx3 z(>6}{sSm-d``z6Y?~9(&jcm)#{ByR2-@44uQxqTk@AmmHN@47r>(*DJwtFcmfHL=A z5On9I^aPaCiUU0CfZ9QX|Iy7#)cvR&=H2zMeDU1VUxI+O>+=ri!S0VPHb+=C#&L>J zNqlnMyJ6>zC!D=C3*oPfBOO4gJyo2*%TKv584nhPzC=ZK;jv@@Uu!&C|405jT#-N#c{R-uWv!u zX&=^}h*OK`qv!zDEbAkqB_1M9V$`O_`K%k8t1cMdyG3?P47&}AwC;cM)4H)00B!hE zAkT~X7VWyJxXeKDgJP_nvAySP#}hS(>7A}T2)pxZIts76aJJ0O-R;;3AO&tbOLc3s*1^wB= z=}Js)ddMcR-aRQ@_g%Fy857QJo4h5<2Ve@}$NKMb(m3(Omto~*!V@}>TvxaxZr4w` z3eP1)X9 z>+RqMW5m-_Th}Lu$-wi>*XpQ8x}?Ti4}i#Db2@4wm#hc(S38u<8NeVxOsIjpSSLDE zDtU(f@0%Shz6``$Glx}k3t)b(Xt~U!3iHgYrdbcdwZhx6dOQ~5 zHld1=oxdf~1B^F*1ugqzKw{R-@S96O`cX69UiYAkt2NsB_z>M^#4z`mnfNTdt)hET zoaCQJxIfiUUEePRj*S&T|F%Pg?`z;jtyEk+!dVH) z)04J;SE{2Hiq}d(8}l^Ap6D(RcHi?K-;+P zmRz0v;s5pIV&C%?*zx@ex#3!oB*`CXN^bZUPjZ9DjKMjF6r&c}5;v#fRj5Ybk%tIA z+hR@U8Oq?^^7Ezm+N#d#*1w?lDw6BJ(yk!pw`E<{;vRHZm@c|ulGCxIa*)T|0;i>wDW9_3iX%nEZEl3jOwCo9dvMCk+?u%uxgZzh0$1Y3 zErJ7-@#Xxk>-P`;!gXIa_x*am-_Pgc0a(G>py^$}heg8j6G!I8%sEdRg@OHVay z7jYIV@1;U*D+d_$@e4r~g5%6FPMAS&bc#-80ij_4g*&!cDu^=X3A&KjorQ5@Q;ENm zkV`Fqi0sO}B}T5osi1#H-{Pb{?}U^;u0^ju3OD-vgi%@Yv6+7Rl7G@u@qeP;kux)I zexJ&tvxeL--+@Xvxq!=t%LR(6W`12FM$kIt{TS1{@7-zjd0mfrK0k&pYu5Q3vHFh* z*{eVQTz)D+Avz!h?}*=T#Eabt=#VP@r+JmUiHLN@@S2|9%mC3Y?>gYqUKPT7Z#a;n za&Jhvm>EGrF#~bsC8sjh4xtizLn>Ed@Tnf0d}5O&!S_8;<5FtaA=Lm4vw!msnuzs8 zsb>3`vUIMoh)-xZ}31BS$(!B=o}0mIZgq%C3Fu!ZlP>@#jWaPhn`P zpAsyjzszXc9O08f6MEmu8CGTYgY~2Eo|wWYedX=jCw1lM=C(s~GEwdv@5SuT)24K(>uT*D6uZ0LOI>eZTT$Erpjv=^Uy2j2Sx-UDf5 zuxQoWpOaA5KZ@ z|12yt|D%>mj<7_|sG4#Nj`hyIsX-1oGM_Kp^9{nFmYc{M7T6!@GNVSpeO11lE4nd; zT5>Bzx#MhgY~2(pfVcA1A6*4^P{Qta_lMA1uJaB;w>Ox2De-=n6Gbu7-}7+$!u421 zeL?nYBN%_Bc`N#6jwVOKnQvL;vj1V@mZ0OYx%=AQlr>mdlasu11s~r3_$s@tb#Hn9 zmhM4n9JiX6P3Fkyqz9?WL9lm$+{=5W$YuE5J^Ew8(4&3sycTBf5zkqe(e&3Q(eI43 zRpT|Go@+n0vKjNY>`4t=*&Jezc?7Al5u1?}dNj?(87X-8uZjC$yl3n@w1R3=v0kXL zGQkt2qmeAox(4cOnf6{_Fkey*W6KhNi#4xzxOZBuyZ1h_ErbNnMJ;EjJbV7uU2I-$ zFE*gb>MZ71*J=J6ijs`$@NqK`N@=Q&JAwE6hETrLG;0T6oiGw28J#5+(aUKA2f_7s zjO6R)UnE^9dTC6WSjo5~+GMjJW>`Xbtw40+9~y}kS7N1EgOSJEwzxlS>;7f1Dz9j$ z#6F$H{5M`_K&)0_n@e!-(NXfmF8ADh|Fc(jFJnqJsza4|AxQ&4B{!BY(-XGi`v-%dW6C&TC4qE(gZV?*4`k*Czj7xDKO06B~-Lsep-N{Ft%`x`~1 zYVdrIkS9gXFsQRfAIHOI?^T!h|yTtW1FfdKy}BD_fSZ*QB-DV2YtYq=pY1#oY)yra|h zuYEcGan<;p?K6GCK(LX@p8Wx7&lM+@OPsu=t_1Lpcu!i)tQaC-C}bHZT&czZK*f&i zuTMGGu*>Vv7-~wWGtm<5^mR#XGa07qJZ!#j6X2Sr$06^5PgE^&U4vT(zlrZ}RTbj7 zNDFOsm`2Fatn8v=9;LkiubDe0tG255mZ;&M33{w{=T_^+GE1BNC($cOn!(&cl>kFS z-_NBHPd(-yV52#G$(wQoM%&Zycq_kmJ+K3(Xmrdv-R#XEa6~o-H1+RfG_-zscG~Sr zih}>2wl1u9MemwjZUZ~Ih3LED?+gbmC0{+MFt^zm<=ov6f%o@*`*%)jW~8w=_|N@) zCXzIy;z_01)?pmGr>*?VHVVBvj!#zK$2%5zI{r>#%HTMCTt;&Fib1rMVSM&pDt7nn z*r#n0jqmwqapJfBPi3n|>)O&L%pdeXHCV;dLr#5vH81o|_)R;NYb+G#q$FE75l*BK z$EJJeoVX2XQdm>#`x|Wg9Enon->r^H6i$$STglx03v5kZo&&;9gRa`b(jr&CPD;?^ z3`U2Rrn_ImQVEzHHOGOj_>wrV)L5^U`Gx8o@@;MsktEk|xRHTlUNs8b>JM36Q5Tll zFk2MXfm`uUEkcJfq`T@#{@}&1Nu)p;q)LkjrqU%mNb?~6;;HIVyb zp920TaGC~b)o@&s2U(X=^E3Uf#Ip<4n%=}E^+C5k)myiHC}&YIIE32uvisM%;+!uV zdQNK@+|)_)&KCP4IHY&OCip^{*@ZmmEbU*6kszK>8%TwzAWs5A#yjhb`ox(<-C(-OSxwp;qv&`}`OJx>5yQS?0_B z&H&+f*51Xmn$F)xXqhr(xYpRMCU=au#&BcNaBS|4s^hs2Be4(7{Y4kHF@vzq_QL7dG4V*>T24tv&l69 z!H(qYN3@tF$V+cle%uBu!5nDn}Uq? z*9cK|KfKJ<{adaZ)f4^v$E)ZhUO1=q4I;!Ude6{%(ND2VqoV6>c+qLQh?fZq@plz; zWF#hUVwU2x?XH>U;ua;=xE;j1xwJCyfj_Ln+|1;Pp?@Nl9`x-qXQ=prUnrG5WWVXm z9d_GK7vG(d*3Px9=WlUN9VT!->LhM{5j*Q*-y4(KPl+IpO@^O`{=hS z?SfL#xHfP1i=g`S0Nd5r=`^q!slLzO?ctsr}&(VkXw? z_VxbS*XwR;{i)*Gs^>DRwb%9miW46RB$z4?CsAE^E6+e zgk|vcVlPe_mXjt(tJVnzNKGo)l$XHyf*Q=o32(*bcOj-e!=FCxa1#2F_>dn*43&LL zCQmrC7Wy^mVrYjT&VD4|F#~lU>$$J*zhbQ6zMkc@R(33&_KN+*K)ZGUHdR0<6ecW8 zUyoWg(q726<00qS;xryt=r)Ar1b^9+0j zg22nLuKFzMVtp9XmY|Sl2_et9Q;>tLnaL~( z=l&e2hX1-nCXh>yhQG*gObWNO_sDWb;H6U{u=AXkh1v}r&rq`BNOx1a>(#HW0?(`V zK_2jCJ?5 zt4WSjweawBwaXvXcop}17*_oq=*u3sLW@t?|B!rs8}M+((@31?PxrbLQeZEuZiIlIc?_8SNXLi%#x*XyKeEE1O6fI(#mxr>P4GfSwN} zh0tKv-Ns&q^;l2(wFAh_J5QUrkyN1>6}y|c+$8K$T?gb|fjkv5vw&pt`(+s3ep+)$ zE_GYPbd&p$t|bC+mY?t{L5F_jz5XRy+*&a0vt>|0xk$58$NL$|mii8xd8-cRx9cjw zNzTdGNLffhO8MOIS3~b0zqMwHC z`TP`B35QqdcxRMCf_6D zb~-8qVF-3qF~K!O(j^;64s=(G9;i)Lf?sBflS1EKT$a}$1S(_8$kWhbxsy!Iy z&!Bc&)}Xt!7CL#Af6!|)6xIiLD6slxm*#&HoFJ*mN{(Zt!?fqy>6g9S%mrW^T=_hE|Dqma zH7})VrsKh38Zmr==$1sW>7d&nS2tIOiKo8eZ%?8p>EU*?^7XJSC6YFozu)q&&cff1 zOE{zNIL|a?<<4kdGhe7qgjxe1y5xm!&JOmB^-N2B{}GGCvD=fv+Ht&wT*8Y0(qdpQ zzt6@X_eqd7=0;vtI5}@CX*nKF*$xyBoi3{Gf-#?aP}7*!@LlsC+a9W&gAu^|Rj9#JE50Zl%_<2C{#MdSMoZ%PF#v(eI7ov|gu8+UT?Adm_h!azq=-njG4?W#6#w@_$y+y(g!t z62~IfQo>g|X7Uyq?7t>{OHP8N`Mp#(liTQ#V=meD)PH%cP;^j^1E5DA*v$V-ouYg+ zS@2&E4XD!-WD7o>jSfZKH3HE9ekd1$t2Z#)hf1*mJcczW;GYL~eG*r7`nJIl#vhG& zhG-YB(v#S^G=rSsT8)w2mk1->YBDR*JnZ{aq^yObJtNtvFxy=<%`Eh%bb|fn{UFa& zD94WkrAz8`d`zYtVeH~qau*BLcpU~au@$DtyaMc4!8XJclG5=zdt0t-59~FIB>QiS z;`+!5&%O+3!0Si-Gr)3TP9N|$9H(QX8fTS$pGF*fW)5XOgRu%QelIST?>J{UsRVbt zOt7wgAFcaXj50Dcbv39WJ2^VM)V0ejKC`pfV)YBgKc+B{u^8qIZq_dUwT?2U4U6J4 zOB?SaEXZkQ&v6sUdnLhp#)2y6r3`>peKp{%cAEbgiE@&wX5frFj1_knnn`K+08DqU zoS)cEvU+05G!YNGS1rCD#P2c(<_|+EqroY|LJqT7>R7kDkchZ-G}i`rRaq*|+>ep< z*rKCxL$^TcDC{^`9f&A#zKUF~$_I3w`)7attGJxf4AZH8hPV>G;Pz~noDmo7nAtuz zERR^00EHNkI+Cj{r2y9!L82qQ)=i6X@7<;FcAZ2`?4C{Bb4Ftzqj27lKjS4#JwJId zd^dbCqBbFrNpQhTHJqb9NAlL47tH0^9Zf%violtKFJ)80(Cj@Ac0j7xmXZ|GtmK9S zSnyid0ezGf<_Sd-2P51Q$PI-sRwN)v#>(G*Ge|)BL>wxyVNc5z9|o71qV<|;~Zr}s*({YeDWE640{trtbAMf}ce1(89k)ygJ zY;jy+#bW~jaFHz3Is+)8E|8cN=E{$nFUEI*l+UMn%CGMjw`9lFP6Ou+z2`u2%Lh;} zKN9BKv`!6uQBLL9>T;6Ha~pvJU&sl#@vcWM6u%)z+}lOF$n(O%!dwJWvB-f*x4YS` zazkTy=BW*OGRQ=ZvGRB*JL=M+vsdD@j|tevj4<|4e@vm)aqfsFyE@#yMVoRQ*k=qq zR0yiE30>D|FEH?NZr$M~&`@8HkAgSHcEQGuu49g`6ogpFh4lRbnYAOOO;N)wEdg6b zEptY+s;N+3j>ky+gEhdY!{ zi9%9U)h)&=-LygF*Wi@SU7PR~z9czw8c-@3ax9Qry`ATu07cg|2%#dJPw)1DxD8W= znPph2z|Q-?XUvX&AOAKaaS6+mz%6pK!5s5&jCxwY@Lkn~G@R9%wRnMUSfJMhXjMNd zcO{(s>z*K{nvBKQdN7gdl*LPC%pD9CQ`J(`U;U#%%E{&p7`^NoxMbm7pyO}yHMaHp73B_X?*yO6#fdMh$y9~%@AUk|g=uP7v zKz1%(4+#8YkwCzXzRr(M!Wdjm5gbzT2>#W+FRjn&<&hE&*F zXSuO_=iei;b%#}8jF|57cL7A}=}i$Xz}uxWMV;_Z2T&g+!lI*!_s2-TTO zTEy@FbVfJOlGyCt(yC*$okuVo5J3^ko;tn~uoM~Ou?8`ZXUs-Qc+4dGc?@BsTXa&0 z&g|dT+MPz|nLeul?CRVG+CoaT-X(PuJ^K>$+F%)uCSJv{EdEVNrf|g1L0kU0fyflA z5jG)viE>+;U23M02Z1xur0~W6!0V8S6}#QBtyg0MadV9{>qBzY{>}-Jdjk?RBkoC) zMWLh9hT~TAIMibDMzj2{i2d>zbM%c1$x#FwZzf`WJ3X_|)7;c8adcO%zM&14HGRhRvAVI-sU ze`remjKAC-;ET)klJ;#mpw7}8ZwaiA-xmpb`m~wq%G{>MM#h|@XuVqmrILa*woo^D{@pPA%H=-Q28y0^j7^Eye5l179{6h+{_94 z?fOilyqaUBM_!rU@bmRf+H^MkQy!xGC9sg1*iCG{zwXz|k5DHb@un=_;V&7_6d2s= z@3XY{%Jj&M7;^yAD!H;i{!d^yU7x+r9tFMLer@1RSPwww-#vXtH@UObOe$F5N^eIJzg zjCXrs+T7sB5B~aH-|}$@5v6tDF;}Jbci_0Xm+N-WpCh6$VaDb^m-s$GSn5e=xW7-x zZ3vj9$3BySpY>Iw!QVDi4!*T-U8`h&@r0^1iTop%0D)P+`mezhVgUugKI3*!qH^NO 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