VPN 设置、偏好保存与主题资源
桌面端通过 Fyne 的设置窗口编辑 VPN 参数、通过 fyne.Preferences 恢复与保存输入,并在启动时安装自定义主题。本页聚焦这些 UI 边界及已核实的资源使用点。
用途与范围
适用于修改桌面端 VPN 主开关、认证方式、主机及浏览器路径,或排查重启后的偏好恢复及主题外观。代理连接和 VPN 认证插件的内部实现不在本页范围内;认证流程可另参阅认证相关页面,代理启动流程参阅客户端运行相关页面。这里仅描述 UI 向运行参数交接的已读取部分,不推断 VPN 插件内部行为。
概述
main 创建具有固定 AppId 的 Fyne 应用,安装 myTheme,创建代理基础输入并加载其偏好;loadVpnUI 在主窗口放置默认勾选的 VPN 开关与设置按钮。按钮调用 VpnSettingsUI.OpenVpnSettings,设置窗口初始化控件后从 fyne.Preferences 恢复输入,关闭设置窗口时保存 VPN 细项。主窗口关闭时另行保存基础输入并执行 VPN 关闭回调。设置窗口中密码为遮蔽输入,但对应偏好写入和恢复代码已注释,不能将它当作持久化凭据。入口、设置窗口、偏好实现。
架构
Sources: main.go, main.go, main.go, vpn_settings_ui.go, preferences.go
图中 resources.GithubIcon 只表示主窗口已验证的图标调用;资源包的生成过程和底层实现未在已读取源码中验证。主题通过 Fyne Settings().SetTheme 注册,偏好则经由 Fyne 接口传给加载/保存函数;两者不是同一个持久化机制。主题注册、资源调用。
设置窗口与运行值交接
VpnSettingsUI 持有七组控件:强制登出、主机加密、主机、用户名、密码、认证方式和浏览器路径。每次 OpenVpnSettings 都重新创建控件并调用 loadVpnPreference;窗口中表单展示共用字段,选项卡分别展示用户名/密码、二维码占位标签 World!、以及 Webview 文件选择器和路径输入。认证方式的 RadioGroup 回调目前只有 todo,选项卡与单选项之间在已读代码中没有同步逻辑。浏览器路径输入先 Disable(),文件选择回调仍可用 SetText 写入。界面构造。
1func loadFilePicker(win fyne.Window, pathContainer *widget.Entry) *widget.Button {
2 selectBtn := widget.NewButton("Select Chrome/Edge/Chromium Browser", func() {
3 fd := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) {
4 if err != nil {
5 return
6 }
7 if reader == nil {
8 return
9 }
10 pathContainer.SetText(reader.URI().Path())
11 }, win)
12
13 fd.Show()
14 fd.SetOnClosed(func() {})
15 })
16 return selectBtn
17}Source: vpn_settings_ui.go
文件选择出错或用户取消时保持原路径;代码只取 URI 的 Path(),未在此处校验浏览器可执行性。LoadSettingsValues(values *vpn.UstbVpn) 从当前控件把强制登出、主机加密、目标主机和认证方式写入给定结构体,并构造含用户名和当前密码的 passwd.UstbVpnPasswdAuth;它不把浏览器路径写入该结构体。getAuthMethodInt 将 Password、QR Code 分别映射为插件常量,其他值统一映射为 Webview;保存偏好也采用相同的后备分支。值加载、偏好映射。
核心流程
Sources: main.go, main.go, vpn_settings_ui.go
设置窗口关闭回调保存细项,主窗口关闭回调保存基础设置;两处时点不同。saveVPNPreference 首先检查 PrefHasPreference,为假就返回;saveBasicPreference 才写入该标志。因而首次打开设置窗口并先关闭它、主窗口尚未保存基础偏好时,VPN 细项不会通过该函数保存。这是从两个回调和守卫条件可直接推出的顺序影响,并非另设事务。保存守卫、窗口关闭回调。
偏好键与默认值
以下默认值指代码初始化的 UI 值;对于已存在的键,恢复逻辑可能覆盖它。存储实现由 fyne.Preferences 提供,当前已读代码未展示具体磁盘路径或加密方式。键定义、控件初始化、窗口初始化。
| 键 | 类型 | 初始 UI 值 / 恢复规则 | 用途 |
|---|---|---|---|
has_preference | bool | 写基础偏好时置 true | 基础和 VPN 细项加载以及 VPN 细项保存的门控 |
local_addr | string | 127.0.0.1:1080;仅非空修剪后覆盖 | SOCKS5 监听地址 |
remote_addr | string | 空;仅非空修剪后覆盖 | 远端地址 |
http_enable | bool | false | HTTP 代理开关 |
http_local_addr | string | 127.0.0.1:1086;仅非空修剪后覆盖 | HTTP 监听地址 |
skip_TSL_verify | bool | false | TLS 校验跳过开关;键名按源码拼写 |
vpn_enable | bool | true;保存值为假才清除勾选 | 主窗口 VPN 开关 |
auth_method | int | 未见显式默认选择;读取整数后不等于 Password/QR Code 则选 Webview | VPN 认证方式 |
vpn_force_logout | bool | true;保存值为假则清除勾选 | 强制登出 |
vpn_host_encrypt | bool | true;保存值为假则清除勾选 | 主机加密 |
vpn_host | string | n.ustb.edu.cn;仅非空修剪后覆盖 | VPN 目标主机 |
vpn_username | string | 空;仅非空修剪后覆盖 | 密码认证用户名 |
vpn_password | string | 空;读写已注释 | 不持久化的密码输入 |
chrome_path | string | 空;仅非空修剪后覆盖 | Webview 浏览器路径 |
注意:loadBasicPreference 在无标志时禁用 HTTP 地址输入后直接返回;有标志时,只有 HTTP 开关被选中才保持地址可用。主窗口还给 HTTP 开关设置了 OnChanged 回调以切换输入可用状态。VPN 布尔偏好只在保存值为假时改变默认勾选状态,无法从“缺失”与“显式假”区分出独立状态。基础恢复、HTTP 回调、VPN 恢复。
主题与图标资源
myTheme 的 Color 特判背景白色、前景深灰、输入背景白色和主色绿色;其他颜色委托给 theme.DefaultTheme(),同时透传 ThemeVariant 参数。Font 根据等宽、粗体和斜体选择 theme.LightTheme() 字体,其中等宽优先于其他样式;Icon 完全委托默认主题。Size 对内联图标、内边距、滚动条、窄滚动条和文字分别返回 20、4、16、3、14,其余委托默认主题。源码标记该文件由 fyne-theme-generator 生成,修改主题时应注意生成文件可能被重新生成覆盖。theme.go。
1func (l *myTheme) Color(n fyne.ThemeColorName, v fyne.ThemeVariant) color.Color {
2 switch n {
3 case theme.ColorNameBackground:
4 return color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
5 case theme.ColorNameForeground:
6 return color.RGBA{R: 0x21, G: 0x21, B: 0x21, A: 0xff}
7 case theme.ColorNameInputBackground:
8 return color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
9 case theme.ColorNamePrimary:
10 return color.RGBA{R: 0x2e, G: 0x85, B: 0x55, A: 0xff}
11 default:
12 return theme.DefaultTheme().Color(n, v)
13 }
14}Source: theme.go
主窗口使用 Fyne 内置 theme.SettingsIcon() 创建 VPN 设置按钮,用 theme.HelpIcon() 做文档入口;同时从 client-ui/resources 导入 GithubIcon() 显示两个仓库链接。资源包底层封装、图标字节和打包规则未从已读实现核实,不能据此断言它与 myTheme.Icon 存在覆盖关系。设置图标、主窗口资源。
用法示例
注册主题并恢复基础输入
入口直接使用 Fyne 应用 ID、主题和偏好接口,而非单独创建持久化服务:
1wssApp := app.NewWithID(AppId)
2wssApp.Settings().SetTheme(&myTheme{})
3
4w := wssApp.NewWindow(AppName)Source: main.go
loadBasicPreference(wssApp.Preferences(), uiLocalAddr, uiRemoteAddr, uiHttpLocalAddr, uiHttpEnable, uiSkipTSLVerify)Source: main.go
VPN 细项保存与运行时读取
关闭设置窗口时从控件向偏好写值;密码写入语句被注释。与此相对,当前运行所需的认证参数由控件值构造:
1pref.SetBool(PrefVpnForceLogout, uiVpnForceLogout.Checked)
2pref.SetBool(PrefVpnHostEncrypt, uiVpnHostEncrypt.Checked)
3pref.SetString(PrefVpnHostInput, uiVpnHostInput.Text)
4pref.SetString(PrefVpnUsername, uiVpnUsername.Text)
5//pref.SetString(PrefVpnPassword,uiVpnPassword.Text)Source: preferences.go
1func (v *VpnSettingsUI) LoadSettingsValues(values *vpn.UstbVpn) {
2 values.ForceLogout = v.uiVpnForceLogout.Checked
3 values.HostEncrypt = v.uiVpnHostEncrypt.Checked
4 values.TargetVpn = v.uiVpnHostInput.Text
5 values.AuthMethod = getAuthMethodInt(v.uiVpnAuthMethod)
6 values.PasswdAuth = passwd.UstbVpnPasswdAuth{
7 Username: v.uiVpnUsername.Text,
8 Password: v.uiVpnPassword.Text,
9 }
10}Source: vpn_settings_ui.go
API 速查
以下是 client-ui 包内函数/方法,不是 HTTP 接口。类型、参数和返回值均以实现为准。偏好接口实现、设置窗口接口。
| 函数/方法 | 参数与返回 | 行为 |
|---|---|---|
OpenVpnSettings(wssApp *fyne.App, pref fyne.Preferences) | 应用指针和偏好;无返回值 | 构建、恢复并展示设置窗口;关闭时尝试保存 |
LoadSettingsValues(values *vpn.UstbVpn) | 待填充结构体指针;无返回值 | 从控件覆盖 VPN 认证运行参数 |
loadFilePicker(win fyne.Window, pathContainer *widget.Entry) *widget.Button | 窗口、目标路径输入;返回按钮 | 打开文件选择对话框,成功时写入 URI 路径 |
getAuthMethodInt(uiVpnAuthMethod *widget.RadioGroup) int | 单选控件;返回插件认证方式整数 | Password / QR Code 明确匹配,否则 Webview |
saveBasicPreference / loadBasicPreference | fyne.Preferences、地址输入、HTTP 和 TLS 开关;无返回值 | 基础设置保存/恢复 |
saveVPNMainPreference / loadVPNMainPreference | fyne.Preferences、VPN 开关;无返回值 | 主开关保存/恢复;保存函数只写布尔值 |
saveVPNPreference / loadVpnPreference | fyne.Preferences、认证单选、两个复选框和四个文本输入;无返回值 | VPN 细项保存/恢复;密码不读写 |
这些函数没有显式返回错误;文件选择回调遇到非空 err 或空 reader 直接返回。已读源码没有对上述接口声明显式 panic 或重试逻辑;Fyne 接口内部的存储失败行为未在本页材料中验证。文件选择。
故障、边界与一致性
- 首次保存顺序:
saveVPNPreference在has_preference为假时直接返回,而saveBasicPreference才将其置为真。用户应留意 VPN 窗口关闭并不保证首次会写入细项;主窗口关闭和细项窗口关闭是两个不同的保存事件。preferences.go、vpn_settings_ui.go。 - 空值恢复:地址、主机、用户名和浏览器路径仅在
TrimSpace后非空时更新控件;空保存值不会覆盖控件默认文本。与此相反,saveBasicPreference和saveVPNPreference从控件直接写入原始Text,并未在写入时修剪。排查“恢复的内容为何不同于保存文本”时,应区分读写方向。preferences.go、preferences.go、preferences.go。 - 密码生命周期:密码输入设有
Password: true,LoadSettingsValues在运行时读取它,但持久化的读写均已注释。PrefVpnPassword常量存在不代表实际上保存密码。vpn_settings_ui.go、vpn_settings_ui.go、preferences.go、preferences.go。 - 选择器与认证选项:文件选择取消或失败不更新路径;认证字符串不匹配 Password/QR Code 时回退 Webview。二维码页只包含占位标签,不能据此推断扫码流程已在该页实现。vpn_settings_ui.go、vpn_settings_ui.go。
- 并发边界:主窗口启动按钮会创建
handles.Wait()goroutine,并更新按钮和状态字段;所读范围内没有这些字段的同步保护。不要由此断言 Fyne UI 更新天然线程安全;本页也不将代理任务并发机制扩展成设置页职责。main.go。
运维与扩展提示
偏好读取使用 Fyne 自身 Preferences(),不是本包自建的文件读写器;已读实现没有偏好迁移、写入确认、密钥存储或异常上报逻辑。要新增可持久化字段,应同时检查控件默认值、Pref* 键、saveVPNPreference/loadVpnPreference 对称性以及 PrefHasPreference 的时序,并确认是否要进入 LoadSettingsValues 的运行结构体;浏览器路径目前只出现在控件和偏好处理中,不能由本页所读代码推断其后续消费方式。preferences.go、vpn_settings_ui.go。
主题扩展时,已有的颜色和尺寸特例仅覆盖少量 Fyne 名称;新增特例需保留未处理名称的默认主题回退。若需要改变全局图标,检查 myTheme.Icon 的默认主题委托;主窗口另有 resource.GithubIcon() 的显式资源调用,两条路径不应混为一谈。theme.go、main.go。本次读取的文件未包含自动化测试,故不声称已有测试覆盖;适合重点验证首次保存顺序、空字符串恢复、密码不持久化和认证值回退。