diff --git a/safari/Mue Extension/Info.plist b/safari/Mue Extension/Info.plist new file mode 100644 index 00000000..9ee504dc --- /dev/null +++ b/safari/Mue Extension/Info.plist @@ -0,0 +1,13 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.Safari.web-extension + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler + + + diff --git a/safari/Mue Extension/Resources/manifest.json b/safari/Mue Extension/Resources/manifest.json new file mode 100644 index 00000000..76385fc6 --- /dev/null +++ b/safari/Mue Extension/Resources/manifest.json @@ -0,0 +1,26 @@ +{ + "manifest_version": 3, + "offline_enabled": true, + "default_locale": "en", + "name": "__MSG_name__", + "description": "__MSG_description__", + "version": "7.1.2", + "homepage_url": "https://muetab.com", + "permissions": ["search"], + "action": { + "default_icon": "icons/128x128.png" + }, + "chrome_url_overrides": { + "newtab": "index.html" + }, + "icons": { + "16": "icons/16x16.png", + "48": "icons/48x48.png", + "128": "icons/128x128.png" + }, + "background": { + "scripts": ["background.js"], + "type": "module", + "persistent": false + } +} diff --git a/safari/Mue Extension/SafariWebExtensionHandler.swift b/safari/Mue Extension/SafariWebExtensionHandler.swift new file mode 100644 index 00000000..75eeb38c --- /dev/null +++ b/safari/Mue Extension/SafariWebExtensionHandler.swift @@ -0,0 +1,42 @@ +// +// SafariWebExtensionHandler.swift +// Mue Extension +// +// Created by David Ralph on 02/01/2026. +// + +import SafariServices +import os.log + +class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { + + 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 + } + + let message: Any? + if #available(iOS 15.0, macOS 11.0, *) { + message = request?.userInfo?[SFExtensionMessageKey] + } else { + message = request?.userInfo?["message"] + } + + os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", String(describing: message), profile?.uuidString ?? "none") + + let response = NSExtensionItem() + if #available(iOS 15.0, macOS 11.0, *) { + response.userInfo = [ SFExtensionMessageKey: [ "echo": message ] ] + } else { + response.userInfo = [ "message": [ "echo": message ] ] + } + + context.completeRequest(returningItems: [ response ], completionHandler: nil) + } + +} diff --git a/safari/Mue.xcodeproj/project.pbxproj b/safari/Mue.xcodeproj/project.pbxproj new file mode 100644 index 00000000..41e3e149 --- /dev/null +++ b/safari/Mue.xcodeproj/project.pbxproj @@ -0,0 +1,539 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 6373522A2F087B5E002EF039 /* Mue Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 637352292F087B5E002EF039 /* Mue Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 6373522B2F087B5E002EF039 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 637352072F087B5D002EF039 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 637352282F087B5E002EF039; + remoteInfo = "Mue Extension"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 637352482F087B5E002EF039 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 6373522A2F087B5E002EF039 /* Mue Extension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 6373520F2F087B5D002EF039 /* Mue.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mue.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 637352292F087B5E002EF039 /* Mue Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Mue Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 637352422F087B5E002EF039 /* Exceptions for "Mue Extension" folder in "Mue Extension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 637352282F087B5E002EF039 /* Mue Extension */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 637352112F087B5D002EF039 /* Mue */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Mue; + sourceTree = ""; + }; + 6373522D2F087B5E002EF039 /* Mue Extension */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 637352422F087B5E002EF039 /* Exceptions for "Mue Extension" folder in "Mue Extension" target */, + ); + explicitFolders = ( + Resources/_locales, + Resources/icons, + Resources/assets, + Resources/src, + ); + path = "Mue Extension"; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6373520C2F087B5D002EF039 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 637352262F087B5E002EF039 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 637352062F087B5D002EF039 = { + isa = PBXGroup; + children = ( + 637352112F087B5D002EF039 /* Mue */, + 6373522D2F087B5E002EF039 /* Mue Extension */, + 637352102F087B5D002EF039 /* Products */, + ); + sourceTree = ""; + }; + 637352102F087B5D002EF039 /* Products */ = { + isa = PBXGroup; + children = ( + 6373520F2F087B5D002EF039 /* Mue.app */, + 637352292F087B5E002EF039 /* Mue Extension.appex */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 6373520E2F087B5D002EF039 /* Mue */ = { + isa = PBXNativeTarget; + buildConfigurationList = 637352492F087B5E002EF039 /* Build configuration list for PBXNativeTarget "Mue" */; + buildPhases = ( + 6373520B2F087B5D002EF039 /* Sources */, + 6373520C2F087B5D002EF039 /* Frameworks */, + 6373520D2F087B5D002EF039 /* Resources */, + 637352482F087B5E002EF039 /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 6373522C2F087B5E002EF039 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 637352112F087B5D002EF039 /* Mue */, + ); + name = Mue; + packageProductDependencies = ( + ); + productName = Mue; + productReference = 6373520F2F087B5D002EF039 /* Mue.app */; + productType = "com.apple.product-type.application"; + }; + 637352282F087B5E002EF039 /* Mue Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 637352432F087B5E002EF039 /* Build configuration list for PBXNativeTarget "Mue Extension" */; + buildPhases = ( + 637352252F087B5E002EF039 /* Sources */, + 637352262F087B5E002EF039 /* Frameworks */, + 637352272F087B5E002EF039 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 6373522D2F087B5E002EF039 /* Mue Extension */, + ); + name = "Mue Extension"; + packageProductDependencies = ( + ); + productName = "Mue Extension"; + productReference = 637352292F087B5E002EF039 /* Mue Extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 637352072F087B5D002EF039 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2620; + LastUpgradeCheck = 2620; + TargetAttributes = { + 6373520E2F087B5D002EF039 = { + CreatedOnToolsVersion = 26.2; + }; + 637352282F087B5E002EF039 = { + CreatedOnToolsVersion = 26.2; + }; + }; + }; + buildConfigurationList = 6373520A2F087B5D002EF039 /* Build configuration list for PBXProject "Mue" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 637352062F087B5D002EF039; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 637352102F087B5D002EF039 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6373520E2F087B5D002EF039 /* Mue */, + 637352282F087B5E002EF039 /* Mue Extension */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 6373520D2F087B5D002EF039 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 637352272F087B5E002EF039 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6373520B2F087B5D002EF039 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 637352252F087B5E002EF039 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 6373522C2F087B5E002EF039 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 637352282F087B5E002EF039 /* Mue Extension */; + targetProxy = 6373522B2F087B5E002EF039 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 637352442F087B5E002EF039 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Mue Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Mue Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "-framework", + SafariServices, + ); + PRODUCT_BUNDLE_IDENTIFIER = mueauthors.mue.Extension; + PRODUCT_NAME = "$(TARGET_NAME)"; + 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; + }; + 637352452F087B5E002EF039 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Mue Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Mue Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "-framework", + SafariServices, + ); + PRODUCT_BUNDLE_IDENTIFIER = mueauthors.mue.Extension; + PRODUCT_NAME = "$(TARGET_NAME)"; + 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; + }; + 637352462F087B5E002EF039 /* 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 = 26.2; + 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; + }; + 637352472F087B5E002EF039 /* 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 = 26.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 6373524A2F087B5E002EF039 /* 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; + 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 = Mue; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMainStoryboardFile = Main; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "-framework", + SafariServices, + "-framework", + WebKit, + ); + PRODUCT_BUNDLE_IDENTIFIER = mueauthors.mue; + 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; + }; + 6373524B2F087B5E002EF039 /* 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; + 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 = Mue; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMainStoryboardFile = Main; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "-framework", + SafariServices, + "-framework", + WebKit, + ); + PRODUCT_BUNDLE_IDENTIFIER = mueauthors.mue; + 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 */ + 6373520A2F087B5D002EF039 /* Build configuration list for PBXProject "Mue" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 637352462F087B5E002EF039 /* Debug */, + 637352472F087B5E002EF039 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 637352432F087B5E002EF039 /* Build configuration list for PBXNativeTarget "Mue Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 637352442F087B5E002EF039 /* Debug */, + 637352452F087B5E002EF039 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 637352492F087B5E002EF039 /* Build configuration list for PBXNativeTarget "Mue" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6373524A2F087B5E002EF039 /* Debug */, + 6373524B2F087B5E002EF039 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 637352072F087B5D002EF039 /* Project object */; +} diff --git a/safari/Mue.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/safari/Mue.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/safari/Mue.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/safari/Mue/AppDelegate.swift b/safari/Mue/AppDelegate.swift new file mode 100644 index 00000000..1f8f65ed --- /dev/null +++ b/safari/Mue/AppDelegate.swift @@ -0,0 +1,21 @@ +// +// AppDelegate.swift +// Mue +// +// Created by David Ralph on 02/01/2026. +// + +import Cocoa + +@main +class AppDelegate: NSObject, NSApplicationDelegate { + + func applicationDidFinishLaunching(_ notification: Notification) { + // Override point for customization after application launch. + } + + func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + +} diff --git a/safari/Mue/Assets.xcassets/AccentColor.colorset/Contents.json b/safari/Mue/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/safari/Mue/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/safari/Mue/Assets.xcassets/AppIcon.appiconset/Contents.json b/safari/Mue/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..3f00db43 --- /dev/null +++ b/safari/Mue/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/safari/Mue/Assets.xcassets/Contents.json b/safari/Mue/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/safari/Mue/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/safari/Mue/Assets.xcassets/LargeIcon.imageset/Contents.json b/safari/Mue/Assets.xcassets/LargeIcon.imageset/Contents.json new file mode 100644 index 00000000..a19a5492 --- /dev/null +++ b/safari/Mue/Assets.xcassets/LargeIcon.imageset/Contents.json @@ -0,0 +1,20 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/safari/Mue/Base.lproj/Main.storyboard b/safari/Mue/Base.lproj/Main.storyboard new file mode 100644 index 00000000..bfe40da2 --- /dev/null +++ b/safari/Mue/Base.lproj/Main.storyboard @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/safari/Mue/Resources/Base.lproj/Main.html b/safari/Mue/Resources/Base.lproj/Main.html new file mode 100644 index 00000000..129e2774 --- /dev/null +++ b/safari/Mue/Resources/Base.lproj/Main.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + Mue Icon +

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

+

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

+

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

+ + + diff --git a/safari/Mue/Resources/Icon.png b/safari/Mue/Resources/Icon.png new file mode 100644 index 00000000..423b491d Binary files /dev/null and b/safari/Mue/Resources/Icon.png differ diff --git a/safari/Mue/Resources/Script.js b/safari/Mue/Resources/Script.js new file mode 100644 index 00000000..d7e0ff8e --- /dev/null +++ b/safari/Mue/Resources/Script.js @@ -0,0 +1,22 @@ +function show(enabled, useSettingsInsteadOfPreferences) { + if (useSettingsInsteadOfPreferences) { + document.getElementsByClassName('state-on')[0].innerText = "Mue’s extension is currently on. You can turn it off in the Extensions section of Safari Settings."; + document.getElementsByClassName('state-off')[0].innerText = "Mue’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 Mue’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/safari/Mue/Resources/Style.css b/safari/Mue/Resources/Style.css new file mode 100644 index 00000000..cbde9e69 --- /dev/null +++ b/safari/Mue/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/safari/Mue/ViewController.swift b/safari/Mue/ViewController.swift new file mode 100644 index 00000000..ea6b1403 --- /dev/null +++ b/safari/Mue/ViewController.swift @@ -0,0 +1,57 @@ +// +// ViewController.swift +// Mue +// +// Created by David Ralph on 02/01/2026. +// + +import Cocoa +import SafariServices +import WebKit + +let extensionBundleIdentifier = "mueauthors.Mue.Extension" + +class ViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHandler { + + @IBOutlet var webView: WKWebView! + + override func viewDidLoad() { + super.viewDidLoad() + + self.webView.navigationDelegate = self + + self.webView.configuration.userContentController.add(self, name: "controller") + + self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!) + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in + guard let state = state, error == nil else { + // Insert code to inform the user that something went wrong. + return + } + + DispatchQueue.main.async { + if #available(macOS 13, *) { + webView.evaluateJavaScript("show(\(state.isEnabled), true)") + } else { + webView.evaluateJavaScript("show(\(state.isEnabled), false)") + } + } + } + } + + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + if (message.body as! String != "open-preferences") { + return; + } + + SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in + DispatchQueue.main.async { + NSApplication.shared.terminate(nil) + } + } + } + +} diff --git a/vite.config.mjs b/vite.config.mjs index e4de6be3..0044f6dd 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -70,6 +70,50 @@ const prepareBuilds = () => ({ { recursive: true }, ); + // safari + const safariResourcesPath = path.resolve(__dirname, './safari/Mue Extension/Resources'); + fs.mkdirSync(safariResourcesPath, { recursive: true }); + + // Copy manifest (already exists in Resources, but ensure it's updated) + // The manifest.json is managed separately for Safari + + // Copy background.js + fs.copyFileSync( + path.resolve(__dirname, './manifest/background.js'), + path.resolve(safariResourcesPath, 'background.js'), + ); + + // Copy built files from dist + fs.cpSync(path.resolve(__dirname, './dist'), safariResourcesPath, { + recursive: true, + filter: (src) => { + // Don't overwrite the manifest.json we've already set up + return !src.endsWith('manifest.json'); + }, + }); + + // Copy icons + fs.cpSync( + path.resolve(__dirname, './src/assets/icons'), + path.resolve(safariResourcesPath, 'icons'), + { recursive: true }, + ); + + // Copy src/assets + fs.mkdirSync(path.resolve(safariResourcesPath, 'src/assets'), { recursive: true }); + fs.cpSync( + path.resolve(__dirname, './src/assets'), + path.resolve(safariResourcesPath, 'src/assets'), + { recursive: true }, + ); + + // Copy locales + fs.cpSync( + path.resolve(__dirname, './manifest/_locales'), + path.resolve(safariResourcesPath, '_locales'), + { recursive: true }, + ); + // create zip const zip = new ADMZip(); zip.addLocalFolder(path.resolve(__dirname, './build/chrome'));