Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save skoona/c1218919cf393c98af474a4bf868009f to your computer and use it in GitHub Desktop.
Save skoona/c1218919cf393c98af474a4bf868009f to your computer and use it in GitHub Desktop.
Test Client for GtkDisplayService.c command-line: `./gtkDisplayClients -a 10.100.1.8 -d 1`, and `./gtkDisplayClient`
/**
* gtk_display_client.c
* gcc -v `pkg-config --cflags --libs glib-2.0 gio-2.0` -O3 -Wall gtk_display_client.c -o gtk_display_client
*
*/
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <gio/gio.h>
#include <glib-unix.h>
#define BUFF_SZ 1024
#define UDP_COMM_PORT 48029
#define MESSAGE_DELAY_INTERVAL 10
typedef struct _controlData {
guint gErrorCount;
GMainLoop *loop;
GSocket *gSock;
GSocketAddress *gsDSAddr;
gchar ch_buff[BUFF_SZ];
gchar ch_display_service_ip[BUFF_SZ];
gchar ch_message[BUFF_SZ];
gchar ch_response[BUFF_SZ];
} ControlData, *PControlData;
typedef struct _signalData {
guint gTimeoutId;
GMainLoop *loop;
gchar * signalName;
} USignalData, *PUSignalData;
gboolean cb_unix_signal_handler(PUSignalData psig);
gboolean cb_udp_comm_request_handler(PControlData pctrl);
gboolean cb_udp_comm_response_handler(GSocket *gSock, GIOCondition condition, PControlData pctrl);
gboolean cb_udp_comm_response_handler(GSocket *gSock, GIOCondition condition, PControlData pctrl) {
GError *error = NULL;
gssize gss_receive = 0;
if ( NULL == pctrl) { /* SHUTDOWN THE MAIN LOOP */
g_message("gtk_display_client::cb_udp_comm_response_handler(error) Invalid Pointer");
return ( G_SOURCE_REMOVE );
}
gss_receive = g_socket_receive(gSock, pctrl->ch_buff, sizeof(pctrl->ch_buff), NULL, &error);
if (error != NULL) { // gss_receive = Number of bytes read, or 0 if the connection was closed by the peer, or -1 on error
g_error("g_socket_receive() => %s", error->message);
g_clear_error(&error);
}
if (gss_receive > 0 ) {
pctrl->ch_buff[gss_receive] = 0;
g_snprintf(pctrl->ch_response, sizeof(pctrl->ch_response), "[%s]==> %s", pctrl->ch_display_service_ip, pctrl->ch_buff);
} else {
g_snprintf(pctrl->ch_response, sizeof(pctrl->ch_response), "%s from: %s", "Error: Input not Usable", pctrl->ch_display_service_ip);
}
g_print("%s\n", pctrl->ch_response);
return (G_SOURCE_CONTINUE);
}
gboolean cb_udp_comm_request_handler(PControlData pctrl) {
GError *error = NULL;
if ( NULL == pctrl) { /* SHUTDOWN THE MAIN LOOP */
g_message("gtk_display_client::cb_udp_comm_request_handler(error) Invalid Pointer");
return ( G_SOURCE_REMOVE );
}
g_socket_send_to (pctrl->gSock, pctrl->gsDSAddr, pctrl->ch_message, strlen(pctrl->ch_message), NULL, &error);
if (error != NULL) { // gss_send = Number of bytes written (which may be less than size ), or -1 on error
g_error("g_socket_send_to() => %s", error->message);
g_clear_error(&error);
pctrl->gErrorCount += 1;
if(pctrl->gErrorCount > 10) {
g_message("gtk_display_client::cb_udp_comm_request_handler(error) Display Service @ %s, Not Responding!", pctrl->ch_display_service_ip);
g_main_loop_quit(pctrl->loop);
return ( G_SOURCE_REMOVE );
} else {
return (G_SOURCE_CONTINUE);
}
}
pctrl->gErrorCount = 0;
g_print("%s\n", pctrl->ch_message);
return (G_SOURCE_CONTINUE);
}
gboolean cb_unix_signal_handler(PUSignalData psig) {
g_source_remove(psig->gTimeoutId);
g_message("gtk_display_client::cb_unix_signal_handler() %s Unix Signal Received => Shutdown Initiated!\n", psig->signalName);
g_main_loop_quit(psig->loop);
return ( G_SOURCE_REMOVE );
}
int main(int argc, char **argv) {
ControlData cData;
USignalData sigTerm;
USignalData sigInt;
USignalData sigHup;
GSource * gCommSource = NULL;
GError *error = NULL;
GSocketAddress *gsAddr = NULL;
GInetAddress *anyAddr = NULL;
guint gMsgDelay = MESSAGE_DELAY_INTERVAL;
guint gUDPPort = UDP_COMM_PORT;
gchar *ch_display_service_ip = NULL; // g_free() if received
gchar *ch_message = "GTK-+3.0 Rocks with GLIB-2.0 on any platform!"; // g_free() if received
GOptionContext *gOptions = NULL;
GOptionEntry pgmOptions[] = {
{"display_service_ip", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &ch_display_service_ip, "IP Address of DisplayService", "pattern 'd.d.d.d'"},
{"display_service_udp_port", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &gUDPPort, "UDP Port Number for DisplayService.", "port number defaults to 48029"},
{"display_message", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &ch_message, "Message to send to DisplayService", "single-quoted-string"},
{"message_interval_delay", 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &gMsgDelay, "Send one message every Interval.", "seconds range 1 to 300, default=10"},
{NULL}
};
gOptions = g_option_context_new ("UDP message display client for IOT; requires -a, Display Service IP Address");
g_option_context_add_main_entries (gOptions, pgmOptions, NULL);
g_option_context_parse (gOptions, &argc, &argv, &error);
if (error != NULL) {
g_error("g_option_context_parse() => %s", error->message);
g_clear_error(&error);
exit(EXIT_FAILURE);
}
g_option_context_free(gOptions);
if (NULL == ch_display_service_ip) {
g_error("IP Address of Display Service is Required!");
exit(EXIT_FAILURE);
} else {
g_snprintf(cData.ch_display_service_ip, sizeof(cData.ch_display_service_ip), "%s", ch_display_service_ip);
g_snprintf(cData.ch_message, sizeof(cData.ch_message), "%s", ch_message);
g_free(ch_display_service_ip);
}
sigHup.loop = sigTerm.loop = sigInt.loop = cData.loop = g_main_loop_new(NULL, FALSE);
sigTerm.signalName = "SIGTERM";
sigInt.signalName = "SIGINT";
sigHup.signalName = "SIGHUP";
cData.gErrorCount = 0;
cData.gSock = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_UDP, &error);
if (error != NULL) {
g_error("g_socket_new() => %s", error->message);
g_clear_error(&error);
exit(EXIT_FAILURE);
}
/* Local Address */
anyAddr = g_inet_address_new_any(G_SOCKET_FAMILY_IPV4);
gsAddr = g_inet_socket_address_new(anyAddr, 0);
g_object_unref(anyAddr);
/* Display Service Address */
anyAddr = g_inet_address_new_from_string(cData.ch_display_service_ip); // g_object_unref(dIP) when done
if (NULL == anyAddr) {
g_error("g_inet_address_new_from_string() Failed => %s", cData.ch_display_service_ip);
exit(EXIT_FAILURE);
}
cData.gsDSAddr = g_inet_socket_address_new(anyAddr, gUDPPort);
g_object_unref(anyAddr);
g_socket_bind(cData.gSock, gsAddr, TRUE, &error);
if (error != NULL) {
g_error("g_socket_bind() => %s", error->message);
g_clear_error(&error);
exit(EXIT_FAILURE);
}
/*
* Setup Timer to drive repeated Message to Display Service */
sigHup.gTimeoutId = sigTerm.gTimeoutId = sigInt.gTimeoutId = g_timeout_add ((gMsgDelay * 1000), (GSourceFunc)cb_udp_comm_request_handler, &cData);
/*
* Create and Add socket to main loop for receiving responses (i.e. polling socket) -- i.e. will not block */
gCommSource = g_socket_create_source (cData.gSock, G_IO_IN, NULL);
g_source_set_callback (gCommSource, (GSourceFunc) cb_udp_comm_response_handler, &cData, NULL); // its really a GSocketSourceFunc
g_source_attach (gCommSource, NULL);
/*
* Handle ctrl-break and kill signals cleanly */
g_unix_signal_add (SIGINT, (GSourceFunc) cb_unix_signal_handler, &sigInt); // SIGINT signal (Ctrl+C)
g_unix_signal_add (SIGHUP, (GSourceFunc) cb_unix_signal_handler, &sigHup);
g_unix_signal_add (SIGTERM,(GSourceFunc) cb_unix_signal_handler, &sigTerm);
g_message("gtk_display_client: Sending to display service at %s, on %d second intervals...\n", cData.ch_display_service_ip, gMsgDelay);
g_main_loop_run(cData.loop);
g_main_loop_unref(cData.loop);
g_socket_close(cData.gSock, NULL);
g_object_unref(cData.gSock);
g_source_unref(gCommSource);
g_object_unref(cData.gsDSAddr);
g_object_unref(gsAddr);
g_message("gtk_display_client: shutdown...");
exit(EXIT_SUCCESS);
}
/**
* gtk_display_service.c
* IOT message display service
* gcc `pkg-config --cflags --libs gtk+-3.0` -O3 -Wall -o gtkDisplayService gtkDisplayService.c
*
* Program Flow:
* 1. Initialize -> parse_options
* 2. upd_socket -> loop -> receive_message_and_push_queue
* 2. AsyncQueue -> loop -> update_listbox_head -> if max_count then delete_listbox_tail
* 2. unix_signal -> loop -> close_loop
* 2. GtkWindow -> loop -> destroy_signal_callback -> close_loop
* 3. Cleanup -> exit
*/
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <glib-unix.h>
#define SZ_SCRATCH_BUFF 1024
#define SZ_RMTADDR_BUFF 256
#define SZ_MESSAGE_BUFF 512
#define SZ_RESPONSE_BUFF 256
#define UDP_COMM_PORT 48029
#define MAX_MESSAGES_VIEWABLE 64
typedef struct _controlData {
guint gMsgSourceId;
guint gCommSourceId;
guint gMsgCount;
GResolver *resolver;
GMainLoop *loop;
GAsyncQueue *queue;
GtkWidget *listBox;
GtkWidget *pg2Target;
gchar ch_buffer[SZ_SCRATCH_BUFF];
} ControlData, *PControlData;
typedef struct _messageData {
gchar ch_remoteAddress[SZ_RMTADDR_BUFF];
gchar ch_message[SZ_MESSAGE_BUFF];
} MsgData, *PMsgData;
typedef struct _signalData {
GMainLoop *loop;
gchar * signalName;
} USignalData, *PUSignalData;
gchar * gio_condition_to_string(GIOCondition condition);
gboolean cb_message_request_handler(PMsgData msg, PControlData pctrl);
gboolean cb_udp_comm_request_handler(GSocket *socket, GIOCondition condition, PControlData pctrl);
gboolean cb_unix_signal_handler(PUSignalData psig);
gboolean listbox_message_viewable_limiter(GtkWidget *listBox, guint maximum_rows);
gboolean listbox_message_create_entry(GtkWidget *listBox, PMsgData msg);
GtkWidget * ui_page_layout(GtkWidget *parent, PControlData pctrl);
GtkWidget * ui_message_page_new(GtkWidget *parent);
GtkWidget * ui_registry_page_new(GtkWidget *parent);
/**
* MessageQueueSource:
*
* Ref: https://developer.gnome.org/gnome-devel-demos/stable/custom-gsource.c.html.en
*
* This is a #GSource which wraps a #GAsyncQueue and is dispatched whenever a
* message can be pulled off the queue. Messages can be enqueued from any
* thread.
*
* The callbacks dispatched by a #MessageQueueSource have type
* #MessageQueueSourceFunc.
*
* #MessageQueueSource supports adding a #GCancellable child source which will
* additionally dispatch if a provided #GCancellable is cancelled.
*/
typedef struct {
GSource parent;
GAsyncQueue *queue; /* owned */
GDestroyNotify destroy_message;
} MessageQueueSource;
/**
* MessageQueueSourceFunc:
* @message: (transfer full) (nullable): message pulled off the queue
* @user_data: user data provided to g_source_set_callback()
*
* Callback function type for #MessageQueueSource.
*/
typedef gboolean (*MessageQueueSourceFunc) (gpointer message, gpointer user_data);
static gboolean message_queue_source_prepare (GSource *source, gint *timeout_) {
MessageQueueSource *message_queue_source = (MessageQueueSource *) source;
return (g_async_queue_length (message_queue_source->queue) > 0);
}
static gboolean message_queue_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) {
MessageQueueSource *message_queue_source = (MessageQueueSource *) source;
gpointer message;
MessageQueueSourceFunc func = (MessageQueueSourceFunc) callback;
/* Pop a message off the queue. */
message = g_async_queue_try_pop (message_queue_source->queue);
/* If there was no message, bail. */
if (message == NULL)
{
/* Keep the source around to handle the next message. */
return TRUE;
}
/* @func may be %NULL if no callback was specified.
* If so, drop the message. */
if (func == NULL)
{
if (message_queue_source->destroy_message != NULL)
{
message_queue_source->destroy_message (message);
}
/* Keep the source around to consume the next message. */
return TRUE;
}
return (func(message, user_data));
}
static void message_queue_source_finalize (GSource *source) {
MessageQueueSource *message_queue_source = (MessageQueueSource *) source;
g_async_queue_unref (message_queue_source->queue);
}
static gboolean message_queue_source_closure_callback (gpointer message, gpointer user_data) {
GClosure *closure = user_data;
GValue param_value = G_VALUE_INIT;
GValue result_value = G_VALUE_INIT;
gboolean retval;
g_warn_if_reached();
/* The invoked function is responsible for freeing @message. */
g_value_init (&result_value, G_TYPE_BOOLEAN);
g_value_init (&param_value, G_TYPE_POINTER);
g_value_set_pointer (&param_value, message);
g_closure_invoke (closure, &result_value, 1, &param_value, NULL);
retval = g_value_get_boolean (&result_value);
g_value_unset (&param_value);
g_value_unset (&result_value);
return (retval);
}
static GSourceFuncs message_queue_source_funcs =
{
message_queue_source_prepare,
NULL, /* check */
message_queue_source_dispatch,
message_queue_source_finalize,
(GSourceFunc) message_queue_source_closure_callback,
NULL,
};
/**
* message_queue_source_new:
* @queue: the queue to check
* @destroy_message: (nullable): function to free a message, or %NULL
* @cancellable: (nullable): a #GCancellable, or %NULL
*
* Create a new #MessageQueueSource, a type of #GSource which dispatches for
* each message queued to it.
*
* If a callback function of type #MessageQueueSourceFunc is connected to the
* returned #GSource using g_source_set_callback(), it will be invoked for each
* message, with the message passed as its first argument. It is responsible for
* freeing the message. If no callback is set, messages are automatically freed
* as they are queued.
*
* Returns: (transfer full): a new #MessageQueueSource
*/
GSource * message_queue_source_new (GAsyncQueue *queue, GDestroyNotify destroy_message, GCancellable *cancellable) {
GSource *source; /* alias of @message_queue_source */
MessageQueueSource *message_queue_source; /* alias of @source */
g_return_val_if_fail (queue != NULL, NULL);
g_return_val_if_fail (cancellable == NULL ||
G_IS_CANCELLABLE (cancellable), NULL);
source = g_source_new (&message_queue_source_funcs, sizeof (MessageQueueSource));
message_queue_source = (MessageQueueSource *) source;
/* The caller can overwrite this name with something more useful later. */
g_source_set_name (source, "MessageQueueSource");
message_queue_source->queue = g_async_queue_ref (queue);
message_queue_source->destroy_message = destroy_message;
/* Add a cancellable source. */
if (cancellable != NULL)
{
GSource *cancellable_source;
cancellable_source = g_cancellable_source_new (cancellable);
g_source_set_dummy_callback (cancellable_source);
g_source_add_child_source (source, cancellable_source);
g_source_unref (cancellable_source);
}
return (source);
}
gboolean cb_unix_signal_handler(PUSignalData psig) {
g_message("gtk_display_service::cb_unix_signal_handler() %s Unix Signal Received => Shutdown Initiated!\n", psig->signalName);
g_main_loop_quit(psig->loop);
return ( G_SOURCE_REMOVE );
}
gboolean cb_udp_comm_request_handler(GSocket *gSock, GIOCondition condition, PControlData pctrl) {
GError *error = NULL;
GSocketAddress *gsRmtAddr = NULL;
GInetAddress *gsAddr = NULL;
PMsgData message = NULL;
gchar * rmtHost = NULL;
gssize gss_send = 0;
gssize gss_receive = 0;
if ((condition & G_IO_HUP) || (condition & G_IO_ERR) || (condition & G_IO_NVAL)) { /* SHUTDOWN THE MAIN LOOP */
g_message("gtk_display_service::cb_udp_comm_request_handler(error) Operational Error / Shutdown Signaled => %s\n", gio_condition_to_string(condition));
g_main_loop_quit(pctrl->loop);
return ( G_SOURCE_REMOVE );
}
if (condition != G_IO_IN) {
g_message("gtk_display_service::cb_udp_comm_request_handler(error) NOT G_IO_IN => %s\n", gio_condition_to_string(condition));
return (G_SOURCE_CONTINUE);
}
/*
* Allocate a new queue message and read incoming request directly into it */
message = g_new0(MsgData,1);
/*
* If socket times out before reading data any operation will error with 'G_IO_ERROR_TIMED_OUT'.
* Read Request Message and get Requestor IP Address or Name
*/
gss_receive = g_socket_receive_from (gSock, &gsRmtAddr, message->ch_message, sizeof(message->ch_message), NULL, &error);
if (error != NULL) { // gss_receive = Number of bytes read, or 0 if the connection was closed by the peer, or -1 on error
g_error("g_socket_receive_from() => %s", error->message);
g_clear_error(&error);
g_free(message);
return (G_SOURCE_CONTINUE);
}
if (gss_receive > 0 ) {
if (G_IS_INET_SOCKET_ADDRESS(gsRmtAddr) ) {
gsAddr = g_inet_socket_address_get_address( G_INET_SOCKET_ADDRESS(gsRmtAddr) );
if ( G_IS_INET_ADDRESS(gsAddr) ) {
g_object_ref(gsAddr);
rmtHost = g_resolver_lookup_by_address (pctrl->resolver, gsAddr, NULL, NULL);
if (NULL == rmtHost) {
rmtHost = g_inet_address_to_string ( gsAddr);
}
}
}
g_snprintf(message->ch_remoteAddress, sizeof(message->ch_remoteAddress), "%s", rmtHost);
g_free(rmtHost);
g_snprintf(pctrl->ch_buffer, sizeof(pctrl->ch_buffer), "%d %s", 202, "Accepted");
} else {
g_snprintf(message->ch_message, sizeof(message->ch_message), "%s", "Error: Input not Usable");
g_snprintf(pctrl->ch_buffer, sizeof(pctrl->ch_buffer), "%d %s", 406, "Not Acceptable");
}
/*
* Send Response to caller */
gss_send = g_socket_send_to (gSock, gsRmtAddr, pctrl->ch_buffer, strlen(pctrl->ch_buffer), NULL, &error);
if (error != NULL) { // gss_send = Number of bytes written (which may be less than size ), or -1 on error
g_error("g_socket_send_to() => %s", error->message);
g_free(message);
g_clear_error(&error);
if ( G_IS_INET_ADDRESS(gsAddr) )
g_object_unref(gsAddr);
if ( G_IS_INET_SOCKET_ADDRESS(gsRmtAddr) )
g_object_unref(gsRmtAddr);
return (G_SOURCE_CONTINUE);
}
/*
* Send it to be processed by a message handler */
g_async_queue_push (pctrl->queue, message);
if ( G_IS_INET_ADDRESS(gsAddr) )
g_object_unref(gsAddr);
if ( G_IS_INET_SOCKET_ADDRESS(gsRmtAddr) )
g_object_unref(gsRmtAddr);
return (G_SOURCE_CONTINUE);
}
/**
* Remove GtkListBox entrys once limit is exceeded
* @param pctrl Pointer to struc with listBox reference
* @param maximum_rows maximum row count to retain in listbox
* @return true if item was deleted, false if no item was deleted
*/
gboolean listbox_message_viewable_limiter(GtkWidget *listBox, guint maximum_rows) {
GList *lbEntries = NULL;
GList *targetLBR = NULL;
GList *targetHB = NULL;
guint count = 0;
gboolean result = FALSE;
g_return_val_if_fail( (NULL != listBox) && (maximum_rows > 0), FALSE);
lbEntries = gtk_container_get_children (GTK_CONTAINER(listBox));
lbEntries = g_list_first(lbEntries);
count = g_list_length(lbEntries);
if (count > maximum_rows) {
targetLBR = g_list_nth(lbEntries, (count - 1));
if (NULL != targetLBR) {
if ( GTK_IS_LIST_BOX_ROW(targetLBR->data) ) {
targetHB = gtk_container_get_children (GTK_CONTAINER(targetLBR->data));
targetHB = g_list_first(targetHB);
if ( GTK_IS_BOX(targetHB->data) ) {
gtk_widget_destroy( GTK_WIDGET(targetHB->data) ); // destroy the GtkBox holding message
gtk_widget_destroy( GTK_WIDGET(targetLBR->data) ); // destroy the GtkListBoxRow that held the GtkBox
}
g_list_free(targetHB);
result = TRUE;
} else {
gtk_widget_destroy( GTK_WIDGET(targetLBR->data) ); // destroying whatever this is causing a memory leak
result = TRUE;
g_warn_if_reached();
}
}
g_list_free(lbEntries);
}
return(result);
}
gboolean listbox_message_create_entry(GtkWidget *listBox, PMsgData msg) {
GtkWidget *lbBox = NULL;
GtkWidget *lbBoxLabel = NULL;
gboolean result = TRUE;
g_return_val_if_fail ((NULL != listBox) && (NULL != msg), FALSE);
/*
* make message elements for the listbox */
lbBox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
lbBoxLabel = gtk_label_new(msg->ch_remoteAddress);
gtk_label_set_justify(GTK_LABEL(lbBoxLabel), GTK_JUSTIFY_LEFT);
gtk_label_set_line_wrap (GTK_LABEL(lbBoxLabel), TRUE);
gtk_box_pack_start(GTK_BOX(lbBox),lbBoxLabel,FALSE,TRUE,6);
lbBoxLabel = gtk_label_new(msg->ch_message);
gtk_label_set_justify(GTK_LABEL(lbBoxLabel), GTK_JUSTIFY_LEFT);
gtk_label_set_line_wrap (GTK_LABEL(lbBoxLabel), TRUE);
gtk_label_set_max_width_chars (GTK_LABEL (lbBoxLabel), 512);
/* messages look centered because we used '_end' vs '_start' */
gtk_box_pack_end(GTK_BOX(lbBox),lbBoxLabel,TRUE,TRUE,6);
gtk_list_box_prepend(GTK_LIST_BOX(listBox), lbBox);
gtk_widget_show_all(GTK_WIDGET(lbBox));
return(result);
}
//MessageQueueSourceFunc
gboolean cb_message_request_handler(PMsgData msg, PControlData pctrl) {
g_return_val_if_fail((NULL != msg) && (NULL != pctrl), G_SOURCE_CONTINUE);
listbox_message_create_entry(pctrl->listBox, msg);
// g_print("[%06d][%20s] %s\n", pctrl->gMsgCount, msg->ch_remoteAddress, msg->ch_message);
pctrl->gMsgCount += 1;
/*
* Cleanup -- no more than MAX_MESSAGES_VIEWABLE:64 */
if (pctrl->gMsgCount >= MAX_MESSAGES_VIEWABLE) {
listbox_message_viewable_limiter( pctrl->listBox, MAX_MESSAGES_VIEWABLE);
}
g_free(msg);
return(G_SOURCE_CONTINUE);
}
gchar * gio_condition_to_string(GIOCondition condition) {
gchar *value = NULL;
switch(condition) {
case G_IO_IN:
value = "There is data to read.";
break;
case G_IO_OUT:
value = "Data can be written (without blocking).";
break;
case G_IO_PRI:
value = "There is urgent data to read.";
break;
case G_IO_ERR:
value = "Error condition.";
break;
case G_IO_HUP:
value = "Hung up (the connection has been broken, usually for pipes and sockets).";
break;
case G_IO_NVAL:
value = "Invalid request. The file descriptor is not open.";
break;
default:
value = "Unknown GIOCondition!";
}
return (value);
}
GtkWidget * ui_message_page_new(GtkWidget *parent) {
GtkWidget *listBox = NULL;
GtkWidget *scrolled = NULL;
GtkWidget *lbBox = NULL;
GtkWidget *lbBoxLabel = NULL;
lbBox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
lbBoxLabel = gtk_label_new("Node");
gtk_box_pack_start(GTK_BOX(lbBox),lbBoxLabel,FALSE,TRUE,6);
lbBoxLabel = gtk_label_new("Messages");
gtk_box_pack_end(GTK_BOX(lbBox),lbBoxLabel,TRUE,TRUE,6);
gtk_box_pack_start(GTK_BOX(parent),lbBox,FALSE,FALSE,2);
/*
* main container is a listbox wrapped by a scrollable window */
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled),GTK_SHADOW_ETCHED_OUT);
gtk_box_pack_start (GTK_BOX (parent), scrolled, TRUE, TRUE, 0);
listBox = gtk_list_box_new();
gtk_container_add (GTK_CONTAINER (scrolled), listBox);
gtk_widget_show_all(lbBox);
return(listBox);
}
GtkWidget * ui_registry_page_new(GtkWidget *parent) {
GtkWidget *listBox = NULL;
GtkWidget *scrolled = NULL;
GtkWidget *lbBox = NULL;
GtkWidget *lbBoxLabel = NULL;
lbBox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
lbBoxLabel = gtk_label_new("Name");
gtk_box_pack_start(GTK_BOX(lbBox),lbBoxLabel,FALSE,TRUE,6);
lbBoxLabel = gtk_label_new("Address");
gtk_box_pack_start(GTK_BOX(lbBox),lbBoxLabel,FALSE,TRUE,6);
lbBoxLabel = gtk_label_new("Port");
gtk_box_pack_end(GTK_BOX(lbBox),lbBoxLabel,TRUE,TRUE,6);
gtk_box_pack_start(GTK_BOX(parent),lbBox,FALSE,FALSE,2);
/*
* main container is a listbox wrapped by a scrollable window */
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled),GTK_SHADOW_ETCHED_OUT);
gtk_box_pack_start (GTK_BOX (parent), scrolled, TRUE, TRUE, 0);
listBox = gtk_list_box_new();
gtk_container_add (GTK_CONTAINER (scrolled), listBox);
gtk_widget_show_all(lbBox);
return(listBox);
}
GtkWidget * ui_page_layout(GtkWidget *parent, PControlData pctrl) {
GtkWidget *grid = NULL;
GtkWidget *switcher = NULL;
GtkWidget *stack = NULL;
GtkWidget *pg1Box = NULL;
GtkWidget *pg2Box = NULL;
grid = gtk_grid_new(); // left, top, width, hieght
switcher = gtk_stack_switcher_new();
gtk_widget_set_hexpand (switcher, TRUE);
gtk_widget_set_halign (switcher, GTK_ALIGN_CENTER);
gtk_grid_attach (GTK_GRID (grid), switcher, 0, 0, 1, 1);
stack = gtk_stack_new();
gtk_widget_set_hexpand (stack, TRUE);
gtk_widget_set_vexpand(stack, TRUE);
gtk_stack_set_homogeneous( GTK_STACK(stack), TRUE);
gtk_stack_set_transition_type( GTK_STACK(stack), GTK_STACK_TRANSITION_TYPE_CROSSFADE);
gtk_stack_set_transition_duration( GTK_STACK(stack), 400);
gtk_grid_attach (GTK_GRID (grid), stack, 0, 1, 1, 1);
pg1Box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
pctrl->listBox = ui_message_page_new(pg1Box);
gtk_stack_add_titled ( GTK_STACK(stack), GTK_WIDGET(pg1Box), "messages", "Messages");
pg2Box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
pctrl->pg2Target = ui_registry_page_new(pg2Box);
gtk_stack_add_titled ( GTK_STACK(stack), GTK_WIDGET(pg2Box), "registry", "Registry");
gtk_stack_switcher_set_stack (GTK_STACK_SWITCHER(switcher), GTK_STACK(stack));
gtk_stack_set_visible_child (GTK_STACK(stack), GTK_WIDGET(pg1Box));
gtk_container_add (GTK_CONTAINER(parent), grid);
gtk_widget_show_all(grid);
return (stack);
}
void cb_gtk_shutdown (GtkWidget *object, gpointer data) {
PUSignalData psig = (PUSignalData)data;
g_message("gtk_display_service::cb_gtk_shutdown() %s Destroy Signal Received => Shutdown Initiated!\n", psig->signalName);
g_main_loop_quit(psig->loop);
}
int main(int argc, char **argv) {
GError *error = NULL;
GMainLoop *loop = NULL;
GSocket *gSock = NULL;
GSocketAddress *gsAddr = NULL;
GSource * gCommSource = NULL;
GSource * gMsgSource = NULL;
guint gCommSourceId = 0;
guint gMsgSourceId = 0;
ControlData cData;
GInetAddress *anyAddr = NULL;
USignalData sigTerm;
USignalData sigInt;
USignalData winDestroy;
GtkWidget *window = NULL;
guint gUDPPort = UDP_COMM_PORT;
GOptionContext *gOptions = NULL;
GOptionEntry pgmOptions[] = {
{"udp_port_number", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &gUDPPort, "UDP Port Number to listen on.", "port number defaults to 48029"},
{NULL}
};
gOptions = g_option_context_new ("UDP message display service for IOT.");
g_option_context_add_group (gOptions, (GOptionGroup *) gtk_get_option_group(TRUE) );
g_option_context_add_main_entries (gOptions, pgmOptions, NULL);
g_option_context_parse (gOptions, &argc, &argv, &error);
if (error != NULL) {
g_error("g_option_context_parse() => %s", error->message);
g_clear_error(&error);
exit(EXIT_FAILURE);
}
g_option_context_free(gOptions);
winDestroy.loop = sigTerm.loop = sigInt.loop = cData.loop = loop = g_main_loop_new(NULL, FALSE);
sigTerm.signalName = "SIGTERM";
sigInt.signalName = "SIGINT";
winDestroy.signalName = "gtkDestroyWindow";
cData.resolver = g_resolver_get_default();
cData.queue = g_async_queue_new();
cData.gMsgCount = 0;
gSock = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_UDP, &error);
if (error != NULL) {
g_error("g_socket_new() => %s", error->message);
g_clear_error(&error);
exit(EXIT_FAILURE);
}
anyAddr = g_inet_address_new_any(G_SOCKET_FAMILY_IPV4);
gsAddr = g_inet_socket_address_new(anyAddr, gUDPPort);
g_socket_bind(gSock, gsAddr, TRUE, &error);
if (error != NULL) {
g_error("g_socket_bind() => %s", error->message);
g_clear_error(&error);
exit(EXIT_FAILURE);
}
/*
* Create and Add socket to main loop for service (i.e. polling socket) */
gCommSource = g_socket_create_source (gSock, G_IO_IN, NULL);
g_source_ref(gCommSource);
g_source_set_callback (gCommSource, (GSourceFunc) cb_udp_comm_request_handler, &cData, NULL); // its really a GSocketSourceFunc
cData.gCommSourceId = gCommSourceId = g_source_attach (gCommSource, NULL);
/*
* Create and Add MessageQueue to main loop for service (i.e. incoming messages) */
gMsgSource = message_queue_source_new (cData.queue, (GDestroyNotify) g_free, NULL);
g_source_ref(gMsgSource);
g_source_set_callback (gMsgSource, (GSourceFunc)cb_message_request_handler, &cData, (GDestroyNotify) g_object_unref);
cData.gMsgSourceId = gMsgSourceId = g_source_attach (gMsgSource, NULL);
/*
* Handle ctrl-break and kill signals cleanly */
g_unix_signal_add (SIGINT, (GSourceFunc) cb_unix_signal_handler, &sigInt); // SIGINT signal (Ctrl+C)
g_unix_signal_add (SIGTERM,(GSourceFunc) cb_unix_signal_handler, &sigTerm);
/*
* Create a GTK+3 Page to show messages */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Gtk Display Service");
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_container_set_border_width(GTK_CONTAINER(window), 10);
gtk_window_set_default_size (GTK_WINDOW (window), 400, 300);
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(cb_gtk_shutdown), &winDestroy);
/*
* make elements for the Page Header */
ui_page_layout(GTK_WIDGET(window), &cData);
gtk_widget_show_all(GTK_WIDGET(window));
g_message("gtk_display_service: Ready to do Good, on port: %d...\n", gUDPPort);
g_main_loop_run(loop);
g_source_unref(gCommSource);
g_async_queue_unref(cData.queue);
g_source_unref(gMsgSource);
g_object_unref(gSock);
g_object_unref(anyAddr);
g_object_unref(gsAddr);
g_object_unref(cData.resolver);
g_main_loop_unref(loop);
g_message("gtk_display_service: shutdown...");
exit(EXIT_SUCCESS);
}
/**
* gtk_display_service.c
* IOT message display service
* gcc `pkg-config --cflags --libs gtk+-3.0` -O3 -Wall -o gtkDisplayService gtkDisplayService.c
*
* Program Flow:
* 1. Initialize -> parse_options
* 2. upd_socket -> loop -> receive_message_and_push_queue
* 2. AsyncQueue -> loop -> update_GtkTreeView_head -> if max_count then delete_GtkTreeView_tail
* 2. unix_signal -> loop -> close_loop
* 2. GtkWindow -> loop -> destroy_signal_callback -> close_loop
* 3. Cleanup -> exit
*/
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <glib-unix.h>
#define SZ_SCRATCH_BUFF 1024
#define SZ_RMTADDR_BUFF 256
#define SZ_MESSAGE_BUFF 512
#define SZ_RESPONSE_BUFF 256
#define UDP_COMM_PORT 48029
#define MAX_MESSAGES_VIEWABLE 64
typedef struct _controlData {
guint gMsgSourceId;
guint gRegSourceId;
guint gCommSourceId;
guint gMsgCount;
guint gRegCount;
GResolver *resolver;
GMainLoop *loop;
GAsyncQueue *queueMessage;
GAsyncQueue *queueRegistry;
GtkWidget *pg1Target;
GtkWidget *pg2Target;
gchar ch_buffer[SZ_SCRATCH_BUFF];
} ControlData, *PControlData;
typedef struct _messageData {
gchar ch_remoteAddress[SZ_RMTADDR_BUFF];
gchar ch_message[SZ_MESSAGE_BUFF];
} MsgData, *PMsgData;
typedef struct _signalData {
GMainLoop *loop;
gchar * signalName;
} USignalData, *PUSignalData;
typedef struct _registryData {
gchar ch_network_message[SZ_RMTADDR_BUFF + 64];
gchar ch_name[SZ_RMTADDR_BUFF];
gchar ch_ip[SZ_RMTADDR_BUFF];
gchar ch_port[SZ_RMTADDR_BUFF];
} RegData, *PRegData;
enum _messages {
COLUMN_NODE,
COLUMN_MESSAGES,
MSG_NUM_COLUMNS
} EMessages;
enum _registry {
COLUMN_NAME,
COLUMN_IP,
COLUMN_PORT,
REG_NUM_COLUMNS
} ERegistry;
static gchar * gio_condition_to_string(GIOCondition condition);
static gboolean cb_message_request_handler(PMsgData msg, PControlData pctrl);
static gboolean cb_registry_request_handler(PRegData reg, PControlData pctrl);
static gboolean cb_udp_comm_request_handler(GSocket *socket, GIOCondition condition, PControlData pctrl);
static gboolean cb_unix_signal_handler(PUSignalData psig);
static GtkWidget * ui_page_layout(GtkWidget *parent, PControlData pctrl);
static GtkWidget * ui_message_page_new(GtkWidget *parent);
static GtkWidget * ui_registry_page_new(GtkWidget *parent);
static gboolean ui_add_message_entry(GtkWidget *treeview, PMsgData pMsg, gboolean limiter);
static gboolean ui_add_registry_entry(GtkWidget *treeview, PRegData pReg, gboolean limiter);
/**
* MessageQueueSource:
*
* Ref: https://developer.gnome.org/gnome-devel-demos/stable/custom-gsource.c.html.en
*
* This is a #GSource which wraps a #GAsyncQueue and is dispatched whenever a
* message can be pulled off the queue. Messages can be enqueued from any
* thread.
*
* The callbacks dispatched by a #MessageQueueSource have type
* #MessageQueueSourceFunc.
*
* #MessageQueueSource supports adding a #GCancellable child source which will
* additionally dispatch if a provided #GCancellable is cancelled.
*/
typedef struct {
GSource parent;
GAsyncQueue *queue; /* owned */
GDestroyNotify destroy_message;
} MessageQueueSource;
/**
* MessageQueueSourceFunc:
* @message: (transfer full) (nullable): message pulled off the queue
* @user_data: user data provided to g_source_set_callback()
*
* Callback function type for #MessageQueueSource.
*/
typedef gboolean (*MessageQueueSourceFunc) (gpointer message, gpointer user_data);
static gboolean message_queue_source_prepare (GSource *source, gint *timeout_) {
MessageQueueSource *message_queue_source = (MessageQueueSource *) source;
return (g_async_queue_length (message_queue_source->queue) > 0);
}
static gboolean message_queue_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) {
MessageQueueSource *message_queue_source = (MessageQueueSource *) source;
gpointer message;
MessageQueueSourceFunc func = (MessageQueueSourceFunc) callback;
/* Pop a message off the queue. */
message = g_async_queue_try_pop (message_queue_source->queue);
/* If there was no message, bail. */
if (message == NULL)
{
/* Keep the source around to handle the next message. */
return TRUE;
}
/* @func may be %NULL if no callback was specified.
* If so, drop the message. */
if (func == NULL)
{
if (message_queue_source->destroy_message != NULL)
{
message_queue_source->destroy_message (message);
}
/* Keep the source around to consume the next message. */
return TRUE;
}
return (func(message, user_data));
}
static void message_queue_source_finalize (GSource *source) {
MessageQueueSource *message_queue_source = (MessageQueueSource *) source;
g_async_queue_unref (message_queue_source->queue);
}
static gboolean message_queue_source_closure_callback (gpointer message, gpointer user_data) {
GClosure *closure = user_data;
GValue param_value = G_VALUE_INIT;
GValue result_value = G_VALUE_INIT;
gboolean retval;
g_warn_if_reached();
/* The invoked function is responsible for freeing @message. */
g_value_init (&result_value, G_TYPE_BOOLEAN);
g_value_init (&param_value, G_TYPE_POINTER);
g_value_set_pointer (&param_value, message);
g_closure_invoke (closure, &result_value, 1, &param_value, NULL);
retval = g_value_get_boolean (&result_value);
g_value_unset (&param_value);
g_value_unset (&result_value);
return (retval);
}
static GSourceFuncs message_queue_source_funcs =
{
message_queue_source_prepare,
NULL, /* check */
message_queue_source_dispatch,
message_queue_source_finalize,
(GSourceFunc) message_queue_source_closure_callback,
NULL,
};
/**
* message_queue_source_new:
* @queue: the queue to check
* @destroy_message: (nullable): function to free a message, or %NULL
* @cancellable: (nullable): a #GCancellable, or %NULL
*
* Create a new #MessageQueueSource, a type of #GSource which dispatches for
* each message queued to it.
*
* If a callback function of type #MessageQueueSourceFunc is connected to the
* returned #GSource using g_source_set_callback(), it will be invoked for each
* message, with the message passed as its first argument. It is responsible for
* freeing the message. If no callback is set, messages are automatically freed
* as they are queued.
*
* Returns: (transfer full): a new #MessageQueueSource
*/
GSource * message_queue_source_new (GAsyncQueue *queue, GDestroyNotify destroy_message, GCancellable *cancellable) {
GSource *source; /* alias of @message_queue_source */
MessageQueueSource *message_queue_source; /* alias of @source */
g_return_val_if_fail (queue != NULL, NULL);
g_return_val_if_fail (cancellable == NULL ||
G_IS_CANCELLABLE (cancellable), NULL);
source = g_source_new (&message_queue_source_funcs, sizeof (MessageQueueSource));
message_queue_source = (MessageQueueSource *) source;
/* The caller can overwrite this name with something more useful later. */
g_source_set_name (source, "MessageQueueSource");
message_queue_source->queue = g_async_queue_ref (queue);
message_queue_source->destroy_message = destroy_message;
/* Add a cancellable source. */
if (cancellable != NULL)
{
GSource *cancellable_source;
cancellable_source = g_cancellable_source_new (cancellable);
g_source_set_dummy_callback (cancellable_source);
g_source_add_child_source (source, cancellable_source);
g_source_unref (cancellable_source);
}
return (source);
}
static gboolean cb_unix_signal_handler(PUSignalData psig) {
g_message("gtk_display_service::cb_unix_signal_handler() %s Unix Signal Received => Shutdown Initiated!\n", psig->signalName);
g_main_loop_quit(psig->loop);
return ( G_SOURCE_REMOVE );
}
static gboolean cb_udp_comm_request_handler(GSocket *gSock, GIOCondition condition, PControlData pctrl) {
GError *error = NULL;
GSocketAddress *gsRmtAddr = NULL;
GInetAddress *gsAddr = NULL;
PMsgData message = NULL;
gchar * rmtHost = NULL;
gssize gss_receive = 0;
if ((condition & G_IO_HUP) || (condition & G_IO_ERR) || (condition & G_IO_NVAL)) { /* SHUTDOWN THE MAIN LOOP */
g_message("gtk_display_service::cb_udp_comm_request_handler(error) Operational Error / Shutdown Signaled => %s\n", gio_condition_to_string(condition));
g_main_loop_quit(pctrl->loop);
return ( G_SOURCE_REMOVE );
}
if (condition != G_IO_IN) {
g_message("gtk_display_service::cb_udp_comm_request_handler(error) NOT G_IO_IN => %s\n", gio_condition_to_string(condition));
return (G_SOURCE_CONTINUE);
}
/*
* Allocate a new queue message and read incoming request directly into it */
message = g_new0(MsgData,1);
/*
* If socket times out before reading data any operation will error with 'G_IO_ERROR_TIMED_OUT'.
* Read Request Message and get Requestor IP Address or Name
*/
gss_receive = g_socket_receive_from (gSock, &gsRmtAddr, message->ch_message, sizeof(message->ch_message), NULL, &error);
if (error != NULL) { // gss_receive = Number of bytes read, or 0 if the connection was closed by the peer, or -1 on error
g_error("g_socket_receive_from() => %s", error->message);
g_clear_error(&error);
g_free(message);
return (G_SOURCE_CONTINUE);
}
if (gss_receive > 0 ) {
if (G_IS_INET_SOCKET_ADDRESS(gsRmtAddr) ) {
gsAddr = g_inet_socket_address_get_address( G_INET_SOCKET_ADDRESS(gsRmtAddr) );
if ( G_IS_INET_ADDRESS(gsAddr) ) {
g_object_ref(gsAddr);
rmtHost = g_resolver_lookup_by_address (pctrl->resolver, gsAddr, NULL, NULL);
if (NULL == rmtHost) {
rmtHost = g_inet_address_to_string ( gsAddr);
}
}
}
g_snprintf(message->ch_remoteAddress, sizeof(message->ch_remoteAddress), "%s", rmtHost);
g_free(rmtHost);
g_snprintf(pctrl->ch_buffer, sizeof(pctrl->ch_buffer), "%d %s", 202, "Accepted");
} else {
g_snprintf(message->ch_message, sizeof(message->ch_message), "%s", "Error: Input not Usable");
g_snprintf(pctrl->ch_buffer, sizeof(pctrl->ch_buffer), "%d %s", 406, "Not Acceptable");
}
/*
* Send Response to caller */
g_socket_send_to (gSock, gsRmtAddr, pctrl->ch_buffer, strlen(pctrl->ch_buffer), NULL, &error);
if (error != NULL) { // gss_send = Number of bytes written (which may be less than size ), or -1 on error
g_error("g_socket_send_to() => %s", error->message);
g_free(message);
g_clear_error(&error);
if ( G_IS_INET_ADDRESS(gsAddr) )
g_object_unref(gsAddr);
if ( G_IS_INET_SOCKET_ADDRESS(gsRmtAddr) )
g_object_unref(gsRmtAddr);
return (G_SOURCE_CONTINUE);
}
/*
* Send it to be processed by a message handler */
g_async_queue_push (pctrl->queueMessage, message);
if ( G_IS_INET_ADDRESS(gsAddr) )
g_object_unref(gsAddr);
if ( G_IS_INET_SOCKET_ADDRESS(gsRmtAddr) )
g_object_unref(gsRmtAddr);
return (G_SOURCE_CONTINUE);
}
//MessageQueueSourceFunc
static gboolean cb_message_request_handler(PMsgData msg, PControlData pctrl) {
g_return_val_if_fail((NULL != msg) && (NULL != pctrl), G_SOURCE_CONTINUE);
// g_print("[%06d][%20s] %s\n", pctrl->gMsgCount, msg->ch_remoteAddress, msg->ch_message);
pctrl->gMsgCount += 1;
ui_add_message_entry( pctrl->pg1Target, msg, (pctrl->gMsgCount >= MAX_MESSAGES_VIEWABLE));
g_free(msg);
return(G_SOURCE_CONTINUE);
}
//MessageQueueSourceFunc
static gboolean cb_registry_request_handler(PRegData reg, PControlData pctrl) {
g_return_val_if_fail((NULL != reg) && (NULL != pctrl), G_SOURCE_CONTINUE);
g_print("[%06d] %s, %s, %s\n", pctrl->gRegCount, reg->ch_name, reg->ch_ip, reg->ch_port);
pctrl->gRegCount += 1;
ui_add_registry_entry( pctrl->pg2Target, reg, FALSE);
g_free(reg);
return(G_SOURCE_CONTINUE);
}
static gchar * gio_condition_to_string(GIOCondition condition) {
gchar *value = NULL;
switch(condition) {
case G_IO_IN:
value = "There is data to read.";
break;
case G_IO_OUT:
value = "Data can be written (without blocking).";
break;
case G_IO_PRI:
value = "There is urgent data to read.";
break;
case G_IO_ERR:
value = "Error condition.";
break;
case G_IO_HUP:
value = "Hung up (the connection has been broken, usually for pipes and sockets).";
break;
case G_IO_NVAL:
value = "Invalid request. The file descriptor is not open.";
break;
default:
value = "Unknown GIOCondition!";
}
return (value);
}
static GtkWidget * ui_message_page_new(GtkWidget *parent) {
GtkWidget *scrolled = NULL;
GtkTreeView *treeview = NULL;
GtkListStore *store = NULL;
GtkCellRenderer *renderer = NULL;
GtkTreeViewColumn *column = NULL;
gtk_box_pack_start (GTK_BOX (parent), gtk_label_new ("Device Messages from the local Internet of Things (IOT).") , FALSE, FALSE, 6);
/*
* main container is a GtkTreeView wrapped by a scrollable window */
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled),GTK_SHADOW_ETCHED_OUT);
gtk_box_pack_start (GTK_BOX (parent), scrolled, TRUE, TRUE, 0);
/* create list store for tree view */
store = gtk_list_store_new (MSG_NUM_COLUMNS,
G_TYPE_STRING,
G_TYPE_STRING);
treeview = GTK_TREE_VIEW(gtk_tree_view_new_with_model (GTK_TREE_MODEL(store)));
gtk_tree_view_set_grid_lines(treeview, GTK_TREE_VIEW_GRID_LINES_HORIZONTAL);
// gtk_tree_view_set_fixed_height_mode(treeview, TRUE);
/* column for service name */
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes ("Node", renderer, "text", COLUMN_NODE, NULL);
gtk_tree_view_column_set_sort_column_id (column, COLUMN_NODE);
gtk_tree_view_append_column (treeview, column);
/* column for IP Address */
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes ("Message", renderer, "text", COLUMN_MESSAGES, NULL);
gtk_tree_view_column_set_sort_column_id (column, COLUMN_MESSAGES);
gtk_tree_view_append_column (treeview, column);
g_object_unref (store);
gtk_tree_view_set_search_column(treeview, COLUMN_NODE);
gtk_tree_view_set_enable_search(treeview, TRUE);
gtk_container_add (GTK_CONTAINER(scrolled), GTK_WIDGET(treeview));
gtk_widget_show_all(parent);
gtk_tree_view_columns_autosize(treeview);
return (GTK_WIDGET(treeview));
}
static GtkWidget * ui_registry_page_new(GtkWidget *parent) {
GtkWidget *scrolled = NULL;
GtkTreeView *treeview = NULL;
GtkListStore *store = NULL;
GtkCellRenderer *renderer = NULL;
GtkTreeViewColumn *column = NULL;
gtk_box_pack_start (GTK_BOX (parent), gtk_label_new ("Raspberry Pi Network Services Registry.") , FALSE, FALSE, 6);
/*
* main container is a GtkTreeView wrapped by a scrollable window */
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled),GTK_SHADOW_ETCHED_OUT);
gtk_box_pack_start (GTK_BOX (parent), scrolled, TRUE, TRUE, 0);
/* create list store for tree view */
store = gtk_list_store_new (REG_NUM_COLUMNS,
G_TYPE_STRING,
G_TYPE_STRING,
G_TYPE_STRING);
treeview = GTK_TREE_VIEW(gtk_tree_view_new_with_model (GTK_TREE_MODEL(store)));
gtk_tree_view_set_grid_lines(treeview, GTK_TREE_VIEW_GRID_LINES_HORIZONTAL);
// gtk_tree_view_set_fixed_height_mode(treeview, TRUE);
/* column for service name */
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes ("Service Name", renderer, "text", COLUMN_NAME, NULL);
gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
gtk_tree_view_append_column (treeview, column);
/* column for IP Address */
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes ("IP Address", renderer, "text", COLUMN_IP, NULL);
gtk_tree_view_column_set_sort_column_id (column, COLUMN_IP);
gtk_tree_view_append_column (treeview, column);
/* column for port number */
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes ("UDP Port", renderer, "text", COLUMN_PORT, NULL);
gtk_tree_view_column_set_sort_column_id (column, COLUMN_PORT);
gtk_tree_view_append_column (treeview, column);
g_object_unref (store);
gtk_tree_view_set_enable_search(treeview, TRUE);
gtk_tree_view_set_search_column (treeview, COLUMN_NAME);
gtk_container_add (GTK_CONTAINER(scrolled), GTK_WIDGET(treeview));
gtk_widget_show_all(parent);
gtk_tree_view_columns_autosize(treeview);
return (GTK_WIDGET(treeview));
}
static gboolean ui_add_message_entry(GtkWidget *treeview, PMsgData pMsg, gboolean limiter) {
gboolean result = TRUE;
GtkListStore *store = GTK_LIST_STORE( gtk_tree_view_get_model(GTK_TREE_VIEW(treeview)) );
GtkTreeIter iter;
gint num_rows = 0;
// insert at head of list
gtk_list_store_insert_with_values (store, NULL, 0, COLUMN_NODE, pMsg->ch_remoteAddress, COLUMN_MESSAGES, pMsg->ch_message, -1);
if (limiter) { // get last iter and remove it
num_rows = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL);
if ( (num_rows > 8) && (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(store), &iter, NULL, (num_rows -1))) ) { // last entry
gtk_tree_model_unref_node(GTK_TREE_MODEL(store), &iter);
gtk_list_store_remove(store, &iter);
}
}
return (result);
}
static gboolean ui_add_registry_entry(GtkWidget *treeview, PRegData pReg, gboolean limiter) {
gboolean result = TRUE;
GtkListStore *store = GTK_LIST_STORE( gtk_tree_view_get_model(GTK_TREE_VIEW(treeview)) );
GtkTreeIter iter;
gint num_rows = 0;
// insert at head of list
gtk_list_store_insert_with_values (store, NULL, 0, COLUMN_NAME, pReg->ch_name, COLUMN_IP, pReg->ch_ip, COLUMN_PORT, pReg->ch_port, -1);
if (limiter) { // get last iter and remove it
num_rows = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL);
if ( (num_rows > 8) && (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(store), &iter, NULL, (num_rows -1))) ) { // last entry
gtk_tree_model_unref_node(GTK_TREE_MODEL(store), &iter);
gtk_list_store_remove(store, &iter);
}
}
return (result);
}
static GtkWidget * ui_page_layout(GtkWidget *parent, PControlData pctrl) {
GtkWidget *grid = NULL;
GtkWidget *switcher = NULL;
GtkWidget *stack = NULL;
GtkWidget *pg1Box = NULL;
GtkWidget *pg2Box = NULL;
grid = gtk_grid_new(); // left, top, width, hieght
switcher = gtk_stack_switcher_new();
gtk_widget_set_hexpand (switcher, TRUE);
gtk_widget_set_halign (switcher, GTK_ALIGN_CENTER);
gtk_grid_attach (GTK_GRID (grid), switcher, 0, 0, 1, 1);
stack = gtk_stack_new();
gtk_widget_set_hexpand (stack, TRUE);
gtk_widget_set_vexpand(stack, TRUE);
gtk_stack_set_homogeneous( GTK_STACK(stack), TRUE);
gtk_stack_set_transition_type( GTK_STACK(stack), GTK_STACK_TRANSITION_TYPE_CROSSFADE);
gtk_stack_set_transition_duration( GTK_STACK(stack), 400);
gtk_grid_attach (GTK_GRID (grid), stack, 0, 1, 1, 1);
pg1Box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
pctrl->pg1Target = ui_message_page_new(pg1Box);
gtk_stack_add_titled ( GTK_STACK(stack), GTK_WIDGET(pg1Box), "messages", "Messages");
pg2Box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
pctrl->pg2Target = ui_registry_page_new(pg2Box);
PRegData preg = g_new0(RegData,1); // validation data only
g_utf8_strncpy(preg->ch_name, "gtk_display_service", sizeof(preg->ch_port));
g_utf8_strncpy(preg->ch_ip, "piface.skoona.net", sizeof(preg->ch_ip));
g_utf8_strncpy(preg->ch_port, "48029", sizeof(preg->ch_port));
ui_add_registry_entry( pctrl->pg2Target, preg, FALSE);
g_free(preg);
gtk_stack_add_titled ( GTK_STACK(stack), GTK_WIDGET(pg2Box), "registry", "Registry");
gtk_stack_switcher_set_stack (GTK_STACK_SWITCHER(switcher), GTK_STACK(stack));
gtk_stack_set_visible_child (GTK_STACK(stack), GTK_WIDGET(pg1Box));
gtk_container_add (GTK_CONTAINER(parent), grid);
gtk_widget_show_all(grid);
return (stack);
}
static void cb_gtk_shutdown (GtkWidget *object, gpointer data) {
PUSignalData psig = (PUSignalData)data;
g_message("gtk_display_service::cb_gtk_shutdown() %s Destroy Signal Received => Shutdown Initiated!\n", psig->signalName);
g_main_loop_quit(psig->loop);
}
int main(int argc, char **argv) {
GError *error = NULL;
GMainLoop *loop = NULL;
GSocket *gSock = NULL;
GSocketAddress *gsAddr = NULL;
GSource * gCommSource = NULL;
GSource * gMsgSource = NULL;
GSource * gRegSource = NULL;
ControlData cData;
GInetAddress *anyAddr = NULL;
USignalData sigTerm;
USignalData sigInt;
USignalData winDestroy;
GtkWidget *window = NULL;
guint gUDPPort = UDP_COMM_PORT;
GOptionContext *gOptions = NULL;
GOptionEntry pgmOptions[] = {
{"udp_port_number", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &gUDPPort, "UDP Port Number to listen on.", "port number defaults to 48029"},
{NULL}
};
gOptions = g_option_context_new ("UDP message display service for IOT.");
g_option_context_add_group (gOptions, (GOptionGroup *) gtk_get_option_group(TRUE) );
g_option_context_add_main_entries (gOptions, pgmOptions, NULL);
g_option_context_parse (gOptions, &argc, &argv, &error);
if (error != NULL) {
g_error("g_option_context_parse() => %s", error->message);
g_clear_error(&error);
exit(EXIT_FAILURE);
}
g_option_context_free(gOptions);
winDestroy.loop = sigTerm.loop = sigInt.loop = cData.loop = loop = g_main_loop_new(NULL, FALSE);
sigTerm.signalName = "SIGTERM";
sigInt.signalName = "SIGINT";
winDestroy.signalName = "gtkDestroyWindow";
cData.resolver = g_resolver_get_default();
cData.queueMessage = g_async_queue_new();
cData.queueRegistry = g_async_queue_new();
cData.gMsgCount = 0;
gSock = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_UDP, &error);
if (error != NULL) {
g_error("g_socket_new() => %s", error->message);
g_clear_error(&error);
exit(EXIT_FAILURE);
}
anyAddr = g_inet_address_new_any(G_SOCKET_FAMILY_IPV4);
gsAddr = g_inet_socket_address_new(anyAddr, gUDPPort);
g_socket_bind(gSock, gsAddr, TRUE, &error);
if (error != NULL) {
g_error("g_socket_bind() => %s", error->message);
g_clear_error(&error);
exit(EXIT_FAILURE);
}
/*
* Create and Add socket to main loop for service (i.e. polling socket) */
gCommSource = g_socket_create_source (gSock, G_IO_IN, NULL);
g_source_ref(gCommSource);
g_source_set_callback (gCommSource, (GSourceFunc) cb_udp_comm_request_handler, &cData, NULL); // its really a GSocketSourceFunc
cData.gCommSourceId = g_source_attach (gCommSource, NULL);
/*
* Create and Add MessageQueue to main loop for service (i.e. incoming messages) */
gMsgSource = message_queue_source_new (cData.queueMessage, (GDestroyNotify) g_free, NULL);
g_source_ref(gMsgSource);
g_source_set_callback (gMsgSource, (GSourceFunc)cb_message_request_handler, &cData, (GDestroyNotify) g_object_unref);
cData.gMsgSourceId = g_source_attach (gMsgSource, NULL);
gRegSource = message_queue_source_new(cData.queueRegistry, (GDestroyNotify) g_free, NULL);
g_source_ref(gRegSource);
g_source_set_callback (gRegSource, (GSourceFunc)cb_registry_request_handler, &cData, (GDestroyNotify) g_object_unref);
cData.gRegSourceId = g_source_attach (gRegSource, NULL);
/*
* Handle ctrl-break and kill signals cleanly */
g_unix_signal_add (SIGINT, (GSourceFunc) cb_unix_signal_handler, &sigInt); // SIGINT signal (Ctrl+C)
g_unix_signal_add (SIGTERM,(GSourceFunc) cb_unix_signal_handler, &sigTerm);
/*
* Create a GTK+3 Page to show messages */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Gtk Display Service");
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_container_set_border_width(GTK_CONTAINER(window), 10);
gtk_window_set_default_size (GTK_WINDOW (window), 400, 300);
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(cb_gtk_shutdown), &winDestroy);
/*
* make elements for the Page Header */
ui_page_layout(GTK_WIDGET(window), &cData);
gtk_widget_show_all(GTK_WIDGET(window));
g_message("gtk_display_service: Ready to do Good, on port: %d...\n", gUDPPort);
g_main_loop_run(loop);
g_source_unref(gCommSource);
g_async_queue_unref(cData.queueRegistry);
g_async_queue_unref(cData.queueMessage);
g_source_unref(gRegSource);
g_source_unref(gMsgSource);
g_object_unref(gSock);
g_object_unref(anyAddr);
g_object_unref(gsAddr);
g_object_unref(cData.resolver);
g_main_loop_unref(loop);
g_message("gtk_display_service: shutdown...");
exit(EXIT_SUCCESS);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment