-
-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathfile_dialog.lua
236 lines (186 loc) · 5.88 KB
/
file_dialog.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
-- File Selection system
-- Part of Live Simulator: 2
-- See copyright notice in main.lua
local love = require("love")
local Luaoop = require("libs.Luaoop")
local log = require("logging")
local fileDialog = {}
local hasShell = os.execute() == 1
if not(hasShell) then
log.warn("fileDiag", "possible file dialog backend detection fail")
end
if love._os == "Windows" and package.preload.ffi then
-- Use native OpenFileName for Windows with FFI
local ffi = require("ffi")
local Comdlg32 = ffi.load("Comdlg32")
ffi.cdef [[
int MultiByteToWideChar(unsigned int codepage, unsigned long flags, const char* str, int strlen, wchar_t* wstr, int wstrlen);
int WideCharToMultiByte(unsigned int codepage, unsigned long flags, const wchar_t* wstr, int wstrlen, char* str, int strlen, char* defchr, int* udefchr);
typedef struct {
unsigned long lStructSize;
void* hwndOwner;
void* hInstance;
const wchar_t* lpstrFilter;
wchar_t* lpstrCustomFilter;
unsigned long nMaxCustFilter;
unsigned long nFilterIndex;
wchar_t* lpstrFile;
unsigned long nMaxFile;
wchar_t* lpstrFileTitle;
unsigned long nMaxFileTitle;
const wchar_t* lpstrInitialDir;
const wchar_t* lpstrTitle;
unsigned long flags;
unsigned short nFileOffset;
unsigned short nFileExtension;
const wchar_t* lpstrDefExt;
unsigned long lCustData;
void* lpfnHook;
const wchar_t* lpTemplateName;
void* pvReserved;
unsigned long dwReserved;
unsigned long flagsEx;
} OPENFILENAMEW;
int GetOpenFileNameW(OPENFILENAMEW *lpofn);
]]
-- Returned length excludes null-terminated string
local function UTF8ToUTF16(utf8)
local ptr = ffi.cast("const char*", utf8)
local len = ffi.C.MultiByteToWideChar(65001, 0, ptr, #utf8, nil, 0)
local utf16 = ffi.new("wchar_t[?]", len)
assert(ffi.C.MultiByteToWideChar(65001, 0, ptr, #utf8, utf16, len) > 0, "Conversion failed")
return utf16, len
end
-- Returned is Lua string
local function UTF16ToUTF8(utf16, len)
len = len or -1
local mblen = ffi.C.WideCharToMultiByte(65001, 0, utf16, len, nil, 0, nil, nil)
local mb = ffi.new("char[?]", mblen)
assert(ffi.C.WideCharToMultiByte(65001, 0, utf16, len, mb, mblen, nil, nil) > 0, "Conversion failed")
return ffi.string(mb, mblen)
end
local allfiles
function fileDialog.open(title, directory, filter, multiple)
local ofnptr = ffi.new("OPENFILENAMEW[1]")
local ofn = ofnptr[0]
local null = ffi.cast("void*", 0)
if not(allfiles) then
allfiles = UTF8ToUTF16("All Files\0*.*\0\0")
end
ofn.lStructSize = ffi.sizeof("OPENFILENAMEW")
ofn.hwndOwner = null
ofn.lpstrFile = ffi.new("wchar_t[32768]")
ofn.nMaxFile = 32767
ofn.nFilterIndex = 1
if filter then
ofn.lpstrFilter = UTF8ToUTF16("Specific Files ("..filter..")\0"..filter:gsub(" ", ";").."\0\0")
else
ofn.lpstrFilter = allfiles
end
if title then
ofn.lpstrTitle = UTF8ToUTF16(title.."\0") -- Lua string len doesn't add null terminator
end
ofn.lpstrFileTitle = nil
ofn.nMaxFileTitle = 0
if directory then
ofn.lpstrInitialDir = UTF8ToUTF16(directory:gsub("/", "\\").."\0")
end
ofn.flags = 0x02081804 + (multiple and 0x00000200 or 0)
if Comdlg32.GetOpenFileNameW(ofnptr) > 0 then
if multiple then
local list = {}
local dir = UTF16ToUTF8(ofn.lpstrFile):sub(1, -2):gsub("\\", "/")
local ptr = ofn.lpstrFile + #dir + 1
if dir:sub(-1) == "/" then
dir = dir:sub(1, -2)
end
while ptr[0] ~= 0 do
local name = UTF16ToUTF8(ptr)
list[#list + 1] = dir.."/"..name:sub(1, -2)
ptr = ptr + #name
end
if #list == 0 then
list[1] = dir
end
return list
else
return UTF16ToUTF8(ofn.lpstrFile)
end
end
if multiple then return {} end
return nil
end
elseif love._os == "Linux" and hasShell then
if os.execute("command -v kdialog >/dev/null 2>&1") == 0 then
function fileDialog.open(title, directory, filter, multiple)
-- title and multiple is not supported unfortunately
local cmdbuild = {}
cmdbuild[#cmdbuild + 1] = "kdialog --getopenfilename"
if directory then
cmdbuild[#cmdbuild + 1] = string.format("%q", directory)
end
if filter then
cmdbuild[#cmdbuild + 1] = string.format("'%s|Specific Files (%s)'", filter, filter)
end
local cmd = assert(io.popen(table.concat(cmdbuild, " ")))
local list = cmd:read("*a")
cmd:close()
if #list > 0 then
list = list:gsub("[\r\n|\r|\n]+", "")
if multiple then
return {list}
else
return list
end
else
if multiple then
return {}
else
return nil
end
end
end
elseif os.execute("command -v zenity >/dev/null 2>&1") == 0 then
function fileDialog.open(title, directory, filter, multiple)
local cmdbuild = {}
cmdbuild[#cmdbuild + 1] = "zenity --file-selection"
if title then
cmdbuild[#cmdbuild + 1] = string.format("--title=%q", title)
end
if filter then
cmdbuild[#cmdbuild + 1] = "--file-filter='"..filter.."'"
end
if multiple then
cmdbuild[#cmdbuild + 1] = "--multiple --separator='|'"
end
if directory then
table.insert(cmdbuild, 1, string.format("(cd %q &&", directory))
cmdbuild[#cmdbuild + 1] = ")"
end
local cmd = assert(io.popen(table.concat(cmdbuild, " ")))
local list = cmd:read("*a")
cmd:close()
if #list == 0 then
if multiple then
return {}
else
return nil
end
end
if multiple then
local filelist = {}
for w in list:gmatch("[^|]+") do
filelist[#filelist + 1] = w:gsub("[\r\n|\r|\n]+", "")
end
return filelist
else
return list
end
end
end
end
-- TODO: use SAF in Android 5.0 and later
function fileDialog.isSupported()
return not(not(fileDialog.open))
end
return fileDialog