diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e4819ed --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/main.go b/main.go index 5de18a2..ea58dba 100644 --- a/main.go +++ b/main.go @@ -1,28 +1,21 @@ package main import ( + "Win2Linux/pkg/bcdedit" "Win2Linux/pkg/windows" _ "embed" "fmt" "os" - "os/exec" - "regexp" - "strings" + + "Win2Linux/pkg/errors" "github.com/getlantern/systray" ) //go:generate go-winres make -type ( - Entry struct { - Key string - Attributes map[string]string - } -) - -var ( - r *regexp.Regexp = regexp.MustCompile(`-+`) +const ( + GeneralFailure int32 = 0x01 ) //go:embed tray.ico @@ -35,10 +28,17 @@ func main() { } func onReady() { - entries := list() systray.SetTitle("Win2Linux") systray.SetTooltip("Switch from Windows to Linux") systray.SetIcon(icon) + entries, err := bcdedit.List() + if err != nil { + if wErr, ok := err.(*errors.WinError); ok { + windows.Fatal("Win2Linux", fmt.Sprintf("error: %s", err), int(wErr.ExitCode())) + } else { + windows.Fatal("Win2Linux", fmt.Sprintf("error: %s", err), 1) + } + } for _, entry := range entries { fmt.Println(entry.Attributes) @@ -49,14 +49,29 @@ func onReady() { } mCustom := systray.AddMenuItem(key, "Switch to"+key) go func() { - <-mCustom.ClickedCh - if uuid == "{fwbootmgr}" { - rebootToFirmware() - return + for { + <-mCustom.ClickedCh + if uuid == "{fwbootmgr}" { + err := bcdedit.RebootToFirmware() + if err != nil { + if wErr, ok := err.(*errors.WinError); ok { + windows.Fatal("Win2Linux", fmt.Sprintf("error: %s", err), int(wErr.ExitCode())) + } else { + windows.Fatal("Win2Linux", fmt.Sprintf("error: %s", err), 1) + } + } + return + } + err := bcdedit.Reboot(uuid) + if err != nil { + if wErr, ok := err.(*errors.WinError); ok { + windows.Fatal("Win2Linux", fmt.Sprintf("error: %s", err), int(wErr.ExitCode())) + } else { + windows.Fatal("Win2Linux", fmt.Sprintf("error: %s", err), 1) + } + } } - reboot(uuid) }() - } systray.AddSeparator() mQuit := systray.AddMenuItem("Quit", "Quit") @@ -70,82 +85,3 @@ func onReady() { func onExit() { // clean up here } - -func list() []Entry { - cmd := exec.Command("bcdedit", "/enum", "firmware") - out, err := cmd.Output() - if err != nil { - fatal(fmt.Sprintf("failed to run bcdedit: %s\n\nRun this program in administrator mode", err), 1) - } - return parse(string(out)) -} - -func parse(out string) []Entry { - lines := strings.Split(out, "\r\n") - section := false - lastLine := "" - secName := "" - a := make(map[string][]string) - for _, l := range lines { - if !section { - if r.Match([]byte(l)) { - secName = lastLine - section = true - } - } else { - if len(l) != 0 { - a[secName] = append(a[secName], l) - } else { - section = false - } - } - - lastLine = l - } - - var entries []Entry - for k, sec := range a { - entry := Entry{ - Key: k, - Attributes: make(map[string]string), - } - for _, l := range sec { - l = strings.Join(strings.Fields(l), " ") - val := strings.Split(l, " ") - if len(val) != 2 { - if len(val) > 2 { - val[1] = strings.Join(val[1:], " ") - } - continue - } - entry.Attributes[val[0]] = val[1] - } - entries = append(entries, entry) - } - return entries -} - -func reboot(uuid string) { - out, err := exec.Command("bcdedit", "/Set", "{fwbootmgr}", "BootSequence", uuid, "/addFirst").Output() - if err != nil { - windows.MessageBox(windows.NULL, fmt.Sprintf("failed to execute bcdedit: %s", out), "Win2Linux", windows.MB_OK) - return - } - - out, err = exec.Command("shutdown", "/r", "/t", "0").Output() - if err != nil { - windows.MessageBox(windows.NULL, fmt.Sprintf("failed to restart the computer: %s", out), "Win2Linux", windows.MB_OK) - } -} - -func rebootToFirmware() { - out, err := exec.Command("shutdown", "/r", "/fw", "/t", "0").Output() - if err != nil { - windows.MessageBox(windows.NULL, fmt.Sprintf("failed to restart the computer: %s", out), "Win2Linux", windows.MB_OK) - } -} - -func fatal(message string, exitCode int) { - windows.MessageBox(windows.NULL, message, "Win2Linux", windows.MB_OK) - os.Exit(exitCode) -} diff --git a/pkg/bcdedit/bcdedit.go b/pkg/bcdedit/bcdedit.go new file mode 100644 index 0000000..b37024a --- /dev/null +++ b/pkg/bcdedit/bcdedit.go @@ -0,0 +1,119 @@ +package bcdedit + +import ( + "Win2Linux/pkg/windows/cmd" + "regexp" + "strings" +) + +type ( + Entry struct { + Key string + Attributes map[string]string + } +) + +var ( + r *regexp.Regexp = regexp.MustCompile(`-+`) +) + +func List() ([]Entry, error) { + args := []string{ + "bcdedit", + "/enum", + "firmware", + } + out, err := cmd.Execute(args) + if err != nil { + return nil, err + } + return parse(out), nil +} + +func Reboot(uuid string) error { + args := []string{ + "bcdedit", + "/bootsequence", + uuid, + "/addfirst", + } + _, err := cmd.Execute(args) + if err != nil { + return err + } + + args = []string{ + "shutdown", + "/r", + "/t", + "0", + } + _, err = cmd.Execute(args) + if err != nil { + return err + } + + return nil +} + +func RebootToFirmware() error { + args := []string{ + "shutdown", + "/r", + "/fw", + "/t", + "0", + } + + _, err := cmd.Execute(args) + if err != nil { + return err + } + + return nil +} + +func parse(out string) []Entry { + lines := strings.Split(out, "\r\n") + section := false + lastLine := "" + secName := "" + a := make(map[string][]string) + for _, l := range lines { + if !section { + if r.Match([]byte(l)) { + secName = lastLine + section = true + } + } else { + if len(l) != 0 { + a[secName] = append(a[secName], l) + } else { + section = false + } + } + + lastLine = l + } + + var entries []Entry + for k, sec := range a { + entry := Entry{ + Key: k, + Attributes: make(map[string]string), + } + for _, l := range sec { + l = strings.Join(strings.Fields(l), " ") + val := strings.Split(l, " ") + if len(val) != 2 { + if len(val) > 2 { + val[1] = strings.Join(val[1:], " ") + } + continue + } + entry.Attributes[val[0]] = val[1] + } + entries = append(entries, entry) + } + return entries +} diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go new file mode 100644 index 0000000..ec91584 --- /dev/null +++ b/pkg/errors/errors.go @@ -0,0 +1,25 @@ +package errors + +import "fmt" + +type ( + WinError struct { + err string + _code uint32 + } +) + +func New(err any, exitCode uint32) *WinError { + return &WinError{ + err: fmt.Sprintf("%v", err), + _code: exitCode, + } +} + +func (e *WinError) Error() string { + return e.err +} + +func (e *WinError) ExitCode() uint32 { + return e._code +} diff --git a/pkg/windows/cmd/cmd.go b/pkg/windows/cmd/cmd.go new file mode 100644 index 0000000..aed7192 --- /dev/null +++ b/pkg/windows/cmd/cmd.go @@ -0,0 +1,18 @@ +package cmd + +import ( + "Win2Linux/pkg/errors" + "fmt" + "os/exec" + "strings" +) + +func Execute(c []string) (string, error) { + a := strings.Join(c, " ") + cmd := exec.Command("cmd", "/d", "/c", "chcp 65001>nul && "+a) + out, err := cmd.CombinedOutput() + if err != nil { + return "", errors.New(fmt.Sprintf("failed to run the command: %s: %s", err, out), uint32(cmd.ProcessState.ExitCode())) + } + return string(out), nil +} diff --git a/pkg/windows/windows.go b/pkg/windows/windows.go index 202cc3f..3728c5a 100644 --- a/pkg/windows/windows.go +++ b/pkg/windows/windows.go @@ -1,6 +1,7 @@ package windows import ( + "os" "syscall" "unsafe" ) @@ -10,8 +11,13 @@ const ( MB_OK = 0 ) +func Fatal(title, message string, exitCode int) { + messageBox(NULL, message, title, MB_OK) + os.Exit(exitCode) +} + // MessageBox of Win32 API. -func MessageBox(hwnd uintptr, caption, title string, flags uint) int { +func messageBox(hwnd uintptr, caption, title string, flags uint) int { _caption, err := syscall.UTF16PtrFromString(caption) if err != nil { panic(err) diff --git a/winres/winres.json b/winres/winres.json index 8043b9f..8fd6ead 100644 --- a/winres/winres.json +++ b/winres/winres.json @@ -18,7 +18,7 @@ "minimum-os": "win10", "execution-level": "requireAdministrator", "ui-access": false, - "auto-elevate": false, + "auto-elevate": true, "dpi-awareness": "system", "disable-theming": false, "disable-window-filtering": false, @@ -36,15 +36,15 @@ "#1": { "0000": { "fixed": { - "file_version": "0.0.1.0", - "product_version": "0.0.1.0" + "file_version": "0.0.3.0", + "product_version": "0.0.3.0" }, "info": { "0409": { "Comments": "Switch from Windows to Linux", "CompanyName": "Aurelie Delhaie", "ProductName": "Win2Linux", - "ProductVersion": "0.0.1.0" + "ProductVersion": "0.0.3.0" } } }