2828#include " threads/waitforcardthread.hpp"
2929
3030#include " utils/utils.hpp"
31+
32+ #include " application.hpp"
3133#include " inputoutputmode.hpp"
3234#include " writeresponse.hpp"
3335
34- #include < QApplication>
35-
3636using namespace pcsc_cpp ;
3737using namespace electronic_id ;
3838
@@ -44,19 +44,11 @@ const QString RESP_USER_CANCEL = QStringLiteral("ERR_WEBEID_USER_CANCELLED");
4444
4545QVariantMap makeErrorObject (const QString& errorCode, const QString& errorMessage)
4646{
47- const auto errorBody = QVariantMap {
48- {QStringLiteral (" code" ), errorCode},
49- {QStringLiteral (" message" ), errorMessage},
50- };
51- return {{QStringLiteral (" error" ), errorBody}};
52- }
53-
54- void interruptThread (QThread* thread)
55- {
56- qDebug () << " Interrupting thread" << uintptr_t (thread);
57- thread->disconnect ();
58- thread->requestInterruption ();
59- ControllerChildThread::waitForControllerNotify.wakeAll ();
47+ return {{QStringLiteral (" error" ),
48+ QVariantMap {
49+ {QStringLiteral (" code" ), errorCode},
50+ {QStringLiteral (" message" ), errorMessage},
51+ }}};
6052}
6153
6254} // namespace
@@ -67,17 +59,19 @@ void Controller::run()
6759 const bool isInCommandLineMode = bool (command);
6860 isInStdinMode = !isInCommandLineMode;
6961
70- qInfo () << qApp->applicationName () << " app" << qApp->applicationVersion () << " running in"
62+ qInfo () << QCoreApplication::applicationName () << " app"
63+ << QCoreApplication::applicationVersion () << " running in"
7164 << (isInStdinMode ? " stdin/stdout" : " command-line" ) << " mode" ;
7265
7366 try {
7467 // TODO: cut out stdin mode separate class to avoid bugs in safari mode
7568 if (isInStdinMode) {
7669 // In stdin/stdout mode we first output the version as required by the WebExtension
7770 // and then wait for the actual command.
78- writeResponseToStdOut (isInStdinMode,
79- {{QStringLiteral (" version" ), qApp->applicationVersion ()}},
80- " get-version" );
71+ writeResponseToStdOut (
72+ isInStdinMode,
73+ {{QStringLiteral (" version" ), QCoreApplication::applicationVersion ()}},
74+ " get-version" );
8175
8276 command = readCommandFromStdin ();
8377 }
@@ -111,56 +105,33 @@ void Controller::startCommandExecution()
111105 REQUIRE_NON_NULL (commandHandler)
112106
113107 // Reader monitor thread setup.
114- WaitForCardThread* waitForCardThread = new WaitForCardThread (this );
108+ auto * waitForCardThread = new WaitForCardThread (this );
109+ connect (this , &Controller::stopCardEventMonitorThread, waitForCardThread,
110+ &WaitForCardThread::requestInterruption);
111+ connect (waitForCardThread, &ControllerChildThread::failure, this ,
112+ &Controller::onCriticalFailure);
115113 connect (waitForCardThread, &WaitForCardThread::statusUpdate, this , &Controller::statusUpdate);
116114 connect (waitForCardThread, &WaitForCardThread::cardsAvailable, this ,
117115 &Controller::onCardsAvailable);
118- saveChildThreadPtrAndConnectFailureFinish (waitForCardThread);
119116
120117 // UI setup.
121- window = WebEidUI::createAndShowDialog (commandHandler->commandType ());
122- connect (this , &Controller::statusUpdate, window, &WebEidUI::onSmartCardStatusUpdate);
123- connectOkCancelWaitingForPinPad ();
118+ createWindow ();
124119
125120 // Finally, start the thread to wait for card insertion after everything is wired up.
126121 waitForCardThread->start ();
127122}
128123
129- void Controller::saveChildThreadPtrAndConnectFailureFinish (ControllerChildThread* childThread)
130- {
131- REQUIRE_NON_NULL (childThread)
132- // Save the thread pointer in child thread tracking map to request interruption and wait for
133- // it to quit in waitForChildThreads().
134- childThreads[uintptr_t (childThread)] = childThread;
135-
136- connect (childThread, &ControllerChildThread::failure, this , &Controller::onCriticalFailure);
137-
138- // When the thread is finished, remove the pointer from the tracking map and call deleteLater()
139- // on it to free the thread object. Although the thread objects are freed through the Qt object
140- // tree ownership system anyway, it is better to delete them immediately when they finish.
141- connect (childThread, &ControllerChildThread::finished, this , [this , childThread]() {
142- QScopedPointer<ControllerChildThread, QScopedPointerDeleteLater> deleteLater {childThread};
143-
144- const auto threadPtrAddress = uintptr_t (childThread);
145- if (childThreads.count (threadPtrAddress) && childThreads[threadPtrAddress]) {
146- childThreads[threadPtrAddress] = nullptr ;
147- childThread->wait ();
148- qDebug () << " Thread" << threadPtrAddress << " finished" ;
149- } else {
150- qWarning () << " Controller child thread" << childThread
151- << " is missing or null in finish slot" ;
152- }
153- });
154- }
155-
156- void Controller::connectOkCancelWaitingForPinPad ()
124+ void Controller::createWindow ()
157125{
158- REQUIRE_NON_NULL (window)
159-
126+ window = WebEidUI::createAndShowDialog (commandHandler->commandType ());
127+ connect (this , &Controller::statusUpdate, window, &WebEidUI::onSmartCardStatusUpdate);
128+ connect (this , &Controller::retry, window, &WebEidUI::onRetry);
129+ connect (window, &WebEidUI::retry, this , &Controller::onRetry);
160130 connect (window, &WebEidUI::accepted, this , &Controller::onDialogOK);
161131 connect (window, &WebEidUI::rejected, this , &Controller::onDialogCancel);
162132 connect (window, &WebEidUI::failure, this , &Controller::onCriticalFailure);
163133 connect (window, &WebEidUI::waitingForPinPad, this , &Controller::onConfirmCommandHandler);
134+ connect (window, &WebEidUI::destroyed, this , [this ] { window = nullptr ; });
164135}
165136
166137void Controller::onCardsAvailable (
@@ -192,9 +163,8 @@ void Controller::onCardsAvailable(
192163void Controller::runCommandHandler (const std::vector<ElectronicID::ptr>& availableEids)
193164{
194165 try {
195- CommandHandlerRunThread * commandHandlerRunThread =
166+ auto * commandHandlerRunThread =
196167 new CommandHandlerRunThread (this , *commandHandler, availableEids);
197- saveChildThreadPtrAndConnectFailureFinish (commandHandlerRunThread);
198168 connectRetry (commandHandlerRunThread);
199169
200170 // When the command handler run thread retrieves certificates successfully, call
@@ -213,51 +183,34 @@ void Controller::runCommandHandler(const std::vector<ElectronicID::ptr>& availab
213183
214184void Controller::onCertificatesLoaded ()
215185{
216- CardEventMonitorThread* cardEventMonitorThread =
217- new CardEventMonitorThread (this , std::string (commandType ()));
218- saveChildThreadPtrAndConnectFailureFinish (cardEventMonitorThread);
219- cardEventMonitorThreadKey = uintptr_t (cardEventMonitorThread);
186+ auto * cardEventMonitorThread = new CardEventMonitorThread (this , commandType ());
187+ connect (this , &Controller::stopCardEventMonitorThread, cardEventMonitorThread,
188+ &CardEventMonitorThread::requestInterruption);
189+ connect (cardEventMonitorThread, &ControllerChildThread::failure, this ,
190+ &Controller::onCriticalFailure);
220191 connect (cardEventMonitorThread, &CardEventMonitorThread::cardEvent, this , &Controller::onRetry);
221192 cardEventMonitorThread->start ();
222193}
223194
224- void Controller::stopCardEventMonitorThread ()
225- {
226- if (cardEventMonitorThreadKey) {
227- try {
228- auto cardEventMonitorThread = childThreads.at (cardEventMonitorThreadKey);
229- cardEventMonitorThreadKey = 0 ;
230- if (cardEventMonitorThread) {
231- interruptThread (cardEventMonitorThread);
232- }
233- } catch (const std::out_of_range&) {
234- qWarning () << " Card event monitor thread" << cardEventMonitorThreadKey
235- << " is missing from childThreads map in stopCardEventMonitorThread()" ;
236- cardEventMonitorThreadKey = 0 ;
237- }
238- }
239- }
240-
241195void Controller::disposeUI ()
242196{
243197 if (window) {
244198 window->disconnect ();
245- // As the Qt::WA_DeleteOnClose flag is set, the dialog is deleted automatically.
246- window->close ();
199+ window-> forceClose ();
200+ window->deleteLater ();
247201 window = nullptr ;
248202 }
249203}
250204
251205void Controller::onConfirmCommandHandler (const EidCertificateAndPinInfo& certAndPinInfo)
252206{
253- stopCardEventMonitorThread ();
207+ emit stopCardEventMonitorThread ();
254208
255209 try {
256- CommandHandlerConfirmThread * commandHandlerConfirmThread =
210+ auto * commandHandlerConfirmThread =
257211 new CommandHandlerConfirmThread (this , *commandHandler, window, certAndPinInfo);
258212 connect (commandHandlerConfirmThread, &CommandHandlerConfirmThread::completed, this ,
259213 &Controller::onCommandHandlerConfirmCompleted);
260- saveChildThreadPtrAndConnectFailureFinish (commandHandlerConfirmThread);
261214 connectRetry (commandHandlerConfirmThread);
262215
263216 commandHandlerConfirmThread->start ();
@@ -308,14 +261,10 @@ void Controller::onRetry()
308261void Controller::connectRetry (const ControllerChildThread* childThread)
309262{
310263 REQUIRE_NON_NULL (childThread)
311- REQUIRE_NON_NULL (window)
312-
313- disconnect (window, &WebEidUI::retry, nullptr , nullptr );
314-
315- connect (childThread, &ControllerChildThread::retry, window, &WebEidUI::onRetry);
264+ connect (childThread, &ControllerChildThread::failure, this , &Controller::onCriticalFailure);
265+ connect (childThread, &ControllerChildThread::retry, this , &Controller::retry);
316266 // This connection handles cancel events from PIN pad.
317- connect (childThread, &ControllerChildThread::cancel, this , &Controller::onPinPadCancel);
318- connect (window, &WebEidUI::retry, this , &Controller::onRetry);
267+ connect (childThread, &ControllerChildThread::cancel, this , &Controller::onDialogCancel);
319268}
320269
321270void Controller::onDialogOK (const EidCertificateAndPinInfo& certAndPinInfo)
@@ -324,68 +273,54 @@ void Controller::onDialogOK(const EidCertificateAndPinInfo& certAndPinInfo)
324273 onConfirmCommandHandler (certAndPinInfo);
325274 } else {
326275 // This should not happen, and when it does, OK should be equivalent to cancel.
327- onPinPadCancel ();
276+ onDialogCancel ();
328277 }
329278}
330279
331280void Controller::onDialogCancel ()
332281{
333- REQUIRE_NON_NULL (window)
334-
335282 qDebug () << " User cancelled" ;
336-
337- // Schedule application exit when the UI dialog is destroyed.
338- connect (window, &WebEidUI::destroyed, this , &Controller::exit);
339-
340283 _result = makeErrorObject (RESP_USER_CANCEL, QStringLiteral (" User cancelled" ));
341284 writeResponseToStdOut (isInStdinMode, _result, commandType ());
342- }
343-
344- void Controller::onPinPadCancel ()
345- {
346- REQUIRE_NON_NULL (window)
347-
348- onDialogCancel ();
349- window->quit ();
285+ exit ();
350286}
351287
352288void Controller::onCriticalFailure (const QString& error)
353289{
290+ emit stopCardEventMonitorThread ();
354291 qCritical () << " Exiting due to command" << std::string (commandType ())
355292 << " fatal error:" << error;
356293 _result =
357294 makeErrorObject (RESP_TECH_ERROR, QStringLiteral (" Technical error, see application logs" ));
358- writeResponseToStdOut (isInStdinMode, _result, commandType ());
359295 disposeUI ();
296+ if (qApp->isSafariExtensionContainingApp ()) {
297+ writeResponseToStdOut (isInStdinMode, _result, commandType ());
298+ }
360299 WebEidUI::showFatalError ();
300+ if (!qApp->isSafariExtensionContainingApp ()) {
301+ // Write the error response to stdout after showing the fatal error dialog. Chrome will
302+ // close the application immediately after this, so the UI dialog may not be visible to the
303+ // user.
304+ writeResponseToStdOut (isInStdinMode, _result, commandType ());
305+ }
361306 exit ();
362307}
363308
364309void Controller::exit ()
365310{
366- if (window) {
367- window->disconnect ();
368- window = nullptr ;
369- }
311+ disposeUI ();
370312 waitForChildThreads ();
371313 emit quit ();
372314}
373315
374316void Controller::waitForChildThreads ()
375317{
376- // Waiting for child threads must not happen in destructor.
377- // See https://tombarta.wordpress.com/2008/07/10/gcc-pure-virtual-method-called/ for details.
378- for (const auto & childThread : childThreads) {
379- auto thread = childThread.second ;
380- if (thread) {
381- interruptThread (thread);
382- // Waiting for PIN input on PIN pad may take a long time, call processEvents() so that
383- // the UI doesn't freeze.
384- while (thread->isRunning ()) {
385- thread->wait (100 ); // in milliseconds
386- QCoreApplication::processEvents ();
387- }
388- }
318+ for (auto * thread : findChildren<QThread*>()) {
319+ qDebug () << " Interrupting thread" << uintptr_t (thread);
320+ thread->disconnect ();
321+ thread->requestInterruption ();
322+ ControllerChildThread::waitForControllerNotify.wakeAll ();
323+ thread->wait ();
389324 }
390325}
391326
0 commit comments