tethering-hider/TetheringHider/TetheringHiderApp.swift

187 lines
7.1 KiB
Swift
Raw Permalink Normal View History

2026-08-12 23:44:59 -05:00
import SwiftUI
import AppKit
@MainActor
final class StatusBarController: NSObject, NSMenuDelegate {
private var statusItem: NSStatusItem?
private let ttlManager: TTLManager
private let authService = AuthenticationService()
private let commandService = SystemCommandService()
init(ttlManager: TTLManager) {
self.ttlManager = ttlManager
super.init()
setupStatusItem()
self.ttlManager.onStatusChanged = { [weak self] in
Task { @MainActor in
self?.updateMenu()
}
}
}
func setupStatusItem() {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
updateMenu()
}
func updateMenu() {
guard let statusItem = statusItem, let button = statusItem.button else { return }
// Status Bar Icon
let imageName = ttlManager.isProtected
? "antenna.radiowaves.left.and.right"
: "antenna.radiowaves.left.and.right.slash"
button.image = NSImage(systemSymbolName: imageName, accessibilityDescription: "Tethering Hider")
let menu = NSMenu()
menu.delegate = self
2026-08-13 10:35:10 -05:00
// 1. Status Header with Cloak/Visible Icon
2026-08-12 23:44:59 -05:00
let statusTitle = ttlManager.isProtected
? String(localized: "Tethering Hider — Hotspot Cloaked")
: String(localized: "Tethering Hider — Standard Tethering")
let headerItem = NSMenuItem(title: statusTitle, action: nil, keyEquivalent: "")
headerItem.isEnabled = false
headerItem.image = NSImage(
systemSymbolName: ttlManager.isProtected ? "eye.slash.fill" : "eye.fill",
accessibilityDescription: nil
)
menu.addItem(headerItem)
menu.addItem(NSMenuItem.separator())
2026-08-13 10:35:10 -05:00
// 2. Toggle Action with Cloak/Uncloak Icon
2026-08-12 23:44:59 -05:00
let toggleTitle = ttlManager.isProtected
? String(localized: "Disable Hotspot Cloak (Set TTL 64)")
: String(localized: "Enable Hotspot Cloak (Set TTL 65)")
let toggleItem = NSMenuItem(title: toggleTitle, action: #selector(toggleAction), keyEquivalent: "")
toggleItem.target = self
toggleItem.image = NSImage(
systemSymbolName: ttlManager.isProtected ? "eye.fill" : "eye.slash.fill",
accessibilityDescription: nil
)
menu.addItem(toggleItem)
menu.addItem(NSMenuItem.separator())
2026-08-13 10:35:10 -05:00
// 3. Info Items with Network & DNS Icons
2026-08-12 23:44:59 -05:00
let ipv4Format = String(localized: "IPv4 TTL: %d")
let ipv4Item = NSMenuItem(title: String(format: ipv4Format, ttlManager.currentTTL), action: nil, keyEquivalent: "")
ipv4Item.isEnabled = false
ipv4Item.image = NSImage(systemSymbolName: "network", accessibilityDescription: nil)
menu.addItem(ipv4Item)
let ipv6Format = String(localized: "IPv6 HLIM: %d")
let ipv6Item = NSMenuItem(title: String(format: ipv6Format, ttlManager.currentIPv6HopLimit), action: nil, keyEquivalent: "")
ipv6Item.isEnabled = false
ipv6Item.image = NSImage(systemSymbolName: "globe", accessibilityDescription: nil)
menu.addItem(ipv6Item)
let dnsTitle = String(localized: "DNS Flush: AUTO")
let dnsItem = NSMenuItem(title: dnsTitle, action: nil, keyEquivalent: "")
dnsItem.isEnabled = false
dnsItem.image = NSImage(systemSymbolName: "arrow.triangle.2.circlepath", accessibilityDescription: nil)
menu.addItem(dnsItem)
menu.addItem(NSMenuItem.separator())
2026-08-13 10:35:10 -05:00
// 4. About Panel Option
2026-08-12 23:44:59 -05:00
let aboutTitle = String(localized: "About Tethering Hider...")
let aboutItem = NSMenuItem(title: aboutTitle, action: #selector(aboutAction), keyEquivalent: "")
aboutItem.target = self
aboutItem.image = NSImage(systemSymbolName: "info.circle.fill", accessibilityDescription: nil)
menu.addItem(aboutItem)
menu.addItem(NSMenuItem.separator())
2026-08-13 10:35:10 -05:00
// 5. Quit Option with Standard macOS Exit Icon
2026-08-12 23:44:59 -05:00
let quitTitle = String(localized: "Quit Tethering Hider")
let quitItem = NSMenuItem(title: quitTitle, action: #selector(quitAction), keyEquivalent: "q")
quitItem.target = self
quitItem.image = NSImage(systemSymbolName: "rectangle.portrait.and.arrow.right", accessibilityDescription: nil)
menu.addItem(quitItem)
statusItem.menu = menu
}
func menuWillOpen(_ menu: NSMenu) {
ttlManager.refreshStatus()
}
@objc private func toggleAction() {
Task { @MainActor in
do {
let authenticated = try await authService.authenticateWithBiometrics()
guard authenticated else { return }
if ttlManager.isProtected {
try await commandService.resetTTL()
ttlManager.didSetTTL(ttlManager.defaultTTL)
} else {
try await commandService.setTTL(ttlManager.targetTTL)
ttlManager.didSetTTL(ttlManager.targetTTL)
}
updateMenu()
} catch {
ttlManager.setError(error.localizedDescription)
}
}
}
@objc private func aboutAction() {
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.1-beta"
2026-08-12 23:44:59 -05:00
let creditsString = String(localized: "Bypasses mobile carrier hotspot tethering detection by adjusting outgoing IPv4 TTL to 65, IPv6 Hop Limit to 65, and flushing system DNS caches.\n\nDesigned for macOS.")
2026-08-13 10:35:10 -05:00
var options: [NSApplication.AboutPanelOptionKey: Any] = [
2026-08-12 23:44:59 -05:00
.credits: NSAttributedString(
string: creditsString,
attributes: [
.font: NSFont.systemFont(ofSize: 11),
.foregroundColor: NSColor.secondaryLabelColor
]
),
.applicationName: "Tethering Hider",
.version: versionString
2026-08-12 23:44:59 -05:00
]
2026-08-13 10:35:10 -05:00
if let appIcon = NSImage(named: NSImage.applicationIconName) ?? NSApplication.shared.applicationIconImage {
options[.applicationIcon] = appIcon
}
2026-08-12 23:44:59 -05:00
NSApp.orderFrontStandardAboutPanel(options: options)
NSApp.activate(ignoringOtherApps: true)
}
@objc private func quitAction() {
NSApp.terminate(nil)
}
}
@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarController: StatusBarController?
let ttlManager = TTLManager()
func applicationWillFinishLaunching(_ notification: Notification) {
NSApp.setActivationPolicy(.accessory)
}
func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.setActivationPolicy(.accessory)
self.statusBarController = StatusBarController(ttlManager: ttlManager)
}
}
@main
struct TetheringHiderApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
Settings {
EmptyView()
}
}
}