From 3771fc05632070f128720727b93dde08108fd8a0 Mon Sep 17 00:00:00 2001 From: Koray Koska Date: Sun, 10 Nov 2019 06:36:50 +0100 Subject: [PATCH] Add share extension (#156) Co-authored-by: Sindre Sorhus --- Gifski.xcodeproj/project.pbxproj | 174 +++++++++++++++++- Gifski/AppDelegate.swift | 21 ++- Gifski/Gifski.entitlements | 4 + Gifski/Info.plist | 15 ++ Gifski/util.swift | 13 ++ .../Base.lproj/ShareViewController.xib | 60 ++++++ ShareExtension/Info.plist | 50 +++++ ShareExtension/ShareExtension.entitlements | 12 ++ ShareExtension/ShareViewController.swift | 100 ++++++++++ readme.md | 6 + 10 files changed, 452 insertions(+), 3 deletions(-) create mode 100644 ShareExtension/Base.lproj/ShareViewController.xib create mode 100644 ShareExtension/Info.plist create mode 100644 ShareExtension/ShareExtension.entitlements create mode 100644 ShareExtension/ShareViewController.swift diff --git a/Gifski.xcodeproj/project.pbxproj b/Gifski.xcodeproj/project.pbxproj index 0694038a..e2ee6b28 100644 --- a/Gifski.xcodeproj/project.pbxproj +++ b/Gifski.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 0E7925202329BDBE00058B94 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79251F2329BDBE00058B94 /* ShareViewController.swift */; }; + 0E7925232329BDBE00058B94 /* ShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E7925212329BDBE00058B94 /* ShareViewController.xib */; }; + 0E7925282329BDBE00058B94 /* ShareExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0E79251B2329BDBE00058B94 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 6D86841721FD283B0044F6FE /* ConversionCompletedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D86841121FD283B0044F6FE /* ConversionCompletedViewController.swift */; }; 6D86841821FD283B0044F6FE /* DraggableFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D86841621FD283B0044F6FE /* DraggableFile.swift */; }; 8548806522B78E8300E97401 /* IntTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8548806422B78E8300E97401 /* IntTextField.swift */; }; @@ -46,6 +49,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 0E7925262329BDBE00058B94 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E3AE627B1E5CD2F300035A2F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0E79251A2329BDBE00058B94; + remoteInfo = ShareExtension; + }; E3807B5A22BE315A00388F50 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5A7524AD20D085FB00F12C99 /* gifski.xcodeproj */; @@ -70,6 +80,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 0E7925292329BDBE00058B94 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 0E7925282329BDBE00058B94 /* ShareExtension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; E34798D21F882FB3003F9142 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -83,6 +104,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0E79251B2329BDBE00058B94 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 0E79251F2329BDBE00058B94 /* ShareViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ShareViewController.swift; sourceTree = ""; usesTabs = 1; }; + 0E7925222329BDBE00058B94 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ShareViewController.xib; sourceTree = ""; }; + 0E7925242329BDBE00058B94 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0E7925252329BDBE00058B94 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = ""; }; 5A7524AD20D085FB00F12C99 /* gifski.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = gifski.xcodeproj; path = "gifski-api/gifski.xcodeproj"; sourceTree = ""; }; 6D86841121FD283B0044F6FE /* ConversionCompletedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ConversionCompletedViewController.swift; sourceTree = ""; usesTabs = 1; }; 6D86841621FD283B0044F6FE /* DraggableFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DraggableFile.swift; sourceTree = ""; usesTabs = 1; }; @@ -127,6 +153,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 0E7925182329BDBE00058B94 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; E3AE62801E5CD2F300035A2F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -140,6 +173,17 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0E79251C2329BDBE00058B94 /* ShareExtension */ = { + isa = PBXGroup; + children = ( + 0E79251F2329BDBE00058B94 /* ShareViewController.swift */, + 0E7925212329BDBE00058B94 /* ShareViewController.xib */, + 0E7925242329BDBE00058B94 /* Info.plist */, + 0E7925252329BDBE00058B94 /* ShareExtension.entitlements */, + ); + path = ShareExtension; + sourceTree = ""; + }; 5A7524AE20D085FB00F12C99 /* Products */ = { isa = PBXGroup; children = ( @@ -189,6 +233,7 @@ isa = PBXGroup; children = ( E3AE62851E5CD2F300035A2F /* Gifski */, + 0E79251C2329BDBE00058B94 /* ShareExtension */, E3AE62841E5CD2F300035A2F /* Products */, E317EE121F88305800359C57 /* Frameworks */, 5A7524AD20D085FB00F12C99 /* gifski.xcodeproj */, @@ -200,6 +245,7 @@ isa = PBXGroup; children = ( E3AE62831E5CD2F300035A2F /* Gifski.app */, + 0E79251B2329BDBE00058B94 /* ShareExtension.appex */, ); name = Products; sourceTree = ""; @@ -241,6 +287,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 0E79251A2329BDBE00058B94 /* ShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0E7925302329BDBE00058B94 /* Build configuration list for PBXNativeTarget "ShareExtension" */; + buildPhases = ( + 0E7925172329BDBE00058B94 /* Sources */, + 0E7925182329BDBE00058B94 /* Frameworks */, + 0E7925192329BDBE00058B94 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ShareExtension; + productName = ShareExtension; + productReference = 0E79251B2329BDBE00058B94 /* ShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; E3AE62821E5CD2F300035A2F /* Gifski */ = { isa = PBXNativeTarget; buildConfigurationList = E3AE62901E5CD2F300035A2F /* Build configuration list for PBXNativeTarget "Gifski" */; @@ -251,10 +314,12 @@ E3AE62811E5CD2F300035A2F /* Resources */, E34798D21F882FB3003F9142 /* Embed Frameworks */, E3A9400C2182D718006981D5 /* Crashlytics */, + 0E7925292329BDBE00058B94 /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + 0E7925272329BDBE00058B94 /* PBXTargetDependency */, ); name = Gifski; productName = "HEIC Converter"; @@ -267,10 +332,13 @@ E3AE627B1E5CD2F300035A2F /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0820; + LastSwiftUpdateCheck = 1100; LastUpgradeCheck = 0930; ORGANIZATIONNAME = "Sindre Sorhus"; TargetAttributes = { + 0E79251A2329BDBE00058B94 = { + CreatedOnToolsVersion = 11.0; + }; E3AE62821E5CD2F300035A2F = { CreatedOnToolsVersion = 8.2.1; LastSwiftMigration = 1020; @@ -305,6 +373,7 @@ projectRoot = ""; targets = ( E3AE62821E5CD2F300035A2F /* Gifski */, + 0E79251A2329BDBE00058B94 /* ShareExtension */, ); }; /* End PBXProject section */ @@ -334,6 +403,14 @@ /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ + 0E7925192329BDBE00058B94 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E7925232329BDBE00058B94 /* ShareViewController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; E3AE62811E5CD2F300035A2F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -387,6 +464,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 0E7925172329BDBE00058B94 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E7925202329BDBE00058B94 /* ShareViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; E3AE627F1E5CD2F300035A2F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -424,7 +509,24 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 0E7925272329BDBE00058B94 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0E79251A2329BDBE00058B94 /* ShareExtension */; + targetProxy = 0E7925262329BDBE00058B94 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ + 0E7925212329BDBE00058B94 /* ShareViewController.xib */ = { + isa = PBXVariantGroup; + children = ( + 0E7925222329BDBE00058B94 /* Base */, + ); + name = ShareViewController.xib; + sourceTree = ""; + usesTabs = 1; + }; E3AE628A1E5CD2F300035A2F /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( @@ -436,6 +538,65 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 0E79252A2329BDBE00058B94 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = YG56YK5RN5; + ENABLE_HARDENED_RUNTIME = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = ShareExtension/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.sindresorhus.Gifski.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 0E79252B2329BDBE00058B94 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = YG56YK5RN5; + ENABLE_HARDENED_RUNTIME = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = ShareExtension/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.sindresorhus.Gifski.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; E3AE628E1E5CD2F300035A2F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -551,6 +712,7 @@ E3AE62911E5CD2F300035A2F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Gifski/Gifski.entitlements; @@ -590,6 +752,7 @@ E3AE62921E5CD2F300035A2F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Gifski/Gifski.entitlements; @@ -627,6 +790,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 0E7925302329BDBE00058B94 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0E79252A2329BDBE00058B94 /* Debug */, + 0E79252B2329BDBE00058B94 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; E3AE627E1E5CD2F300035A2F /* Build configuration list for PBXProject "Gifski" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Gifski/AppDelegate.swift b/Gifski/AppDelegate.swift index a21fd82a..4e09df24 100644 --- a/Gifski/AppDelegate.swift +++ b/Gifski/AppDelegate.swift @@ -54,13 +54,30 @@ final class AppDelegate: NSObject, NSApplicationDelegate { return } + var sharedVideoUrl = videoUrl + + if videoUrl.host == "shareExtension" { + if let path = videoUrl.queryParameters["path"], + let appIdentifierPrefix = Bundle.main.infoDictionary?["AppIdentifierPrefix"] as? String, + let videoUrlString = path.removingPercentEncoding, + let appGroupShareVideoUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "\(appIdentifierPrefix).gifski_video_share_group")?.appendingPathComponent(videoUrlString) { + sharedVideoUrl = appGroupShareVideoUrl + } else { + NSAlert.showModal( + for: mainWindowController.window, + message: "Could not retrieve shared video" + ) + return + } + } + // TODO: Simplify this. Make a function that calls the input when the app finished launching, or right away if it already has. if hasFinishedLaunching { - mainWindowController.convert(videoUrl) + mainWindowController.convert(sharedVideoUrl.absoluteURL) } else { // This method is called before `applicationDidFinishLaunching`, // so we buffer it up a video is "Open with" this app - urlToConvertOnLaunch = videoUrl + urlToConvertOnLaunch = sharedVideoUrl.absoluteURL } } diff --git a/Gifski/Gifski.entitlements b/Gifski/Gifski.entitlements index a0463869..de255f7d 100644 --- a/Gifski/Gifski.entitlements +++ b/Gifski/Gifski.entitlements @@ -4,6 +4,10 @@ com.apple.security.app-sandbox + com.apple.security.application-groups + + $(TeamIdentifierPrefix).gifski_video_share_group + com.apple.security.files.user-selected.read-write com.apple.security.network.client diff --git a/Gifski/Info.plist b/Gifski/Info.plist index 8acaca25..4f54da81 100644 --- a/Gifski/Info.plist +++ b/Gifski/Info.plist @@ -2,6 +2,8 @@ + AppIdentifierPrefix + $(AppIdentifierPrefix) CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDocumentTypes @@ -37,6 +39,19 @@ $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString $(MARKETING_VERSION) + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + com.sindresorhus.Gifski + CFBundleURLSchemes + + gifski + + + CFBundleVersion $(CURRENT_PROJECT_VERSION) Fabric diff --git a/Gifski/util.swift b/Gifski/util.swift index 7200eba4..09178fa8 100644 --- a/Gifski/util.swift +++ b/Gifski/util.swift @@ -2358,6 +2358,19 @@ extension URL { func setMetadata(key: MetadataKey, value: T) throws { try attributes.set("com.apple.metadata:\(key.attributeKey)", value: value) } + + var queryParameters: [String: String] { + guard + let components = URLComponents(url: self, resolvingAgainstBaseURL: true), + let queryItems = components.queryItems + else { + return [:] + } + + return queryItems.reduce(into: [String: String]()) { result, item in + result[item.name] = item.value + } + } } extension NSViewController { diff --git a/ShareExtension/Base.lproj/ShareViewController.xib b/ShareExtension/Base.lproj/ShareViewController.xib new file mode 100644 index 00000000..0ff47c17 --- /dev/null +++ b/ShareExtension/Base.lproj/ShareViewController.xib @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ShareExtension/Info.plist b/ShareExtension/Info.plist new file mode 100644 index 00000000..5568de28 --- /dev/null +++ b/ShareExtension/Info.plist @@ -0,0 +1,50 @@ + + + + + AppIdentifierPrefix + $(AppIdentifierPrefix) + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Gifski + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + SUBQUERY ( + extensionItems, + $extensionItem, + SUBQUERY ( + $extensionItem.attachments, + $attachment, + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.mpeg-4" || + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.apple.m4v-video" || + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.apple.quicktime-movie" + ).@count == $extensionItem.attachments.@count + ).@count == 1 + + NSExtensionPointIdentifier + com.apple.share-services + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ShareViewController + + + diff --git a/ShareExtension/ShareExtension.entitlements b/ShareExtension/ShareExtension.entitlements new file mode 100644 index 00000000..82b99312 --- /dev/null +++ b/ShareExtension/ShareExtension.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + $(TeamIdentifierPrefix).gifski_video_share_group + + + diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift new file mode 100644 index 00000000..f542c0a3 --- /dev/null +++ b/ShareExtension/ShareViewController.swift @@ -0,0 +1,100 @@ +import Cocoa + +final class ShareViewController: NSViewController { + override var nibName: NSNib.Name? { "ShareViewController" } + + @IBOutlet private var errorLabel: NSTextField! + @IBOutlet private var errorButtonOk: NSButton! + + // swiftlint:disable:next prohibited_super_call + override func loadView() { + super.loadView() + + // Make error views invisible + errorLabel.isHidden = true + errorButtonOk.isHidden = true + + guard + let item = (extensionContext?.inputItems[0] as? NSExtensionItem)?.attachments?.first + else { + presentError(message: "The shared item does not contain an attachment") + return + } + + var typeIdentifier: String + if item.hasItemConformingToTypeIdentifier("public.mpeg-4") { + typeIdentifier = "public.mpeg-4" + } else if item.hasItemConformingToTypeIdentifier("com.apple.m4v-video") { + typeIdentifier = "com.apple.m4v-video" + } else if item.hasItemConformingToTypeIdentifier("com.apple.quicktime-movie") { + typeIdentifier = "com.apple.quicktime-movie" + } else { + presentError(message: "The shared item is not in a valid video format") + return + } + + item.loadFileRepresentation(forTypeIdentifier: typeIdentifier) { url, error in + guard let url = url else { + self.presentError(message: error?.localizedDescription ?? "Unknown error") + return + } + + let shareUrl = "\(url.lastPathComponent)" + let appIdentifierPrefix = Bundle.main.infoDictionary!["AppIdentifierPrefix"] as! String + + guard + let appGroupShareVideUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "\(appIdentifierPrefix).gifski_video_share_group")?.appendingPathComponent(shareUrl) + else { + self.presentError(message: "Could not share the video with the main app") + return + } + + try? FileManager.default.removeItem(at: appGroupShareVideUrl) + do { + try FileManager.default.copyItem(at: url, to: appGroupShareVideUrl) + } catch { + self.presentError(message: error.localizedDescription) + return + } + + guard + let gifski = self.createMainAppUrl( + queryItems: [ + URLQueryItem( + name: "path", + value: shareUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + ) + ] + ) + else { + self.presentError(message: "Could not share the video with the main app") + return + } + + DispatchQueue.main.sync { + NSWorkspace.shared.open(gifski) + self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + } + } + } + + private func presentError(message: String) { + errorLabel.isHidden = false + errorButtonOk.isHidden = false + errorLabel.stringValue = message + } + + private func createMainAppUrl(queryItems: [URLQueryItem] = []) -> URL? { + var components = URLComponents() + components.scheme = "gifski" + components.host = "shareExtension" + components.queryItems = queryItems + return components.url + } + + // MARK: - Actions + + @IBAction private func errorButtonOkClicked(_ sender: Any) { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + } +} diff --git a/readme.md b/readme.md index 71c47449..0b3a5751 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,12 @@ In the width/height input fields in the save panel, press the arrow up/down keys Gifski includes a [system service](https://www.computerworld.com/article/2476298/os-x-a-quick-guide-to-services-on-your-mac.html) that lets you quickly convert a video to GIF from the **Services** menu in any app that provides a compatible video file. +### Share extension + +Gifski includes a share extension that lets you share videos to Gifski. Just select Gifski from the Share menu of any macOS app. + +> Tip: You can share a macOS screen recording with Gifski by clicking on the thumbnail that pops up once you are done recording and selecting “Share” from there. + ## Screenshots