updating interface in GTK - c

I want to make an application that displays the time (ultimately I am trying to hack together my own simple smart mirror without using the existing smart mirror APIs). I am using GTK3 for the UI however I am having trouble figuring out a solution to make the UI update the time (I am not experienced in front end or GTK so bear with me).
I have tried placing loops around parts of the view3 code shown below however I have discovered that once gtk_main() is called I can't get out of the gtk main loop so that the while loop starts over and recalls the time function I wrote.
I have tried using functions like gtk_main_iteration_do(gtk_false()) (false so that it doesn't block) but I clearly don't understand enough about these functions because it's not working. If I leave gtk_main() out of the loop obviously gtk_main() never gets called and my application window won't even open up.
I have shown the relevant code in main below and following that the definition of the get_time_string() function I wrote.
int
main (int argc,
char *argv[])
{
// initialization and setting up window, packing widgets, etc
// view3
// populate buffer with time string
// and insert into text view
view3 = gtk_text_view_new();
gtk_widget_set_name(view3, "view3");
buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view3));
gtk_text_buffer_get_iter_at_offset(buffer, &iter, 0);
gtk_text_buffer_insert(buffer, &iter, get_time_string(), -1);
gtk_text_view_set_editable(GTK_TEXT_VIEW(view3), FALSE);
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view3), FALSE);
// More widget packing, setting up UI
gtk_widget_show_all(window);
gtk_main();
return 0;
}
Definition of get_time_string()
char* get_time_string(){
time_t time_var = time(NULL);
struct tm *info;
char *time_string = calloc(100, sizeof(char));
info = localtime( &time_var );
strftime(time_string, 100, "%I:%M %p", info);
return time_string;
}

You should not play with mainloop iterations unless you really need or know what to do.
The trick is to use g_timeout_add or g_idle_add and their variants. As you want the time to update at regular intervals (for minute resolution you will update every 60 seconds) then you can use g_timeout_add_seconds.
For illustration purposes, i'll add the seconds to your time string and update every second, using your get_time_string function but creating a very simple window to show just a time label:
#include <time.h>
#include <stdlib.h>
#include <gtk/gtk.h>
char* get_time_string(){
time_t time_var = time(NULL);
struct tm *info;
char *time_string = calloc(100, sizeof(char));
info = localtime( &time_var );
strftime(time_string, 100, "%I:%M:%S %p", info);
return time_string;
}
gboolean update_label_time (gpointer user_data) {
gchar *t = get_time_string();
gtk_label_set_text(GTK_LABEL(user_data), t);
g_free (t);
return G_SOURCE_CONTINUE;
}
int main (int argc, char *argv[])
{
gchar *t;
GtkWidget *window;
GtkWidget *label_time;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW(window), 300, 200);
t = get_time_string();
label_time = gtk_label_new (t);
g_free (t);
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
gtk_container_add (GTK_CONTAINER(window), label_time);
g_timeout_add_seconds(0.5, update_label_time, label_time);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
The result should be a window with a label updating every second:
GLib has its own time functions which you should use.

Related

How to connect a cursor motion signal to a function (GTK)

I'm writing a C program using the GTK libraries that reads the position of the cursor and writes it out to another function. I've written code that's able to accurately read the cursor position data, but I can't figure out how to connect a cursor motion event to a function.
Here is the code I have so far:
int main(int argc, char **argv){
GdkWindow *window;
GtkWidget *gtk_window;
GdkDevice *mouse;
gint x = 0;
gint y = 0;
gdk_init(&argc, &argv);
gtk_init(&argc, &argv);
gtk_window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_show(gtk_window);
window = gtk_widget_get_window(GTK_WIDGET(gtk_window));
gdk_window_maximize(window);
gdk_window_set_title(window, "write-mouse");
GdkSeat *seat = gdk_display_get_default_seat (gdk_display_get_default ());
mouse = gdk_seat_get_pointer (seat);
gdk_window_get_device_position (window, mouse, &x, &y, NULL);
//read cursor position at startup to test code
g_print("X = %d\n", x);
g_print("Y = %d\n", y);
//g_signal_connect (mouse, "something?", G_CALLBACK (print_mouse), NULL);
gtk_main();
}
static void print_mouse(GdkDevice *mouse, gpointer data){
gint x = 0;
gint y = 0;
//do I need to pass the window object to "print_mouse" function too?
//can I point to it using some gtk function?
gdk_window_get_device_position (window, mouse, &x, &y, NULL);
g_print("X = %d\n", x);
g_print("Y = %d\n", y);
}
I assume I need to use "g_signal_connect()" to connect the cursor motion event to the "print_mouse" function, but I can't figure out what to put as the arguments to make it work.
What should I put in the "detailed_signal" field of "g_signal_connect()?
Is there a better way to do this entirely?
your something is "motion-notify-event" which gives data as a GdkEventMotion event: https://developer.gnome.org/gtk2/stable/GtkWidget.html#GtkWidget-motion-notify-event
I think you will also need to ensure to gtk_widget_add_events(widget, GDK_POINTER_MOTION_MASK); when constructing the widget. For performance reasons you may want to set this up to only run code at a certain refresh rate so as not too overburden the cpu.

In gtk, how can I switch to a new screen with new buttons, and other widgets, but on the same window?

In gtk, I am trying to make a game that displays the creator in big letters for 3 (three) seconds before displaying the main menu. I know that nothing is wrong /w the function DisplayMainMenu. For some reason this makes it just show the Asadefa screen for ever. How can I fix?
#define NOTHING
#define EMPTYSTRING "\0"
int main(int argc, char *argv[]) {
gtk_init (&argc, &argv);
Window = gtk_window_new(0);
Box = gtk_hbox_new(00, 00);
gtk_window_set_position(GTK_WINDOW(Window), 1);
gtk_window_set_default_size(GTK_WINDOW(Window),800, 500);
gtk_window_set_title(GTK_WINDOW(Window), "Fakecraft" );
g_signal_connect(Window, "destroy", gtk_main_quit, NULL);
gtk_container_set_border_width(GTK_CONTAINER(Window),16);
char *TXT=("<span font=\"72\">ASADEFA </span>");
GtkWidget (*Label) = gtk_label_new(EMPTYSTRING);
gtk_container_add(GTK_CONTAINER(Window), Box);
gtk_container_add(GTK_CONTAINER(Box), Label);
gtk_label_set_markup(GTK_LABEL(Label), TXT);
gtk_widget_show_all(Window);
gtk_main( NOTHING NOTHING );
sleep(3);
DisplayMainMenu();
return 0;
}
Don't use sleep. GTK+ uses an event loop to react to event, so you won't reach your sleep call until gtk_main_quit has been called, which is already too late. You're supposed to use g_timout_add_seconds instead to add an event source that will call your callback after your 3 seconds. In that callback, you can call your DisplayMainMenu function.

Weird behaviour with g_signal_connect on GTK

I'm trying to send a pointer to a structure I created to a function using g_signal_connect but was getting a segmentation fault. After some testing, I decided to print the pointer's address inside the function that was being called (this function is responsible for drawing on a drawing_area, which means it is called regularly via g_timeout_add) and, alternating, it prints "(nil)" or the correct address. I have no clue what could be causing this.
typedef struct{
...
}prog_info
gboolean on_draw_event(GtkWidget *draw_area ,
cairo_t *cr ,
prog_info *info)
{
printf("\ninfo: %p",info); //this will give "(nil)" or the correct
address, alternating
...
}
gboolean time_handler (GtkWidget *draw_area)
{
if ((!GTK_IS_WIDGET(draw_area)) || (!gtk_widget_get_window (draw_area)))
return FALSE;
gtk_widget_queue_draw(draw_area);
return TRUE;
}
int main(int argc, char *argv[]) {
...
prog_info *info;
info =(prog_info *) calloc (1,sizeof(prog_info));
...
g_signal_connect(G_OBJECT(draw_area), "draw", G_CALLBACK(on_draw_event), info);
g_timeout_add (10, (GSourceFunc) time_handler, draw_area);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
Solved
Had two different g_signal_connect, one of which was ruining everything.

How to refresh a GTK image?

Hi people from Stackoverflow, I have some knowledge of programming, but not very much. My idea is to program something like a video-game or visualizer in c, to do so I make and process somehow an array that represents an image and it has to be visualized and always being refreshed, like a video-game. I have the algorithm already done, I need to optimize it, I mean I know how to create the array representing an image at anytime, what I need now is to visualize it like an animation and later optimize with opencl.
The program that I want to make has to do something like this:
"create the image"
"render it",
"create the image"
"render it",
...
For that reason I know that I could start easily from a very simple example and I don't have to learn everything of GTK. I have been intensively searching for simple examples and trying to understand how it works but it didn't help, I need just to do that simple action, refresh it. A command somewhere should be enough.
#include <gtk/gtk.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <glib.h>
const int Width = 1200, Height = 800;
char *buffer;
int i = 0;
GdkPixbuf *pixbuf;
GtkWidget *window;
GtkWidget* image;
void delay(int number_of_seconds){
int milli_seconds = 1000 * number_of_seconds;
clock_t start_time = clock();
while (clock() < start_time + milli_seconds);
}
int main (int argc, char *argv[]){
buffer = (char*)malloc(3 * Width * Height);
// CREATE AN IMAGE IN BUFFER SOMEHOW
// buffer = something;
//
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
pixbuf = gdk_pixbuf_new_from_data (buffer, GDK_COLORSPACE_RGB, FALSE, 8, Width, Height, (Width)*3, NULL, NULL);
image = gtk_image_new_from_pixbuf (pixbuf);
gtk_container_add(GTK_CONTAINER (window), image);
gtk_window_set_title (GTK_WINDOW (window), "Visualizador");
gtk_widget_show_all (window);
delay(0.04);
gtk_main ();
free (buffer);
return 0;
}
The program is really simple, when I create/load the image there it shows a picture, but I would like to put the function already programmed there, to return a "buffer" and then refresh the displayed image.
I read that gtk_widget_queue_draw has something to do, or gdk_pixbuf_simple_anim_add_frame or g_timeout_add but I have no idea how they work and what to put in this program.
Thank you in advance!
If you want a moving image or something that needs to get refreshed a lot then I would suggest using a GtkDrawingArea and implement it's draw signal, for that you'll need to use cairo. It should look something like this:
static gboolean
your_draw_cb(GtkWidget *widget, cairo_t *context, gpointer data)
{
// Do your drawing
return FALSE;
}
int main()
{
// Some code before ...
GtkWidget *area = gtk_drawing_area_new();
g_signal_connect(G_OBJECT(area), "draw", G_CALLBACK(your_draw_cb), your_data);
gtk_container_add(GTK_CONTAINER(your_container), area);
// More code ...
}
Each time you want to refresh it you should call
gtk_widget_queue_draw(area);
to get the draw signal called.
You won't need to store a buffer as that comes inside the cairo context.
But you may want to use other libraries for this purpose, like SDL2 or OpenGL as they are designed for videogames.
I hope this helps.

Figuring out a C loop

This is one of the default gnome screensavers - "personal slideshow". It displays pictures from some location, pausing ~10s between pictures. Anyone know how it's looping & pausing?
I'm trying to increase the delay, but I'm hesitant to add sleep() without first knowing how it's doing it.
Thanks
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include "gs-theme-window.h"
#include "gs-theme-engine.h"
#include "gste-slideshow.h"
#include "xdg-user-dir-lookup.h"
int
main (int argc, char **argv)
{
GSThemeEngine *engine;
GtkWidget *window;
GError *error;
gboolean ret;
char *location = NULL;
char *background_color = NULL;
gboolean sort_images = FALSE;
gboolean no_stretch = FALSE;
GOptionEntry entries [] = {
{ "location", 0, 0, G_OPTION_ARG_STRING, &location,
N_("Location to get images from"), N_("PATH") },
{ "background-color", 0, 0, G_OPTION_ARG_STRING, &background_color,
N_("Color to use for images background"), N_("\"#rrggbb\"") },
{ "sort-images", 0, 0, G_OPTION_ARG_NONE, &sort_images,
N_("Do not randomize pictures from location"), NULL },
{ "no-stretch", 0, 0, G_OPTION_ARG_NONE, &no_stretch,
N_("Do not try to stretch images on screen"), NULL },
{ NULL }
};
bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
error = NULL;
g_thread_init (NULL);
ret = gtk_init_with_args (&argc, &argv,
NULL,
entries,
NULL,
&error);
if (! ret) {
g_message ("%s", error->message);
g_error_free (error);
exit (1);
}
g_chdir (g_get_home_dir ());
g_set_prgname ("slideshow");
window = gs_theme_window_new ();
g_signal_connect (G_OBJECT (window), "delete-event",
G_CALLBACK (gtk_main_quit), NULL);
engine = g_object_new (GSTE_TYPE_SLIDESHOW, NULL);
if (location == NULL) {
location = xdg_user_dir_lookup ("PICTURES");
if (location == NULL ||
strcmp (location, "/tmp") == 0 ||
strcmp (location, g_get_home_dir ()) == 0) {
free (location);
location = g_build_filename (g_get_home_dir (), "Pictures", NULL);
}
}
if (location != NULL) {
g_object_set (engine, "images-location", location, NULL);
}
if (sort_images) {
g_object_set (engine, "sort-images", sort_images, NULL);
}
if (background_color != NULL) {
g_object_set (engine, "background-color", background_color, NULL);
}
if (no_stretch) {
g_object_set (engine, "no-stretch", no_stretch, NULL);
}
gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (engine));
gtk_widget_show (GTK_WIDGET (engine));
gtk_window_set_default_size (GTK_WINDOW (window), 640, 480);
gtk_widget_show (window);
gtk_main ();
return 0;
}
It looks like this program is using a slideshow engine from GStreamer. This is pointed to by the variable engine. GStreamer is a media processing framework that has lots of reusable components for audio and video. In this case, the slideshow engine reads images from a directory and displays them.
You can see the program tweaking various settings of the engine using g_object_set. Maybe there's another setting you can tweak to change the delay?
EDIT: The relevant source for the slide show is gste-slideshow.c. It looks like the key is start_new_load function, which is usually called with IMAGE_LOAD_TIMEOUT, a constant set to 10000 (milliseconds). There doesn't seem to be any way to set this value from the outside.
The show/delay/switch loop isn't in the code you've posted here, it's elsewhere.
The show/delay/switch is controlled by the GTK Slideshow, which is instantiated here:
engine = g_object_new (GSTE_TYPE_SLIDESHOW, NULL);
The delay is actually set in the gste_slideshow.c file, found here. The gste_slideshow_real_show function is located at line 890.
Enjoy.
Once you find where the timer is implemented you should change the value there. I'd be reluctant to use sleep() for the delay. Find where the loop is and modify the delay method that's already in the code. Adding extra sleeps may cause your screensaver to become unresponsive at times, especially if you aren't sure of what you're doing.
The main event loop is almost certainly inside of the gtk_main() function. However, you're barking up the wrong tree. The job of gtk_main() is pump events -- it detects low-level events (such as keyboard/mouse input and OS timers), converts them to higher-level events, and passes those onto the application code.
In this case, the application is the engine object, which is an object of type GSTE_TYPE_SLIDESHOW. Looking at the source code, we see that in the start_new_load() function, it creates a timer object that is ultimately responsible for the delays between pictures. This is initialized by a hard-coded value of 10000 ms.
If you want to change the timeout, you'll have to recompile the project and make the timeout configurable somehow.
I would think there's probably some sort of timer in the includes:
#include "gs-theme-window.h"
#include "gs-theme-engine.h"
#include "gste-slideshow.h"
There's nothing in this code that has anything about a delay. Check the implementation of engine and check the code in the slideshow include.

Resources