golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/ui/confview.go (about)

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  package ui
     7  
     8  import (
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/lxn/walk"
    14  	"github.com/lxn/win"
    15  
    16  	"golang.zx2c4.com/wireguard/windows/conf"
    17  	"golang.zx2c4.com/wireguard/windows/l18n"
    18  	"golang.zx2c4.com/wireguard/windows/manager"
    19  )
    20  
    21  type widgetsLine interface {
    22  	widgets() (walk.Widget, walk.Widget)
    23  }
    24  
    25  type widgetsLinesView interface {
    26  	widgetsLines() []widgetsLine
    27  }
    28  
    29  type labelStatusLine struct {
    30  	label           *walk.TextLabel
    31  	statusComposite *walk.Composite
    32  	statusImage     *walk.ImageView
    33  	statusLabel     *walk.LineEdit
    34  }
    35  
    36  type labelTextLine struct {
    37  	label *walk.TextLabel
    38  	text  *walk.TextEdit
    39  }
    40  
    41  type toggleActiveLine struct {
    42  	composite *walk.Composite
    43  	button    *walk.PushButton
    44  }
    45  
    46  type interfaceView struct {
    47  	status       *labelStatusLine
    48  	publicKey    *labelTextLine
    49  	listenPort   *labelTextLine
    50  	mtu          *labelTextLine
    51  	addresses    *labelTextLine
    52  	dns          *labelTextLine
    53  	scripts      *labelTextLine
    54  	table        *labelTextLine
    55  	toggleActive *toggleActiveLine
    56  	lines        []widgetsLine
    57  }
    58  
    59  type peerView struct {
    60  	publicKey           *labelTextLine
    61  	presharedKey        *labelTextLine
    62  	allowedIPs          *labelTextLine
    63  	endpoint            *labelTextLine
    64  	persistentKeepalive *labelTextLine
    65  	latestHandshake     *labelTextLine
    66  	transfer            *labelTextLine
    67  	lines               []widgetsLine
    68  }
    69  
    70  type ConfView struct {
    71  	*walk.ScrollView
    72  	name            *walk.GroupBox
    73  	interfaze       *interfaceView
    74  	peers           map[conf.Key]*peerView
    75  	tunnelChangedCB *manager.TunnelChangeCallback
    76  	tunnel          *manager.Tunnel
    77  	updateTicker    *time.Ticker
    78  }
    79  
    80  func (lsl *labelStatusLine) widgets() (walk.Widget, walk.Widget) {
    81  	return lsl.label, lsl.statusComposite
    82  }
    83  
    84  func (lsl *labelStatusLine) update(state manager.TunnelState) {
    85  	icon, err := iconForState(state, 14)
    86  	if err == nil {
    87  		lsl.statusImage.SetImage(icon)
    88  	} else {
    89  		lsl.statusImage.SetImage(nil)
    90  	}
    91  
    92  	s, e := lsl.statusLabel.TextSelection()
    93  	lsl.statusLabel.SetText(textForState(state, false))
    94  	lsl.statusLabel.SetTextSelection(s, e)
    95  }
    96  
    97  func (lsl *labelStatusLine) Dispose() {
    98  	lsl.label.Dispose()
    99  	lsl.statusComposite.Dispose()
   100  }
   101  
   102  func newLabelStatusLine(parent walk.Container) (*labelStatusLine, error) {
   103  	var err error
   104  	var disposables walk.Disposables
   105  	defer disposables.Treat()
   106  
   107  	lsl := new(labelStatusLine)
   108  
   109  	if lsl.label, err = walk.NewTextLabel(parent); err != nil {
   110  		return nil, err
   111  	}
   112  	disposables.Add(lsl.label)
   113  	lsl.label.SetText(l18n.Sprintf("Status:"))
   114  	lsl.label.SetTextAlignment(walk.AlignHFarVNear)
   115  
   116  	if lsl.statusComposite, err = walk.NewComposite(parent); err != nil {
   117  		return nil, err
   118  	}
   119  	disposables.Add(lsl.statusComposite)
   120  	layout := walk.NewHBoxLayout()
   121  	layout.SetMargins(walk.Margins{})
   122  	layout.SetAlignment(walk.AlignHNearVNear)
   123  	layout.SetSpacing(0)
   124  	lsl.statusComposite.SetLayout(layout)
   125  
   126  	if lsl.statusImage, err = walk.NewImageView(lsl.statusComposite); err != nil {
   127  		return nil, err
   128  	}
   129  	disposables.Add(lsl.statusImage)
   130  	lsl.statusImage.SetMargin(2)
   131  	lsl.statusImage.SetMode(walk.ImageViewModeIdeal)
   132  
   133  	if lsl.statusLabel, err = walk.NewLineEdit(lsl.statusComposite); err != nil {
   134  		return nil, err
   135  	}
   136  	disposables.Add(lsl.statusLabel)
   137  	win.SetWindowLong(lsl.statusLabel.Handle(), win.GWL_EXSTYLE, win.GetWindowLong(lsl.statusLabel.Handle(), win.GWL_EXSTYLE)&^win.WS_EX_CLIENTEDGE)
   138  	lsl.statusLabel.SetReadOnly(true)
   139  	lsl.statusLabel.SetBackground(walk.NullBrush())
   140  	lsl.statusLabel.FocusedChanged().Attach(func() {
   141  		lsl.statusLabel.SetTextSelection(0, 0)
   142  	})
   143  	lsl.update(manager.TunnelUnknown)
   144  	lsl.statusLabel.Accessibility().SetRole(walk.AccRoleStatictext)
   145  
   146  	disposables.Spare()
   147  
   148  	return lsl, nil
   149  }
   150  
   151  func (lt *labelTextLine) widgets() (walk.Widget, walk.Widget) {
   152  	return lt.label, lt.text
   153  }
   154  
   155  func (lt *labelTextLine) show(text string) {
   156  	s, e := lt.text.TextSelection()
   157  	lt.text.SetText(text)
   158  	lt.label.SetVisible(true)
   159  	lt.text.SetVisible(true)
   160  	lt.text.SetTextSelection(s, e)
   161  }
   162  
   163  func (lt *labelTextLine) hide() {
   164  	lt.text.SetText("")
   165  	lt.label.SetVisible(false)
   166  	lt.text.SetVisible(false)
   167  }
   168  
   169  func (lt *labelTextLine) Dispose() {
   170  	lt.label.Dispose()
   171  	lt.text.Dispose()
   172  }
   173  
   174  func newLabelTextLine(fieldName string, parent walk.Container) (*labelTextLine, error) {
   175  	var err error
   176  	var disposables walk.Disposables
   177  	defer disposables.Treat()
   178  
   179  	lt := new(labelTextLine)
   180  
   181  	if lt.label, err = walk.NewTextLabel(parent); err != nil {
   182  		return nil, err
   183  	}
   184  	disposables.Add(lt.label)
   185  	lt.label.SetText(fieldName)
   186  	lt.label.SetTextAlignment(walk.AlignHFarVNear)
   187  	lt.label.SetVisible(false)
   188  
   189  	if lt.text, err = walk.NewTextEdit(parent); err != nil {
   190  		return nil, err
   191  	}
   192  	disposables.Add(lt.text)
   193  	win.SetWindowLong(lt.text.Handle(), win.GWL_EXSTYLE, win.GetWindowLong(lt.text.Handle(), win.GWL_EXSTYLE)&^win.WS_EX_CLIENTEDGE)
   194  	lt.text.SetCompactHeight(true)
   195  	lt.text.SetReadOnly(true)
   196  	lt.text.SetBackground(walk.NullBrush())
   197  	lt.text.SetVisible(false)
   198  	lt.text.FocusedChanged().Attach(func() {
   199  		lt.text.SetTextSelection(0, 0)
   200  	})
   201  	lt.text.Accessibility().SetRole(walk.AccRoleStatictext)
   202  
   203  	disposables.Spare()
   204  
   205  	return lt, nil
   206  }
   207  
   208  func (tal *toggleActiveLine) widgets() (walk.Widget, walk.Widget) {
   209  	return nil, tal.composite
   210  }
   211  
   212  func (tal *toggleActiveLine) updateGlobal(globalState manager.TunnelState) {
   213  	tal.button.SetEnabled(globalState == manager.TunnelStarted || globalState == manager.TunnelStopped)
   214  }
   215  
   216  func (tal *toggleActiveLine) update(state manager.TunnelState) {
   217  	var text string
   218  
   219  	switch state {
   220  	case manager.TunnelStarted:
   221  		text = l18n.Sprintf("&Deactivate")
   222  	case manager.TunnelStopped:
   223  		text = l18n.Sprintf("&Activate")
   224  	case manager.TunnelStarting, manager.TunnelStopping:
   225  		text = textForState(state, true)
   226  	default:
   227  		text = ""
   228  	}
   229  
   230  	tal.button.SetText(text)
   231  	tal.button.SetVisible(state != manager.TunnelUnknown)
   232  }
   233  
   234  func (tal *toggleActiveLine) Dispose() {
   235  	tal.composite.Dispose()
   236  }
   237  
   238  func newToggleActiveLine(parent walk.Container) (*toggleActiveLine, error) {
   239  	var err error
   240  	var disposables walk.Disposables
   241  	defer disposables.Treat()
   242  
   243  	tal := new(toggleActiveLine)
   244  
   245  	if tal.composite, err = walk.NewComposite(parent); err != nil {
   246  		return nil, err
   247  	}
   248  	disposables.Add(tal.composite)
   249  	layout := walk.NewHBoxLayout()
   250  	layout.SetMargins(walk.Margins{0, 0, 0, 6})
   251  	tal.composite.SetLayout(layout)
   252  
   253  	if tal.button, err = walk.NewPushButton(tal.composite); err != nil {
   254  		return nil, err
   255  	}
   256  	disposables.Add(tal.button)
   257  	walk.NewHSpacer(tal.composite)
   258  	tal.update(manager.TunnelStopped)
   259  
   260  	disposables.Spare()
   261  
   262  	return tal, nil
   263  }
   264  
   265  type labelTextLineItem struct {
   266  	label string
   267  	ptr   **labelTextLine
   268  }
   269  
   270  func createLabelTextLines(items []labelTextLineItem, parent walk.Container, disposables *walk.Disposables) ([]widgetsLine, error) {
   271  	var err error
   272  	var disps walk.Disposables
   273  	defer disps.Treat()
   274  
   275  	wls := make([]widgetsLine, len(items))
   276  	for i, item := range items {
   277  		if *item.ptr, err = newLabelTextLine(item.label, parent); err != nil {
   278  			return nil, err
   279  		}
   280  		disps.Add(*item.ptr)
   281  		if disposables != nil {
   282  			disposables.Add(*item.ptr)
   283  		}
   284  		wls[i] = *item.ptr
   285  	}
   286  
   287  	disps.Spare()
   288  
   289  	return wls, nil
   290  }
   291  
   292  func newInterfaceView(parent walk.Container) (*interfaceView, error) {
   293  	var err error
   294  	var disposables walk.Disposables
   295  	defer disposables.Treat()
   296  
   297  	iv := new(interfaceView)
   298  
   299  	if iv.status, err = newLabelStatusLine(parent); err != nil {
   300  		return nil, err
   301  	}
   302  	disposables.Add(iv.status)
   303  
   304  	items := []labelTextLineItem{
   305  		{l18n.Sprintf("Public key:"), &iv.publicKey},
   306  		{l18n.Sprintf("Listen port:"), &iv.listenPort},
   307  		{l18n.Sprintf("MTU:"), &iv.mtu},
   308  		{l18n.Sprintf("Addresses:"), &iv.addresses},
   309  		{l18n.Sprintf("DNS servers:"), &iv.dns},
   310  		{l18n.Sprintf("Scripts:"), &iv.scripts},
   311  		{l18n.Sprintf("Table:"), &iv.table},
   312  	}
   313  	if iv.lines, err = createLabelTextLines(items, parent, &disposables); err != nil {
   314  		return nil, err
   315  	}
   316  
   317  	if iv.toggleActive, err = newToggleActiveLine(parent); err != nil {
   318  		return nil, err
   319  	}
   320  	disposables.Add(iv.toggleActive)
   321  
   322  	iv.lines = append([]widgetsLine{iv.status}, append(iv.lines, iv.toggleActive)...)
   323  
   324  	layoutInGrid(iv, parent.Layout().(*walk.GridLayout))
   325  
   326  	disposables.Spare()
   327  
   328  	return iv, nil
   329  }
   330  
   331  func newPeerView(parent walk.Container) (*peerView, error) {
   332  	pv := new(peerView)
   333  
   334  	items := []labelTextLineItem{
   335  		{l18n.Sprintf("Public key:"), &pv.publicKey},
   336  		{l18n.Sprintf("Preshared key:"), &pv.presharedKey},
   337  		{l18n.Sprintf("Allowed IPs:"), &pv.allowedIPs},
   338  		{l18n.Sprintf("Endpoint:"), &pv.endpoint},
   339  		{l18n.Sprintf("Persistent keepalive:"), &pv.persistentKeepalive},
   340  		{l18n.Sprintf("Latest handshake:"), &pv.latestHandshake},
   341  		{l18n.Sprintf("Transfer:"), &pv.transfer},
   342  	}
   343  	var err error
   344  	if pv.lines, err = createLabelTextLines(items, parent, nil); err != nil {
   345  		return nil, err
   346  	}
   347  
   348  	layoutInGrid(pv, parent.Layout().(*walk.GridLayout))
   349  
   350  	return pv, nil
   351  }
   352  
   353  func layoutInGrid(view widgetsLinesView, layout *walk.GridLayout) {
   354  	for i, l := range view.widgetsLines() {
   355  		w1, w2 := l.widgets()
   356  
   357  		if w1 != nil {
   358  			layout.SetRange(w1, walk.Rectangle{0, i, 1, 1})
   359  		}
   360  		if w2 != nil {
   361  			layout.SetRange(w2, walk.Rectangle{2, i, 1, 1})
   362  		}
   363  	}
   364  }
   365  
   366  func (iv *interfaceView) widgetsLines() []widgetsLine {
   367  	return iv.lines
   368  }
   369  
   370  func (iv *interfaceView) apply(c *conf.Interface) {
   371  	if IsAdmin {
   372  		iv.publicKey.show(c.PrivateKey.Public().String())
   373  	} else {
   374  		iv.publicKey.hide()
   375  	}
   376  
   377  	if c.ListenPort > 0 {
   378  		iv.listenPort.show(strconv.Itoa(int(c.ListenPort)))
   379  	} else {
   380  		iv.listenPort.hide()
   381  	}
   382  
   383  	if c.MTU > 0 {
   384  		iv.mtu.show(strconv.Itoa(int(c.MTU)))
   385  	} else {
   386  		iv.mtu.hide()
   387  	}
   388  
   389  	if len(c.Addresses) > 0 {
   390  		addrStrings := make([]string, len(c.Addresses))
   391  		for i, address := range c.Addresses {
   392  			addrStrings[i] = address.String()
   393  		}
   394  		iv.addresses.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
   395  	} else {
   396  		iv.addresses.hide()
   397  	}
   398  
   399  	if len(c.DNS)+len(c.DNSSearch) > 0 {
   400  		addrStrings := make([]string, 0, len(c.DNS)+len(c.DNSSearch))
   401  		for _, address := range c.DNS {
   402  			addrStrings = append(addrStrings, address.String())
   403  		}
   404  		addrStrings = append(addrStrings, c.DNSSearch...)
   405  		iv.dns.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
   406  	} else {
   407  		iv.dns.hide()
   408  	}
   409  
   410  	var scriptsInUse []string
   411  	if len(c.PreUp) > 0 {
   412  		scriptsInUse = append(scriptsInUse, l18n.Sprintf("pre-up"))
   413  	}
   414  	if len(c.PostUp) > 0 {
   415  		scriptsInUse = append(scriptsInUse, l18n.Sprintf("post-up"))
   416  	}
   417  	if len(c.PreDown) > 0 {
   418  		scriptsInUse = append(scriptsInUse, l18n.Sprintf("pre-down"))
   419  	}
   420  	if len(c.PostDown) > 0 {
   421  		scriptsInUse = append(scriptsInUse, l18n.Sprintf("post-down"))
   422  	}
   423  	if len(scriptsInUse) > 0 {
   424  		if conf.AdminBool("DangerousScriptExecution") {
   425  			iv.scripts.show(strings.Join(scriptsInUse, l18n.EnumerationSeparator()))
   426  		} else {
   427  			iv.scripts.show(l18n.Sprintf("disabled, per policy"))
   428  		}
   429  	} else {
   430  		iv.scripts.hide()
   431  	}
   432  
   433  	if c.TableOff {
   434  		iv.table.show(l18n.Sprintf("off"))
   435  	} else {
   436  		iv.table.hide()
   437  	}
   438  }
   439  
   440  func (pv *peerView) widgetsLines() []widgetsLine {
   441  	return pv.lines
   442  }
   443  
   444  func (pv *peerView) apply(c *conf.Peer) {
   445  	if IsAdmin {
   446  		pv.publicKey.show(c.PublicKey.String())
   447  	} else {
   448  		pv.publicKey.hide()
   449  	}
   450  
   451  	if !c.PresharedKey.IsZero() && IsAdmin {
   452  		pv.presharedKey.show(l18n.Sprintf("enabled"))
   453  	} else {
   454  		pv.presharedKey.hide()
   455  	}
   456  
   457  	if len(c.AllowedIPs) > 0 {
   458  		addrStrings := make([]string, len(c.AllowedIPs))
   459  		for i, address := range c.AllowedIPs {
   460  			addrStrings[i] = address.String()
   461  		}
   462  		pv.allowedIPs.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
   463  	} else {
   464  		pv.allowedIPs.hide()
   465  	}
   466  
   467  	if !c.Endpoint.IsEmpty() {
   468  		pv.endpoint.show(c.Endpoint.String())
   469  	} else {
   470  		pv.endpoint.hide()
   471  	}
   472  
   473  	if c.PersistentKeepalive > 0 {
   474  		pv.persistentKeepalive.show(strconv.Itoa(int(c.PersistentKeepalive)))
   475  	} else {
   476  		pv.persistentKeepalive.hide()
   477  	}
   478  
   479  	if !c.LastHandshakeTime.IsEmpty() {
   480  		pv.latestHandshake.show(c.LastHandshakeTime.String())
   481  	} else {
   482  		pv.latestHandshake.hide()
   483  	}
   484  
   485  	if c.RxBytes > 0 || c.TxBytes > 0 {
   486  		pv.transfer.show(l18n.Sprintf("%s received, %s sent", c.RxBytes.String(), c.TxBytes.String()))
   487  	} else {
   488  		pv.transfer.hide()
   489  	}
   490  }
   491  
   492  func newPaddedGroupGrid(parent walk.Container) (group *walk.GroupBox, err error) {
   493  	group, err = walk.NewGroupBox(parent)
   494  	if err != nil {
   495  		return nil, err
   496  	}
   497  	defer func() {
   498  		if err != nil {
   499  			group.Dispose()
   500  		}
   501  	}()
   502  	layout := walk.NewGridLayout()
   503  	layout.SetMargins(walk.Margins{10, 5, 10, 5})
   504  	layout.SetSpacing(0)
   505  	err = group.SetLayout(layout)
   506  	if err != nil {
   507  		return nil, err
   508  	}
   509  	spacer, err := walk.NewSpacerWithCfg(group, &walk.SpacerCfg{walk.GrowableHorz | walk.GreedyHorz, walk.Size{10, 0}, false})
   510  	if err != nil {
   511  		return nil, err
   512  	}
   513  	layout.SetRange(spacer, walk.Rectangle{1, 0, 1, 1})
   514  	return group, nil
   515  }
   516  
   517  func NewConfView(parent walk.Container) (*ConfView, error) {
   518  	var err error
   519  	var disposables walk.Disposables
   520  	defer disposables.Treat()
   521  
   522  	cv := new(ConfView)
   523  	if cv.ScrollView, err = walk.NewScrollView(parent); err != nil {
   524  		return nil, err
   525  	}
   526  	disposables.Add(cv)
   527  	vlayout := walk.NewVBoxLayout()
   528  	vlayout.SetMargins(walk.Margins{5, 0, 5, 0})
   529  	cv.SetLayout(vlayout)
   530  	if cv.name, err = newPaddedGroupGrid(cv); err != nil {
   531  		return nil, err
   532  	}
   533  	if cv.interfaze, err = newInterfaceView(cv.name); err != nil {
   534  		return nil, err
   535  	}
   536  	cv.interfaze.toggleActive.button.Clicked().Attach(cv.onToggleActiveClicked)
   537  	cv.peers = make(map[conf.Key]*peerView)
   538  	cv.tunnelChangedCB = manager.IPCClientRegisterTunnelChange(cv.onTunnelChanged)
   539  	cv.SetTunnel(nil)
   540  	globalState, err := manager.IPCClientGlobalState()
   541  	if err != nil {
   542  		return nil, err
   543  	}
   544  	cv.interfaze.toggleActive.updateGlobal(globalState)
   545  
   546  	if err := walk.InitWrapperWindow(cv); err != nil {
   547  		return nil, err
   548  	}
   549  	cv.SetDoubleBuffering(true)
   550  	cv.updateTicker = time.NewTicker(time.Second)
   551  	go func() {
   552  		for range cv.updateTicker.C {
   553  			if !cv.Visible() || !cv.Form().Visible() || win.IsIconic(cv.Form().Handle()) {
   554  				continue
   555  			}
   556  			if cv.tunnel != nil {
   557  				tunnel := cv.tunnel
   558  				var state manager.TunnelState
   559  				var config conf.Config
   560  				if state, _ = tunnel.State(); state == manager.TunnelStarted {
   561  					config, _ = tunnel.RuntimeConfig()
   562  				}
   563  				if config.Name == "" {
   564  					config, _ = tunnel.StoredConfig()
   565  				}
   566  				cv.Synchronize(func() {
   567  					cv.setTunnel(tunnel, &config, state)
   568  				})
   569  			}
   570  		}
   571  	}()
   572  
   573  	disposables.Spare()
   574  
   575  	return cv, nil
   576  }
   577  
   578  func (cv *ConfView) Dispose() {
   579  	if cv.tunnelChangedCB != nil {
   580  		cv.tunnelChangedCB.Unregister()
   581  		cv.tunnelChangedCB = nil
   582  	}
   583  	if cv.updateTicker != nil {
   584  		cv.updateTicker.Stop()
   585  		cv.updateTicker = nil
   586  	}
   587  	cv.ScrollView.Dispose()
   588  }
   589  
   590  func (cv *ConfView) onToggleActiveClicked() {
   591  	cv.interfaze.toggleActive.button.SetEnabled(false)
   592  	go func() {
   593  		oldState, err := cv.tunnel.Toggle()
   594  		if err != nil {
   595  			cv.Synchronize(func() {
   596  				if oldState == manager.TunnelUnknown {
   597  					showErrorCustom(cv.Form(), l18n.Sprintf("Failed to determine tunnel state"), err.Error())
   598  				} else if oldState == manager.TunnelStopped {
   599  					showErrorCustom(cv.Form(), l18n.Sprintf("Failed to activate tunnel"), err.Error())
   600  				} else if oldState == manager.TunnelStarted {
   601  					showErrorCustom(cv.Form(), l18n.Sprintf("Failed to deactivate tunnel"), err.Error())
   602  				}
   603  			})
   604  		}
   605  	}()
   606  }
   607  
   608  func (cv *ConfView) onTunnelChanged(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) {
   609  	cv.Synchronize(func() {
   610  		cv.interfaze.toggleActive.updateGlobal(globalState)
   611  		if cv.tunnel != nil && cv.tunnel.Name == tunnel.Name {
   612  			cv.interfaze.status.update(state)
   613  			cv.interfaze.toggleActive.update(state)
   614  		}
   615  	})
   616  	if cv.tunnel != nil && cv.tunnel.Name == tunnel.Name {
   617  		var config conf.Config
   618  		if state == manager.TunnelStarted {
   619  			config, _ = tunnel.RuntimeConfig()
   620  		}
   621  		if config.Name == "" {
   622  			config, _ = tunnel.StoredConfig()
   623  		}
   624  		cv.Synchronize(func() {
   625  			cv.setTunnel(tunnel, &config, state)
   626  		})
   627  	}
   628  }
   629  
   630  func (cv *ConfView) SetTunnel(tunnel *manager.Tunnel) {
   631  	cv.tunnel = tunnel // XXX: This races with the read in the updateTicker, but it's pointer-sized!
   632  
   633  	var config conf.Config
   634  	var state manager.TunnelState
   635  	if tunnel != nil {
   636  		go func() {
   637  			if state, _ = tunnel.State(); state == manager.TunnelStarted {
   638  				config, _ = tunnel.RuntimeConfig()
   639  			}
   640  			if config.Name == "" {
   641  				config, _ = tunnel.StoredConfig()
   642  			}
   643  			cv.Synchronize(func() {
   644  				cv.setTunnel(tunnel, &config, state)
   645  			})
   646  		}()
   647  	} else {
   648  		cv.setTunnel(tunnel, &config, state)
   649  	}
   650  }
   651  
   652  func (cv *ConfView) setTunnel(tunnel *manager.Tunnel, config *conf.Config, state manager.TunnelState) {
   653  	if !(cv.tunnel == nil || tunnel == nil || tunnel.Name == cv.tunnel.Name) {
   654  		return
   655  	}
   656  
   657  	title := l18n.Sprintf("Interface: %s", config.Name)
   658  	if cv.name.Title() != title {
   659  		cv.SetSuspended(true)
   660  		defer cv.SetSuspended(false)
   661  		cv.name.SetTitle(title)
   662  	}
   663  	cv.name.SetVisible(tunnel != nil)
   664  
   665  	cv.interfaze.apply(&config.Interface)
   666  	cv.interfaze.status.update(state)
   667  	cv.interfaze.toggleActive.update(state)
   668  	inverse := make(map[*peerView]bool, len(cv.peers))
   669  	all := make([]*peerView, 0, len(cv.peers))
   670  	for _, pv := range cv.peers {
   671  		inverse[pv] = true
   672  		all = append(all, pv)
   673  	}
   674  	someMatch := false
   675  	for _, peer := range config.Peers {
   676  		_, ok := cv.peers[peer.PublicKey]
   677  		if ok {
   678  			someMatch = true
   679  			break
   680  		}
   681  	}
   682  	for _, peer := range config.Peers {
   683  		if pv := cv.peers[peer.PublicKey]; (!someMatch && len(all) > 0) || pv != nil {
   684  			if pv == nil {
   685  				pv = all[0]
   686  				all = all[1:]
   687  				k, e := conf.NewPrivateKeyFromString(pv.publicKey.text.Text())
   688  				if e != nil {
   689  					continue
   690  				}
   691  				delete(cv.peers, *k)
   692  				cv.peers[peer.PublicKey] = pv
   693  			}
   694  			pv.apply(&peer)
   695  			inverse[pv] = false
   696  		} else {
   697  			group, err := newPaddedGroupGrid(cv)
   698  			if err != nil {
   699  				continue
   700  			}
   701  			group.SetTitle(l18n.Sprintf("Peer"))
   702  			pv, err := newPeerView(group)
   703  			if err != nil {
   704  				group.Dispose()
   705  				continue
   706  			}
   707  			pv.apply(&peer)
   708  			cv.peers[peer.PublicKey] = pv
   709  		}
   710  	}
   711  	for pv, remove := range inverse {
   712  		if !remove {
   713  			continue
   714  		}
   715  		k, e := conf.NewPrivateKeyFromString(pv.publicKey.text.Text())
   716  		if e != nil {
   717  			continue
   718  		}
   719  		delete(cv.peers, *k)
   720  		groupBox := pv.publicKey.label.Parent().AsContainerBase().Parent().(*walk.GroupBox)
   721  		groupBox.SetVisible(false)
   722  		groupBox.Parent().Children().Remove(groupBox)
   723  		groupBox.Dispose()
   724  	}
   725  }