Add CampusNAS menu bar agent
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>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user