2828#include " threads/waitforcardthread.hpp"
2929
3030#include " utils/utils.hpp"
31+
3132#include " inputoutputmode.hpp"
3233#include " writeresponse.hpp"
3334
34- #include < QApplication >
35+ #include < QCoreApplication >
3536
3637using namespace pcsc_cpp ;
3738using namespace electronic_id ;
@@ -44,19 +45,11 @@ const QString RESP_USER_CANCEL = QStringLiteral("ERR_WEBEID_USER_CANCELLED");
4445
4546QVariantMap makeErrorObject (const QString& errorCode, const QString& errorMessage)
4647{
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 ();
48+ return {{QStringLiteral (" error" ),
49+ QVariantMap {
50+ {QStringLiteral (" code" ), errorCode},
51+ {QStringLiteral (" message" ), errorMessage},
52+ }}};
6053}
6154
6255} // namespace
@@ -67,17 +60,19 @@ void Controller::run()
6760 const bool isInCommandLineMode = bool (command);
6861 isInStdinMode = !isInCommandLineMode;
6962
70- qInfo () << qApp->applicationName () << " app" << qApp->applicationVersion () << " running in"
63+ qInfo () << QCoreApplication::applicationName () << " app"
64+ << QCoreApplication::applicationVersion () << " running in"
7165 << (isInStdinMode ? " stdin/stdout" : " command-line" ) << " mode" ;
7266
7367 try {
7468 // TODO: cut out stdin mode separate class to avoid bugs in safari mode
7569 if (isInStdinMode) {
7670 // In stdin/stdout mode we first output the version as required by the WebExtension
7771 // and then wait for the actual command.
78- writeResponseToStdOut (isInStdinMode,
79- {{QStringLiteral (" version" ), qApp->applicationVersion ()}},
80- " get-version" );
72+ writeResponseToStdOut (
73+ isInStdinMode,
74+ {{QStringLiteral (" version" ), QCoreApplication::applicationVersion ()}},
75+ " get-version" );
8176
8277 command = readCommandFromStdin ();
8378 }
@@ -111,56 +106,32 @@ void Controller::startCommandExecution()
111106 REQUIRE_NON_NULL (commandHandler)
112107
113108 // Reader monitor thread setup.
114- WaitForCardThread* waitForCardThread = new WaitForCardThread (this );
109+ auto * waitForCardThread = new WaitForCardThread (this );
110+ connect (this , &Controller::stopCardEventMonitorThread, waitForCardThread,
111+ &WaitForCardThread::requestInterruption);
112+ connect (waitForCardThread, &ControllerChildThread::failure, this ,
113+ &Controller::onCriticalFailure);
115114 connect (waitForCardThread, &WaitForCardThread::statusUpdate, this , &Controller::statusUpdate);
116115 connect (waitForCardThread, &WaitForCardThread::cardsAvailable, this ,
117116 &Controller::onCardsAvailable);
118- saveChildThreadPtrAndConnectFailureFinish (waitForCardThread);
119117
120118 // UI setup.
121- window = WebEidUI::createAndShowDialog (commandHandler->commandType ());
122- connect (this , &Controller::statusUpdate, window, &WebEidUI::onSmartCardStatusUpdate);
123- connectOkCancelWaitingForPinPad ();
119+ createWindow ();
124120
125121 // Finally, start the thread to wait for card insertion after everything is wired up.
126122 waitForCardThread->start ();
127123}
128124
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 ()
125+ void Controller::createWindow ()
157126{
158- REQUIRE_NON_NULL (window)
159-
127+ window = WebEidUI::createAndShowDialog (commandHandler->commandType ());
128+ connect (this , &Controller::statusUpdate, window, &WebEidUI::onSmartCardStatusUpdate);
129+ connect (this , &Controller::retry, window, &WebEidUI::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,10 @@ 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);
168+ connect (commandHandlerRunThread, &ControllerChildThread::failure, this ,
169+ &Controller::onCriticalFailure);
198170 connectRetry (commandHandlerRunThread);
199171
200172 // When the command handler run thread retrieves certificates successfully, call
@@ -213,51 +185,36 @@ void Controller::runCommandHandler(const std::vector<ElectronicID::ptr>& availab
213185
214186void Controller::onCertificatesLoaded ()
215187{
216- CardEventMonitorThread* cardEventMonitorThread =
217- new CardEventMonitorThread (this , std::string (commandType ()));
218- saveChildThreadPtrAndConnectFailureFinish (cardEventMonitorThread);
219- cardEventMonitorThreadKey = uintptr_t (cardEventMonitorThread);
188+ auto * cardEventMonitorThread = new CardEventMonitorThread (this , commandType ());
189+ connect (this , &Controller::stopCardEventMonitorThread, cardEventMonitorThread,
190+ &CardEventMonitorThread::requestInterruption);
191+ connect (cardEventMonitorThread, &ControllerChildThread::failure, this ,
192+ &Controller::onCriticalFailure);
220193 connect (cardEventMonitorThread, &CardEventMonitorThread::cardEvent, this , &Controller::onRetry);
221194 cardEventMonitorThread->start ();
222195}
223196
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-
241197void Controller::disposeUI ()
242198{
243199 if (window) {
244200 window->disconnect ();
245- // As the Qt::WA_DeleteOnClose flag is set, the dialog is deleted automatically.
246- window->close ();
201+ window-> forceClose ();
202+ window->deleteLater ();
247203 window = nullptr ;
248204 }
249205}
250206
251207void Controller::onConfirmCommandHandler (const EidCertificateAndPinInfo& certAndPinInfo)
252208{
253- stopCardEventMonitorThread ();
209+ emit stopCardEventMonitorThread ();
254210
255211 try {
256- CommandHandlerConfirmThread * commandHandlerConfirmThread =
212+ auto * commandHandlerConfirmThread =
257213 new CommandHandlerConfirmThread (this , *commandHandler, window, certAndPinInfo);
258214 connect (commandHandlerConfirmThread, &CommandHandlerConfirmThread::completed, this ,
259215 &Controller::onCommandHandlerConfirmCompleted);
260- saveChildThreadPtrAndConnectFailureFinish (commandHandlerConfirmThread);
216+ connect (commandHandlerConfirmThread, &ControllerChildThread::failure, this ,
217+ &Controller::onCriticalFailure);
261218 connectRetry (commandHandlerConfirmThread);
262219
263220 commandHandlerConfirmThread->start ();
@@ -308,14 +265,9 @@ void Controller::onRetry()
308265void Controller::connectRetry (const ControllerChildThread* childThread)
309266{
310267 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);
268+ connect (childThread, &ControllerChildThread::retry, this , &Controller::retry);
316269 // This connection handles cancel events from PIN pad.
317- connect (childThread, &ControllerChildThread::cancel, this , &Controller::onPinPadCancel);
318- connect (window, &WebEidUI::retry, this , &Controller::onRetry);
270+ connect (childThread, &ControllerChildThread::cancel, this , &Controller::onDialogCancel);
319271}
320272
321273void Controller::onDialogOK (const EidCertificateAndPinInfo& certAndPinInfo)
@@ -324,67 +276,50 @@ void Controller::onDialogOK(const EidCertificateAndPinInfo& certAndPinInfo)
324276 onConfirmCommandHandler (certAndPinInfo);
325277 } else {
326278 // This should not happen, and when it does, OK should be equivalent to cancel.
327- onPinPadCancel ();
279+ onDialogCancel ();
328280 }
329281}
330282
331283void Controller::onDialogCancel ()
332284{
333- REQUIRE_NON_NULL (window)
334-
335285 qDebug () << " User cancelled" ;
336-
337- // Schedule application exit when the UI dialog is destroyed.
338- connect (window, &WebEidUI::destroyed, this , &Controller::exit);
339-
340286 _result = makeErrorObject (RESP_USER_CANCEL, QStringLiteral (" User cancelled" ));
341287 writeResponseToStdOut (isInStdinMode, _result, commandType ());
342- }
343-
344- void Controller::onPinPadCancel ()
345- {
346- REQUIRE_NON_NULL (window)
347-
348- onDialogCancel ();
349- window->quit ();
288+ exit ();
350289}
351290
352291void Controller::onCriticalFailure (const QString& error)
353292{
293+ emit stopCardEventMonitorThread ();
354294 qCritical () << " Exiting due to command" << std::string (commandType ())
355295 << " fatal error:" << error;
356296 _result =
357297 makeErrorObject (RESP_TECH_ERROR, QStringLiteral (" Technical error, see application logs" ));
358- writeResponseToStdOut (isInStdinMode, _result, commandType ());
359298 disposeUI ();
360299 WebEidUI::showFatalError ();
300+ // Write the error response to stdout before exiting. Chrome will close the application
301+ // immediately after this, so the UI dialog may not be visible to the user.
302+ writeResponseToStdOut (isInStdinMode, _result, commandType ());
361303 exit ();
362304}
363305
364306void Controller::exit ()
365307{
366- if (window) {
367- window->disconnect ();
368- window = nullptr ;
369- }
308+ disposeUI ();
370309 waitForChildThreads ();
371310 emit quit ();
372311}
373312
374313void Controller::waitForChildThreads ()
375314{
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- }
315+ for (auto * thread : findChildren<QThread*>()) {
316+ qDebug () << " Interrupting thread" << uintptr_t (thread);
317+ thread->disconnect ();
318+ thread->requestInterruption ();
319+ ControllerChildThread::waitForControllerNotify.wakeAll ();
320+ while (thread->isRunning ()) {
321+ using namespace std ::chrono_literals;
322+ thread->wait (100ms);
388323 }
389324 }
390325}
0 commit comments