Repository Wiki
genshen/wssocks-plugin-ustb

跨平台 GUI 客户端配置与生命周期

基于 Fyne 的 client-ui 把代理监听、远端连接和 USTB VPN 认证设置汇集在窗口中,并通过 extra.TaskHandles 启停客户端。本页以用户可见配置、偏好保存及启动/停止过程为边界。

用途与范围

适合需要理解 GUI 如何把表单值传入客户端、配置何时持久化,以及窗口关闭时如何清理任务的开发者。这里只讨论跨平台 client-ui;命令行客户端、macOS 原生 SwiftUI 客户端和 VPN 协议/认证服务端的实现属于相邻主题,不在本页展开。VPN 认证窗口与二维码交互仅解释其 GUI 接口;底层 VPN 登录和 extra.TaskHandles 内部实现不在已读取的源码范围内。

概述

入口 main 以 AppId 创建 Fyne 应用、设置主题并建立主窗口;预填 SOCKS5 地址 127.0.0.1:1080、HTTP 地址 127.0.0.1:1086,然后从 fyne.Preferences 恢复基本设置。主表单含远端地址、认证令牌输入框、HTTP 开关和 TLS 校验开关;启动选项的实际组装见下文:在已读取的启动代码中,认证令牌输入框没有被传入 extra.Options,因此不要仅凭表单存在就推断它参与认证。主窗口与默认值、选项组装。

架构

Loading diagram...

Source: main.go, preferences.go, vpn_settings_ui.go

main 持有主窗口、代理表单和 TaskHandles;VPN 设置按钮调用 VpnSettingsUI.OpenVpnSettings 打开单独的窗口。两组设置均通过同一 Fyne 偏好接口读写,而运行时选择通过 onLoadValue() 进入 extra.Options.UstbVpn。图中的 extra.Options 表示传给 StartWssocks 的输入,并不表示 TaskHandles 创建该类型。主窗口、VPN 设置窗口。

主窗口的运行生命周期

启动按钮只处理 btnStopped 与 btnRunning 两个入口状态:运行时先标记 btnStopping、设置 ignoreWaitErr、通知关闭,然后立即重置按钮;停止时读取当前表单与 onLoadValue(),调用 StartWssocks,成功后标记运行并起一个 goroutine 等待任务结束。btnStarting、btnStopping 期间的点击没有相应分支。启动失败展示错误对话框并恢复为停止态;等待返回的错误只有在 ignoreWaitErr 为 false 时才展示。注意此标志和按钮状态同时被回调与 goroutine 修改;已读取代码没有展示同步机制,不应假定这里具有无竞争保证。启动与等待。

Loading diagram...

Source: main.go, main.go

启动选项是点击时从控件抓取,而非修改表单时持续写入;HTTP 勾选变化仅启用或禁用 HTTP 地址输入框。退出主窗口时,如果状态仍为运行中则调用 NotifyCloseWrapper(),随后写入基本偏好并调用 onVpnClose();源码没有在该关闭回调中等待任务退出,也没有在此分支把状态恢复为停止。HTTP 控件、窗口关闭。

Loading diagram...

Source: main.go, main.go

配置读取与保存

PrefHasPreference 是恢复配置的门闩:首次运行时基本配置保留控件初值并禁用 HTTP 地址框;VPN 主开关保留默认勾选;VPN 详细配置不覆盖新窗口的初值。保存基本配置会先把该标志写为 true。之后读取地址、主机、用户名与浏览器路径时会跳过空白字符串并使用 strings.TrimSpace;布尔配置通过 Bool 恢复。VPN 详情只在已有偏好标志时保存,因此首次打开并关闭 VPN 设置窗口但尚未关闭主窗口时,该保存路径会提前返回。基本读取及保存、门闩和加载。

设置键/控件类型首次显示值行为
local_addrstring127.0.0.1:1080SOCKS5 监听地址;关闭主窗口保存
remote_addrstring空远端地址;关闭主窗口保存
http_enableboolfalse控制 HTTP 地址控件的可用状态
http_local_addrstring127.0.0.1:1086HTTP 监听地址
skip_TSL_verifyboolfalse控件名/偏好键使用 TSL 拼写;实际传给 SkipTLSVerify
vpn_enablebooltrueVPN 主开关;其保存由 VPN 关闭回调承担
vpn_force_logout, vpn_host_encryptbooltrue、trueVPN 设置窗口勾选框
vpn_hoststringn.ustb.edu.cnVPN 主机输入
vpn_username, vpn_passwordstring空、空用户名可保存;密码保存和恢复语句被注释
auth_methodint取决于 RadioGroup 未选中时的状态选项映射为 vpn.VpnAuthMethod* 整数;未选中回落到 Webview
chrome_pathstring空文件选择器得到 URI path 后存入偏好

以上默认值是控件构造值而非统一偏好默认配置;auth_method 未明确设置初始选中项,不能将其认定为默认密码认证。主窗口控件、VPN 控件、偏好键与映射。

使用示例:从仓库提取的关键代码

以下片段是客户端现有实现,不是需要额外编写的调用示例。主窗口启动时把代理设置和 VPN 配置打包后提交;uiAuthToken 虽在表单里创建,下面的结构体赋值并未引用它。

go
1options := extra.Options{ 2 Options: client.Options{ 3 LocalSocks5Addr: uiLocalAddr.Text, 4 HttpEnabled: uiHttpEnable.Checked, 5 LocalHttpAddr: uiHttpLocalAddr.Text, 6 SkipTLSVerify: uiSkipTSLVerify.Checked, 7 }, 8 UstbVpn: onLoadValue(), 9 RemoteAddr: uiRemoteAddr.Text, 10} 11btnStatus = btnStarting 12btnStart.SetText("Loading") 13if err := handles.StartWssocks(options); err != nil { 14 // log error 15 dialog.ShowError(err, w) 16 btnStart.SetText("Start") 17 btnStatus = btnStopped 18 return 19}

Source: main.go

下面的基本设置写入发生在窗口关闭阶段。PrefHasPreference 先置位,使下次启动的加载路径生效;代码没有实时保存输入变化。

go
1func saveBasicPreference(pref fyne.Preferences, uiLocalAddr, uiRemoteAddr, 2 uiHttpLocalAddr *widget.Entry, uiHttpEnable *widget.Check, 3 uiSkipTSLVerify *widget.Check) { 4 pref.SetBool(PrefHasPreference, true) 5 pref.SetString(PrefLocalAddr, uiLocalAddr.Text) 6 pref.SetString(PrefRemoteAddr, uiRemoteAddr.Text) 7 8 pref.SetBool(PrefHttpEnable, uiHttpEnable.Checked) 9 pref.SetString(PrefHttpLocalAddr, uiHttpLocalAddr.Text) 10 pref.SetBool(PrefSkipTSLVerify, uiSkipTSLVerify.Checked) 11}

Source: preferences.go

VPN 设置在单独的窗口关闭时写入;密码输入框用于运行时认证结构体,但持久化密码的 SetString 被注释,避免误认为“记住密码”已实现。

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

VPN 设置及认证交互

OpenVpnSettings 每次建立控件和题为 VPN Auth Settings 的新窗口,先用 loadVpnPreference 覆盖已有配置,再放入 Password、QR Code、Webview 三个标签页。QR 标签页在此设置窗口中仅显示 World! 标签;实际二维码认证界面由 FyneQrCodeAuth.ShowQrCodeAndWait 创建,二者不是同一个窗口。浏览器文件选择器对取消和错误直接返回,成功后将 reader.URI().Path() 写入禁用的路径输入框;关闭设置窗口触发 saveVPNPreference。设置窗口、二维码窗口。

二维码认证先编码 qr.GenQrCodeContent(),打开含二维码和 Finish 按钮的窗口,然后在 30 秒超时与 scanned 通道之间选择。按 Finish 后关闭窗口并调用 WaitStatus;超时则关闭窗口并返回错误。WaitStatus 调用 qrcode2.WaitQrState(qr.Sid) 再调用 RedirectToLogin,出错打印并向上传递,成功返回 nil, nil;函数内还留有 HTTP 取消的 TODO,因此 30 秒限制只直接包围等待按钮的阶段,不能推断覆盖后续网络调用。二维码编码与超时、状态查询。

API 速览

方法/函数输入返回及已验证的行为
saveBasicPreference(pref fyne.Preferences, uiLocalAddr, uiRemoteAddr, uiHttpLocalAddr *widget.Entry, uiHttpEnable, uiSkipTSLVerify *widget.Check)偏好存储和控件无返回值;写入主表单并标记已配置
loadBasicPreference(pref fyne.Preferences, uiLocalAddr, uiRemoteAddr, uiHttpLocalAddr *widget.Entry, uiHttpEnable, uiSkipTSLVerify *widget.Check)同上无返回值;首启禁用 HTTP 地址,已有配置时按键恢复
(*VpnSettingsUI).OpenVpnSettings(wssApp *fyne.App, pref fyne.Preferences)应用指针与偏好无返回值;显示独立窗口,关闭时保存 VPN 详情
(*VpnSettingsUI).LoadSettingsValues(values *vpn.UstbVpn)待填充 VPN 配置指针无返回值;写入运行时认证字段
NewQrCodeAuth(app *fyne.App) qrcode2.QrCodeAuth应用指针返回 Fyne 二维码认证实现
(*FyneQrCodeAuth).ShowQrCodeAndWait(client *http.Client, cookies []*http.Cookie, qr qrcode2.QrImg) ([]*http.Cookie, error)HTTP 客户端、Cookie、二维码数据编码/超时/后续状态或重定向失败时返回错误;成功路径见 WaitStatus

签名及返回路径见 偏好函数、VPN 设置函数、二维码函数。

故障、边界与运维注意事项

  • 启动和自然退出:StartWssocks 返回错误时只弹出错误并复位;成功后独立 goroutine 调用 Wait()。人工停止前将 ignoreWaitErr 置为 true,用于抑制该路径的等待错误提示,但异步读写没有在所见代码中受锁保护。状态回调。
  • **关闭行为:**关闭主窗口只有在 btnRunning 时显式通知包装器关闭;btnStarting 和 btnStopping 不匹配该分支。随后保存基本配置并调用 VPN 关闭回调,未在该回调里等待退出。关闭回调。
  • 首次配置的写入顺序:saveVPNPreference 在 has_preference 为 false 时直接返回,而主窗口关闭时才由 saveBasicPreference 将其设为 true。对于首次打开 VPN 设置后立即关闭设置窗口的用户,不能期待那次关闭已保存其详细设置。偏好保存。
  • **隐私与未接线输入:**密码控件设为 Password: true,密码写入/恢复偏好的语句被注释;基本表单的 auth token 在已读取的启动选项赋值中没有使用。维护时应分别检查控件、偏好与 extra.Options 的映射,不要从标签名称推断已生效。密码控件、未持久化密码、选项构造。
  • **二维码等待:**二维码编码错误立即返回;未在 30 秒内点 Finish 时返回超时错误;点 Finish 后的查询/重定向错误继续向上传播。WaitStatus 中的 HTTP 取消尚标记 TODO。scanned 是容量为 1 的局部通道。二维码实现。
  • **扩展配置:**增加新 GUI 字段时,需分别考虑控件初始值、load*Preference/save*Preference、启动时 extra.Options 映射及窗口关闭时机;VPN 方法的 UI 字符串映射在 getAuthMethodInt 和偏好写入/读取逻辑中均出现,新增方法需要检查这些分支。VPN 映射、偏好映射。

相关链接