1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "gtk3_kde5_filepicker_ipc.hxx"
24 #include <system_error>
26 #include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
28 #include <vcl/svapp.hxx>
29 #include <vcl/sysdata.hxx>
30 #include <vcl/syswin.hxx>
33 #include <osl/process.h>
37 #include <boost/filesystem/path.hpp>
41 using namespace ::com::sun::star::ui::dialogs
;
47 OUString
applicationDirPath()
49 OUString applicationFilePath
;
50 osl_getExecutableFile(&applicationFilePath
.pData
);
51 OUString applicationSystemPath
;
52 osl_getSystemPathFromFileURL(applicationFilePath
.pData
, &applicationSystemPath
.pData
);
53 const auto utf8Path
= applicationSystemPath
.toUtf8();
54 auto ret
= boost::filesystem::path(utf8Path
.getStr(), utf8Path
.getStr() + utf8Path
.getLength());
55 ret
.remove_filename();
56 return OUString::fromUtf8(OString(ret
.c_str(), strlen(ret
.c_str())));
59 OUString
findPickerExecutable()
61 const auto path
= applicationDirPath();
62 const OUString
app("lo_kde5filepicker");
64 osl_searchFileURL(app
.pData
, path
.pData
, &ret
.pData
);
66 throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory
),
67 "could not find lo_kde5filepicker executable");
72 void readIpcArg(std::istream
& stream
, OUString
& str
)
74 const auto buffer
= readIpcStringArg(stream
);
75 str
= OUString::fromUtf8(OString(buffer
.data(), buffer
.size()));
78 void readIpcArg(std::istream
& stream
, css::uno::Sequence
<OUString
>& seq
)
80 uint32_t numFiles
= 0;
82 stream
.ignore(); // skip space;
83 seq
.realloc(numFiles
);
84 for (size_t i
= 0; i
< numFiles
; ++i
)
86 readIpcArg(stream
, seq
[i
]);
90 void sendIpcArg(std::ostream
& stream
, const OUString
& string
)
92 const auto utf8
= string
.toUtf8();
93 sendIpcStringArg(stream
, utf8
.getLength(), utf8
.getStr());
96 OUString
getResString(const char* pResId
)
98 if (pResId
== nullptr)
101 return VclResId(pResId
);
104 // handles the IPC commands for dialog execution and ends the dummy Gtk dialog once the IPC response is there
105 static void handleIpcForExecute(Gtk3KDE5FilePickerIpc
* pFilePickerIpc
, GtkWidget
* pDummyDialog
,
108 auto id
= pFilePickerIpc
->sendCommand(Commands::Execute
);
109 pFilePickerIpc
->readResponse(id
, *bResult
);
111 // end the dummy dialog
112 gtk_widget_hide(pDummyDialog
);
115 // Gtk3KDE5FilePicker
117 Gtk3KDE5FilePickerIpc::Gtk3KDE5FilePickerIpc()
119 const auto exe
= findPickerExecutable();
120 oslProcessError result
;
121 oslSecurity pSecurity
= osl_getCurrentSecurity();
122 result
= osl_executeProcess_WithRedirectedIO(exe
.pData
, nullptr, 0, osl_Process_NORMAL
,
123 pSecurity
, nullptr, nullptr, 0, &m_process
,
124 &m_inputWrite
, &m_outputRead
, nullptr);
125 osl_freeSecurityHandle(pSecurity
);
126 if (result
!= osl_Process_E_None
)
127 throw std::system_error(std::make_error_code(std::errc::no_such_process
),
128 "could not start lo_kde5filepicker executable");
131 Gtk3KDE5FilePickerIpc::~Gtk3KDE5FilePickerIpc()
136 sendCommand(Commands::Quit
);
137 osl_joinProcess(m_process
);
140 osl_closeFile(m_inputWrite
);
142 osl_closeFile(m_outputRead
);
143 osl_freeProcessHandle(m_process
);
146 sal_Int16
Gtk3KDE5FilePickerIpc::execute()
148 auto restoreMainWindow
= blockMainWindow();
150 // dummy gtk dialog that will take care of processing events,
151 // not meant to be actually seen by user
152 GtkWidget
* pDummyDialog
= gtk_dialog_new();
154 bool accepted
= false;
156 // send IPC command and read response in a separate thread
157 std::thread
aIpcHandler(&handleIpcForExecute
, this, pDummyDialog
, &accepted
);
159 // make dummy dialog not to be seen by user
160 gtk_window_set_decorated(GTK_WINDOW(pDummyDialog
), false);
161 gtk_window_set_default_size(GTK_WINDOW(pDummyDialog
), 0, 0);
162 gtk_window_set_accept_focus(GTK_WINDOW(pDummyDialog
), false);
163 // gtk_widget_set_opacity() only has the desired effect when widget is already shown
164 gtk_widget_show(pDummyDialog
);
165 gtk_widget_set_opacity(pDummyDialog
, 0);
166 // run dialog, leaving event processing to GTK
167 // dialog will be closed by the separate 'aIpcHandler' thread once the IPC response is there
168 gtk_dialog_run(GTK_DIALOG(pDummyDialog
));
172 gtk_widget_destroy(pDummyDialog
);
174 if (restoreMainWindow
)
177 return accepted
? ExecutableDialogResults::OK
: ExecutableDialogResults::CANCEL
;
180 static gboolean
ignoreDeleteEvent(GtkWidget
* /*widget*/, GdkEvent
* /*event*/,
181 gpointer
/*user_data*/)
186 std::function
<void()> Gtk3KDE5FilePickerIpc::blockMainWindow()
188 vcl::Window
* pParentWin
= Application::GetDefDialogParent();
192 const SystemEnvData
* pSysData
= static_cast<SystemWindow
*>(pParentWin
)->GetSystemData();
196 sendCommand(Commands::SetWinId
, pSysData
->aWindow
);
198 auto* pMainWindow
= static_cast<GtkWidget
*>(pSysData
->pWidget
);
202 SolarMutexGuard guard
;
203 auto deleteEventSignalId
= g_signal_lookup("delete_event", gtk_widget_get_type());
205 // disable the mainwindow
206 gtk_widget_set_sensitive(pMainWindow
, false);
208 // block the GtkSalFrame delete_event handler
209 auto blockedHandler
= g_signal_handler_find(
210 pMainWindow
, static_cast<GSignalMatchType
>(G_SIGNAL_MATCH_ID
| G_SIGNAL_MATCH_DATA
),
211 deleteEventSignalId
, 0, nullptr, nullptr, pSysData
->pSalFrame
);
212 g_signal_handler_block(pMainWindow
, blockedHandler
);
214 // prevent the window from being closed
215 auto ignoreDeleteEventHandler
216 = g_signal_connect(pMainWindow
, "delete_event", G_CALLBACK(ignoreDeleteEvent
), nullptr);
218 return [pMainWindow
, ignoreDeleteEventHandler
, blockedHandler
] {
219 SolarMutexGuard cleanupGuard
;
221 gtk_widget_set_sensitive(pMainWindow
, true);
223 // allow it to be closed again
224 g_signal_handler_disconnect(pMainWindow
, ignoreDeleteEventHandler
);
226 // unblock the GtkSalFrame handler
227 g_signal_handler_unblock(pMainWindow
, blockedHandler
);
231 void Gtk3KDE5FilePickerIpc::writeResponseLine(const std::string
& line
)
233 sal_uInt64 bytesWritten
= 0;
234 osl_writeFile(m_inputWrite
, line
.c_str(), line
.size(), &bytesWritten
);
237 std::string
Gtk3KDE5FilePickerIpc::readResponseLine()
239 if (!m_responseBuffer
.empty()) // check whether we have a line in our buffer
241 std::size_t it
= m_responseBuffer
.find('\n');
242 if (it
!= std::string::npos
)
244 auto ret
= m_responseBuffer
.substr(0, it
);
245 m_responseBuffer
.erase(0, it
+ 1);
250 const sal_uInt64 BUF_SIZE
= 1024;
251 char buffer
[BUF_SIZE
];
254 sal_uInt64 bytesRead
= 0;
255 auto err
= osl_readFile(m_outputRead
, buffer
, BUF_SIZE
, &bytesRead
);
256 auto it
= std::find(buffer
, buffer
+ bytesRead
, '\n');
257 if (it
!= buffer
+ bytesRead
) // check whether the chunk we read contains an EOL
259 // if so, append that part to the buffer and return it
260 std::string ret
= m_responseBuffer
.append(buffer
, it
);
261 // but keep anything else we may have read in our buffer
263 m_responseBuffer
.assign(it
, buffer
+ bytesRead
);
266 // otherwise append everything we read to the buffer and try again
267 m_responseBuffer
.append(buffer
, bytesRead
);
269 if (err
!= osl_File_E_None
&& err
!= osl_File_E_AGAIN
)
275 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */