aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--ringlogger/cli_test.go80
-rw-r--r--ringlogger/global.go31
-rw-r--r--ringlogger/ringlogger_windows.go207
-rw-r--r--service/errors.go6
-rw-r--r--service/ipc_client.go5
-rw-r--r--service/ipc_server.go6
-rw-r--r--service/service_manager.go51
-rw-r--r--service/service_tunnel.go47
-rw-r--r--ui/logview.go128
-rw-r--r--ui/ui.go16
10 files changed, 498 insertions, 79 deletions
diff --git a/ringlogger/cli_test.go b/ringlogger/cli_test.go
new file mode 100644
index 00000000..64057b34
--- /dev/null
+++ b/ringlogger/cli_test.go
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package ringlogger
+
+import (
+ "fmt"
+ "os"
+ "sync"
+ "testing"
+ "time"
+)
+
+func TestThreads(t *testing.T) {
+ wg := sync.WaitGroup{}
+ wg.Add(2)
+ go func() {
+ rl, err := NewRinglogger("ringlogger_test.bin", "ONE")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i := 0; i < 1024; i++ {
+ fmt.Fprintf(rl, "bla bla bla %d", i)
+ }
+ rl.Close()
+ wg.Done()
+ }()
+ go func() {
+ rl, err := NewRinglogger("ringlogger_test.bin", "TWO")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i := 1024; i < 2047; i++ {
+ fmt.Fprintf(rl, "bla bla bla %d", i)
+ }
+ rl.Close()
+ wg.Done()
+ }()
+ wg.Wait()
+}
+
+func TestWriteText(t *testing.T) {
+ rl, err := NewRinglogger("ringlogger_test.bin", "TXT")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(os.Args) != 3 {
+ t.Fatal("Should pass exactly one argument")
+ }
+ fmt.Fprintf(rl, os.Args[2])
+ rl.Close()
+}
+
+func TestDump(t *testing.T) {
+ rl, err := NewRinglogger("ringlogger_test.bin", "DMP")
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = rl.WriteTo(os.Stdout)
+ if err != nil {
+ t.Fatal(err)
+ }
+ rl.Close()
+}
+
+func TestFollow(t *testing.T) {
+ rl, err := NewRinglogger("ringlogger_test.bin", "FOL")
+ if err != nil {
+ t.Fatal(err)
+ }
+ cursor := CursorAll
+ for {
+ cursor = rl.FollowFromCursor(cursor, func(line string, stamp time.Time) {
+ fmt.Printf("%v: %s\n", stamp, line)
+ })
+ time.Sleep(300 * time.Millisecond)
+ }
+}
diff --git a/ringlogger/global.go b/ringlogger/global.go
new file mode 100644
index 00000000..da79b6de
--- /dev/null
+++ b/ringlogger/global.go
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package ringlogger
+
+import (
+ "golang.zx2c4.com/wireguard/windows/conf"
+ "log"
+ "path"
+)
+
+var Global *Ringlogger
+
+func InitGlobalLogger(tag string) error {
+ if Global != nil {
+ return nil
+ }
+ root, err := conf.RootDirectory()
+ if err != nil {
+ return err
+ }
+ Global, err = NewRinglogger(path.Join(root, "log.bin"), tag)
+ if err != nil {
+ return err
+ }
+ log.SetOutput(Global)
+ log.SetFlags(0)
+ return nil
+}
diff --git a/ringlogger/ringlogger_windows.go b/ringlogger/ringlogger_windows.go
new file mode 100644
index 00000000..1a49570c
--- /dev/null
+++ b/ringlogger/ringlogger_windows.go
@@ -0,0 +1,207 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package ringlogger
+
+import (
+ "bytes"
+ "fmt"
+ "golang.org/x/sys/windows"
+ "io"
+ "os"
+ "runtime"
+ "sync/atomic"
+ "time"
+ "unsafe"
+)
+
+const (
+ maxLogLineLength = 512
+ maxLines = 2048
+ magic = 0xbadbabe
+)
+
+type logLine struct {
+ timeNs int64
+ line [maxLogLineLength]byte
+}
+
+type logMem struct {
+ nextIndex uint32
+ lines [maxLines]logLine
+ magic uint32
+}
+
+type Ringlogger struct {
+ tag string
+ file *os.File
+ mapping windows.Handle
+ log *logMem
+}
+
+func NewRinglogger(filename string, tag string) (*Ringlogger, error) {
+ file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
+ if err != nil {
+ return nil, err
+ }
+ err = file.Truncate(int64(unsafe.Sizeof(logMem{})))
+ if err != nil {
+ return nil, err
+ }
+ mapping, err := windows.CreateFileMapping(windows.Handle(file.Fd()), nil, windows.PAGE_READWRITE, 0, 0, nil)
+ if err != nil {
+ return nil, err
+ }
+ view, err := windows.MapViewOfFile(mapping, windows.FILE_MAP_WRITE, 0, 0, 0)
+ if err != nil {
+ return nil, err
+ }
+ if err != nil {
+ windows.CloseHandle(mapping)
+ return nil, err
+ }
+ log := (*logMem)(unsafe.Pointer(view))
+ if log.magic != magic {
+ bytes := (*[unsafe.Sizeof(logMem{})]byte)(unsafe.Pointer(log))
+ for i := range bytes {
+ bytes[i] = 0
+ }
+ log.magic = magic
+ windows.FlushViewOfFile(view, uintptr(len(bytes)))
+ }
+
+ rl := &Ringlogger{
+ tag: tag,
+ file: file,
+ mapping: mapping,
+ log: log,
+ }
+ runtime.SetFinalizer(rl, (*Ringlogger).Close)
+ return rl, nil
+}
+
+func (rl *Ringlogger) Write(p []byte) (n int, err error) {
+ // Race: This isn't synchronized with the fetch_add below, so items might be slightly out of order.
+ ts := time.Now().UnixNano()
+
+ if rl.log == nil {
+ return 0, io.EOF
+ }
+
+ // Race: More than maxLines writers and this will clash.
+ index := atomic.AddUint32(&rl.log.nextIndex, 1) - 1
+ line := &rl.log.lines[index%maxLines]
+
+ // Race: Before this line executes, we'll display old data after new data.
+ atomic.StoreInt64(&line.timeNs, 0)
+ for i := range line.line {
+ line.line[i] = 0
+ }
+
+ text := []byte(fmt.Sprintf("[%s] %s", rl.tag, bytes.TrimSpace(p)))
+ if len(text) > maxLogLineLength-1 {
+ text = text[:maxLogLineLength-1]
+ }
+ line.line[len(text)] = 0
+ copy(line.line[:], text[:])
+ atomic.StoreInt64(&line.timeNs, ts)
+
+ windows.FlushViewOfFile((uintptr)(unsafe.Pointer(&rl.log.nextIndex)), unsafe.Sizeof(rl.log.nextIndex))
+ windows.FlushViewOfFile((uintptr)(unsafe.Pointer(line)), unsafe.Sizeof(*line))
+
+ return len(p), nil
+}
+
+func (rl *Ringlogger) WriteTo(out io.Writer) (n int64, err error) {
+ if rl.log == nil {
+ return 0, io.EOF
+ }
+ log := *rl.log
+ i := log.nextIndex
+ for l := 0; l < maxLines; l++ {
+ line := &log.lines[i%maxLines]
+ if line.timeNs == 0 {
+ i++
+ continue
+ }
+ index := bytes.IndexByte(line.line[:], 0)
+ if index < 1 {
+ i++
+ continue
+ }
+ var bytes int
+ bytes, err = fmt.Fprintf(out, "%s: %s\n", time.Unix(0, line.timeNs).Format("2006-01-02 15:04:05.000000"), line.line[:index])
+ if err != nil {
+ return
+ }
+ n += int64(bytes)
+ i++
+ }
+ return
+}
+
+const CursorAll = ^uint32(0)
+
+type FollowLine struct {
+ Line string
+ Stamp time.Time
+}
+
+func (rl *Ringlogger) FollowFromCursor(cursor uint32) ([]FollowLine, uint32) {
+ followLines := make([]FollowLine, 0, maxLines)
+
+ if rl.log == nil {
+ return followLines, cursor
+ }
+
+ log := *rl.log
+ i := cursor
+
+ if i == CursorAll {
+ i = log.nextIndex
+ }
+
+ for l := 0; l < maxLines; l++ {
+ line := &log.lines[i%maxLines]
+ if cursor != CursorAll && i%maxLines == log.nextIndex%maxLines {
+ break
+ }
+ if line.timeNs == 0 {
+ if cursor == CursorAll {
+ i++
+ continue
+ } else {
+ break
+ }
+ }
+ index := bytes.IndexByte(line.line[:], 0)
+ if index > 0 {
+ followLines = append(followLines, FollowLine{string(line.line[:index]), time.Unix(0, line.timeNs)})
+ }
+ i++
+ cursor = i % maxLines
+ }
+ return followLines, cursor
+}
+
+func (rl *Ringlogger) Close() error {
+ if rl.file != nil {
+ rl.file.Close()
+ rl.file = nil
+ }
+ if rl.log != nil {
+ windows.UnmapViewOfFile((uintptr)(unsafe.Pointer(rl.log)))
+ rl.log = nil
+ }
+ if rl.mapping != 0 {
+ windows.CloseHandle(rl.mapping)
+ rl.mapping = 0
+ }
+ return nil
+}
+
+func (rl *Ringlogger) Filename() string {
+ return rl.file.Name()
+}
diff --git a/service/errors.go b/service/errors.go
index fd6bc6ab..02ca33dc 100644
--- a/service/errors.go
+++ b/service/errors.go
@@ -15,7 +15,7 @@ type Error uint32
const (
ErrorSuccess Error = iota
- ErrorEventlogOpen
+ ErrorRingloggerOpen
ErrorLoadConfiguration
ErrorCreateWintun
ErrorDetermineWintunName
@@ -36,8 +36,8 @@ func (e Error) Error() string {
switch e {
case ErrorSuccess:
return "No error."
- case ErrorEventlogOpen:
- return "Unable to open Windows event log."
+ case ErrorRingloggerOpen:
+ return "Unable to open log file."
case ErrorDetermineExecutablePath:
return "Unable to determine path of running executable."
case ErrorLoadConfiguration:
diff --git a/service/ipc_client.go b/service/ipc_client.go
index e6295b91..7e260ea7 100644
--- a/service/ipc_client.go
+++ b/service/ipc_client.go
@@ -141,6 +141,11 @@ func IPCClientQuit(stopTunnelsOnQuit bool) (bool, error) {
return alreadyQuit, rpcClient.Call("ManagerService.Quit", stopTunnelsOnQuit, &alreadyQuit)
}
+func IPCClientLogFilePath() (string, error) {
+ var path string
+ return path, rpcClient.Call("ManagerService.LogFilePath", uintptr(0), &path)
+}
+
func IPCClientRegisterTunnelChange(cb func(tunnel *Tunnel, state TunnelState, err error)) *TunnelChangeCallback {
s := &TunnelChangeCallback{cb}
tunnelChangeCallbacks[s] = true
diff --git a/service/ipc_server.go b/service/ipc_server.go
index 17ea67c2..079dc85c 100644
--- a/service/ipc_server.go
+++ b/service/ipc_server.go
@@ -11,6 +11,7 @@ import (
"github.com/Microsoft/go-winio"
"golang.org/x/sys/windows/svc"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/ringlogger"
"io/ioutil"
"net/rpc"
"os"
@@ -206,6 +207,11 @@ func (s *ManagerService) Quit(stopTunnelsOnQuit bool, alreadyQuit *bool) error {
return nil
}
+func (s *ManagerService) LogFilePath(unused uintptr, filepath *string) error {
+ *filepath = ringlogger.Global.Filename()
+ return nil
+}
+
func IPCServerListen(reader *os.File, writer *os.File, events *os.File) error {
service := &ManagerService{events: events}
diff --git a/service/service_manager.go b/service/service_manager.go
index 5f9758ec..9fffbfe3 100644
--- a/service/service_manager.go
+++ b/service/service_manager.go
@@ -6,15 +6,13 @@
package service
import (
- "fmt"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
- "golang.org/x/sys/windows/svc/eventlog"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/ringlogger"
"log"
"os"
"runtime/debug"
- "strconv"
"sync"
"syscall"
"unsafe"
@@ -59,21 +57,9 @@ type wellKnownSidType uint32
type managerService struct{}
-type elogger struct {
- *eventlog.Log
-}
-
-func (elog elogger) Write(p []byte) (n int, err error) {
- msg := string(p)
- n = len(msg)
- err = elog.Warning(1, msg)
- return
-}
-
func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
changes <- svc.Status{State: svc.StartPending}
- var elog *eventlog.Log
var err error
serviceError := ErrorSuccess
@@ -81,26 +67,19 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
svcSpecificEC, exitCode = determineErrorCode(err, serviceError)
logErr := combineErrors(err, serviceError)
if logErr != nil {
- if elog != nil {
- elog.Error(1, logErr.Error())
- } else {
- fmt.Println(logErr.Error())
- }
+ log.Print(logErr)
}
changes <- svc.Status{State: svc.StopPending}
}()
- //TODO: remember to clean this up in the msi uninstaller
- eventlog.InstallAsEventCreate("WireGuard", eventlog.Info|eventlog.Warning|eventlog.Error)
- elog, err = eventlog.Open("WireGuard")
+ err = ringlogger.InitGlobalLogger("MGR")
if err != nil {
- serviceError = ErrorEventlogOpen
+ serviceError = ErrorRingloggerOpen
return
}
- log.SetOutput(elogger{elog})
defer func() {
if x := recover(); x != nil {
- elog.Error(1, fmt.Sprintf("%v:\n%s", x, string(debug.Stack())))
+ log.Printf("%v:\n%s", x, string(debug.Stack()))
panic(x)
}
}()
@@ -148,7 +127,7 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
//TODO: Isn't it better to use an impersonation token and userToken.IsMember instead?
gs, err := userToken.GetTokenGroups()
if err != nil {
- elog.Error(1, "Unable to lookup user groups from token: "+err.Error())
+ log.Printf("Unable to lookup user groups from token: %v", err)
return
}
p := unsafe.Pointer(&gs.Groups[0])
@@ -167,12 +146,12 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
user, err := userToken.GetTokenUser()
if err != nil {
- elog.Error(1, "Unable to lookup user from token: "+err.Error())
+ log.Printf("Unable to lookup user from token: %v", err)
return
}
username, domain, accType, err := user.User.Sid.LookupAccount("")
if err != nil {
- elog.Error(1, "Unable to lookup username from sid: "+err.Error())
+ log.Printf("Unable to lookup username from sid: %v", err)
return
}
if accType != windows.SidTypeUser {
@@ -181,17 +160,17 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
ourReader, theirReader, theirReaderStr, ourWriter, theirWriter, theirWriterStr, err := inheritableSocketpairEmulation()
if err != nil {
- elog.Error(1, "Unable to create two inheritable pipes: "+err.Error())
+ log.Printf("Unable to create two inheritable pipes: %v", err)
return
}
ourEvents, theirEvents, theirEventStr, err := inheritableEvents()
err = IPCServerListen(ourReader, ourWriter, ourEvents)
if err != nil {
- elog.Error(1, "Unable to listen on IPC pipes: "+err.Error())
+ log.Printf("Unable to listen on IPC pipes: %v", err)
return
}
- elog.Info(1, "Starting UI process for user: "+username+", domain: "+domain)
+ log.Printf("Starting UI process for user: '%s@%s'", username, domain)
attr := &os.ProcAttr{
Sys: &syscall.SysProcAttr{
Token: syscall.Token(userToken),
@@ -203,7 +182,7 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
theirWriter.Close()
theirEvents.Close()
if err != nil {
- elog.Error(1, "Unable to start manager UI process: "+err.Error())
+ log.Printf("Unable to start manager UI process: %v", err)
return
}
@@ -260,7 +239,7 @@ loop:
}
sessionNotification := (*wtsSessionNotification)(unsafe.Pointer(c.EventData))
if uintptr(sessionNotification.size) != unsafe.Sizeof(*sessionNotification) {
- elog.Error(1, "Unexpected size of WTSSESSION_NOTIFICATION: "+strconv.Itoa(int(sessionNotification.size)))
+ log.Printf("Unexpected size of WTSSESSION_NOTIFICATION: %d", sessionNotification.size)
continue
}
if c.EventType == wtsSessionLogoff {
@@ -278,7 +257,7 @@ loop:
}
default:
- elog.Info(1, fmt.Sprintf("Unexpected service control request #%d\n", c))
+ log.Printf("Unexpected service control request #%d", c)
}
}
}
@@ -292,7 +271,7 @@ loop:
if uninstall {
err = UninstallManager()
if err != nil {
- elog.Error(1, "Unable to uninstaller manager when quitting: "+err.Error())
+ log.Printf("Unable to uninstaller manager when quitting: %v", err)
}
}
return
diff --git a/service/service_tunnel.go b/service/service_tunnel.go
index 2e615932..f8c843f6 100644
--- a/service/service_tunnel.go
+++ b/service/service_tunnel.go
@@ -9,36 +9,18 @@ import (
"bufio"
"fmt"
"golang.org/x/sys/windows/svc"
- "golang.org/x/sys/windows/svc/eventlog"
"golang.zx2c4.com/winipcfg"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/ipc"
"golang.zx2c4.com/wireguard/tun"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/ringlogger"
"log"
"net"
"runtime/debug"
"strings"
)
-type confElogger struct {
- elog *eventlog.Log
- conf *conf.Config
- level int
-}
-
-func (elog confElogger) Write(p []byte) (n int, err error) {
- msg := elog.conf.Name + ": " + string(p)
- n = len(msg)
- switch elog.level {
- case 1, 2:
- err = elog.elog.Info(1, msg)
- case 3:
- err = elog.elog.Error(1, msg)
- }
- return
-}
-
type tunnelService struct {
path string
}
@@ -49,7 +31,6 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
var dev *device.Device
var uapi net.Listener
var routeChangeCallback *winipcfg.RouteChangeCallback
- var elog *eventlog.Log
var logger *device.Logger
var err error
serviceError := ErrorSuccess
@@ -59,11 +40,9 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
logErr := combineErrors(err, serviceError)
if logErr != nil {
if logger != nil {
- logger.Error.Println(logErr.Error())
- } else if elog != nil {
- elog.Error(1, logErr.Error())
+ logger.Error.Print(logErr)
} else {
- fmt.Println(logErr.Error())
+ log.Print(logErr)
}
}
changes <- svc.Status{State: svc.StopPending}
@@ -76,22 +55,17 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
if dev != nil {
dev.Close()
}
- if elog != nil {
- elog.Info(1, "Shutting down")
- }
+ log.Print("Shutting down")
}()
- //TODO: remember to clean this up in the msi uninstaller
- eventlog.InstallAsEventCreate("WireGuard", eventlog.Info|eventlog.Warning|eventlog.Error)
- elog, err = eventlog.Open("WireGuard")
+ err = ringlogger.InitGlobalLogger("TUN")
if err != nil {
- serviceError = ErrorEventlogOpen
+ serviceError = ErrorRingloggerOpen
return
}
- log.SetOutput(elogger{elog})
defer func() {
if x := recover(); x != nil {
- elog.Error(1, fmt.Sprintf("%v:\n%s", x, string(debug.Stack())))
+ log.Printf("%v:\n%s", x, string(debug.Stack()))
panic(x)
}
}()
@@ -102,11 +76,8 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
return
}
- logger = &device.Logger{
- Debug: log.New(&confElogger{elog: elog, conf: conf, level: 1}, "", 0),
- Info: log.New(&confElogger{elog: elog, conf: conf, level: 2}, "", 0),
- Error: log.New(&confElogger{elog: elog, conf: conf, level: 3}, "", 0),
- }
+ stdLog := log.New(ringlogger.Global, fmt.Sprintf("[%s] ", conf.Name), 0)
+ logger = &device.Logger{stdLog, stdLog, stdLog}
logger.Info.Println("Starting wireguard-go version", device.WireGuardGoVersion)
logger.Debug.Println("Debug log enabled")
diff --git a/ui/logview.go b/ui/logview.go
new file mode 100644
index 00000000..e7a8a237
--- /dev/null
+++ b/ui/logview.go
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package ui
+
+import (
+ "errors"
+ "fmt"
+ "github.com/lxn/walk"
+ "github.com/lxn/win"
+ "golang.zx2c4.com/wireguard/windows/ringlogger"
+ "strings"
+ "syscall"
+ "time"
+ "unsafe"
+)
+
+type LogView struct {
+ walk.WidgetBase
+ logChan chan string
+}
+
+const (
+ TEM_APPENDTEXT = win.WM_USER + 6
+)
+
+func NewLogView(parent walk.Container, rl *ringlogger.Ringlogger) (*LogView, error) {
+ lc := make(chan string, 1024)
+ lv := &LogView{logChan: lc}
+
+ if err := walk.InitWidget(
+ lv,
+ parent,
+ "EDIT",
+ win.WS_TABSTOP|win.WS_VISIBLE|win.WS_VSCROLL|win.ES_MULTILINE|win.ES_WANTRETURN,
+ win.WS_EX_CLIENTEDGE); err != nil {
+ return nil, err
+ }
+ lv.setReadOnly(true)
+ lv.SendMessage(win.EM_SETLIMITTEXT, 4294967295, 0)
+
+ go func() {
+ var lines []ringlogger.FollowLine
+ cursor := ringlogger.CursorAll
+ for {
+ if lv.IsDisposed() {
+ return
+ }
+ lines, cursor = rl.FollowFromCursor(cursor)
+ sb := strings.Builder{}
+ for _, line := range lines {
+ sb.WriteString(fmt.Sprintf("%s: %s\r\n", line.Stamp.Format("2006-01-02 15:04:05.000000"), strings.ReplaceAll(line.Line, "\n", "\r\n")))
+ }
+ newLines := sb.String()
+ if len(newLines) > 0 {
+ lv.AppendText(newLines)
+ }
+ time.Sleep(300 * time.Millisecond)
+ }
+ }()
+ return lv, nil
+}
+
+func (*LogView) LayoutFlags() walk.LayoutFlags {
+ return walk.ShrinkableHorz | walk.ShrinkableVert | walk.GrowableHorz | walk.GrowableVert | walk.GreedyHorz | walk.GreedyVert
+}
+
+func (*LogView) MinSizeHint() walk.Size {
+ return walk.Size{20, 12}
+}
+
+func (*LogView) SizeHint() walk.Size {
+ return walk.Size{100, 100}
+}
+
+func (lv *LogView) setTextSelection(start, end int) {
+ lv.SendMessage(win.EM_SETSEL, uintptr(start), uintptr(end))
+}
+
+func (lv *LogView) textLength() int {
+ return int(lv.SendMessage(0x000E, uintptr(0), uintptr(0)))
+}
+
+func (lv *LogView) AppendText(value string) {
+ textLength := lv.textLength()
+ lv.setTextSelection(textLength, textLength)
+ lv.SendMessage(win.EM_REPLACESEL, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(value))))
+}
+
+func (lv *LogView) setReadOnly(readOnly bool) error {
+ if 0 == lv.SendMessage(win.EM_SETREADONLY, uintptr(win.BoolToBOOL(readOnly)), 0) {
+ return errors.New("fail to call EM_SETREADONLY")
+ }
+
+ return nil
+}
+
+func (lv *LogView) PostAppendText(value string) {
+ lv.logChan <- value
+ win.PostMessage(lv.Handle(), TEM_APPENDTEXT, 0, 0)
+}
+
+func (lv *LogView) Write(p []byte) (int, error) {
+ lv.PostAppendText(string(p) + "\r\n")
+ return len(p), nil
+}
+
+func (lv *LogView) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
+ switch msg {
+ case win.WM_GETDLGCODE:
+ if wParam == win.VK_RETURN {
+ return win.DLGC_WANTALLKEYS
+ }
+
+ return win.DLGC_HASSETSEL | win.DLGC_WANTARROWS | win.DLGC_WANTCHARS
+ case TEM_APPENDTEXT:
+ select {
+ case value := <-lv.logChan:
+ lv.AppendText(value)
+ default:
+ return 0
+ }
+ }
+
+ return lv.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
+}
diff --git a/ui/ui.go b/ui/ui.go
index 7bc49489..bbae9980 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -10,6 +10,7 @@ import (
"github.com/lxn/walk"
"github.com/lxn/win"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/ringlogger"
"golang.zx2c4.com/wireguard/windows/service"
"golang.zx2c4.com/wireguard/windows/ui/syntax"
"os"
@@ -54,7 +55,7 @@ func RunUI() {
tray.SetToolTip("WireGuard: Deactivated")
tray.SetVisible(true)
- mw.SetSize(walk.Size{900, 800})
+ mw.SetSize(walk.Size{900, 1400})
mw.SetLayout(walk.NewVBoxLayout())
mw.SetIcon(icon)
mw.Closing().Attach(func(canceled *bool, reason walk.CloseReason) {
@@ -178,6 +179,17 @@ func RunUI() {
restoreState = false
})
+ logfile, err := service.IPCClientLogFilePath()
+ var logger *ringlogger.Ringlogger
+ if err == nil {
+ logger, err = ringlogger.NewRinglogger(logfile, "GUI")
+ }
+ if err != nil {
+ walk.MsgBox(nil, "Unable to initialize logging", fmt.Sprintf("%v\n\nFile: %s", err, logfile), walk.MsgBoxIconError)
+ return
+ }
+ NewLogView(mw, logger)
+
quitAction := walk.NewAction()
quitAction.SetText("Exit")
quit = func() {
@@ -249,7 +261,7 @@ func RunUI() {
setServiceState(tunnel, state, err == nil)
if err != nil {
if mw.Visible() {
- walk.MsgBox(mw, "Tunnel Error", err.Error()+"\n\nPlease consult the Windows Event Log for more information.", walk.MsgBoxIconWarning)
+ walk.MsgBox(mw, "Tunnel Error", err.Error()+"\n\nPlease consult the log for more information.", walk.MsgBoxIconWarning)
} else {
tray.ShowError("WireGuard Tunnel Error", err.Error())
}