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