I'm trying to display a message dialog if the pid of running program is valid, This is my essencial code:
Gtk::Main kit (argc, argv);
Glib::RefPtr<Gtk::Builder> refBuilder = Gtk::Builder::create();
try { refBuilder->add_from_file (UI_PATH); }
catch (const Glib::FileError& ex) {
std::cout << "FileError: " << ex.what() << std::endl;
return 1;
}
catch (const Gtk::BuilderError& ex) {
std::cout << "BuilderError: " << ex.what() << std::endl;
return 1;
}
FormUI * ui = 0;
refBuilder->get_widget_derived ("window1", ui);
if (ui) {
kit.run (*ui);
}
delete ui;
Constructor:
signal_delete_event ().connect (sigc::mem_fun (*this, &FormUI::on_delete_event));
method:
bool FormUI::on_delete_event (GdkEventAny* event) {
if (_pid) {
bool retState;
Gtk::MessageDialog md(*this, Glib::ustring::compose ("<b>%1</b>", _("Warning: youtube-dl in process")), true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true);
md.set_title (PACKAGE_STRING);
md.set_secondary_text (_("Closing can generate a corrupted file, do you want to continue anyway?"));
if (md.run() == Gtk::RESPONSE_YES) {
kill (_pid, 0);
retState = false;
} else {
retState = true;
}
md.hide ();
return retState;
}
return false;
}
With the above, if pid is valid it displays the messagedialog as expected, but if I hit "yes" (to exit the application) it displays another messagedialog..why?
You said that FormUI is derived from Gtk::Window. Gtk::Window has a virtual method on_delete_event() that is automatically connected to the delete-event signal, no questions asked. Oops, you implemented a virtual method without knowing it! So what you did by calling
signal_delete_event ().connect (sigc::mem_fun (*this, &FormUI::on_delete_event));
was unknowingly connect that signal twice, and because Gtk::Window::on_delete_event() is virtual, both connections go to your own method.
Okay, so why do we still get two dialog boxes? Doesn't returning false mean to close the window? Not really.
delete-event is a GDK event. GDK events always return a boolean value: if the value is false (GDK_EVENT_PROPAGATE), the next signal handler in the signal connection chain is run, and if the return value is true (GDK_EVENT_STOP), no further signal in the signal connection chain is run.
It just so happens that if you don't stop a delete-event from propagating through the signal connection chain, the window is destroyed. So when there was only one handler connected, returning false from that handler would effectively destroy the window.
But now you have two handlers connected. The first one will return false, which causes the second one to run, and you get your second message dialog. When that one returns false, you get your window being destroyed.
Hopefully that should explain this problem. You can solve this by either not calling signal_delete_event().connect() or by changing the method name to something else. Be sure to watch the gtkmm documentation to make sure you aren't accidentally using other virtual methods that are automatically connected to signals (I'm not sure why gtkmm provides these virtual methods; convenience?). And be sure to understand how GDK events work; you'll need to know this if you ever play with GDK events for real (such as handling input in a Gtk::DrawingArea).
Related
I make a Bomberman (or DynaBlast) game clone with multiplayer. Server game and client game communicate through messages using QTcpSocket. Typical workflow for playing network game is following:
Server player execs NetworkGame dialog, among other things this dialog creates NetworkGame object
Client player execs ClientGame dialog, among other things this dialog creates ClientGame object
Client chooses IP of server and clicks "Connect"
Server accepts connections, server and clients are now able to send messages each other
Client sends "ready"-message, server start game
When game over, the game object emits GameStatusChanged(GameStatus newStatus) signal. This signal connected to MainWindow, which execs GameOverDialog. If player chooses "Play again" at GameOverDialog, MainWindow execs NetworkGame or ClientGame dialogs again and we are at the first points.
So, after first game is over, second exec of ClientGameDialog blocks QTcpSocket work in the way it cann't read data or emit QTcpSocket::readyRead signal (I don't know which one point exactly). ClientGameDialog's GUI is responsive, it can send messages to server, but it cann't read messages. At the same time NetworkGame and NetworkGameDialog work properly - they are able to send and receive messages. I checked all my classes several times and don't see any significant difference.
I think full code is to huge to post it here, so I gave UML a try. This is a chart for most important classes. Green arrow designates Qt's child-parent relations, starting at a child QObject it points to a parent.
When Socket class receives new message through QTcpSocket interface, it emits messageReceived(const Message& message) signal; other classes can connect to this signal via slots and handle messages. I don't see what Client, ServerWorker, Server classes can do with event loop, the just help to process raw data from QTcpSocket and deliver messages to other classes, particulary to Game and Dialog classes.
Here is some code (I have some code duplications, I leave them until better times). Creating game:
// Server game
void MainWindow::startNetworkGame() // User clicked "Start network game" button
{
const auto& player = mainMenuWidget_->selectedPlayer();
gameDialogs_ = createGameDialogs(this, GameType::Server, player);
auto answer = gameDialogs_.creationDialog->exec();
if (answer == QDialog::Accepted) {
auto initializationData = gameDialogs_.creationDialog->initializationData();
initializeGame(initializationData);
startGame(initializationData);
}
}
// Client game
void MainWindow::connectToServer() // User clicked "Connect to server" button
{
const auto& player = mainMenuWidget_->selectedPlayer();
gameDialogs_ = createGameDialogs(this, GameType::Client, player);
auto answer = gameDialogs_.creationDialog->exec(); // At first time it works fine
if (answer == QDialog::Accepted) {
auto initializationData = gameDialogs_.creationDialog->initializationData();
initializeGame(initializationData);
startGame(initializationData);
}
}
Next snippet is code for processing GameStatusChanged signal when game was over:
void MainWindow::gameStatusChanged(GameStatus newStatus)
{
if (newStatus == GameStatus::GameOver) {
auto* gameOverDialog = gameDialogs_.gameOverDialog;
gameOverDialog->setGameResult(gameData_.game->gameResult());
auto gameOverDialogAnswer = gameOverDialog->exec();
if (gameOverDialogAnswer == QDialog::Accepted) {
gameDialogs_.creationDialog->reset();
auto answer = gameDialogs_.creationDialog->exec(); // At this point client cann't receive messages, but server can.
if (answer == QDialog::Accepted) {
auto initializationData = gameDialogs_.creationDialog->initializationData();
initializeGame(initializationData);
startGame(initializationData);
} else {
showMainMenu();
}
} else {
showMainMenu();
}
}
}
I suspect that ClientGameDialog's event loop (is it indeed has it's own event loop) doesn't processes QTcpSocket's events. I tried to replace exec() with open methods for client dialog:
void MainWindow::gameStatusChanged(GameStatus newStatus)
{
if (newStatus == GameStatus::GameOver) {
auto* gameOverDialog = gameDialogs_.gameOverDialog;
gameOverDialog->setGameResult(gameData_.game->gameResult());
auto gameOverDialogAnswer = gameOverDialog->exec();
if (gameOverDialogAnswer == QDialog::Accepted) {
gameDialogs_.creationDialog->reset();
auto d = qobject_cast<ClientGameDialog*>(gameDialogs_.creationDialog);
if (d) {
gameDialogs_.creationDialog->open();
} else {
auto answer = gameDialogs_.creationDialog->exec();
if (answer == QDialog::Accepted) {
auto initializationData = gameDialogs_.creationDialog->initializationData();
initializeGame(initializationData);
startGame(initializationData);
} else {
showMainMenu();
}
}
} else {
showMainMenu();
}
}
}
It works, but I want to find where was the problem. Maybe someone can prompt, where to search a solution. Main questions are: where the difference between Server and Client code flows and why Client code works fine at the first time and breaks at the second.
This is because QDialog::exec is a synchronous blocking operation that will stop the event loop of the main thread and start a new event loop for this dialog. This most of the time is not a problem unless you are doing some continuous work on the main thread such as processing QTCPSocket
Instead of using QDialog::exec use QDialog::open which is asynchronous and does not start a new event loop, you can simply connect signals from the dialog to read the results once the user will accept/close the dialog.
If you require blocking dialogs then you also can simply offload QTcpSocket to another thread and do whole processing asynchronously and only emit required updates to the main GUI thread.
Current, I'm using a hackish way – a global variable – to make RPC handlers able to detect that the Server has been (about to be) called Shutdown().
bool g_ServerIsNotDead = true; // Hack!
Status StreamServiceImpl::GetCurrentTemperature(ServerContext *context_,
const UpdateInterval *request_,
ServerWriter<Temperature> *stream_)
{
auto currentTemp = 100.0f;
while(g_ServerIsNotDead) // Hack!!!
{
qDebug() << QThread::currentThreadId() << currentTemp << "farenheit.";
Temperature message;
message.set_temperature(currentTemp);
stream_->Write(message);
QThread::sleep(2);
currentTemp += 1.0f;
}
return Status::OK;
}
void insideSomeFunction() {
// Testing shutdown 5 seconds later
QTimer::singleShot(std::chrono::seconds(5), this, [=]() {
qDebug() << "Shuting down!";
g_ServerIsNotDead = false; // Hack!!
this->server->Shutdown(); // This method actually blocks until all RPC handlers have exited, believe it or not!
emit shutdown();
qDebug() << "All dead.";
});
}
Ref: https://github.com/C0D1UM/grpc-qt-example/blob/master/rpc_server/hellostream_server.cpp
It would be really nice if I could somehow check that Server has been Shutdown() from grpc::ServerContext, but I didn't see any relevant methods to achieve this.
Even better if someone could propose a way to take out the while loop completely (?). I'm using Qt so everything is event-driven.
So, I think it's worth making clear what Shutdown does. There's a detailed comment about this but basically, server Shutdown doesn't fail, cancel, or kill your existing in-progress calls (unless you use the deadline argument and the gRPC C++ async API).
Rather, it stops listening for new connections, stops accepting new calls, fails requested-but-not-yet-accepted calls. If you want to fail or terminate your calls at shutdown, you can do it at application-level code as you've done above.
I would just recommend that instead of using a global variable, you should use a member function of your StreamServiceImpl class so that you can support multiple services running in the same process if you choose.
We can use ServerContext::IsCancelled as a breaking/termination criteria in streaming APIs. I changed GetCurrentTemperature(...) as follows (just replaced g_ServerIsNotDead with !context_->IsCancelled()) and it worked:
Status StreamServiceImpl::GetCurrentTemperature(ServerContext *context_,
const UpdateInterval *request_,
ServerWriter<Temperature> *stream_) {
auto currentTemp = 100.0f;
while(!context_->IsCancelled) {
qDebug() << QThread::currentThreadId() << currentTemp << "farenheit.";
Temperature message;
message.set_temperature(currentTemp);
stream_->Write(message);
QThread::sleep(2);
currentTemp += 1.0f;
}
return Status::OK;
}
I'm currently trying to create a C++ wrapper for my program to use GStreamer.
So I have created a Class "Audio" with a method "play" that starts a stream. Because of the call to g_main_loop_run inside it, it won't return until the main loop quits.
I dont want that behaviour and thus I'm trying to make an async call to another method within the play method, which would then start the main loop and allow the play method to return.
It currently looks like this:
void play(const char* uri) {
stop();
if (uri) {
g_object_set(G_OBJECT(pipeline), "uri", uri, NULL);
} else {
cout << "Please specify an URI you wish to play" << endl;
return;
}
gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING);
auto handle = async( launch::async, &Audio::playAsync, this, uri);
}
void playAsync(const char* uri) {
cout << "playing async" << endl;
g_main_loop_run(this->getLoop());
}
But the result is that the program is getting unresponsive... the playAsync method is called (its printing the cout), and playback starts and continues to play, but the GUI becomes totally unresponsive and can only be quit by killing the program.
Do you have any help for me?
regards, tagelicht
Async returns future and future must be finished (it waits for the result) when it goes out of scope.
It's exactly the same as using just async(...); as handle goes out of scope just after assigment.
I have the following code:
void Processmethod()
{
QDialog *ProcessMessage = new QDialog;
Ui::DialogProcessMessage Dialog;
Dialog.setupUi(ProcessMessage);
ProcessMessage->setModal(true);
ProcessMessage->setAttribute(Qt::WA_DeleteOnClose);
ProcessMessage->show();
qApp->processEvents();
processmethodONE();
processmethodTWO();
processmethodTHREE();
}
void processmethodONE()
{
QString ProcessCommand = "w8 " + blablubli";
Prozess.setWorkingDirectory(Path); //QProcess "Prozess" is globaly defined
Prozess.setStandardOutputFile(Path); //in my class
QThread* thread = new QThread;
Prozess.moveToThread(thread);
Prozess.start(ProcessCommand);
while(!Prozess.waitForFinished(2000))
{
std::cerr << "Process running " << std::endl;
}
QProcess::ExitStatus Status = Prozess.exitStatus();
if (Status == 0)
{
std::cout << "File created!" << std::endl;
}
}
In this source code I try to open a popup dialog before some processes are starting. problem is that the dialog is not clickable, but on the dialog I want to create a button to abort the running method. As you can see I tried using QThread to run the process(es) in another thread, but still I can't click the dialog. Furthermore if I open my application (GUI) with the "application/x-executable"-file the dialogs content is missing when activating the above shown method. How can I fix these problems? Where am I wrong? greetings
void processmethodONE()
{
QThread* thread = new QThread;
Prozess.moveToThread(thread);
Prozess.start(ProcessComand);
Here you moved the QProcess to another thread. But then you call start() on it. That's already not thread-safe.
while(!Prozess.waitForFinished(2000))
{
std::cerr << "Process running " << std::endl;
}
This blocks and makes using a thread useless. Also, it's not thread-safe.
You should instead not use threads but:
remove the waitForFinished() call
Connect the finished() and error() signals of the QProcess to slots which then start the next step, i.e. processMethodTWO.
I would also advise against reusing QProcess objects and just create a new one for each step.
While I still don't fully understand your recently updated code example, I feel this might be your issue:
while(!Prozess.waitForFinished(2000))
{
std::cerr << "Process running " << std::endl;
}
Wherever you are really calling this in your original code is blocking while waiting for Prozess to finish.
Use a brand new QProcess instance for each one, and connect their finished() signals to a SLOT that will get called when they have finished. Don't manually poll them and block. This will allow you to completely get rid of QThreads altogether.
I have an app that has a progress bar & spawns a worker thread to do some work & report back progress. The dialog class overrides the customEvent method so that I can process events that are being passed to the gui thread via the worker thread. Before I was using a QThread derived class as the worker thread and I changed it to use ACE_Thread_Manager->spawn() with a static function for the worker.
The problem shows up when I run the app and press the button so the worker is spawned & starts doing work. When it sends the signal to increment the progress bar I get the following errors logged to std out.
QPixmap: It is not safe to use pixmaps outside the GUI thread
This seems to happen when the progressBar->setValue() is called. So it seems like the setting of the progress bar is happening in a different thread than the main gui thread. I'm unclear as to how that's possible. I'm under the impression that I have a main gui thread which has my gui & the customEvent method is on that same thread and the worker is on it's own thread. Is this assumption wrong? And is there any difference when using the QThread derived class versus the static run_svc method?
Any help would be appreciated. The code snippets for the customEvent handler, run_svc, and button handler code are below and the code is attached.
void MyDlgEx::customEvent(QEvent * e)
{
if (e->type() == IdNumOperations)
{
NumOperations* pEvt = static_cast<NumOperations*>(e);
_steps = 0;
cout << "Num Operations = " << pEvt->operations() << endl;
}
else if (e->type() == IdStep)
{
if (_steps % 10 == 0)
{
cout << "Step++ = " << _steps << endl;
}
_steps++;
_progressBar->setValue(_steps);
}
}
void* MyDlgEx::run_svc(void* args)
{
auto_ptr<ThreadArgs> thread_args(static_cast<ThreadArgs*>(args));
QApplication::sendEvent((QObject*)thread_args->m_pDlg, new NumOperations(300));
// does some work that takes time -- ommitted for clarity
// called in a loop
QApplication::sendEvent((QObject*)thread_args->m_pDlg, new Step());
QApplication::sendEvent((QObject*)thread_args->m_pDlg, new Completed());
return 0;
}
Button Handler
Commented out lines where where I used a QT class derived from QThread. Using ACE has to spawn the thread has uncovered this issue.
void MyDlgEx::btnShowProgress_clicked()
{
//_pProc = new ProcessThread(this);
//_pProc->run();
auto_ptr<ThreadArgs> thread_args(new ThreadArgs(this));
if (ACE_Thread_Manager::instance()->spawn(
MyDlgEx::run_svc,
static_cast<void*>(thread_args.get()),
THR_DETACHED | THR_SCOPE_SYSTEM) == -1)
cout << "Failed to spawn thread." << endl;
thread_args.release();
}
Try calling QApplication::postEvent(...) instead of QApplication::sendEvent(). The docs say that sendEvent sends the event directly, meaning that it calls the customEvent() function directly from the other thread. postEvent() adds the event to the event queue where it can later be dispatched to customEvent() by the main GUI event loop.
Just because the customEvent() function is a member of an object created in the main GUI thread doesn't mean another thread cannot call the function. I believe that is what is happening when you call QApplication::sendEvent() from another thread.