Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cordova/apple/ios): add mac catalyst support #1630

Merged
merged 77 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
caab056
Replace the `copy-www-build-step` script with build phases.
sbruens Apr 24, 2023
cfc0a56
Add Mac Catalyst as a destination.
sbruens Apr 24, 2023
6d5dc15
Update README to load xcworkspace from `./src/cordova/apple/` instead…
sbruens Apr 28, 2023
59e2573
Set separate bundle identifier for the Mac app built with Mac Catalyst.
sbruens Apr 29, 2023
6c17efc
Add `Package.resolved` back. It was likely accidentally removed in #1…
sbruens Apr 29, 2023
5d047ba
Update local storage source directory for UIWebView->WKWebView migrat…
sbruens May 1, 2023
eb34314
Pin `cordova-ios` dep to fix for splashscreen aspect ratio.
sbruens May 1, 2023
27c7b16
Rename `Images.xcassets` to `Assets.xcassets`.
sbruens May 1, 2023
99f8e11
Add the status bar button images.
sbruens May 5, 2023
c805cfa
Use `LSUIElement` to indicate this is a background app and shouldn't …
sbruens May 5, 2023
c921133
Add an AppKit bridge and use it to create a status bar menu in catalyst.
sbruens May 8, 2023
dea0058
Re-open the application window when use selects "Open" command from t…
sbruens May 9, 2023
a3d87a7
Stop any VPN connections on app termination.
sbruens May 9, 2023
0bd0cf3
Use `clang-format` to format Objective-C files.
sbruens May 9, 2023
1a9ccba
Use `SwiftFormat` to format Swift files.
sbruens May 9, 2023
b1890cd
Set separate bundle identifier for the Mac Catalyst app built for rel…
sbruens May 9, 2023
8b31acf
Configure the app window to be a specific size.
sbruens Jul 7, 2023
604cf08
Enable the embedded app launcher as a login item.
sbruens Jul 7, 2023
598a463
Use the macos app group in `PacketTunnelProvider` when on Catalyst.
sbruens Jul 12, 2023
f00bd3d
Add some logs to the `OutlineStatusItemController` for easier debugging.
sbruens Jul 13, 2023
7f6a4ea
Do not try and add a `StatusItemController` automatically on AppKitBr…
sbruens Jul 13, 2023
117b0ce
Ignore locally loaded XCFramework bundles.
sbruens Jul 13, 2023
0fe6a43
Add launcher to Catalyst.
sbruens Jul 13, 2023
2314814
Run `clang-format` over `.m` files.
sbruens Jul 13, 2023
f9080fe
Ensure Sentry logs the right app group on Catalyst.
sbruens Jul 13, 2023
9323995
Run macos code on Catalyst in a few more places.
sbruens Jul 13, 2023
b600af9
Do not run some code for `macOS` on `Catalyst`.
sbruens Jul 17, 2023
a0fcc01
Add prefix to log message for consistency.
sbruens Jul 17, 2023
9a2552a
Use the `NETunnelProviderManager` to determine whether Outline was co…
sbruens Jul 19, 2023
3221092
Automatic project data changes made by Xcode.
sbruens Jul 19, 2023
9215e05
Set miminum macos deployment target to 10.15.
sbruens Jul 19, 2023
50706b9
Set bundle ID for launcher to `launcher3`.
sbruens Jul 20, 2023
47ae814
Move `AppDelegate` logic to category extension instead of overwriting…
sbruens Jul 24, 2023
9ff6ecf
Move Catalyst specific `AppDelegate` code to Swift.
sbruens Jul 24, 2023
5b533c6
Use `SwiftFormat` to format Swift files.
sbruens Jul 24, 2023
28314ab
Replace `NSLog` with `DDLog`.
sbruens Jul 24, 2023
1cf4d01
Use `SwiftFormat` to format Swift files.
sbruens Jul 24, 2023
7da81f9
Fix notifications for macos bundle and undo formatting to make the di…
sbruens Jul 24, 2023
141262a
Remove local `Tun2socks.xcframework` which got accidentally committed.
sbruens Jul 24, 2023
cd48188
Undo README changes as they have already been updated in a previous PR.
sbruens Jul 24, 2023
a46c9f8
Move catalyst code into `OutlineAppleLib`.
sbruens Jul 26, 2023
04c734e
Merge remote-tracking branch 'origin/master' into sbruens/catalyst
sbruens Jul 26, 2023
a34a4f8
Remove reference to `OutlineLauncher`'s plist.
sbruens Jul 26, 2023
68ffbdd
Fix some bad merges.
sbruens Jul 26, 2023
cea2ba4
Remove an unnecessary `#if` guard.
sbruens Jul 26, 2023
4da2bee
Add a TODO to i18n the menu strings, which will happen in a fast-follow.
sbruens Jul 26, 2023
74a6475
Review changes.
sbruens Jul 26, 2023
0c7469a
More changes for review.
sbruens Jul 26, 2023
935844d
Mark static function as public.
sbruens Jul 26, 2023
eff9b8d
More changes.
sbruens Jul 26, 2023
c7132ee
Use enum instead of boolean to represent connection status.
sbruens Jul 26, 2023
6273649
Clean up some entitlement changes that aren't actually needed.
sbruens Jul 26, 2023
3c24a95
Undo some entitlement changes that were needed to create outgoing con…
sbruens Jul 26, 2023
71a751b
Remove a single unneeded entitlement.
sbruens Jul 26, 2023
9dccb61
Only compile `OutlineCatalystApp` on Catalyst.
sbruens Jul 26, 2023
ef11009
Fix ios build.
sbruens Jul 26, 2023
868f11e
Merge remote-tracking branch 'origin/master' into sbruens/catalyst
sbruens Jul 27, 2023
44123bc
Add some method docs and change methods to standalone functions wher…
sbruens Aug 8, 2023
863ac93
Remove unneeded `Sources` sections.
sbruens Aug 8, 2023
53a409e
Revert "Remove unneeded `Sources` sections."
sbruens Aug 9, 2023
682bd4b
Remove unneeded build phases.
sbruens Aug 9, 2023
767da66
Remove seemingly unneeded info list items.
sbruens Aug 9, 2023
351a6f9
Add the "compile sources" build phase back to the `AppKitBridge` bundle.
sbruens Aug 9, 2023
ce84470
Fix macos build.
sbruens Aug 9, 2023
cb46285
Move `OutlineCatalystApp` target into `OutlineAppleLib` product.
sbruens Aug 9, 2023
fb05e14
Rely on `NSNotification` extension from `OutlineAppleLib`.
sbruens Aug 9, 2023
ecdd922
Remove `Tun2socks` and `OutlineTunnel` dependency from launcher and a…
sbruens Aug 9, 2023
04d5a60
Select "Optimize interface for Mac" at the project level for Mac Cata…
sbruens Aug 9, 2023
f49f26f
Move much of the OutlinePlugin into a Swift package, leaving only Cor…
sbruens Aug 14, 2023
04370b8
Move status menu assets into Swift package.
sbruens Aug 15, 2023
1b85943
No need to explicitly state resources for the Assets catalog as Xcode…
sbruens Aug 15, 2023
f1b83fc
Update the macos client to use the renamed `CDVOutline` plugin.
sbruens Aug 15, 2023
48148d7
Revert `OutlinePlugin` changes and instead use a `NSNotification` tar…
sbruens Aug 15, 2023
5615598
`OutlineNotifications` to `OutlineNotification`.
sbruens Aug 15, 2023
85c5cde
Add some TODOs.
sbruens Aug 15, 2023
30d7b37
Remove unnecessary parameter.
sbruens Aug 15, 2023
aa5bbcb
Merge branch 'master' into sbruens/catalyst
sbruens Aug 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
293 changes: 267 additions & 26 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"copy-webpack-plugin": "^5.1.1",
"cordova-android": "^11.0.0",
"cordova-browser": "~6.0.0",
"cordova-ios": "~6.3.0",
"cordova-ios": "github:apache/cordova-ios#1a5cd45e2243b239b5045a0ade9d2da1d779b72a",
"cordova-lib": "^11.0.0",
"cordova-osx": "github:apache/cordova-osx",
"cordova-plugin-clipboard": "github:Jigsaw-Code/outline-cordova-plugin-clipboard#v2.0.0",
Expand Down
1 change: 1 addition & 0 deletions src/cordova/apple/OutlineAppleLib/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/.build
/Packages
/*.xcodeproj
/*.xcframework
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
Expand Down
51 changes: 42 additions & 9 deletions src/cordova/apple/OutlineAppleLib/Package.swift
sbruens marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,71 @@ let package = Package(
products: [
.library(
name: "OutlineAppleLib",
targets: ["Tun2socks", "OutlineSentryLogger", "OutlineTunnel"]),
targets: ["Tun2socks", "OutlineSentryLogger", "OutlineTunnel", "OutlineCatalystApp", "OutlineNotification"]
),
.library(
name: "OutlineLauncher",
targets: ["OutlineLauncher"]
),
.library(
name: "OutlineAppKitBridge",
targets: ["OutlineAppKitBridge"]
),
.library(
name: "PacketTunnelProvider",
targets: ["PacketTunnelProvider"]),
targets: ["PacketTunnelProvider"]
),
],
dependencies: [
.package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", from: "3.7.4"),
.package(url: "https://github.com/getsentry/sentry-cocoa", from: "7.31.3"),
],
targets: [
.target(
name: "OutlineLauncher",
dependencies:
["CocoaLumberjack",
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
"OutlineCatalystApp"]
),
.target(
name: "OutlineCatalystApp",
dependencies: [
"OutlineAppKitBridge",
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
]
),
.target(
name: "OutlineAppKitBridge",
dependencies: [
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
"OutlineNotification",
]
),
.target(
name: "PacketTunnelProvider",
dependencies:
["CocoaLumberjack",
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
"Tun2socks",
"OutlineTunnel"
],
["CocoaLumberjack",
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
"Tun2socks",
"OutlineTunnel"],
cSettings: [
.headerSearchPath("Internal"),
]
),
.target(name: "OutlineNotification"),
.target(
name: "OutlineSentryLogger",
dependencies: [
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
.product(name: "Sentry", package: "sentry-cocoa")
.product(name: "Sentry", package: "sentry-cocoa"),
]
),
.target(
name: "OutlineTunnel",
dependencies: [
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
"Tun2socks",
]
),
.binaryTarget(
Expand All @@ -50,6 +82,7 @@ let package = Package(
),
.testTarget(
name: "OutlineTunnelTest",
dependencies: ["OutlineTunnel", "PacketTunnelProvider"]),
dependencies: ["OutlineTunnel", "PacketTunnelProvider"]
),
]
)
5 changes: 3 additions & 2 deletions src/cordova/apple/OutlineAppleLib/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# OutlineTun2Socks
# OutlineAppleLib

This package provides the Outline Tun2socks framework as a Swift Package.
This package provides all Outline Apple logic, including the Outline Tun2socks
framework, as a Swift Package.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2023 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#if os(macOS)
import AppKit
import CocoaLumberjackSwift
import ServiceManagement

public class AppKitBridge: NSObject, AppKitBridgeProtocol {
private var statusItemController: StatusItemController?
static let kAppGroup = "QT8Z3Q9V3A.org.outline.macos.client"
static let kAppLauncherName = "launcher3"

override public required init() {
super.init()
}

/// Terminates the application.
@objc public func terminate() {
NSApp.terminate(self)
}

/// Set the connection status in the app's menu in the system-wide menu bar.
@objc public func setConnectionStatus(_ status: ConnectionStatus) {
if statusItemController == nil {
DDLogInfo("[AppKitBridge] No status item controller found. Creating one now.")
statusItemController = StatusItemController()
}
statusItemController!.setStatus(status: status)
}

/// Enables or disables the embedded app launcher as a login item.
@objc public func setAppLauncherEnabled(_ isEnabled: Bool) {
guard let launcherBundleId = getLauncherBundleId() else {
return DDLogError("[AppKitBridge] Unable to set launcher for missing bundle ID.")
}

if !SMLoginItemSetEnabled(launcherBundleId as! CFString, isEnabled) {
return DDLogError("[AppKitBridge] Failed to set enable=\(isEnabled) for launcher \(launcherBundleId).")
}

return DDLogInfo("[AppKitBridge] Successfully set enable=\(isEnabled) for launcher \(launcherBundleId).")
}

/// Loads the main application from a given launcher bundle.
@objc public func loadMainApp(_ launcherBundleId: String) {
// Retrieve the main app's bundle ID programmatically from the embedded launcher bundle ID.
let mainAppBundleId = getMainBundleId(launcherBundleId)
DDLogInfo("[AppKitBridge] Loading main app \(mainAppBundleId) from launcher \(launcherBundleId).")

let descriptor = NSAppleEventDescriptor(string: launcherBundleId)
NSWorkspace.shared.launchApplication(withBundleIdentifier: mainAppBundleId,
options: [.withoutActivation, .andHide],
additionalEventParamDescriptor: descriptor,
launchIdentifier: nil)
}
}

/// Returns the embedded launcher application's bundle ID.
private func getLauncherBundleId() -> String? {
guard let bundleId = Bundle.main.bundleIdentifier else {
DDLogError("[AppKitBridge] Failed to retrieve the application's bundle ID.")
return nil
}
return String(format: "%@.%@", bundleId, AppKitBridge.kAppLauncherName)
}

/// Returns the main application's bundle ID from the embedded launcher bundle ID.
private func getMainBundleId(_ launcherBundleId: String) -> String {
sbruens marked this conversation as resolved.
Show resolved Hide resolved
return (launcherBundleId as NSString).deletingPathExtension
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2023 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

@objc
public protocol AppKitBridgeProtocol: NSObjectProtocol {
init()

func terminate()

func setConnectionStatus(_ status: ConnectionStatus)

func setAppLauncherEnabled(_ isEnabled: Bool)

func loadMainApp(_ launcherBundleId: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2023 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

@objc public enum ConnectionStatus: Int {
case unknown
case connected
case disconnected
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "outline-black-off-2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "outline-black-on-2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2023 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#if os(macOS)
import AppKit
import CocoaLumberjackSwift
import OutlineNotification

var StatusItem = NSStatusItem()

class StatusItemController: NSObject {
let connectionStatusMenuItem = NSMenuItem(title: MenuTitle.statusDisconnected,
action: nil,
keyEquivalent: "")

private enum AppIconImage {
static let statusConnected = getImage(name: "status_bar_button_image_connected")
static let statusDisconnected = getImage(name: "status_bar_button_image")
}

// TODO: Internationalize these user-facing strings.
private enum MenuTitle {
static let open = "Open"
static let quit = "Quit"
static let statusConnected = "Connected"
static let statusDisconnected = "Disconnected"
}

override init() {
super.init()

DDLogInfo("[StatusItemController] Creating status menu")
StatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
setStatus(status: .disconnected)

let menu = NSMenu()
let openMenuItem = NSMenuItem(title: MenuTitle.open, action: #selector(openApplication), keyEquivalent: "o")
openMenuItem.target = self
menu.addItem(openMenuItem)
menu.addItem(connectionStatusMenuItem)
menu.addItem(NSMenuItem.separator())
let closeMenuItem = NSMenuItem(title: MenuTitle.quit, action: #selector(closeApplication), keyEquivalent: "q")
closeMenuItem.target = self
menu.addItem(closeMenuItem)
StatusItem.menu = menu
}

func setStatus(status: ConnectionStatus) {
let isConnected = status == .connected
let appIconImage = isConnected ? AppIconImage.statusConnected : AppIconImage.statusDisconnected
appIconImage.isTemplate = true
StatusItem.button?.image = appIconImage

let connectionStatusTitle = isConnected ? MenuTitle.statusConnected : MenuTitle.statusDisconnected
connectionStatusMenuItem.title = connectionStatusTitle
}

@objc func openApplication(_: AnyObject?) {
DDLogInfo("[StatusItemController] Opening application")
NSApp.activate(ignoringOtherApps: true)
guard let uiWindow = getUiWindow() else {
return
}
uiWindow.makeKeyAndOrderFront(self)
}

@objc func closeApplication(_: AnyObject?) {
DDLogInfo("[StatusItemController] Closing application")
NotificationCenter.default.post(name: .kAppQuit, object: nil)
NSApplication.shared.terminate(self)
}
}

private func getUiWindow() -> NSWindow? {
sbruens marked this conversation as resolved.
Show resolved Hide resolved
for window in NSApp.windows {
if String(describing: window).contains("UINSWindow") {
return window
}
}
return nil
}

private func getImage(name: String) -> NSImage {
guard let image = Bundle.module.image(forResource: NSImage.Name(name)) else {
fatalError("Unable to load image asset named \(name).")
}
return image
}

#endif
Loading
Loading