github.com/AlpineAIO/wails/v2@v2.0.0-beta.32.0.20240505041856-1047a8fa5fef/internal/frontend/desktop/windows/single_instance.go (about) 1 //go:build windows 2 3 package windows 4 5 import ( 6 "encoding/json" 7 "github.com/AlpineAIO/wails/v2/internal/frontend/desktop/windows/winc/w32" 8 "github.com/AlpineAIO/wails/v2/pkg/options" 9 "golang.org/x/sys/windows" 10 "log" 11 "os" 12 "syscall" 13 "unsafe" 14 ) 15 16 type COPYDATASTRUCT struct { 17 dwData uintptr 18 cbData uint32 19 lpData uintptr 20 } 21 22 // WMCOPYDATA_SINGLE_INSTANCE_DATA we define our own type for WM_COPYDATA message 23 const WMCOPYDATA_SINGLE_INSTANCE_DATA = 1542 24 25 func SendMessage(hwnd w32.HWND, data string) { 26 arrUtf16, _ := syscall.UTF16FromString(data) 27 28 pCopyData := new(COPYDATASTRUCT) 29 pCopyData.dwData = WMCOPYDATA_SINGLE_INSTANCE_DATA 30 pCopyData.cbData = uint32(len(arrUtf16)*2 + 1) 31 pCopyData.lpData = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(data))) 32 33 w32.SendMessage(hwnd, w32.WM_COPYDATA, 0, uintptr(unsafe.Pointer(pCopyData))) 34 } 35 36 // SetupSingleInstance single instance Windows app 37 func SetupSingleInstance(uniqueId string) { 38 id := "wails-app-" + uniqueId 39 40 className := id + "-sic" 41 windowName := id + "-siw" 42 mutexName := id + "sim" 43 44 _, err := windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(mutexName)) 45 46 if err != nil { 47 if err == windows.ERROR_ALREADY_EXISTS { 48 // app is already running 49 hwnd := w32.FindWindowW(windows.StringToUTF16Ptr(className), windows.StringToUTF16Ptr(windowName)) 50 51 if hwnd != 0 { 52 data := options.SecondInstanceData{ 53 Args: os.Args[1:], 54 } 55 data.WorkingDirectory, err = os.Getwd() 56 if err != nil { 57 log.Printf("Failed to get working directory: %v", err) 58 return 59 } 60 serialized, err := json.Marshal(data) 61 if err != nil { 62 log.Printf("Failed to marshal data: %v", err) 63 return 64 } 65 66 SendMessage(hwnd, string(serialized)) 67 // exit second instance of app after sending message 68 os.Exit(0) 69 } 70 // if we got any other unknown error we will just start new application instance 71 } 72 } else { 73 createEventTargetWindow(className, windowName) 74 } 75 } 76 77 func createEventTargetWindow(className string, windowName string) w32.HWND { 78 // callback handler in the event target window 79 wndProc := func( 80 hwnd w32.HWND, msg uint32, wparam w32.WPARAM, lparam w32.LPARAM, 81 ) w32.LRESULT { 82 if msg == w32.WM_COPYDATA { 83 ldata := (*COPYDATASTRUCT)(unsafe.Pointer(lparam)) 84 85 if ldata.dwData == WMCOPYDATA_SINGLE_INSTANCE_DATA { 86 serialized := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ldata.lpData))) 87 88 var secondInstanceData options.SecondInstanceData 89 90 err := json.Unmarshal([]byte(serialized), &secondInstanceData) 91 92 if err == nil { 93 secondInstanceBuffer <- secondInstanceData 94 } 95 } 96 97 return w32.LRESULT(0) 98 } 99 100 return w32.DefWindowProc(hwnd, msg, wparam, lparam) 101 } 102 103 var class w32.WNDCLASSEX 104 class.Size = uint32(unsafe.Sizeof(class)) 105 class.Style = 0 106 class.WndProc = syscall.NewCallback(wndProc) 107 class.ClsExtra = 0 108 class.WndExtra = 0 109 class.Instance = w32.GetModuleHandle("") 110 class.Icon = 0 111 class.Cursor = 0 112 class.Background = 0 113 class.MenuName = nil 114 class.ClassName = windows.StringToUTF16Ptr(className) 115 class.IconSm = 0 116 117 w32.RegisterClassEx(&class) 118 119 // create event window that will not be visible for user 120 hwnd := w32.CreateWindowEx( 121 0, 122 windows.StringToUTF16Ptr(className), 123 windows.StringToUTF16Ptr(windowName), 124 0, 125 0, 126 0, 127 0, 128 0, 129 w32.HWND_MESSAGE, 130 0, 131 w32.GetModuleHandle(""), 132 nil, 133 ) 134 135 return hwnd 136 }