Search code examples
c++pthreadsglibgtkmm

In GTKMM, on_draw method stops being called after invalidate occurs in separated thread


Using GTKMM, I'm extending the DrawingArea widget with the idea that an external process provides it with images. My CameraDrawingArea will then display the images at the right size using Cairo.

Each time an image arrives, I store it and I call the invalidate method, which eventually ends up in a call to on_draw, where I can resize and display the image.

My problem is the following:

  • The first 10 or 20 images are displayed as I expected.
  • After a while, the images keep coming from the provider process, I keep calling invalidate
  • but on_draw is not called any more.

To show it here, I've simplified the code so that there is nothing external to the class, and no link with other libraries. I've replaced the process providing the images by a method with for-loops, and the display of the image by printing a simple text in the middle of the widget area:

  • In the constructor I launch a new std::thread to call the doCapture method in the same instance. I also set up a font description, to use it later.
  • The doCapture method is a silly CPU eater, that does nothing except calling from time to time the refreshDrawing method, as long as keepCapturing is not false.
  • refreshDrawing invalidates the whole window's rectangle via a call to invalidate.
  • Gtk's magic is suppose to call on_draw and provide a Cairo context to draw whatever. In my case, for tests purposes, I draw a brownish centered integer.
  • The class destructor stops the thread by set keepCapturing to false, and waits for termination with a join.
    #include "camera-drawing-area.hpp"

    #include <iostream>

    CameraDrawingArea::CameraDrawingArea():
    captureThread(nullptr) {
        fontDescription.set_family("Monospace");
        fontDescription.set_weight(Pango::WEIGHT_BOLD);
        fontDescription.set_size(30 * Pango::SCALE);

        keepCapturing = true;
        captureThread = new std::thread([this] { 
                doCapture(); 
                });
    }

    void CameraDrawingArea::doCapture() {
        while (keepCapturing) {
            float f = 0.0;
            for (int n = 0; n < 1000; n++) {
                for (int m = 0; m < 1000; m++) {
                    for (int o = 0; o < 500; o++) {
                        f += 1.2;
                    }
                }
            }
            std::cout << "doCapture - " << f << std::endl; 
            refreshDrawing();
        }
    }

    void CameraDrawingArea::refreshDrawing() {
        auto win = get_window();
        if (win) {
            win->invalidate(false);
            std::cout << "refreshDrawing" << std::endl; 
        }
    }

    bool CameraDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
        std::cout << "on_draw" << std::endl; 

        static char buffer[50];
        static int n = 0;
        sprintf(buffer, "-%d-", n++);

        Gtk::Allocation allocation = get_allocation();
        const int width = allocation.get_width();
        const int height = allocation.get_height();


        auto layout = create_pango_layout(buffer);
        layout->set_font_description(fontDescription);

        int textWidth, textHeight;
        layout->get_pixel_size(textWidth, textHeight);
        cr->set_source_rgb(0.5, 0.2, 0.1);
        cr->move_to((width - textWidth) / 2, (height - textHeight) / 2);
        layout->show_in_cairo_context(cr);
        cr->stroke();

        return true;
    }

    CameraDrawingArea::~CameraDrawingArea() {
        keepCapturing = false;
        captureThread->join();
        free(captureThread);
    }

And this is my header file:

    #ifndef CAMERA_DRAWING_AREA_HPP
    #define CAMERA_DRAWING_AREA_HPP

    #include <gtkmm.h>
    #include <thread>

    class CameraDrawingArea : public Gtk::DrawingArea {
    public:
        CameraDrawingArea();
        virtual ~CameraDrawingArea();

    protected:
        bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;

    private:
        bool keepCapturing;
        void doCapture();
        void refreshDrawing();
        std::thread* captureThread;
        Pango::FontDescription fontDescription;
    };
    #endif

The problem manifests itself as follows:

  • When starting the application, it faithfully displays 1, 2, 3...
  • Between 5th and 20th iteration (it's random, but rarely outside these ranges), it stops refreshing.
  • Because of the cout, I can see that refreshDrawing is called be sure that invalidate is also called, but on_draw isn't.

Also, if I stop the application before it stops refreshing, then it ends up nicely. But, if I stop the application after it stops refreshing, then I see this message below (the ID value varies):

GLib-CRITICAL **: 10:05:04.716: Source ID 25 was not found when attempting to remove it

I'm quite sure that I do something wrong, but clueless about what. Any help would be appreciated.

I also checked the following questions, but they don't seem to be related with my case:


Solution

  • You can't use GTK methods from any other thread than the one in which you started the GTK main loop. Probably the win->invalidate() call is causing things to go wrong here.

    Instead, use Glib::Dispatcher to communicate with the main thread, or use gdk_threads_add_idle() for a more C-style solution.