Repository Wiki
genshen/wssocks-plugin-ustb

VPN 设置、偏好保存与主题资源

桌面端通过 Fyne 的设置窗口编辑 VPN 参数、通过 fyne.Preferences 恢复与保存输入,并在启动时安装自定义主题。本页聚焦这些 UI 边界及已核实的资源使用点。

用途与范围

适用于修改桌面端 VPN 主开关、认证方式、主机及浏览器路径,或排查重启后的偏好恢复及主题外观。代理连接和 VPN 认证插件的内部实现不在本页范围内;认证流程可另参阅认证相关页面,代理启动流程参阅客户端运行相关页面。这里仅描述 UI 向运行参数交接的已读取部分,不推断 VPN 插件内部行为。

概述

main 创建具有固定 AppId 的 Fyne 应用,安装 myTheme,创建代理基础输入并加载其偏好;loadVpnUI 在主窗口放置默认勾选的 VPN 开关与设置按钮。按钮调用 VpnSettingsUI.OpenVpnSettings,设置窗口初始化控件后从 fyne.Preferences 恢复输入,关闭设置窗口时保存 VPN 细项。主窗口关闭时另行保存基础输入并执行 VPN 关闭回调。设置窗口中密码为遮蔽输入,但对应偏好写入和恢复代码已注释,不能将它当作持久化凭据。入口、设置窗口、偏好实现。

架构

Loading diagram...

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 写入。界面构造。

go
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;保存偏好也采用相同的后备分支。值加载、偏好映射。

核心流程

Loading diagram...

Sources: main.go, main.go, vpn_settings_ui.go

设置窗口关闭回调保存细项,主窗口关闭回调保存基础设置;两处时点不同。saveVPNPreference 首先检查 PrefHasPreference,为假就返回;saveBasicPreference 才写入该标志。因而首次打开设置窗口并先关闭它、主窗口尚未保存基础偏好时,VPN 细项不会通过该函数保存。这是从两个回调和守卫条件可直接推出的顺序影响,并非另设事务。保存守卫、窗口关闭回调。

偏好键与默认值

以下默认值指代码初始化的 UI 值;对于已存在的键,恢复逻辑可能覆盖它。存储实现由 fyne.Preferences 提供,当前已读代码未展示具体磁盘路径或加密方式。键定义、控件初始化、窗口初始化。

键类型初始 UI 值 / 恢复规则用途
has_preferencebool写基础偏好时置 true基础和 VPN 细项加载以及 VPN 细项保存的门控
local_addrstring127.0.0.1:1080;仅非空修剪后覆盖SOCKS5 监听地址
remote_addrstring空;仅非空修剪后覆盖远端地址
http_enableboolfalseHTTP 代理开关
http_local_addrstring127.0.0.1:1086;仅非空修剪后覆盖HTTP 监听地址
skip_TSL_verifyboolfalseTLS 校验跳过开关;键名按源码拼写
vpn_enablebooltrue;保存值为假才清除勾选主窗口 VPN 开关
auth_methodint未见显式默认选择;读取整数后不等于 Password/QR Code 则选 WebviewVPN 认证方式
vpn_force_logoutbooltrue;保存值为假则清除勾选强制登出
vpn_host_encryptbooltrue;保存值为假则清除勾选主机加密
vpn_hoststringn.ustb.edu.cn;仅非空修剪后覆盖VPN 目标主机
vpn_usernamestring空;仅非空修剪后覆盖密码认证用户名
vpn_passwordstring空;读写已注释不持久化的密码输入
chrome_pathstring空;仅非空修剪后覆盖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。

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、主题和偏好接口,而非单独创建持久化服务:

go
1wssApp := app.NewWithID(AppId) 2wssApp.Settings().SetTheme(&myTheme{}) 3 4w := wssApp.NewWindow(AppName)

Source: main.go

go
loadBasicPreference(wssApp.Preferences(), uiLocalAddr, uiRemoteAddr, uiHttpLocalAddr, uiHttpEnable, uiSkipTSLVerify)

Source: main.go

VPN 细项保存与运行时读取

关闭设置窗口时从控件向偏好写值;密码写入语句被注释。与此相对,当前运行所需的认证参数由控件值构造:

go
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

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 / loadBasicPreferencefyne.Preferences、地址输入、HTTP 和 TLS 开关;无返回值基础设置保存/恢复
saveVPNMainPreference / loadVPNMainPreferencefyne.Preferences、VPN 开关;无返回值主开关保存/恢复;保存函数只写布尔值
saveVPNPreference / loadVpnPreferencefyne.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。本次读取的文件未包含自动化测试,故不声称已有测试覆盖;适合重点验证首次保存顺序、空字符串恢复、密码不持久化和认证值回退。

相关链接