tethering-hider/TetheringHider/TetheringHiderApp.swift
2026-08-12 23:44:59 -05:00

183 lines
6.7 KiB
Swift

//
// TetheringHiderApp.swift
//
//
// Created by Jesús David Chapman Vélez on 12/08/26.
//
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
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())
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())
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())
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())
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 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.")
let options: [NSApplication.AboutPanelOptionKey: Any] = [
.credits: NSAttributedString(
string: creditsString,
attributes: [
.font: NSFont.systemFont(ofSize: 11),
.foregroundColor: NSColor.secondaryLabelColor
]
),
.applicationName: "Tethering Hider",
.version: "1.0.0"
]
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()
}
}
}