mirror of
https://github.com/likelovewant/ollama-for-amd.git
synced 2025-12-25 07:58:01 +00:00
app: add code for macOS and Windows apps under 'app' (#12933)
* app: add code for macOS and Windows apps under 'app' * app: add readme * app: windows and linux only for now * ci: fix ui CI validation --------- Co-authored-by: jmorganca <jmorganca@gmail.com>
This commit is contained in:
241
app/dialog/dlgs_windows.go
Normal file
241
app/dialog/dlgs_windows.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"github.com/TheTitanrain/w32"
|
||||
)
|
||||
|
||||
const multiFileBufferSize = w32.MAX_PATH * 10
|
||||
|
||||
type WinDlgError int
|
||||
|
||||
func (e WinDlgError) Error() string {
|
||||
return fmt.Sprintf("CommDlgExtendedError: %#x", e)
|
||||
}
|
||||
|
||||
func err() error {
|
||||
e := w32.CommDlgExtendedError()
|
||||
if e == 0 {
|
||||
return ErrCancelled
|
||||
}
|
||||
return WinDlgError(e)
|
||||
}
|
||||
|
||||
func (b *MsgBuilder) yesNo() bool {
|
||||
r := w32.MessageBox(w32.HWND(0), b.Msg, firstOf(b.Dlg.Title, "Confirm?"), w32.MB_YESNO)
|
||||
return r == w32.IDYES
|
||||
}
|
||||
|
||||
func (b *MsgBuilder) info() {
|
||||
w32.MessageBox(w32.HWND(0), b.Msg, firstOf(b.Dlg.Title, "Information"), w32.MB_OK|w32.MB_ICONINFORMATION)
|
||||
}
|
||||
|
||||
func (b *MsgBuilder) error() {
|
||||
w32.MessageBox(w32.HWND(0), b.Msg, firstOf(b.Dlg.Title, "Error"), w32.MB_OK|w32.MB_ICONERROR)
|
||||
}
|
||||
|
||||
type filedlg struct {
|
||||
buf []uint16
|
||||
filters []uint16
|
||||
opf *w32.OPENFILENAME
|
||||
}
|
||||
|
||||
func (d filedlg) Filename() string {
|
||||
i := 0
|
||||
for i < len(d.buf) && d.buf[i] != 0 {
|
||||
i++
|
||||
}
|
||||
return string(utf16.Decode(d.buf[:i]))
|
||||
}
|
||||
|
||||
func (d filedlg) parseMultipleFilenames() []string {
|
||||
var files []string
|
||||
i := 0
|
||||
|
||||
// Find first null terminator (directory path)
|
||||
for i < len(d.buf) && d.buf[i] != 0 {
|
||||
i++
|
||||
}
|
||||
|
||||
if i >= len(d.buf) {
|
||||
return files
|
||||
}
|
||||
|
||||
// Get directory path
|
||||
dirPath := string(utf16.Decode(d.buf[:i]))
|
||||
i++ // Skip null terminator
|
||||
|
||||
// Check if there are more files (multiple selection)
|
||||
if i < len(d.buf) && d.buf[i] != 0 {
|
||||
// Multiple files selected - parse filenames
|
||||
for i < len(d.buf) {
|
||||
start := i
|
||||
// Find next null terminator
|
||||
for i < len(d.buf) && d.buf[i] != 0 {
|
||||
i++
|
||||
}
|
||||
if i >= len(d.buf) {
|
||||
break
|
||||
}
|
||||
|
||||
if start < i {
|
||||
filename := string(utf16.Decode(d.buf[start:i]))
|
||||
if dirPath != "" {
|
||||
files = append(files, dirPath+"\\"+filename)
|
||||
} else {
|
||||
files = append(files, filename)
|
||||
}
|
||||
}
|
||||
i++ // Skip null terminator
|
||||
if i >= len(d.buf) || d.buf[i] == 0 {
|
||||
break // End of list
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single file selected
|
||||
files = append(files, dirPath)
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
func (b *FileBuilder) load() (string, error) {
|
||||
d := openfile(w32.OFN_FILEMUSTEXIST|w32.OFN_NOCHANGEDIR, b)
|
||||
if w32.GetOpenFileName(d.opf) {
|
||||
return d.Filename(), nil
|
||||
}
|
||||
return "", err()
|
||||
}
|
||||
|
||||
func (b *FileBuilder) loadMultiple() ([]string, error) {
|
||||
d := openfile(w32.OFN_FILEMUSTEXIST|w32.OFN_NOCHANGEDIR|w32.OFN_ALLOWMULTISELECT|w32.OFN_EXPLORER, b)
|
||||
d.buf = make([]uint16, multiFileBufferSize)
|
||||
d.opf.File = utf16ptr(d.buf)
|
||||
d.opf.MaxFile = uint32(len(d.buf))
|
||||
|
||||
if w32.GetOpenFileName(d.opf) {
|
||||
return d.parseMultipleFilenames(), nil
|
||||
}
|
||||
return nil, err()
|
||||
}
|
||||
|
||||
func (b *FileBuilder) save() (string, error) {
|
||||
d := openfile(w32.OFN_OVERWRITEPROMPT|w32.OFN_NOCHANGEDIR, b)
|
||||
if w32.GetSaveFileName(d.opf) {
|
||||
return d.Filename(), nil
|
||||
}
|
||||
return "", err()
|
||||
}
|
||||
|
||||
/* syscall.UTF16PtrFromString not sufficient because we need to encode embedded NUL bytes */
|
||||
func utf16ptr(utf16 []uint16) *uint16 {
|
||||
if utf16[len(utf16)-1] != 0 {
|
||||
panic("refusing to make ptr to non-NUL terminated utf16 slice")
|
||||
}
|
||||
h := (*reflect.SliceHeader)(unsafe.Pointer(&utf16))
|
||||
return (*uint16)(unsafe.Pointer(h.Data))
|
||||
}
|
||||
|
||||
func utf16slice(ptr *uint16) []uint16 { //nolint:unused
|
||||
hdr := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(ptr)), Len: 1, Cap: 1}
|
||||
slice := *((*[]uint16)(unsafe.Pointer(&hdr))) //nolint:govet
|
||||
i := 0
|
||||
for slice[len(slice)-1] != 0 {
|
||||
i++
|
||||
}
|
||||
hdr.Len = i
|
||||
slice = *((*[]uint16)(unsafe.Pointer(&hdr))) //nolint:govet
|
||||
return slice
|
||||
}
|
||||
|
||||
func openfile(flags uint32, b *FileBuilder) (d filedlg) {
|
||||
d.buf = make([]uint16, w32.MAX_PATH)
|
||||
if b.StartFile != "" {
|
||||
initialName, _ := syscall.UTF16FromString(b.StartFile)
|
||||
for i := 0; i < len(initialName) && i < w32.MAX_PATH; i++ {
|
||||
d.buf[i] = initialName[i]
|
||||
}
|
||||
}
|
||||
d.opf = &w32.OPENFILENAME{
|
||||
File: utf16ptr(d.buf),
|
||||
MaxFile: uint32(len(d.buf)),
|
||||
Flags: flags,
|
||||
}
|
||||
d.opf.StructSize = uint32(unsafe.Sizeof(*d.opf))
|
||||
if b.StartDir != "" {
|
||||
d.opf.InitialDir, _ = syscall.UTF16PtrFromString(b.StartDir)
|
||||
}
|
||||
if b.Dlg.Title != "" {
|
||||
d.opf.Title, _ = syscall.UTF16PtrFromString(b.Dlg.Title)
|
||||
}
|
||||
for _, filt := range b.Filters {
|
||||
/* build utf16 string of form "Music File\0*.mp3;*.ogg;*.wav;\0" */
|
||||
d.filters = append(d.filters, utf16.Encode([]rune(filt.Desc))...)
|
||||
d.filters = append(d.filters, 0)
|
||||
for _, ext := range filt.Extensions {
|
||||
s := fmt.Sprintf("*.%s;", ext)
|
||||
d.filters = append(d.filters, utf16.Encode([]rune(s))...)
|
||||
}
|
||||
d.filters = append(d.filters, 0)
|
||||
}
|
||||
if d.filters != nil {
|
||||
d.filters = append(d.filters, 0, 0) // two extra NUL chars to terminate the list
|
||||
d.opf.Filter = utf16ptr(d.filters)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
type dirdlg struct {
|
||||
bi *w32.BROWSEINFO
|
||||
}
|
||||
|
||||
const (
|
||||
bffm_INITIALIZED = 1
|
||||
bffm_SELCHANGED = 2
|
||||
bffm_VALIDATEFAILEDA = 3
|
||||
bffm_VALIDATEFAILEDW = 4
|
||||
bffm_SETSTATUSTEXTA = (w32.WM_USER + 100)
|
||||
bffm_SETSTATUSTEXTW = (w32.WM_USER + 104)
|
||||
bffm_ENABLEOK = (w32.WM_USER + 101)
|
||||
bffm_SETSELECTIONA = (w32.WM_USER + 102)
|
||||
bffm_SETSELECTIONW = (w32.WM_USER + 103)
|
||||
bffm_SETOKTEXT = (w32.WM_USER + 105)
|
||||
bffm_SETEXPANDED = (w32.WM_USER + 106)
|
||||
bffm_SETSTATUSTEXT = bffm_SETSTATUSTEXTW
|
||||
bffm_SETSELECTION = bffm_SETSELECTIONW
|
||||
bffm_VALIDATEFAILED = bffm_VALIDATEFAILEDW
|
||||
)
|
||||
|
||||
func callbackDefaultDir(hwnd w32.HWND, msg uint, lParam, lpData uintptr) int {
|
||||
if msg == bffm_INITIALIZED {
|
||||
_ = w32.SendMessage(hwnd, bffm_SETSELECTION, w32.TRUE, lpData)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func selectdir(b *DirectoryBuilder) (d dirdlg) {
|
||||
d.bi = &w32.BROWSEINFO{Flags: w32.BIF_RETURNONLYFSDIRS | w32.BIF_NEWDIALOGSTYLE}
|
||||
if b.Dlg.Title != "" {
|
||||
d.bi.Title, _ = syscall.UTF16PtrFromString(b.Dlg.Title)
|
||||
}
|
||||
if b.StartDir != "" {
|
||||
s16, _ := syscall.UTF16PtrFromString(b.StartDir)
|
||||
d.bi.LParam = uintptr(unsafe.Pointer(s16))
|
||||
d.bi.CallbackFunc = syscall.NewCallback(callbackDefaultDir)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (b *DirectoryBuilder) browse() (string, error) {
|
||||
d := selectdir(b)
|
||||
res := w32.SHBrowseForFolder(d.bi)
|
||||
if res == 0 {
|
||||
return "", ErrCancelled
|
||||
}
|
||||
return w32.SHGetPathFromIDList(res), nil
|
||||
}
|
||||
Reference in New Issue
Block a user