Skip to content

Commit 4366cc9

Browse files
committed
[dlgs] Add new laf-dlgs-proc exe to show the file dialog in another process (fix aseprite/aseprite#4412)
1 parent 39706c1 commit 4366cc9

File tree

6 files changed

+570
-5
lines changed

6 files changed

+570
-5
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ option(LAF_WITH_TESTS "Enable LAF tests" ON)
3333
option(LAF_WITH_CLIP "Enable clip module (required for future drag-and-drop feature)" ON)
3434
if(WIN32)
3535
option(LAF_WITH_IME "Enable IME for CJK input" OFF)
36+
option(LAF_WITH_DLGS_PROC "Enable laf-dlgs-process.exe for Windows to open the FileDialog through an external process" OFF)
37+
set(LAF_DLGS_PROC_NAME "laf-dlgs-process" CACHE STRING "Name of the process to show native dialogs")
3638
endif()
3739
set(LAF_BACKEND ${LAF_DEFAULT_BACKEND} CACHE STRING "Select laf backend")
3840
set_property(CACHE LAF_BACKEND PROPERTY STRINGS "none" "skia")

dlgs/CMakeLists.txt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
# laf-dlgs
2-
# Copyright (C) 2024 Igara Studio S.A.
2+
# Copyright (C) 2024-2025 Igara Studio S.A.
33

44
add_library(laf-dlgs file_dialog.cpp)
55
target_link_libraries(laf-dlgs laf-base)
66

77
if(WIN32)
8-
target_sources(laf-dlgs PRIVATE file_dialog_win.cpp)
8+
target_sources(laf-dlgs PRIVATE win/file_dialog_win.cpp)
99
elseif(APPLE)
1010
target_sources(laf-dlgs PRIVATE file_dialog_osx.mm)
1111
else()
1212
target_sources(laf-dlgs PRIVATE file_dialog_x11.cpp)
1313
endif()
14+
15+
if(LAF_WITH_DLGS_PROC)
16+
add_executable(laf-dlgs-proc win/dlgs_process.cpp)
17+
target_link_libraries(laf-dlgs-proc laf-base laf-dlgs)
18+
set_target_properties(laf-dlgs-proc PROPERTIES
19+
OUTPUT_NAME ${LAF_DLGS_PROC_NAME})
20+
21+
target_compile_definitions(laf-dlgs PRIVATE
22+
-DLAF_DLGS_PROC_NAME="${LAF_DLGS_PROC_NAME}")
23+
target_sources(laf-dlgs PRIVATE win/file_dialog_win_safe.cpp)
24+
endif()

dlgs/file_dialog.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ namespace dlgs {
2020
class FileDialog;
2121
using FileDialogRef = base::Ref<FileDialog>;
2222

23+
#if LAF_WINDOWS
24+
class FileDialogDelegate {
25+
public:
26+
virtual ~FileDialogDelegate() {}
27+
virtual void onFolderChange(const std::string& path) = 0;
28+
};
29+
#endif // LAF_WINDOWS
30+
2331
class FileDialog : public base::RefCount {
2432
public:
2533
enum class Type {
@@ -36,7 +44,10 @@ class FileDialog : public base::RefCount {
3644
};
3745

3846
struct Spec {
39-
#if LAF_MACOS
47+
#if LAF_WINDOWS
48+
// Listen events of the FileDialog.
49+
FileDialogDelegate* delegate = nullptr;
50+
#elif LAF_MACOS
4051
// Indicates which is the "Edit" menu (NSMenuItem*) with
4152
// Undo/Redo/Cut/Copy/Paste/etc. commands. Used by the
4253
// FileDialogOSX impl to completely replace the "Edit" menu with a
@@ -58,6 +69,10 @@ class FileDialog : public base::RefCount {
5869
static FileDialogRef make(const Spec& spec);
5970
#if LAF_WINDOWS
6071
static FileDialogRef makeWin(const Spec& spec);
72+
static FileDialogRef makeWinUnsafe(const Spec& spec);
73+
#ifdef LAF_DLGS_PROC_NAME
74+
static FileDialogRef makeWinSafe(const Spec& spec);
75+
#endif
6176
#elif LAF_MACOS
6277
static FileDialogRef makeOSX(const Spec& spec);
6378
#elif LAF_LINUX

dlgs/win/dlgs_process.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// laf-dlgs
2+
// Copyright (C) 2024-2025 Igara Studio S.A.
3+
//
4+
// This file is released under the terms of the MIT license.
5+
// Read LICENSE.txt for more information.
6+
//
7+
// Based in the work done in https://github.com/dacap/safedlgs/
8+
// prototype.
9+
//
10+
11+
#ifdef HAVE_CONFIG_H
12+
#include "config.h"
13+
#endif
14+
15+
#ifdef LAF_DLGS_PROC_NAME
16+
#error LAF_DLGS_PROC_NAME must not be defined for laf-dlgs-proc
17+
#endif
18+
19+
#include "base/program_options.h"
20+
#include "base/split_string.h"
21+
#include "base/string.h"
22+
#include "base/win/coinit.h"
23+
#include "dlgs/file_dialog.h"
24+
25+
#include <werapi.h>
26+
#include <windows.h>
27+
28+
#include <cstdio>
29+
30+
class Delegate : public dlgs::FileDialogDelegate {
31+
public:
32+
void onFolderChange(const std::string& path) override
33+
{
34+
// Print folder name in stdout.
35+
std::printf("%s\\\n", path.c_str());
36+
std::fflush(stdout);
37+
}
38+
};
39+
40+
int wmain(int argc, wchar_t** wargv)
41+
{
42+
base::ProgramOptions po;
43+
auto& parent(po.add("parent").requiresValue("<parent>"));
44+
auto& type(po.add("type").requiresValue("<open|openfiles|openfolder|save>"));
45+
auto& title(po.add("title").requiresValue("<title>"));
46+
auto& filename(po.add("filename").requiresValue("<filename>"));
47+
auto& defaultExtension(po.add("defaultextension").requiresValue("<extension>"));
48+
auto& addFilter(po.add("addfilter").requiresValue("<extension>;<description>"));
49+
50+
// Convert args to utf8 to parse them.
51+
{
52+
std::vector<std::string> argv_str(argc);
53+
for (int i = 0; i < argc; ++i)
54+
argv_str[i] = base::to_utf8(wargv[i]);
55+
56+
std::vector<const char*> argv(argc);
57+
for (int i = 0; i < argc; ++i)
58+
argv[i] = argv_str[i].data();
59+
60+
po.parse(argc, argv.data());
61+
}
62+
63+
// Initialize COM library.
64+
base::CoInit com;
65+
66+
// Avoid showing the "dlgs_process.exe has stopped working" dialog
67+
// when this crashes.
68+
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
69+
WerSetFlags(WER_FAULT_REPORTING_NO_UI);
70+
71+
Delegate delegate;
72+
dlgs::FileDialog::Spec spec;
73+
spec.delegate = &delegate;
74+
75+
dlgs::FileDialogRef dlg = dlgs::FileDialog::makeWinUnsafe(spec);
76+
77+
// Get the parent HWND from the arguments.
78+
HWND parentHandle = nullptr;
79+
{
80+
const std::string parentString = po.value_of(parent);
81+
if (!parentString.empty())
82+
parentHandle = (HWND)std::strtoull(parentString.c_str(), nullptr, 0);
83+
}
84+
85+
bool multipleFiles = false;
86+
if (po.value_of(type) == "save")
87+
dlg->setType(dlgs::FileDialog::Type::SaveFile);
88+
else if (po.value_of(type) == "openfiles") {
89+
dlg->setType(dlgs::FileDialog::Type::OpenFiles);
90+
multipleFiles = true;
91+
}
92+
else if (po.value_of(type) == "openfolder")
93+
dlg->setType(dlgs::FileDialog::Type::OpenFolder);
94+
else
95+
dlg->setType(dlgs::FileDialog::Type::OpenFile);
96+
97+
if (po.enabled(title))
98+
dlg->setTitle(po.value_of(title));
99+
100+
if (po.enabled(filename))
101+
dlg->setFileName(po.value_of(filename));
102+
103+
if (po.enabled(defaultExtension))
104+
dlg->setDefaultExtension(po.value_of(defaultExtension));
105+
106+
for (auto v : po.values()) {
107+
if (v.option() == &addFilter) {
108+
std::vector<std::string> result;
109+
base::split_string(v.value(), result, "=");
110+
if (result.size() == 2)
111+
dlg->addFilter(result[0], result[1]);
112+
else if (result.size() == 1)
113+
dlg->addFilter(result[0], result[0]);
114+
}
115+
}
116+
117+
dlgs::FileDialog::Result result = dlg->show(parentHandle);
118+
119+
// Print a "OK" signal so the parent process knowns that native
120+
// show() impl didn't crash.
121+
std::printf("OK\n");
122+
123+
if (result == dlgs::FileDialog::Result::Error)
124+
return 1;
125+
else if (result == dlgs::FileDialog::Result::Cancel)
126+
return 0;
127+
128+
// Print file name(s)
129+
if (multipleFiles) {
130+
base::paths files;
131+
dlg->getMultipleFileNames(files);
132+
for (auto fn : files)
133+
std::printf("%s\n", fn.c_str());
134+
}
135+
else
136+
std::printf("%s\n", dlg->fileName().c_str());
137+
138+
std::fflush(stdout);
139+
return 0;
140+
}
Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// laf-dlgs
2-
// Copyright (C) 2020-2024 Igara Studio S.A.
2+
// Copyright (C) 2020-2025 Igara Studio S.A.
33
// Copyright (C) 2015-2018 David Capello
44
//
55
// This file is released under the terms of the MIT license.
@@ -26,9 +26,94 @@ namespace dlgs {
2626
// 32k is the limit for Win95/98/Me/NT4/2000/XP with ANSI version
2727
#define FILENAME_BUFSIZE (1024 * 32)
2828

29+
// IFileDialogEvents impl used to change the OnFolderChange() events
30+
// and know the latest visited folder/location. In case of crash we
31+
// can use this location.
32+
class FileDialogEvents : public IFileDialogEvents {
33+
public:
34+
FileDialogEvents(FileDialogDelegate* delegate) : m_delegate(delegate) {}
35+
36+
// IUnknown impl
37+
IFACEMETHOD(QueryInterface)(REFIID riid, void** ppv)
38+
{
39+
if (riid == __uuidof(IFileDialogEvents)) {
40+
*ppv = this;
41+
return S_OK;
42+
}
43+
return E_NOINTERFACE;
44+
}
45+
46+
IFACEMETHOD_(ULONG, AddRef)() { return InterlockedIncrement(&m_ref); }
47+
48+
IFACEMETHOD_(ULONG, Release)()
49+
{
50+
ULONG ref = InterlockedDecrement(&m_ref);
51+
if (!ref)
52+
delete this;
53+
return ref;
54+
}
55+
56+
// IFileDialogEvents impl
57+
IFACEMETHOD(OnFileOk)(IFileDialog* dlg) { return S_OK; }
58+
IFACEMETHOD(OnFolderChanging)(IFileDialog* dlg, IShellItem* psiFolder) { return S_OK; }
59+
IFACEMETHOD(OnFolderChange)(IFileDialog* dlg)
60+
{
61+
base::ComPtr<IShellItem> item;
62+
HRESULT hr = dlg->GetFolder(&item);
63+
if (FAILED(hr))
64+
return hr;
65+
66+
LPWSTR name = nullptr;
67+
hr = item->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &name);
68+
if (FAILED(hr))
69+
return hr;
70+
71+
if (m_delegate)
72+
m_delegate->onFolderChange(base::to_utf8(name));
73+
74+
CoTaskMemFree(name);
75+
return S_OK;
76+
}
77+
IFACEMETHOD(OnSelectionChange)(IFileDialog* dlg) { return S_OK; }
78+
IFACEMETHOD(OnShareViolation)(IFileDialog* dlg,
79+
IShellItem* psi,
80+
FDE_SHAREVIOLATION_RESPONSE* pResponse)
81+
{
82+
return S_OK;
83+
}
84+
IFACEMETHOD(OnTypeChange)(IFileDialog* dlg) { return S_OK; }
85+
IFACEMETHOD(OnOverwrite)(IFileDialog* dlg, IShellItem* psi, FDE_OVERWRITE_RESPONSE* pResponse)
86+
{
87+
return S_OK;
88+
}
89+
90+
private:
91+
FileDialogDelegate* m_delegate = nullptr;
92+
long m_ref = 1;
93+
};
94+
95+
// A helper to call IFileDialog::Advise/Unadvise() methods.
96+
class ScopedAdvise {
97+
public:
98+
ScopedAdvise(IFileDialog* dlg, IFileDialogEvents* events) : m_dlg(dlg)
99+
{
100+
m_dlg->Advise(events, &m_cookie);
101+
}
102+
~ScopedAdvise()
103+
{
104+
if (m_cookie)
105+
m_dlg->Unadvise(m_cookie);
106+
}
107+
108+
private:
109+
base::ComPtr<IFileDialog> m_dlg;
110+
DWORD m_cookie = 0;
111+
};
112+
113+
// FileDialog impl for Windows
29114
class FileDialogWin : public FileDialog {
30115
public:
31-
FileDialogWin(const Spec& spec) : m_filename(FILENAME_BUFSIZE), m_defFilter(0) {}
116+
FileDialogWin(const Spec& spec) : m_spec(spec), m_filename(FILENAME_BUFSIZE), m_defFilter(0) {}
32117

33118
std::string fileName() override { return base::to_utf8(&m_filename[0]); }
34119

@@ -125,6 +210,9 @@ class FileDialogWin : public FileDialog {
125210
return hr;
126211
}
127212

213+
FileDialogEvents fde(m_spec.delegate);
214+
ScopedAdvise advise(dlg.get(), &fde);
215+
128216
hr = dlg->Show((HWND)parent);
129217
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
130218
result = Result::Cancel;
@@ -332,13 +420,23 @@ class FileDialogWin : public FileDialog {
332420
return filters;
333421
}
334422

423+
Spec m_spec;
335424
int m_defFilter;
336425
std::vector<WCHAR> m_filename;
337426
base::paths m_filenames;
338427
std::wstring m_initialDir;
339428
};
340429

341430
FileDialogRef FileDialog::makeWin(const Spec& spec)
431+
{
432+
#ifdef LAF_DLGS_PROC_NAME
433+
return FileDialog::makeWinSafe(spec);
434+
#else
435+
return FileDialog::makeWinUnsafe(spec);
436+
#endif
437+
}
438+
439+
FileDialogRef FileDialog::makeWinUnsafe(const Spec& spec)
342440
{
343441
return base::make_ref<FileDialogWin>(spec);
344442
}

0 commit comments

Comments
 (0)