//go:build windows package filedialog import ( "path/filepath" "strings" "syscall" "unsafe" "golang.org/x/sys/windows" ) var ( comdlg32 = windows.NewLazySystemDLL("comdlg32.dll") procGetOpenFileName = comdlg32.NewProc("GetOpenFileNameW") procGetSaveFileName = comdlg32.NewProc("GetSaveFileNameW") ) const ( ofnExplorer = 0x00080000 ofnFileMustExist = 0x00001000 ofnPathMustExist = 0x00000800 ofnOverwritePrompt = 0x00000002 ofnHideReadOnly = 0x00000004 maxPath = 32768 ) type openFileNameW struct { LStructSize uint32 HwndOwner windows.HWND HInstance windows.Handle LpstrFilter *uint16 LpstrCustomFilter *uint16 NMaxCustFilter uint32 NFilterIndex uint32 LpstrFile *uint16 NMaxFile uint32 LpstrFileTitle *uint16 NMaxFileTitle uint32 LpstrInitialDir *uint16 LpstrTitle *uint16 Flags uint32 NFileOffset uint16 NFileExtension uint16 LpstrDefExt *uint16 LCustData uintptr LpfnHook uintptr LpTemplateName *uint16 PvReserved unsafe.Pointer DwReserved uint32 FlagsEx uint32 } func jsonFilter() *uint16 { // Double-null terminated pairs: label\0pattern\0label\0pattern\0\0 s := "JSON (*.json)\x00*.json\x00All files\x00*.*\x00" return &utf16FromString(s)[0] } func utf16FromString(s string) []uint16 { u, err := syscall.UTF16FromString(s) if err != nil { return []uint16{0} } return u } func ensureJSONExt(path string) string { if strings.EqualFold(filepath.Ext(path), ".json") { return path } return path + ".json" } // SaveJSON shows a Save As dialog for a .json file. func SaveJSON(title, defaultName string) (string, error) { if title == "" { title = "Сохранить список серверов" } if defaultName == "" { defaultName = "navis-servers.json" } buf := make([]uint16, maxPath) copy(buf, utf16FromString(defaultName)) filter := jsonFilter() titlePtr, _ := syscall.UTF16PtrFromString(title) defExt, _ := syscall.UTF16PtrFromString("json") ofn := openFileNameW{ LStructSize: uint32(unsafe.Sizeof(openFileNameW{})), LpstrFilter: filter, NFilterIndex: 1, LpstrFile: &buf[0], NMaxFile: uint32(len(buf)), LpstrTitle: titlePtr, Flags: ofnExplorer | ofnPathMustExist | ofnOverwritePrompt | ofnHideReadOnly, LpstrDefExt: defExt, } r, _, _ := procGetSaveFileName.Call(uintptr(unsafe.Pointer(&ofn))) if r == 0 { return "", ErrCanceled } path := windows.UTF16ToString(buf) if path == "" { return "", ErrCanceled } return ensureJSONExt(path), nil } // OpenJSON shows an Open dialog for a .json file. func OpenJSON(title string) (string, error) { if title == "" { title = "Загрузить список серверов" } buf := make([]uint16, maxPath) filter := jsonFilter() titlePtr, _ := syscall.UTF16PtrFromString(title) ofn := openFileNameW{ LStructSize: uint32(unsafe.Sizeof(openFileNameW{})), LpstrFilter: filter, NFilterIndex: 1, LpstrFile: &buf[0], NMaxFile: uint32(len(buf)), LpstrTitle: titlePtr, Flags: ofnExplorer | ofnFileMustExist | ofnPathMustExist | ofnHideReadOnly, } r, _, _ := procGetOpenFileName.Call(uintptr(unsafe.Pointer(&ofn))) if r == 0 { return "", ErrCanceled } path := windows.UTF16ToString(buf) if path == "" { return "", ErrCanceled } return path, nil }