aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/confview.go119
-rw-r--r--ui/manage_tunnels.go24
-rw-r--r--ui/tunnelsview.go64
3 files changed, 176 insertions, 31 deletions
diff --git a/ui/confview.go b/ui/confview.go
index 761db562..215d2889 100644
--- a/ui/confview.go
+++ b/ui/confview.go
@@ -16,6 +16,7 @@ import (
"github.com/lxn/win"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/service"
)
type labelTextLine struct {
@@ -43,10 +44,13 @@ type peerView struct {
type ConfView struct {
*walk.ScrollView
- name *walk.GroupBox
- interfaze *interfaceView
- peers map[conf.Key]*peerView
+ name *walk.GroupBox
+ status *walk.CustomWidget
+ interfaze *interfaceView
+ toggleActive *walk.PushButton
+ peers map[conf.Key]*peerView
+ tunnel *service.Tunnel
originalWndProc uintptr
creatingThread uint32
}
@@ -228,10 +232,22 @@ func NewConfView(parent walk.Container) (*ConfView, error) {
cv.SetLayout(walk.NewVBoxLayout())
cv.name, _ = newPaddedGroupGrid(cv)
cv.interfaze = newInterfaceView(cv.name)
+ toggleActiveContainer, _ := walk.NewComposite(cv.name)
+ tacl := walk.NewHBoxLayout()
+ tacl.SetMargins(walk.Margins{})
+ toggleActiveContainer.SetLayout(tacl)
+ ivVal := reflect.ValueOf(cv.interfaze).Elem()
+ cv.name.Layout().(*walk.GridLayout).SetRange(toggleActiveContainer, walk.Rectangle{0, ivVal.NumField(), 3, 1})
+ cv.toggleActive, _ = walk.NewPushButton(toggleActiveContainer)
+ cv.toggleActive.SetText("Activate")
+ cv.toggleActive.Clicked().Attach(cv.onToggleActiveClicked)
+ walk.NewHSpacer(toggleActiveContainer)
cv.peers = make(map[conf.Key]*peerView)
cv.creatingThread = windows.GetCurrentThreadId()
win.SetWindowLongPtr(cv.Handle(), win.GWLP_USERDATA, uintptr(unsafe.Pointer(cv)))
cv.originalWndProc = win.SetWindowLongPtr(cv.Handle(), win.GWL_WNDPROC, crossThreadMessageHijack)
+ service.IPCClientRegisterTunnelChange(cv.onTunnelChanged)
+ cv.setTunnel(nil)
return cv, nil
}
@@ -241,21 +257,101 @@ const crossThreadUpdate = win.WM_APP + 17
var crossThreadMessageHijack = windows.NewCallback(func(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
cv := (*ConfView)(unsafe.Pointer(win.GetWindowLongPtr(hwnd, win.GWLP_USERDATA)))
if msg == crossThreadUpdate {
- cv.setConfiguration((*conf.Config)(unsafe.Pointer(wParam)))
+ cv.setTunnel((*service.Tunnel)(unsafe.Pointer(wParam)))
return 0
}
return win.CallWindowProc(cv.originalWndProc, hwnd, msg, wParam, lParam)
})
-func (cv *ConfView) SetConfiguration(c *conf.Config) {
+func (cv *ConfView) onToggleActiveClicked() {
+ state, err := cv.tunnel.State()
+ if err != nil {
+ walk.MsgBox(cv.Form(), "Failed to retrieve tunnel state", fmt.Sprintf("Error: %s", err.Error()), walk.MsgBoxIconError)
+ return
+ }
+
+ cv.toggleActive.SetEnabled(false)
+
+ switch state {
+ case service.TunnelStarted:
+ if err := cv.tunnel.Stop(); err != nil {
+ walk.MsgBox(cv.Form(), "Failed to stop tunnel", fmt.Sprintf("Error: %s", err.Error()), walk.MsgBoxIconError)
+ }
+
+ case service.TunnelStopped:
+ if err := cv.tunnel.Start(); err != nil {
+ walk.MsgBox(cv.Form(), "Failed to start tunnel", fmt.Sprintf("Error: %s", err.Error()), walk.MsgBoxIconError)
+ }
+
+ default:
+ panic("unexpected state")
+ }
+
+ cv.setTunnel(cv.tunnel)
+}
+
+func (cv *ConfView) onTunnelChanged(tunnel *service.Tunnel, state service.TunnelState, err error) {
+ if cv.tunnel == nil || cv.tunnel.Name != tunnel.Name {
+ return
+ }
+
+ cv.updateTunnelStatus(state)
+}
+
+func (cv *ConfView) updateTunnelStatus(state service.TunnelState) {
+ cv.toggleActive.SetVisible(cv.tunnel != nil)
+
+ if cv.tunnel == nil {
+ return
+ }
+
+ var enabled bool
+ var text string
+
+ switch state {
+ case service.TunnelStarted:
+ enabled, text = true, "Deactivate"
+
+ case service.TunnelStarting:
+ enabled, text = false, "Activating..."
+
+ case service.TunnelStopped:
+ enabled, text = true, "Activate"
+
+ case service.TunnelStopping:
+ enabled, text = false, "Deactivating..."
+
+ default:
+ enabled, text = false, "Unknown state"
+ }
+
+ cv.toggleActive.SetEnabled(enabled)
+ cv.toggleActive.SetText(text)
+}
+
+func (cv *ConfView) SetTunnel(tunnel *service.Tunnel) {
if cv.creatingThread == windows.GetCurrentThreadId() {
- cv.setConfiguration(c)
+ cv.setTunnel(tunnel)
} else {
- cv.SendMessage(crossThreadUpdate, uintptr(unsafe.Pointer(c)), 0)
+ cv.SendMessage(crossThreadUpdate, uintptr(unsafe.Pointer(tunnel)), 0)
}
}
-func (cv *ConfView) setConfiguration(c *conf.Config) {
+func (cv *ConfView) setTunnel(tunnel *service.Tunnel) {
+ cv.tunnel = tunnel
+
+ var state service.TunnelState
+ var config conf.Config
+ if tunnel != nil {
+ if state, _ = tunnel.State(); state == service.TunnelStarted {
+ config, _ = tunnel.RuntimeConfig()
+ } else {
+ config, _ = tunnel.StoredConfig()
+ }
+ }
+
+ cv.name.SetVisible(tunnel != nil)
+
hasSuspended := false
suspend := func() {
if !hasSuspended {
@@ -268,16 +364,17 @@ func (cv *ConfView) setConfiguration(c *conf.Config) {
cv.SetSuspended(false)
}
}()
- title := "Interface: " + c.Name
+ title := "Interface: " + config.Name
if cv.name.Title() != title {
cv.name.SetTitle(title)
}
- cv.interfaze.apply(&c.Interface)
+ cv.interfaze.apply(&config.Interface)
+ cv.updateTunnelStatus(state)
inverse := make(map[*peerView]bool, len(cv.peers))
for _, pv := range cv.peers {
inverse[pv] = true
}
- for _, peer := range c.Peers {
+ for _, peer := range config.Peers {
if pv := cv.peers[peer.PublicKey]; pv != nil {
pv.apply(&peer)
inverse[pv] = false
diff --git a/ui/manage_tunnels.go b/ui/manage_tunnels.go
index 4568d394..6ab20873 100644
--- a/ui/manage_tunnels.go
+++ b/ui/manage_tunnels.go
@@ -61,10 +61,16 @@ func (mtw *ManageTunnelsWindow) setup() error {
tunnelsContainer, _ := walk.NewComposite(splitter)
tunnelsContainer.SetLayout(walk.NewVBoxLayout())
+ splitter.SetFixed(tunnelsContainer, true)
+
mtw.tunnelsView, _ = NewTunnelsView(tunnelsContainer)
mtw.tunnelsView.ItemActivated().Attach(mtw.onEditTunnel)
mtw.tunnelsView.CurrentIndexChanged().Attach(mtw.updateConfView)
+ service.IPCClientRegisterTunnelChange(func(tunnel *service.Tunnel, state service.TunnelState, err error) {
+ mtw.tunnelsView.Invalidate()
+ })
+
// ToolBar actions
{
// HACK: Because of https://github.com/lxn/walk/issues/481
@@ -168,19 +174,7 @@ func (mtw *ManageTunnelsWindow) updateConfView() {
return
}
- currentTunnel := mtw.tunnelsView.CurrentTunnel()
- if currentTunnel == nil {
- // TODO: config must be non-nil right now
- // mtw.confView.SetConfiguration(nil)
- return
- }
-
- config, err := currentTunnel.RuntimeConfig()
- if err != nil {
- return
- }
-
- mtw.confView.SetConfiguration(&config)
+ mtw.confView.SetTunnel(mtw.tunnelsView.CurrentTunnel())
}
func (mtw *ManageTunnelsWindow) runTunnelEdit(tunnel *service.Tunnel) *conf.Config {
@@ -199,7 +193,7 @@ func (mtw *ManageTunnelsWindow) runTunnelEdit(tunnel *service.Tunnel) *conf.Conf
} else {
title = "Edit tunnel"
name = tunnel.Name
- config, _ = tunnel.RuntimeConfig()
+ config, _ = tunnel.StoredConfig()
}
dlg, _ := walk.NewDialog(mtw)
@@ -416,7 +410,7 @@ func (mtw *ManageTunnelsWindow) addTunnel(config *conf.Config) {
}
}
- mtw.confView.SetConfiguration(config)
+ mtw.confView.SetTunnel(&tunnel)
}
func (mtw *ManageTunnelsWindow) deleteTunnel(tunnel *service.Tunnel) {
diff --git a/ui/tunnelsview.go b/ui/tunnelsview.go
index c1bba252..6f1deed0 100644
--- a/ui/tunnelsview.go
+++ b/ui/tunnelsview.go
@@ -17,7 +17,6 @@ type TunnelModel struct {
walk.TableModelBase
walk.SorterBase
- // TODO: also store the state to display a small icon as the first column
tunnels []service.Tunnel
}
@@ -58,7 +57,11 @@ func (t *TunnelModel) Sort(col int, order walk.SortOrder) error {
type TunnelsView struct {
*walk.TableView
- model *TunnelModel
+ model *TunnelModel
+ stoppedBrush *walk.SolidColorBrush
+ startingBrush *walk.SolidColorBrush
+ startedBrush *walk.SolidColorBrush
+ statusPen *walk.CosmeticPen
}
func NewTunnelsView(parent walk.Container) (*TunnelsView, error) {
@@ -70,15 +73,66 @@ func NewTunnelsView(parent walk.Container) (*TunnelsView, error) {
model := &TunnelModel{}
tv.SetModel(model)
- tv.SetAlternatingRowBGColor(walk.RGB(239, 239, 239))
tv.SetLastColumnStretched(true)
tv.SetHeaderHidden(true)
tv.Columns().Add(walk.NewTableViewColumn())
- return &TunnelsView{
+ tunnelsView := &TunnelsView{
TableView: tv,
model: model,
- }, nil
+ }
+
+ tunnelsView.stoppedBrush, _ = walk.NewSolidColorBrush(walk.RGB(239, 239, 239))
+ tunnelsView.AddDisposable(tunnelsView.stoppedBrush)
+
+ tunnelsView.startingBrush, _ = walk.NewSolidColorBrush(walk.RGB(255, 211, 31))
+ tunnelsView.AddDisposable(tunnelsView.startingBrush)
+
+ tunnelsView.startedBrush, _ = walk.NewSolidColorBrush(walk.RGB(0, 255, 0))
+ tunnelsView.AddDisposable(tunnelsView.startedBrush)
+
+ tunnelsView.statusPen, _ = walk.NewCosmeticPen(walk.PenSolid, walk.RGB(191, 191, 191))
+ tunnelsView.AddDisposable(tunnelsView.statusPen)
+
+ tv.SetCellStyler(tunnelsView)
+
+ return tunnelsView, nil
+}
+
+func (tv *TunnelsView) StyleCell(style *walk.CellStyle) {
+ canvas := style.Canvas()
+ if canvas == nil {
+ return
+ }
+
+ tunnel := tv.model.tunnels[style.Row()]
+
+ var brush *walk.SolidColorBrush
+ state, _ := tunnel.State()
+ switch state {
+ case service.TunnelStarted:
+ brush = tv.startedBrush
+
+ case service.TunnelStarting:
+ brush = tv.startingBrush
+
+ default:
+ brush = tv.stoppedBrush
+ }
+
+ b := style.Bounds()
+
+ b.X = b.Height
+ b.Width -= b.Height
+ canvas.DrawText(tunnel.Name, tv.Font(), 0, b, walk.TextVCenter)
+
+ b.X = 4
+ b.Y += 4
+ b.Height -= 8
+ b.Width = b.Height
+
+ canvas.FillEllipse(brush, b)
+ canvas.DrawEllipse(tv.statusPen, b)
}
func (tv *TunnelsView) CurrentTunnel() *service.Tunnel {