Native macOS agent that keeps the office VPN and the NAS shares working without the shell wrapper it replaces. - VPN: full port of the `ovpn` script — host-route pinning, gateway discovery via networksetup, tunnel liveness by address plus pushed route, startup grace, restart of a wedged daemon. Root steps are batched into one system authorization prompt. - SMB: mounts via NetFS, discovers shares from the server, detects mounts event-driven through NSWorkspace, matches the host by resolved alias so externally created mounts are recognised. - Credentials never pass through the process: the VPN auth file is written by `security` straight to disk, and the password is stored via SecItemAdd rather than a command line visible in ps. - UI: status window, settings editor, ⌘+/⌘− zoom that re-lays out rather than scaling a bitmap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
54 lines
2.1 KiB
Swift
54 lines
2.1 KiB
Swift
import SwiftUI
|
|
|
|
/// Button whose chrome scales, not just its label.
|
|
///
|
|
/// The system `.bordered` style sizes its padding, height and corner radius
|
|
/// from `controlSize`, which has four fixed steps — setting a larger font on it
|
|
/// grows the text inside a button that stays the same size. This style derives
|
|
/// every dimension from the zoom multiplier instead.
|
|
struct ScaledButtonStyle: ButtonStyle {
|
|
let scale: CGFloat
|
|
/// Text-only variant, for secondary actions like "Открыть".
|
|
var borderless = false
|
|
|
|
func makeBody(configuration: Configuration) -> some View {
|
|
Label(configuration: configuration, scale: scale, borderless: borderless)
|
|
}
|
|
|
|
/// Not named `Body`: that collides with `ButtonStyle`'s own associated type.
|
|
private struct Label: View {
|
|
let configuration: ButtonStyleConfiguration
|
|
let scale: CGFloat
|
|
let borderless: Bool
|
|
/// A custom style has to dim itself — SwiftUI only does that for the
|
|
/// built-in ones.
|
|
@Environment(\.isEnabled) private var isEnabled
|
|
|
|
var body: some View {
|
|
configuration.label
|
|
.font(.system(size: 12 * scale))
|
|
.lineLimit(1)
|
|
.padding(.horizontal, borderless ? 2 * scale : 10 * scale)
|
|
.padding(.vertical, borderless ? 1 * scale : 5 * scale)
|
|
.background(background)
|
|
.foregroundStyle(borderless ? Color.accentColor : Color.primary)
|
|
.opacity(isEnabled ? (configuration.isPressed ? 0.7 : 1) : 0.4)
|
|
.contentShape(Rectangle())
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var background: some View {
|
|
if borderless {
|
|
Color.clear
|
|
} else {
|
|
RoundedRectangle(cornerRadius: 6 * scale, style: .continuous)
|
|
.fill(Color.primary.opacity(configuration.isPressed ? 0.16 : 0.08))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 6 * scale, style: .continuous)
|
|
.strokeBorder(Color.primary.opacity(0.12), lineWidth: 1)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|