* [PATCH 0/2] iOS 27 build and launch fixes
@ 2026-10-10 16:03 Loren McQuade
2026-10-10 16:03 ` [PATCH 1/2] WireGuardKitC: include sys/types.h Loren McQuade
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Loren McQuade @ 2026-10-10 16:03 UTC (permalink / raw)
To: wireguard
Two small fixes for building and running the iOS app with the iOS 27 SDK.
1/2 WireGuardKitC fails to build because u_int32_t, u_char and
u_int16_t are not visible to the module without including
<sys/types.h>.
2/2 An app built with the iOS 27 SDK that has no UIScene manifest is
killed at launch (EXC_BREAKPOINT in
UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption). This moves
the window into a SceneDelegate. The app delegate's state restoration is
not called once scenes are in use, and a scene's restoration activity was
not handed back after a force-quit when I tried it (the scene saved one,
session.stateRestorationActivity was nil on the next launch), so the open
tunnel is remembered in UserDefaults instead. I'm happy to do that
differently if you'd prefer.
Tested on an iPhone 15 Pro running iOS 27.0: the app launches, importing a
.conf from Files works, quick actions work, and the last open tunnel comes
back after a force-quit and relaunch. The iOS app archives and the macOS
target builds with Xcode 27. Not tested: iPad, or any iOS before 27.0.
swiftlint reports nothing in the changed files.
I'll send a separate message about a tunnel stall I see on cellular.
Loren McQuade (2):
WireGuardKitC: include sys/types.h
iOS: adopt the UIScene lifecycle
Sources/WireGuardApp/UI/iOS/AppDelegate.swift | 100 +++++++++---------
Sources/WireGuardApp/UI/iOS/Info.plist | 17 +++
.../ViewController/MainViewController.swift | 13 +++
Sources/WireGuardKitC/WireGuardKitC.h | 2 +
4 files changed, 81 insertions(+), 51 deletions(-)
--
2.54.0 (Apple Git-157)
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/2] WireGuardKitC: include sys/types.h
2026-10-10 16:03 [PATCH 0/2] iOS 27 build and launch fixes Loren McQuade
@ 2026-10-10 16:03 ` Loren McQuade
2026-10-10 16:03 ` [PATCH 2/2] iOS: adopt the UIScene lifecycle Loren McQuade
2026-10-27 20:14 ` [PATCH 0/2] iOS 27 build and launch fixes John
2 siblings, 0 replies; 4+ messages in thread
From: Loren McQuade @ 2026-10-10 16:03 UTC (permalink / raw)
To: wireguard
With the iOS 27 SDK the WireGuardKitC module fails to build:
error: declaration of 'u_int32_t' must be imported from module
'_DarwinFoundation1.unsigned_types.u_int32_t' before it is required
The same goes for u_char and u_int16_t. Include sys/types.h before using
them.
Signed-off-by: Loren McQuade <loren.mcquade@gmail.com>
---
Sources/WireGuardKitC/WireGuardKitC.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Sources/WireGuardKitC/WireGuardKitC.h b/Sources/WireGuardKitC/WireGuardKitC.h
index 54e4783..84c53f7 100644
--- a/Sources/WireGuardKitC/WireGuardKitC.h
+++ b/Sources/WireGuardKitC/WireGuardKitC.h
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
+#include <sys/types.h>
+
#include "key.h"
#include "x25519.h"
--
2.54.0 (Apple Git-157)
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/2] iOS: adopt the UIScene lifecycle
2026-10-10 16:03 [PATCH 0/2] iOS 27 build and launch fixes Loren McQuade
2026-10-10 16:03 ` [PATCH 1/2] WireGuardKitC: include sys/types.h Loren McQuade
@ 2026-10-10 16:03 ` Loren McQuade
2026-10-27 20:14 ` [PATCH 0/2] iOS 27 build and launch fixes John
2 siblings, 0 replies; 4+ messages in thread
From: Loren McQuade @ 2026-10-10 16:03 UTC (permalink / raw)
To: wireguard
Apps built with the iOS 27 SDK that have no scene manifest are killed at
launch, with EXC_BREAKPOINT in
UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption.
Move the window and main view controller out of the app delegate into a
SceneDelegate, and handle URL opens and quick actions there. The
UIApplicationSceneManifest in Info.plist names the delegate.
The app delegate's state restoration, which brought back the tunnel detail
screen after a relaunch, is not called once the app uses scenes, and a
scene's restoration activity is not handed back after the user force-quits
the app (checked on a device: session.stateRestorationActivity is nil on the
next launch although the scene saved one). So remember the open tunnel in
UserDefaults when the scene resigns active, and reopen it at launch unless
the app was launched for a URL or a quick action.
Signed-off-by: Loren McQuade <loren.mcquade@gmail.com>
---
Sources/WireGuardApp/UI/iOS/AppDelegate.swift | 100 +++++++++---------
Sources/WireGuardApp/UI/iOS/Info.plist | 17 +++
.../ViewController/MainViewController.swift | 13 +++
3 files changed, 79 insertions(+), 51 deletions(-)
diff --git a/Sources/WireGuardApp/UI/iOS/AppDelegate.swift b/Sources/WireGuardApp/UI/iOS/AppDelegate.swift
index fbb09c7..24c7cd8 100644
--- a/Sources/WireGuardApp/UI/iOS/AppDelegate.swift
+++ b/Sources/WireGuardApp/UI/iOS/AppDelegate.swift
@@ -7,78 +7,76 @@ import os.log
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
- var window: UIWindow?
var mainVC: MainViewController?
- var isLaunchedForSpecificAction = false
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
- if let launchOptions = launchOptions {
- if launchOptions[.url] != nil || launchOptions[.shortcutItem] != nil {
- isLaunchedForSpecificAction = true
- }
- }
+ return true
+ }
+}
- let window = UIWindow(frame: UIScreen.main.bounds)
- self.window = window
+/// Owns the window and main view controller. iOS 27 requires apps built with its SDK to adopt the
+/// UIScene lifecycle; the app-delegate-owned window used to be enough.
+class SceneDelegate: UIResponder, UIWindowSceneDelegate {
+ private static let lastDisplayedTunnelKey = "lastDisplayedTunnelName"
+
+ var window: UIWindow?
+
+ private var appDelegate: AppDelegate? {
+ return UIApplication.shared.delegate as? AppDelegate
+ }
+
+ func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
+ guard let windowScene = scene as? UIWindowScene else { return }
let mainVC = MainViewController()
+ let window = UIWindow(windowScene: windowScene)
window.rootViewController = mainVC
window.makeKeyAndVisible()
+ self.window = window
+ appDelegate?.mainVC = mainVC
- self.mainVC = mainVC
-
- return true
- }
-
- func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
- mainVC?.importFromDisposableFile(url: url)
- return true
+ if !connectionOptions.urlContexts.isEmpty || connectionOptions.shortcutItem != nil {
+ if let url = connectionOptions.urlContexts.first?.url {
+ mainVC.importFromDisposableFile(url: url)
+ }
+ if let shortcutItem = connectionOptions.shortcutItem {
+ handle(shortcutItem: shortcutItem)
+ }
+ } else if let tunnelName = UserDefaults.standard.string(forKey: Self.lastDisplayedTunnelKey) {
+ mainVC.showTunnelDetailForTunnel(named: tunnelName, animated: false, shouldToggleStatus: false)
+ }
}
- func applicationDidBecomeActive(_ application: UIApplication) {
- mainVC?.refreshTunnelConnectionStatuses()
+ func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
+ guard let url = URLContexts.first?.url else { return }
+ appDelegate?.mainVC?.importFromDisposableFile(url: url)
}
- func applicationWillResignActive(_ application: UIApplication) {
- guard let allTunnelNames = mainVC?.allTunnelNames() else { return }
- application.shortcutItems = QuickActionItem.createItems(allTunnelNames: allTunnelNames)
+ func sceneDidBecomeActive(_ scene: UIScene) {
+ appDelegate?.mainVC?.refreshTunnelConnectionStatuses()
}
- func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
- guard shortcutItem.type == QuickActionItem.type else {
- completionHandler(false)
- return
+ func sceneWillResignActive(_ scene: UIScene) {
+ // iOS discards a scene's saved state when the user force-quits, so remember the open tunnel ourselves.
+ if let tunnelName = appDelegate?.mainVC?.displayedTunnelName {
+ UserDefaults.standard.set(tunnelName, forKey: Self.lastDisplayedTunnelKey)
+ } else {
+ UserDefaults.standard.removeObject(forKey: Self.lastDisplayedTunnelKey)
}
- let tunnelName = shortcutItem.localizedTitle
- mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false, shouldToggleStatus: true)
- completionHandler(true)
- }
-}
-
-extension AppDelegate {
- func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
- return true
+ guard let allTunnelNames = appDelegate?.mainVC?.allTunnelNames() else { return }
+ UIApplication.shared.shortcutItems = QuickActionItem.createItems(allTunnelNames: allTunnelNames)
}
- func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
- return !self.isLaunchedForSpecificAction
+ func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
+ completionHandler(handle(shortcutItem: shortcutItem))
}
- func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
- guard let vcIdentifier = identifierComponents.last else { return nil }
- if vcIdentifier.hasPrefix("TunnelDetailVC:") {
- let tunnelName = String(vcIdentifier.suffix(vcIdentifier.count - "TunnelDetailVC:".count))
- if let tunnelsManager = mainVC?.tunnelsManager {
- if let tunnel = tunnelsManager.tunnel(named: tunnelName) {
- return TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
- }
- } else {
- // Show it when tunnelsManager is available
- mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false, shouldToggleStatus: false)
- }
- }
- return nil
+ @discardableResult
+ private func handle(shortcutItem: UIApplicationShortcutItem) -> Bool {
+ guard shortcutItem.type == QuickActionItem.type else { return false }
+ appDelegate?.mainVC?.showTunnelDetailForTunnel(named: shortcutItem.localizedTitle, animated: false, shouldToggleStatus: true)
+ return true
}
}
diff --git a/Sources/WireGuardApp/UI/iOS/Info.plist b/Sources/WireGuardApp/UI/iOS/Info.plist
index 7d91077..108b0d2 100644
--- a/Sources/WireGuardApp/UI/iOS/Info.plist
+++ b/Sources/WireGuardApp/UI/iOS/Info.plist
@@ -80,6 +80,23 @@
<false/>
<key>NSCameraUsageDescription</key>
<string>Localized</string>
+ <key>UIApplicationSceneManifest</key>
+ <dict>
+ <key>UIApplicationSupportsMultipleScenes</key>
+ <false/>
+ <key>UISceneConfigurations</key>
+ <dict>
+ <key>UIWindowSceneSessionRoleApplication</key>
+ <array>
+ <dict>
+ <key>UISceneConfigurationName</key>
+ <string>Default Configuration</string>
+ <key>UISceneDelegateClassName</key>
+ <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
+ </dict>
+ </array>
+ </dict>
+ </dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
diff --git a/Sources/WireGuardApp/UI/iOS/ViewController/MainViewController.swift b/Sources/WireGuardApp/UI/iOS/ViewController/MainViewController.swift
index 8542296..6c67033 100644
--- a/Sources/WireGuardApp/UI/iOS/ViewController/MainViewController.swift
+++ b/Sources/WireGuardApp/UI/iOS/ViewController/MainViewController.swift
@@ -88,6 +88,19 @@ extension MainViewController {
}
}
+ /// Name of the tunnel whose detail screen is currently in a navigation stack, if any.
+ var displayedTunnelName: String? {
+ func detail(in controllers: [UIViewController]) -> TunnelDetailTableViewController? {
+ for controller in controllers {
+ if let detail = controller as? TunnelDetailTableViewController { return detail }
+ if let navigationController = controller as? UINavigationController,
+ let detail = detail(in: navigationController.viewControllers) { return detail }
+ }
+ return nil
+ }
+ return detail(in: viewControllers)?.tunnel.name
+ }
+
func showTunnelDetailForTunnel(named tunnelName: String, animated: Bool, shouldToggleStatus: Bool) {
let showTunnelDetailBlock: (TunnelsManager) -> Void = { [weak self] tunnelsManager in
guard let self = self else { return }
--
2.54.0 (Apple Git-157)
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 0/2] iOS 27 build and launch fixes
2026-10-10 16:03 [PATCH 0/2] iOS 27 build and launch fixes Loren McQuade
2026-10-10 16:03 ` [PATCH 1/2] WireGuardKitC: include sys/types.h Loren McQuade
2026-10-10 16:03 ` [PATCH 2/2] iOS: adopt the UIScene lifecycle Loren McQuade
@ 2026-10-27 20:14 ` John
2 siblings, 0 replies; 4+ messages in thread
From: John @ 2026-10-27 20:14 UTC (permalink / raw)
To: Loren McQuade; +Cc: wireguard
On Monday, October 26th, 2026 at 3:54 PM, Loren McQuade <loren.mcquade@gmail.com> wrote:
> Two small fixes for building and running the iOS app with the iOS 27 SDK.
Built this series on top of master with the iOS 27 SDK and installed it on an iPhone running iOS 27.0.1. Without it the app does not build, and does not launch once the include is fixed; with it the app builds, launches, shows the existing tunnels, connects, and On-Demand works.
Tested-by: John Audia <therealgraysky@proton.me>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-27 20:15 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-10 16:03 [PATCH 0/2] iOS 27 build and launch fixes Loren McQuade
2026-10-10 16:03 ` [PATCH 1/2] WireGuardKitC: include sys/types.h Loren McQuade
2026-10-10 16:03 ` [PATCH 2/2] iOS: adopt the UIScene lifecycle Loren McQuade
2026-10-27 20:14 ` [PATCH 0/2] iOS 27 build and launch fixes John
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.