arblant
firefox-vpn-client / core / internal / desktop / platform_windows.go
platform_windows.go97 lines · 2.9 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
//go:build windows

package desktop

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"os"
	"strings"
	"unsafe"

	"github.com/armanlife/firefox-vpn-client/core/internal/service"

	"golang.org/x/sys/windows"
)

var (
	kernel32                  = windows.NewLazySystemDLL("kernel32.dll")
	procGetConsoleProcessList = kernel32.NewProc("GetConsoleProcessList")
	procFreeConsole           = kernel32.NewProc("FreeConsole")
)

// StartedWithOwnConsole reports whether this process is alone on its
// console, which is the case when it was started from Explorer rather than
// from a terminal.
func StartedWithOwnConsole() bool {
	var pids [4]uint32
	n, _, _ := procGetConsoleProcessList.Call(uintptr(unsafe.Pointer(&pids[0])), uintptr(len(pids)))
	return n == 1
}

// DetachConsole closes the console window of a process started from
// Explorer; the tray icon replaces it.
func DetachConsole() {
	_, _, _ = procFreeConsole.Call()
}

// acquireSingleInstance makes sure only one desktop host (the tray icon or
// the app mode) runs per data folder. The returned release function is a
// no-op when another instance owns it.
func acquireSingleInstance(paths service.DataPaths) (release func(), ok bool) {
	name := trayMutexName(paths)
	h, err := windows.CreateMutex(nil, false, name)
	if errors.Is(err, windows.ERROR_ALREADY_EXISTS) {
		if h != 0 {
			_ = windows.CloseHandle(h)
		}
		return func() {}, false
	}
	if err != nil {
		// Without a mutex, prefer running over refusing to start.
		return func() {}, true
	}
	return func() { _ = windows.CloseHandle(h) }, true
}

func trayMutexName(paths service.DataPaths) *uint16 {
	sum := sha256.Sum256([]byte(strings.ToLower(paths.Dir)))
	name, _ := windows.UTF16PtrFromString(`Local\FirefoxVPN-tray-` + hex.EncodeToString(sum[:8]))
	return name
}

// notify does nothing on Windows. A toast needs a WinRT binding or a
// hidden script host, and antivirus heuristics flag the latter; the tray
// shows the state in its icon and tooltip, and the desktop app shows its
// own notifications.
func notify(title, body string) {}

// browserAvailable reports whether openBrowser can show a page: always on
// Windows.
func browserAvailable() bool { return true }

// openBrowser opens url in the default browser.
func openBrowser(url string) error {
	verb, _ := windows.UTF16PtrFromString("open")
	target, err := windows.UTF16PtrFromString(url)
	if err != nil {
		return err
	}
	return windows.ShellExecute(0, verb, target, nil, nil, windows.SW_SHOWNORMAL)
}

// enableTerminalColors turns on ANSI escape processing for the console
// (Windows 10 1511 and later) and sets the window title.
func enableTerminalColors() bool {
	h := windows.Handle(os.Stdout.Fd())
	var mode uint32
	if err := windows.GetConsoleMode(h, &mode); err != nil {
		return false
	}
	if err := windows.SetConsoleMode(h, mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING|windows.ENABLE_PROCESSED_OUTPUT); err != nil {
		return false
	}
	os.Stdout.WriteString("\x1b]0;Firefox VPN\x07")
	return true
}