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  }