GUI using GTK in C is not displaying real-time values being fed in

I am trying to build a GUI application for an activity monitor in C, and decided to use GTK library along with Glade to help with the design. (Using Ubuntu 20.04)

Upon pressing the button, values are displayed in the respective positions and are updated upon every click. The only problem is I need it to update by itself in real-time, so I shifted the code into an infinite loop with sleep(1), so it updates them after every 1 second. But values are not even being displayed on the GUI now. To test if the code is even being executed, I tried printing values on the console from different parts of the code, and they are indeed being printed.

Things I've tried, but didn't work:

  1. Switching between loops and recursions, both failed.
  2. Replacing the sleep() function with a self-made timer using time.h library
  3. Encapsulate the GUI-displaying code into a function, and have that whole function called in a loop.
  4. Forcing the refresh on GUI using GDK functions, so it updates the GUI manually in every iteration.
  5. Used gtk_show_all in different parts of the code to force it to display at the end of every iteration.

I think it has to do with the button trigger, and output is only updated on the GUI after the callback function is executed (from my observation with console prints). So I am trying to have the button programmatically pressed in intervals, to avoid having to click it myself every time, but could not find much on the topic.

If you can think of any way to make this work or an alternative to the approach I am taking, kindly help out. The main idea is that the output GUI should have values updated in real-time, regardless of the button.

Thanks in advance!

This is the function used to print out the values on the GUI:

struct timespec tm;
tm.tv_sec  = 0;
tm.tv_nsec = 1000 * 1000 * 1000;

myproc_t* myprocs = NULL;
unsigned int myprocs_len = 0;

//call to function that will return the processes and their specifications 
sample_processes(&myprocs, &myprocs_len, tm);


if(s == 0){
        // sort by CPU usage 
        qsort(myprocs, myprocs_len, sizeof(myprocs[0]), myproc_comp_pcpu);
    }
else if(s == 1){
        // sort by Memory usage 
        qsort(myprocs, myprocs_len, sizeof(myprocs[0]), myproc_comp_rss);
    }



for (i = 0; i < myprocs_len && i < 5; i++) 
{
    if (strlen(myprocs[i].cmd) == 0) {
        break;
    }

    //convert specs read from /proc file to string format
    sprintf(pid, "%d", myprocs[i].tid);
    sprintf(cpu, "%.2f",myprocs[i].pcpu);
    sprintf(memory, "%lu", myprocs[i].vm_rss/1000);
    sprintf(cmd, "%s", myprocs[i].cmd); 
    
    switch(i)
    {
        case 0:
            gtk_label_set_text(GTK_LABEL(PID1), pid);
            gtk_label_set_text(GTK_LABEL(CPU1), cpu);
            gtk_label_set_text(GTK_LABEL(MEM1), memory);
            gtk_label_set_text(GTK_LABEL(CMD1), cmd);   
        case 1:
            gtk_label_set_text(GTK_LABEL(PID2), pid);
            gtk_label_set_text(GTK_LABEL(CPU2), cpu);
            gtk_label_set_text(GTK_LABEL(MEM2), memory);
            gtk_label_set_text(GTK_LABEL(CMD2), cmd);
        case 2:
            gtk_label_set_text(GTK_LABEL(PID3), pid);
            gtk_label_set_text(GTK_LABEL(CPU3), cpu);
            gtk_label_set_text(GTK_LABEL(MEM3), memory);
            gtk_label_set_text(GTK_LABEL(CMD3), cmd);
        case 3:
            gtk_label_set_text(GTK_LABEL(PID4), pid);
            gtk_label_set_text(GTK_LABEL(CPU4), cpu);
            gtk_label_set_text(GTK_LABEL(MEM4), memory);
            gtk_label_set_text(GTK_LABEL(CMD4), cmd);   
        case 4:
            gtk_label_set_text(GTK_LABEL(PID5), pid);
            gtk_label_set_text(GTK_LABEL(CPU5), cpu);
            gtk_label_set_text(GTK_LABEL(MEM5), memory);
            gtk_label_set_text(GTK_LABEL(CMD5), cmd);       
    }
}

Upvotes: 1

Views: 296

Answers (2)

ntd
ntd

Reputation: 7434

To expand on what @nielsdg correctly said, UI code based on event loop (such as GTK) must limit the blocking code to the bare minimum.

/* Never do this: it will freeze the UI */
static void
on_button_clicked()
{
    do {
        /* Your code here */
        sleep(1);
    while (condition);
}

Instead, unroll your code and leverage the main event loop:

static gboolean
iteration(gpointer user_data)
{
    /* Your code here */
    return condition ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
}

static void
on_button_clicked()
{
    g_timeout_add(1000, iteration, NULL);
}

This just gives you the main idea. The code above has some problems, above all if you click twice it will happily start two cooperative loops.

Upvotes: 0

nielsdg
nielsdg

Reputation: 2733

Using a loop with sleep(1) or anything that blocks is always a no-go, since that means you're effectively blocking the UI thread from doing any actual work. The normal workflow is the following:

  1. You want to have a main loop running, either by using gtk_main() or gtk_application_new() and connecting to the "activate" signal (which will be called when you call gtk_application_run()).
  2. Initialize your backend code
  3. Initialize your UI code, in which you create the necessary widgets for each process. At this point, you probably already want to make it visible using gtk_widget_show() and friends
  4. For the periodic updates, you should post a periodic event to the main loop, which you can using API like g_timeout_add_seconds (). In the callback you can call gtk_label_set_text() on the labels you created in the previous step. As long as the callback returns G_SOURCE_CONTINUE, the callback will keep being called periodically at the specified interval

Upvotes: 1

Related Questions