Last active
May 11, 2016 02:43
-
-
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`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 (¶m_value, G_TYPE_POINTER); | |
g_value_set_pointer (¶m_value, message); | |
g_closure_invoke (closure, &result_value, 1, ¶m_value, NULL); | |
retval = g_value_get_boolean (&result_value); | |
g_value_unset (¶m_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); | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 (¶m_value, G_TYPE_POINTER); | |
g_value_set_pointer (¶m_value, message); | |
g_closure_invoke (closure, &result_value, 1, ¶m_value, NULL); | |
retval = g_value_get_boolean (&result_value); | |
g_value_unset (¶m_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