Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
EasyTier.xcodeproj/project.xcworkspace/xcuserdata/
xcuserdata
build.log
compile_commands.json

CLAUDE.md
11 changes: 11 additions & 0 deletions ControlWidgets/Assets.xcassets/AccentColor.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
35 changes: 35 additions & 0 deletions ControlWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
6 changes: 6 additions & 0 deletions ControlWidgets/Assets.xcassets/Contents.json
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,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
9 changes: 9 additions & 0 deletions ControlWidgets/ControlWidgetsBundle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import WidgetKit
import SwiftUI

@main
struct ControlWidgetsBundle: WidgetBundle {
var body: some Widget {
ControlWidgetsControl()
}
}
78 changes: 78 additions & 0 deletions ControlWidgets/ControlWidgetsControl.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import AppIntents
import SwiftUI
import WidgetKit
import NetworkExtension

struct ControlWidgetsControl: ControlWidget {
static let kind: String = "site.yinmo.easytier.controlwidgets"

var body: some ControlWidgetConfiguration {
StaticControlConfiguration(
kind: Self.kind,
provider: VPNControlProvider()
) { isConnected in
ControlWidgetToggle(
"EasyTier",
isOn: isConnected,
action: ToggleVPNIntent()
) { isOn in
Label(isOn ? "vpn_connected" : "vpn_disconnected", systemImage: "network")
.controlWidgetActionHint(isOn ? "vpn_disconnect" : "vpn_connect")
}
}
.displayName("EasyTier")
.description("toggle_vpn_connection")
}
}

extension ControlWidgetsControl {
struct VPNControlProvider: ControlValueProvider {
var previewValue: Bool {
false
}

func currentValue() async throws -> Bool {
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
guard let manager = managers.first else {
return false
}
return [.connecting, .connected, .reasserting].contains(manager.connection.status)
}
}
}

struct ToggleVPNIntent: SetValueIntent {
static let title: LocalizedStringResource = "toggle_vpn"

@Parameter(title: "vpn_connected")
var value: Bool

func perform() async throws -> some IntentResult {
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
guard let manager = managers.first else {
return .result()
}
Comment on lines +50 to +54
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the shortcuts, ToggleVPNIntent creates a new NETunnelProviderManager instance on each invocation rather than reusing a shared instance. While this fetches the current state, simultaneous operations from the widget and the main app could potentially conflict when both try to manage the VPN connection state.

Copilot uses AI. Check for mistakes.

if value {
// Connect - need to load config from App Group
let defaults = UserDefaults(suiteName: "group.site.yinmo.easytier")
guard let configData = defaults?.data(forKey: "LastVPNConfig"),
let config = try? JSONDecoder().decode([String: String].self, from: configData) else {
// Try to start with empty options as fallback
try manager.connection.startVPNTunnel()
return .result()
Comment on lines +61 to +63
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the config data is not available in the App Group, the code falls back to starting the VPN tunnel with empty options. This will likely fail to establish a connection because the VPN tunnel requires configuration data (network name, secret, etc.). Consider throwing an error or returning early with a more descriptive error message instead of attempting to connect with empty options.

Suggested change
// Try to start with empty options as fallback
try manager.connection.startVPNTunnel()
return .result()
// Config is required to start the VPN tunnel; fail explicitly instead of using empty options.
throw NSError(
domain: "site.yinmo.easytier.ToggleVPNIntent",
code: 1,
userInfo: [
NSLocalizedDescriptionKey: "VPN configuration is not available. Please open EasyTier to configure the VPN before connecting from the widget."
]
)

Copilot uses AI. Check for mistakes.
}

// Convert to NSDictionary for VPN options
var options: [String: NSObject] = [:]
for (key, val) in config {
options[key] = val as NSString
}
try manager.connection.startVPNTunnel(options: options)
} else {
manager.connection.stopVPNTunnel()
}

return .result()
}
}
10 changes: 10 additions & 0 deletions ControlWidgets/ControlWidgetsExtension.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.site.yinmo.easytier</string>
</array>
</dict>
</plist>
11 changes: 11 additions & 0 deletions ControlWidgets/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>
Loading