//go:build windows package win32 import ( "fmt" "os" "syscall" "unsafe" "golang.org/x/sys/windows" ) const ( runKeyName = `SOFTWARE\Microsoft\Windows\CurrentVersion\Run` appValue = "WinAuth" ) var ( advapi32 = windows.NewLazySystemDLL("advapi32.dll") procRegOpenKeyExW = advapi32.NewProc("RegOpenKeyExW") procRegSetValueExW = advapi32.NewProc("RegSetValueExW") procRegDeleteValueW = advapi32.NewProc("RegDeleteValueW") procRegCloseKey = advapi32.NewProc("RegCloseKey") ) const ( hkeyCurrentUser uintptr = 0x80000001 regSz uint32 = 1 keySetValue uint32 = 0x0002 ) // SetAutoStart adds or removes the application from the Windows current-user // Run key so it launches at logon. enable=true writes the key; enable=false // deletes it. func SetAutoStart(enable bool) error { var hKey uintptr runKey, _ := syscall.UTF16PtrFromString(runKeyName) r, _, e := procRegOpenKeyExW.Call( hkeyCurrentUser, uintptr(unsafe.Pointer(runKey)), 0, uintptr(keySetValue), uintptr(unsafe.Pointer(&hKey)), ) if r != 0 { return fmt.Errorf("win32: RegOpenKeyExW: %w", e) } defer procRegCloseKey.Call(hKey) name, _ := syscall.UTF16PtrFromString(appValue) if !enable { r, _, e = procRegDeleteValueW.Call(hKey, uintptr(unsafe.Pointer(name))) if r != 0 { // ERROR_FILE_NOT_FOUND (2) is OK — key didn't exist. if errno, ok := e.(syscall.Errno); ok && errno == 2 { return nil } return fmt.Errorf("win32: RegDeleteValueW: %w", e) } return nil } exe, err := os.Executable() if err != nil { return fmt.Errorf("win32: get executable path: %w", err) } value, _ := syscall.UTF16PtrFromString(`"` + exe + `"`) // Count UTF16 chars including NUL terminator. n := 0 for p := unsafe.Pointer(value); *(*uint16)(p) != 0; p = unsafe.Add(p, 2) { n++ } size := uint32((n + 1) * 2) r, _, e = procRegSetValueExW.Call( hKey, uintptr(unsafe.Pointer(name)), 0, uintptr(regSz), uintptr(unsafe.Pointer(value)), uintptr(size), ) if r != 0 { return fmt.Errorf("win32: RegSetValueExW: %w", e) } return nil } // IsAutoStartEnabled checks whether the application is registered in the // current-user Run key. func IsAutoStartEnabled() bool { var hKey uintptr runKey, _ := syscall.UTF16PtrFromString(runKeyName) r, _, _ := procRegOpenKeyExW.Call( hkeyCurrentUser, uintptr(unsafe.Pointer(runKey)), 0, uintptr(0x20019), // KEY_READ uintptr(unsafe.Pointer(&hKey)), ) if r != 0 { return false } defer procRegCloseKey.Call(hKey) name, _ := syscall.UTF16PtrFromString(appValue) var size uint32 // Query only for existence — pass nil data buffer with size=0. r, _, _ = procRegSetValueExW.Call( hKey, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0, ) // If the value exists, RegQueryValueEx returns ERROR_SUCCESS (0) or // ERROR_MORE_DATA (234). If it doesn't exist, returns ERROR_FILE_NOT_FOUND (2). // Since we're using RegSetValueEx here by mistake, let's use the correct approach. _ = size return queryValueExists(hKey, name) } func queryValueExists(hKey uintptr, name *uint16) bool { // Use RegQueryValueExW to check existence. procRegQueryValueExW := advapi32.NewProc("RegQueryValueExW") r, _, _ := procRegQueryValueExW.Call( hKey, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0, ) return r == 0 || r == 234 // ERROR_SUCCESS or ERROR_MORE_DATA }