[gssdp] sniffer: Port to a more modern GTK
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gssdp] sniffer: Port to a more modern GTK
- Date: Sat, 5 Jun 2021 23:04:55 +0000 (UTC)
commit ee0772817dd5729d42a2f3f3d3e1ea6a45a5fc66
Author: Jens Georg <mail jensge org>
Date: Tue Oct 6 23:16:16 2020 +0200
sniffer: Port to a more modern GTK
Using appliation windows, actions, etc.
tools/gssdp-device-sniffer.c | 761 +++---------------------------
tools/gssdp-device-sniffer.gresource.xml | 3 +-
tools/gssdp-device-sniffer.ui | 362 --------------
tools/main-window.c | 779 +++++++++++++++++++++++++++++++
tools/main-window.h | 18 +
tools/main-window.ui | 395 ++++++++++++++++
tools/meson.build | 2 +
tools/window-menu.ui | 18 -
8 files changed, 1269 insertions(+), 1069 deletions(-)
---
diff --git a/tools/gssdp-device-sniffer.c b/tools/gssdp-device-sniffer.c
index 4020130..725e667 100644
--- a/tools/gssdp-device-sniffer.c
+++ b/tools/gssdp-device-sniffer.c
@@ -17,6 +17,8 @@
* Boston, MA 02110-1301, USA.
*/
+#include "main-window.h"
+
#include <libgssdp/gssdp.h>
#include <libgssdp/gssdp-client-private.h>
#include <libsoup/soup.h>
@@ -24,716 +26,101 @@
#include <string.h>
#include <stdlib.h>
-#define UI_RESOURCE "/org/gupnp/GSSDP/DeviceSniffer.ui"
-#define MENU_RESOURCE "/org/gupnp/GSSDP/WindowMenu.ui"
-
-static char *interface = NULL;
-
-typedef enum {
- PACKET_STORE_COLUMN_TIME,
- PACKET_STORE_COLUMN_IP,
- PACKET_STORE_COLUMN_INTERFACE,
- PACKET_STORE_COLUMN_PACKET_TYPE,
- PACKET_STORE_COLUMN_TARGET,
- PACKET_STORE_COLUMN_HEADERS,
- PACKET_STORE_COLUMN_RAW_ARRIVAL_TIME
-} PACKET_STORE_COLUMNS;
-
-typedef enum {
- DEVICE_STORE_COLUMN_UUID,
- DEVICE_STORE_COLUMN_FIRST_SEEN,
- DEVICE_STORE_COLUMN_TYPE,
- DEVICE_STORE_COLUMN_LOCATION
-} DEVICE_STORE_COLUMNS;
-
-
-GtkBuilder *builder;
-GSSDPResourceBrowser *resource_browser;
-GSSDPClient *client;
-char *ip_filter = NULL;
-gboolean capture_packets = TRUE;
-gboolean prefer_v6 = FALSE;
-
-GOptionEntry entries[] =
-{
- {"interface", 'i', 0, G_OPTION_ARG_STRING, &interface, "Network interface to listen on", NULL },
- { "prefer-v6", '6', 0, G_OPTION_ARG_NONE, &prefer_v6, "Prefer IPv6 for the client", NULL },
- { "prefer-v4", '4', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &prefer_v6, "Prefer IPv4 for the
client", NULL },
- { NULL }
-};
-
-void
-on_enable_packet_capture_activate (GtkToggleButton *menuitem,
- gpointer user_data);
-
-void
-on_address_filter_dialog_response (GtkDialog *dialog,
- gint response,
- gpointer user_data);
-
-gboolean
-on_delete_event (GtkWidget *widget, GdkEvent *event, gpointer user_data);
-
-G_MODULE_EXPORT void
-on_enable_packet_capture_activate (GtkToggleButton *menuitem,
- gpointer user_data)
-{
- const gchar *icon_name = NULL;
-
- capture_packets = gtk_toggle_button_get_active (menuitem);
- icon_name = capture_packets
- ? "media-playback-stop-symbolic"
- : "media-playback-start-symbolic";
-
- gtk_image_set_from_icon_name (GTK_IMAGE (user_data),
- icon_name,
- GTK_ICON_SIZE_BUTTON);
-}
-
-static void
-clear_packet_treeview (void)
-{
- GtkWidget *treeview;
- GtkTreeModel *model;
-
- treeview = GTK_WIDGET(gtk_builder_get_object (builder, "packet-treeview"));
- g_assert (treeview != NULL);
- model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
- gtk_list_store_clear (GTK_LIST_STORE (model));
-}
-
-static void
-on_details_activate (GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- GtkWidget *scrolled_window = NULL;
-
- scrolled_window = GTK_WIDGET (gtk_builder_get_object (builder,
- "packet-details-scrolledwindow"));
-
- g_object_set (G_OBJECT (scrolled_window), "visible", g_variant_get_boolean (parameter), NULL);
- g_simple_action_set_state (action, parameter);
-}
-
-static void
-packet_header_to_string (const char *header_name,
- const char *header_val,
- GString **text)
-{
- g_string_append_printf (*text, "%s: %s\n",
- header_name,
- header_val);
-}
-
-static void
-clear_textbuffer (GtkTextBuffer *textbuffer)
-{
- GtkTextIter start, end;
-
- gtk_text_buffer_get_bounds (textbuffer, &start, &end);
- gtk_text_buffer_delete (textbuffer, &start, &end);
-}
-
-static void
-update_packet_details (const char *text, unsigned int len)
-{
- GtkWidget *textview;
- GtkTextBuffer *textbuffer;
-
- textview = GTK_WIDGET(gtk_builder_get_object (builder, "packet-details-textview"));
- g_assert (textview != NULL);
- textbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
-
- clear_textbuffer (textbuffer);
- gtk_text_buffer_insert_at_cursor (textbuffer, text, len);
-}
-
-static void
-display_packet (GDateTime *arrival_time, SoupMessageHeaders *packet_headers)
-{
- GString *text;
- char *time = NULL;
-
- time = g_date_time_format_iso8601 (arrival_time);
- text = g_string_new ("Received on: ");
- g_string_append (text, time);
- g_string_append (text, "\nHeaders:\n\n");
- g_free (time);
-
- soup_message_headers_foreach (packet_headers,
- (SoupMessageHeadersForeachFunc)
- packet_header_to_string,
- &text);
-
- update_packet_details (text->str, text->len);
- g_string_free (text, TRUE);
-}
static void
-on_packet_selected (GtkTreeSelection *selection,
- G_GNUC_UNUSED gpointer user_data)
+on_activate (GtkApplication *app)
{
- GtkTreeModel *model;
- GtkTreeIter iter;
- GDateTime *arrival_time;
-
- if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
- SoupMessageHeaders *packet_headers;
+ GtkWindow *window;
- gtk_tree_model_get (model,
- &iter,
- PACKET_STORE_COLUMN_HEADERS,
- &packet_headers,
- PACKET_STORE_COLUMN_RAW_ARRIVAL_TIME,
- &arrival_time,
- -1);
- display_packet (arrival_time, packet_headers);
- g_boxed_free (SOUP_TYPE_MESSAGE_HEADERS, packet_headers);
- g_date_time_unref (arrival_time);
+ window = gtk_application_get_active_window (app);
+ if (window == NULL) {
+ window = g_object_new (GSSDP_DEVICE_SNIFFER_TYPE_MAIN_WINDOW,
+ "application",
+ app,
+ NULL);
}
- else
- update_packet_details ("", 0);
+ gtk_window_present (window);
}
-static const char *message_types[] = {"M-SEARCH", "RESPONSE", "NOTIFY"};
-
-static char **
-packet_to_treeview_data (const gchar *from_ip,
- GDateTime *arrival_time,
- _GSSDPMessageType type,
- SoupMessageHeaders *headers)
+static int
+on_command_line (GtkApplication *app,
+ GApplicationCommandLine *cmdline,
+ gpointer user_data)
{
- char **packet_data;
- const char *target;
+ char *iface = NULL;
+ GSocketFamily family = G_SOCKET_FAMILY_INVALID;
+ gboolean six = FALSE;
- packet_data = g_new0 (char *, 6);
- /* Set the Time */
- packet_data[0] = g_date_time_format (arrival_time, "%R");
+ GVariantDict *args = g_application_command_line_get_options_dict (cmdline);
- /* Now the Source Address */
- packet_data[1] = g_strdup (from_ip);
-
- packet_data[2] = g_strdup (gssdp_client_get_interface (client));
+ GOptionContext *context = g_option_context_new (NULL);
+ g_option_context_set_help_enabled (context, FALSE);
- /* Now the Packet Type */
- packet_data[3] = g_strdup (message_types[type]);
-
- /* Now the Packet Information */
- if (type == _GSSDP_DISCOVERY_RESPONSE)
- target = soup_message_headers_get_one (headers, "ST");
- else
- target = soup_message_headers_get_one (headers, "NT");
-
- packet_data[4] = g_strdup (target);
- packet_data[5] = NULL;
+ g_variant_dict_lookup (args, "interface" ,"s", &iface);
+ g_variant_dict_lookup (args, "prefer-v6", "b", &six);
- return packet_data;
-}
-
-static void
-append_packet (const gchar *from_ip,
- GDateTime *arrival_time,
- _GSSDPMessageType type,
- SoupMessageHeaders *headers)
-{
- GtkWidget *treeview;
- GtkListStore *liststore;
- GtkTreeIter iter;
- char **packet_data;
-
- treeview = GTK_WIDGET(gtk_builder_get_object (builder, "packet-treeview"));
- g_assert (treeview != NULL);
- liststore = GTK_LIST_STORE (
- gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)));
- g_assert (liststore != NULL);
-
- packet_data = packet_to_treeview_data (from_ip,
- arrival_time,
- type,
- headers);
-
- gtk_list_store_insert_with_values (liststore, &iter, 0,
- PACKET_STORE_COLUMN_TIME, packet_data[0],
- PACKET_STORE_COLUMN_IP, packet_data[1],
- PACKET_STORE_COLUMN_INTERFACE, packet_data[2],
- PACKET_STORE_COLUMN_PACKET_TYPE, packet_data[3],
- PACKET_STORE_COLUMN_TARGET, packet_data[4],
- PACKET_STORE_COLUMN_HEADERS, headers,
- PACKET_STORE_COLUMN_RAW_ARRIVAL_TIME, arrival_time,
- -1);
- g_strfreev (packet_data);
-}
-
-static void
-on_ssdp_message (G_GNUC_UNUSED GSSDPClient *ssdp_client,
- G_GNUC_UNUSED const gchar *from_ip,
- G_GNUC_UNUSED gushort from_port,
- _GSSDPMessageType type,
- SoupMessageHeaders *headers,
- G_GNUC_UNUSED gpointer user_data)
-{
- GDateTime *arrival_time;
-
- if (type == _GSSDP_DISCOVERY_REQUEST)
- return;
- if (ip_filter != NULL && strcmp (ip_filter, from_ip) != 0)
- return;
- if (!capture_packets)
- return;
-
- arrival_time = g_date_time_new_now_local ();
- append_packet (from_ip, arrival_time, type, headers);
- g_date_time_unref (arrival_time);
-}
-
-static gboolean
-find_device (GtkTreeModel *model, const char *uuid, GtkTreeIter *iter)
-{
- gboolean found = FALSE;
- gboolean more;
-
- more = gtk_tree_model_get_iter_first (model, iter);
- while (more) {
- char *device_uuid;
- gtk_tree_model_get (model,
- iter,
- 0, &device_uuid, -1);
- found = g_strcmp0 (device_uuid, uuid) == 0;
- g_free (device_uuid);
-
- if (found)
- break;
- more = gtk_tree_model_iter_next (model, iter);
- }
- return found;
-}
-
-static void
-append_device (const char *uuid,
- const char *first_notify,
- const char *device_type,
- const char *location)
-{
- GtkWidget *treeview;
- GtkTreeModel *model;
- GtkTreeIter iter;
-
- treeview = GTK_WIDGET(gtk_builder_get_object (builder, "device-details-treeview"));
- g_assert (treeview != NULL);
- model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
- g_assert (model != NULL);
-
- if (!find_device (model, uuid, &iter)) {
- gtk_list_store_insert_with_values (GTK_LIST_STORE (model),
- &iter, 0,
- DEVICE_STORE_COLUMN_UUID, uuid,
- DEVICE_STORE_COLUMN_FIRST_SEEN, first_notify,
- DEVICE_STORE_COLUMN_LOCATION, location, -1);
- }
-
- if (device_type) {
- gtk_list_store_set (GTK_LIST_STORE (model), &iter,
- DEVICE_STORE_COLUMN_TYPE, device_type, -1);
+ if (six) {
+ family = G_SOCKET_FAMILY_IPV6;
+ } else {
+ family = G_SOCKET_FAMILY_IPV4;
}
-}
-
-static void
-resource_available_cb (G_GNUC_UNUSED GSSDPResourceBrowser *ssdp_resource_browser,
- const char *usn,
- GList *locations)
-{
- char **usn_tokens;
- char *uuid;
- char *device_type = NULL;
- GDateTime *current_time = NULL;
- char *first_notify;
-
- current_time = g_date_time_new_now_local ();
- first_notify = g_date_time_format (current_time, "%R");
- g_date_time_unref (current_time);
-
- usn_tokens = g_strsplit (usn, "::", -1);
- g_assert (usn_tokens != NULL && usn_tokens[0] != NULL);
-
- uuid = usn_tokens[0] + 5; /* skip the prefix 'uuid:' */
-
- if (usn_tokens[1] && strlen(usn_tokens[1]) != 0) {
- char **urn_tokens;
-
- urn_tokens = g_strsplit (usn_tokens[1], ":device:", -1);
-
- if (urn_tokens[1])
- device_type = g_strdup (urn_tokens[1]);
- g_strfreev (urn_tokens);
- }
-
- /* Device Announcement */
- append_device (uuid,
- first_notify,
- device_type,
- (char *) locations->data);
-
- g_free (device_type);
- g_free (first_notify);
- g_strfreev (usn_tokens);
-}
-
-static void
-remove_device (const char *uuid)
-{
- GtkWidget *treeview;
- GtkTreeModel *model;
- GtkTreeIter iter;
-
- treeview = GTK_WIDGET(gtk_builder_get_object (builder, "device-details-treeview"));
- g_assert (treeview != NULL);
- model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
- g_assert (model != NULL);
-
- if (find_device (model, uuid, &iter)) {
- gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
- }
-}
-
-static void
-resource_unavailable_cb (G_GNUC_UNUSED GSSDPResourceBrowser *ssdp_resource_browser,
- const char *usn)
-{
- char **usn_tokens;
- char *uuid;
-
- usn_tokens = g_strsplit (usn, "::", -1);
- g_assert (usn_tokens != NULL && usn_tokens[0] != NULL);
- uuid = usn_tokens[0] + 5; /* skip the prefix 'uuid:' */
-
- remove_device (uuid);
-
- g_strfreev (usn_tokens);
-}
-
-G_MODULE_EXPORT
-void
-on_use_filter_radiobutton_toggled (GtkToggleButton *togglebutton,
- G_GNUC_UNUSED gpointer user_data)
-{
- GtkWidget *filter_hbox;
- gboolean use_filter;
-
- filter_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "address-entry0"));
- g_assert (filter_hbox != NULL);
-
- use_filter = gtk_toggle_button_get_active (togglebutton);
- gtk_widget_set_sensitive (filter_hbox, use_filter);
-}
-
-static char *
-get_ip_filter (void)
-{
- GtkEntry *entry;
- GInetAddress *addr;
+ GtkWindow *window;
+ window = g_object_new (GSSDP_DEVICE_SNIFFER_TYPE_MAIN_WINDOW,
+ "application",
+ app,
+ "address-family",
+ family,
+ "interface",
+ iface,
+ NULL);
+
+ gtk_window_present (window);
+
+ return EXIT_SUCCESS;
+}
+
+GOptionEntry entries[] = { { "interface",
+ 'i',
+ 0,
+ G_OPTION_ARG_STRING,
+ NULL,
+ "Network interface to listen on",
+ NULL },
+ { "prefer-v6",
+ '6',
+ 0,
+ G_OPTION_ARG_NONE,
+ NULL,
+ "Prefer IPv6 for the client",
+ NULL },
+ { NULL , 0, 0, 0, NULL, NULL, NULL} };
- entry = GTK_ENTRY (gtk_builder_get_object (builder, "address-entry0"));
- addr = g_inet_address_new_from_string (gtk_entry_get_text (entry));
-
- if (addr == NULL) {
- g_warning ("Filter not a valid IP address");
-
- return NULL;
- }
- g_object_unref (addr);
-
- return g_strdup (gtk_entry_get_text (entry));
-}
-
-G_MODULE_EXPORT
-void
-on_address_filter_dialog_response (GtkDialog *dialog,
- G_GNUC_UNUSED gint response,
- G_GNUC_UNUSED gpointer user_data)
-{
- GtkWidget *use_filter_radio;
-
- gtk_widget_hide (GTK_WIDGET (dialog));
-
- use_filter_radio = GTK_WIDGET(gtk_builder_get_object (builder, "use-filter-radiobutton"));
- g_assert (use_filter_radio != NULL);
-
- if (response != GTK_RESPONSE_OK)
- return;
-
- g_free (ip_filter);
- if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (use_filter_radio))) {
- ip_filter = get_ip_filter ();
- }
-
- else
- ip_filter = NULL;
-}
-
-static gboolean
-on_treeview_popup_menu (GtkWidget *tv, GdkEventButton *event, gpointer user_data)
-{
- if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
- gtk_menu_popup_at_pointer (GTK_MENU (user_data), (GdkEvent *)event);
- }
-
- return FALSE;
-}
-
-static void
-setup_treeviews (void)
-{
- GtkWidget *treeview;
- GtkTreeSelection *selection;
- GtkWidget *menu = NULL;
-
- treeview = GTK_WIDGET(gtk_builder_get_object (builder,
- "packet-treeview"));
-
- selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
- g_signal_connect (selection,
- "changed",
- G_CALLBACK (on_packet_selected),
- (gpointer *) treeview);
- menu = gtk_menu_new_from_model (G_MENU_MODEL (gtk_builder_get_object (builder,
- "sniffer-context-menu")));
- gtk_menu_attach_to_widget (GTK_MENU (menu), treeview, NULL);
- g_signal_connect (G_OBJECT (treeview),
- "button-press-event",
- G_CALLBACK (on_treeview_popup_menu),
- menu);
-}
-
-G_MODULE_EXPORT
-gboolean
-on_delete_event (G_GNUC_UNUSED GtkWidget *widget,
- G_GNUC_UNUSED GdkEvent *event,
- G_GNUC_UNUSED gpointer user_data)
-{
- gtk_main_quit ();
-
- return TRUE;
-}
-
-static void
-on_show_address_filter (GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- GtkWidget *dialog = NULL;
-
- dialog = GTK_WIDGET (gtk_builder_get_object (builder,
- "address-filter-dialog"));
- gtk_widget_show (dialog);
-}
-
-static void
-on_set_address_filter (GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- GtkWidget *treeview = NULL;
- GtkTreeSelection *selection = NULL;
- GtkTreeModel *model = NULL;
- GtkTreeIter iter;
- GtkWidget *use_filter_radio;
-
- treeview = GTK_WIDGET (gtk_builder_get_object (builder, "packet-treeview"));
- use_filter_radio = GTK_WIDGET(gtk_builder_get_object (builder, "use-filter-radiobutton"));
-
- selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
- gtk_tree_selection_get_selected (selection, &model, &iter);
- g_free (ip_filter);
- gtk_tree_model_get (model, &iter,
- PACKET_STORE_COLUMN_IP, &ip_filter, -1);
- gtk_entry_set_text (GTK_ENTRY (gtk_builder_get_object (builder, "address-entry0")),
- ip_filter);
-
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (use_filter_radio), TRUE);
-}
-
-static void
-on_about (GSimpleAction *action, GVariant *parameter, gpointer user_data)
-{
- GtkWidget *dialog = NULL;
-
- dialog = GTK_WIDGET (gtk_builder_get_object (builder,
- "about-dialog"));
- gtk_widget_show (dialog);
-}
-
-
-G_MODULE_EXPORT
-void
-on_clear_packet_capture_activate (G_GNUC_UNUSED GtkMenuItem *menuitem,
- G_GNUC_UNUSED gpointer user_data)
-{
- clear_packet_treeview ();
-}
-
-static void
-on_trigger_rescan (GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- gssdp_resource_browser_rescan (resource_browser);
-}
-
-static GActionEntry actions[] = {
- { "trigger-rescan", on_trigger_rescan },
- { "set-address-filter", on_set_address_filter },
- { "show-packet-details", NULL, NULL, "true", on_details_activate },
- { "show-address-filter", on_show_address_filter },
- { "about", on_about }
-};
-
-static gboolean
-init_ui (gint *argc, gchar **argv[])
-{
- GtkWidget *main_window;
- gint window_width, window_height;
- GError *error = NULL;
- GOptionContext *context;
- double w, h;
- GSimpleActionGroup *group = NULL;
-
- context = g_option_context_new ("- graphical SSDP debug tool");
- g_option_context_add_main_entries (context, entries, NULL);
- g_option_context_add_group (context, gtk_get_option_group (TRUE));
- if (!g_option_context_parse (context, argc, argv, &error)) {
- g_print ("Failed to parse options: %s\n", error->message);
- g_error_free (error);
-
- return FALSE;
- }
-
- builder = gtk_builder_new();
- if (gtk_builder_add_from_resource(builder, UI_RESOURCE, NULL) == 0)
- return FALSE;
-
- if (gtk_builder_add_from_resource (builder, MENU_RESOURCE, NULL) == 0)
- return FALSE;
-
- gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (gtk_builder_get_object (builder, "window-menu")),
- G_MENU_MODEL (gtk_builder_get_object (builder, "sniffer-window-menu")));
-
- main_window = GTK_WIDGET(gtk_builder_get_object (builder, "main-window"));
- g_assert (main_window != NULL);
-
- group = g_simple_action_group_new ();
- gtk_widget_insert_action_group (GTK_WIDGET (main_window), "win", G_ACTION_GROUP (group));
- g_action_map_add_action_entries (G_ACTION_MAP (group), actions, G_N_ELEMENTS (actions), NULL);
-
-
-#if GTK_CHECK_VERSION(3,22,0)
- gtk_widget_realize (main_window);
- {
- GdkWindow *window = gtk_widget_get_window (main_window);
- GdkDisplay *display = gdk_display_get_default ();
- GdkMonitor *monitor = gdk_display_get_monitor_at_window (display,
- window);
- GdkRectangle rectangle;
-
- gdk_monitor_get_geometry (monitor, &rectangle);
- w = rectangle.width * 0.75;
- h = rectangle.height * 0.75;
- }
-
-#else
- w = gdk_screen_width () * 0.75;
- h = gdk_screen_height () * 0.75;
-#endif
-
- window_width = CLAMP ((int) w, 10, 1000);
- window_height = CLAMP ((int) h, 10, 800);
- gtk_window_set_default_size (GTK_WINDOW (main_window),
- window_width,
- window_height);
-
- gtk_builder_connect_signals (builder, NULL);
- setup_treeviews ();
- gtk_widget_show_all (main_window);
-
- return TRUE;
-}
-
-static void
-deinit_ui (void)
-{
- g_object_unref (builder);
-}
-
-static gboolean
-init_upnp (void)
+gint
+main (gint argc, gchar *argv[])
{
- GError *error;
-
- error = NULL;
- client = g_initable_new (GSSDP_TYPE_CLIENT,
- NULL,
- &error,
- "address-family", prefer_v6 ? G_SOCKET_FAMILY_IPV6 : G_SOCKET_FAMILY_IPV4,
- "interface", interface,
- NULL);
- if (error) {
- g_printerr ("Error creating the GSSDP client: %s\n",
- error->message);
+ g_type_ensure (G_TYPE_DATE_TIME);
- g_error_free (error);
+ GtkApplication *app =
+ gtk_application_new ("org.gupnp.GSSDP.DeviceSniffer",
+ G_APPLICATION_HANDLES_COMMAND_LINE);
- return FALSE;
- }
+ g_application_add_main_option_entries (G_APPLICATION (app), entries);
+ g_application_set_option_context_parameter_string (
+ G_APPLICATION (app),
+ "- graphical SSDP debug tool");
- resource_browser = gssdp_resource_browser_new (client,
- GSSDP_ALL_RESOURCES);
-
- g_signal_connect (client,
- "message-received",
- G_CALLBACK (on_ssdp_message),
- NULL);
- g_signal_connect (resource_browser,
- "resource-available",
- G_CALLBACK (resource_available_cb),
+ g_signal_connect (G_OBJECT (app),
+ "command-line",
+ G_CALLBACK (on_command_line),
NULL);
- g_signal_connect (resource_browser,
- "resource-unavailable",
- G_CALLBACK (resource_unavailable_cb),
+ g_signal_connect (G_OBJECT (app),
+ "activate",
+ G_CALLBACK (on_activate),
NULL);
- gssdp_resource_browser_set_active (resource_browser, TRUE);
-
- return TRUE;
-}
-
-static void
-deinit_upnp (void)
-{
- g_object_unref (resource_browser);
- g_object_unref (client);
-}
-
-gint
-main (gint argc, gchar *argv[])
-{
- g_type_ensure (G_TYPE_DATE_TIME);
-
- if (!init_ui (&argc, &argv)) {
- return -2;
- }
-
- if (!init_upnp ()) {
- return -3;
- }
-
- gtk_main ();
-
- deinit_upnp ();
- deinit_ui ();
-
- return 0;
+ return g_application_run (G_APPLICATION (app), argc, argv);
}
diff --git a/tools/gssdp-device-sniffer.gresource.xml b/tools/gssdp-device-sniffer.gresource.xml
index ec8dc93..6a9d0a3 100644
--- a/tools/gssdp-device-sniffer.gresource.xml
+++ b/tools/gssdp-device-sniffer.gresource.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gupnp/GSSDP">
- <file preprocess="xml-stripblanks" alias="DeviceSniffer.ui">gssdp-device-sniffer.ui</file>
- <file alias="WindowMenu.ui">window-menu.ui</file>
+ <file preprocess="xml-stripblanks" alias="MainWindow.ui">main-window.ui</file>
</gresource>
</gresources>
diff --git a/tools/gssdp-device-sniffer.ui b/tools/gssdp-device-sniffer.ui
index 95faf76..a18d6b1 100644
--- a/tools/gssdp-device-sniffer.ui
+++ b/tools/gssdp-device-sniffer.ui
@@ -2,368 +2,6 @@
<!-- Generated with glade 3.22.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
- <object class="GtkListStore" id="device_store">
- <columns>
- <!-- column-name uid -->
- <column type="gchararray"/>
- <!-- column-name first-seen -->
- <column type="gchararray"/>
- <!-- column-name device-type -->
- <column type="gchararray"/>
- <!-- column-name location -->
- <column type="gchararray"/>
- </columns>
- </object>
- <object class="GtkListStore" id="packet_store">
- <columns>
- <!-- column-name time -->
- <column type="gchararray"/>
- <!-- column-name source-address -->
- <column type="gchararray"/>
- <!-- column-name interface -->
- <column type="gchararray"/>
- <!-- column-name type -->
- <column type="gchararray"/>
- <!-- column-name information -->
- <column type="gchararray"/>
- <!-- column-name SoupMessageHeader1 -->
- <column type="SoupMessageHeaders"/>
- <!-- column-name GDateTime1 -->
- <column type="GDateTime"/>
- </columns>
- </object>
- <object class="GtkWindow" id="main-window">
- <property name="can_focus">False</property>
- <signal name="delete-event" handler="on_delete_event" swapped="no"/>
- <child type="titlebar">
- <object class="GtkHeaderBar">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="show_close_button">True</property>
- <child>
- <object class="GtkBox">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkButton">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="tooltip_text" translatable="yes">Clear packet capture</property>
- <signal name="clicked" handler="on_clear_packet_capture_activate" swapped="no"/>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="icon_name">edit-clear-all-symbolic</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkToggleButton">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="tooltip_text" translatable="yes">Toggle packet capture</property>
- <property name="active">True</property>
- <signal name="toggled" handler="on_enable_packet_capture_activate" object="capture-image"
swapped="no"/>
- <child>
- <object class="GtkImage" id="capture-image">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="icon_name">media-playback-stop-symbolic</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="tooltip_text" translatable="yes">Trigger a rescan of the network</property>
- <property name="action_name">win.trigger-rescan</property>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="icon_name">view-refresh-symbolic</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">2</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="position">1</property>
- </packing>
- </child>
- <child type="title">
- <object class="GtkStackSwitcher">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="halign">center</property>
- <property name="icon_size">0</property>
- <property name="stack">stack1</property>
- </object>
- </child>
- <child>
- <object class="GtkMenuButton" id="window-menu">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="icon_name">open-menu-symbolic</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="pack_type">end</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkStack" id="stack1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkBox" id="vbox">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="border_width">6</property>
- <property name="orientation">vertical</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkScrolledWindow" id="packet-scrolledwindow">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="shadow_type">in</property>
- <child>
- <object class="GtkTreeView" id="packet-treeview">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="model">packet_store</property>
- <child internal-child="selection">
- <object class="GtkTreeSelection"/>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="title" translatable="yes">Time</property>
- <child>
- <object class="GtkCellRendererText"/>
- <attributes>
- <attribute name="markup">0</attribute>
- </attributes>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="title" translatable="yes">Source Address</property>
- <child>
- <object class="GtkCellRendererText"/>
- <attributes>
- <attribute name="markup">1</attribute>
- </attributes>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="title" translatable="yes">Interface</property>
- <child>
- <object class="GtkCellRendererText"/>
- <attributes>
- <attribute name="markup">2</attribute>
- </attributes>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="title" translatable="yes">Packet Type</property>
- <child>
- <object class="GtkCellRendererText"/>
- <attributes>
- <attribute name="markup">3</attribute>
- </attributes>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="title" translatable="yes">Packet Information</property>
- <child>
- <object class="GtkCellRendererText"/>
- <attributes>
- <attribute name="markup">4</attribute>
- </attributes>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkScrolledWindow" id="packet-details-scrolledwindow">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="shadow_type">in</property>
- <child>
- <object class="GtkTextView" id="packet-details-textview">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="editable">False</property>
- <property name="monospace">True</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="name">page0</property>
- <property name="title" translatable="yes">Packets</property>
- </packing>
- </child>
- <child>
- <object class="GtkScrolledWindow" id="device-details-scrolledwindow">
- <property name="name">Bar</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="margin_top">6</property>
- <property name="margin_bottom">6</property>
- <property name="shadow_type">in</property>
- <child>
- <object class="GtkTreeView" id="device-details-treeview">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="model">device_store</property>
- <child internal-child="selection">
- <object class="GtkTreeSelection"/>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="title" translatable="yes">Unique Identifier</property>
- <child>
- <object class="GtkCellRendererText"/>
- <attributes>
- <attribute name="text">0</attribute>
- </attributes>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="title" translatable="yes">First Notify</property>
- <child>
- <object class="GtkCellRendererText"/>
- <attributes>
- <attribute name="text">1</attribute>
- </attributes>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="title" translatable="yes">Device Type</property>
- <child>
- <object class="GtkCellRendererText"/>
- <attributes>
- <attribute name="text">2</attribute>
- </attributes>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkTreeViewColumn">
- <property name="title" translatable="yes">Location</property>
- <child>
- <object class="GtkCellRendererText"/>
- <attributes>
- <attribute name="text">3</attribute>
- </attributes>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="name">page1</property>
- <property name="title" translatable="yes">Device history</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- </child>
- </object>
- <object class="GtkAboutDialog" id="about-dialog">
- <property name="can_focus">False</property>
- <property name="resizable">False</property>
- <property name="type_hint">normal</property>
- <property name="transient_for">main-window</property>
- <property name="copyright" translatable="yes">Copyright © 2007 Zeeshan Ali (Khattak)</property>
- <property name="comments" translatable="yes">A Device Sniffer tool based on GSSDP framework.
-Inspired by Intel Tools for UPnP.</property>
- <property name="authors">Zeeshan Ali (Khattak) <zeeshanak gnome org></property>
- <property name="translator_credits" translatable="yes" comments="TRANSLATORS: Replace this string with
your names, one name per line.">translator-credits</property>
- <property name="logo_icon_name"/>
- <property name="license_type">lgpl-2-1</property>
- <signal name="delete-event" handler="gtk_widget_hide" object="about-dialog" swapped="yes"/>
- <signal name="response" handler="gtk_widget_hide" object="about-dialog" swapped="yes"/>
- <child type="titlebar">
- <placeholder/>
- </child>
- <child internal-child="vbox">
- <object class="GtkBox" id="aboutdialog-vbox1">
- <property name="can_focus">False</property>
- <property name="orientation">vertical</property>
- <child internal-child="action_area">
- <object class="GtkButtonBox" id="aboutdialog-action_area1">
- <property name="can_focus">False</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="pack_type">end</property>
- <property name="position">0</property>
- </packing>
- </child>
- </object>
- </child>
- </object>
<object class="GtkDialog" id="address-filter-dialog">
<property name="can_focus">False</property>
<property name="border_width">7</property>
diff --git a/tools/main-window.c b/tools/main-window.c
new file mode 100644
index 0000000..99a22b7
--- /dev/null
+++ b/tools/main-window.c
@@ -0,0 +1,779 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "main-window.h"
+
+#include <gio/gio.h>
+#include <glib.h>
+
+#include <glib/gi18n.h>
+
+#include <libgssdp/gssdp-client-private.h>
+#include <libgssdp/gssdp.h>
+
+#include <libsoup/soup.h>
+
+typedef enum
+{
+ PACKET_STORE_COLUMN_TIME,
+ PACKET_STORE_COLUMN_IP,
+ PACKET_STORE_COLUMN_INTERFACE,
+ PACKET_STORE_COLUMN_PACKET_TYPE,
+ PACKET_STORE_COLUMN_TARGET,
+ PACKET_STORE_COLUMN_HEADERS,
+ PACKET_STORE_COLUMN_RAW_ARRIVAL_TIME
+} PACKET_STORE_COLUMNS;
+
+typedef enum {
+ DEVICE_STORE_COLUMN_UUID,
+ DEVICE_STORE_COLUMN_FIRST_SEEN,
+ DEVICE_STORE_COLUMN_TYPE,
+ DEVICE_STORE_COLUMN_LOCATION
+} DEVICE_STORE_COLUMNS;
+
+enum
+{
+ PROP_0,
+ PROP_INTERFACE,
+ PROP_ADDRESS_FAMILY,
+ PROP_CAPTURE,
+};
+
+struct _GSSDPDeviceSnifferMainWindow {
+ GtkApplicationWindow parent_instance;
+
+ // SSDP related parameters
+ char *interface;
+ GSocketFamily family;
+ GSSDPClient *client;
+ GSSDPResourceBrowser *browser;
+
+ // Bound child widgets
+ GtkWidget *packet_treeview;
+ GtkWidget *packet_textview;
+ GtkWidget *capture_button_image;
+ GtkWidget *device_treeview;
+ GtkWidget *details_scrolled;
+ GMenuModel *sniffer_context_menu;
+ GtkWidget *searchbar;
+ GtkWidget *address_filter;
+ GtkWidget *searchbutton;
+
+ // Other widgets
+ GtkWidget *context_menu;
+
+ // Options
+ gboolean capture;
+};
+
+G_DEFINE_TYPE (GSSDPDeviceSnifferMainWindow,
+ gssdp_device_sniffer_main_window,
+ GTK_TYPE_APPLICATION_WINDOW)
+
+
+static void
+main_window_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GSSDPDeviceSnifferMainWindow *self =
+ GSSDP_DEVICE_SNIFFER_MAIN_WINDOW (object);
+
+ switch (property_id) {
+ case PROP_CAPTURE:
+ g_value_set_boolean (value, self->capture);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+main_window_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GSSDPDeviceSnifferMainWindow *self =
+ GSSDP_DEVICE_SNIFFER_MAIN_WINDOW (object);
+
+ switch (property_id) {
+ case PROP_INTERFACE:
+ self->interface = g_value_dup_string (value);
+ break;
+ case PROP_ADDRESS_FAMILY:
+ self->family = g_value_get_enum (value);
+ break;
+ case PROP_CAPTURE:
+ self->capture = g_value_get_boolean (value);
+ const char *icon_name = self->capture
+ ? "media-playback-stop-symbolic"
+ : "media-playback-start-symbolic";
+
+ gtk_image_set_from_icon_name (GTK_IMAGE (self->capture_button_image),
+ icon_name,
+ GTK_ICON_SIZE_BUTTON);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static const char *message_types[] = { "M-SEARCH", "RESPONSE", "NOTIFY" };
+
+static char **
+packet_to_treeview_data (const gchar *from_ip,
+ char *client_interface,
+ GDateTime *arrival_time,
+ _GSSDPMessageType type,
+ SoupMessageHeaders *headers)
+{
+ char **packet_data;
+ const char *target;
+
+ packet_data = g_new0 (char *, 6);
+
+ /* Set the Time */
+ packet_data[0] = g_date_time_format (arrival_time, "%R");
+
+ /* Now the Source Address */
+ packet_data[1] = g_strdup (from_ip);
+
+ packet_data[2] = client_interface;
+
+ /* Now the Packet Type */
+ packet_data[3] = g_strdup (message_types[type]);
+
+ /* Now the Packet Information */
+ if (type == _GSSDP_DISCOVERY_RESPONSE)
+ target = soup_message_headers_get_one (headers, "ST");
+ else
+ target = soup_message_headers_get_one (headers, "NT");
+
+ packet_data[4] = g_strdup (target);
+ packet_data[5] = NULL;
+
+ return packet_data;
+}
+
+static void
+append_packet (GSSDPDeviceSnifferMainWindow *self,
+ const gchar *from_ip,
+ GDateTime *arrival_time,
+ _GSSDPMessageType type,
+ SoupMessageHeaders *headers)
+{
+ GtkListStore *liststore;
+ GtkTreeIter iter;
+ char **packet_data;
+
+ liststore = GTK_LIST_STORE (gtk_tree_view_get_model (
+ GTK_TREE_VIEW (self->packet_treeview)));
+ g_assert (liststore != NULL);
+
+ char *client_interface =
+ g_strdup (gssdp_client_get_interface (self->client));
+
+ packet_data = packet_to_treeview_data (from_ip,
+ client_interface,
+ arrival_time,
+ type,
+ headers);
+
+ gtk_list_store_insert_with_values (liststore,
+ &iter,
+ 0,
+ PACKET_STORE_COLUMN_TIME,
+ packet_data[0],
+ PACKET_STORE_COLUMN_IP,
+ packet_data[1],
+ PACKET_STORE_COLUMN_INTERFACE,
+ packet_data[2],
+ PACKET_STORE_COLUMN_PACKET_TYPE,
+ packet_data[3],
+ PACKET_STORE_COLUMN_TARGET,
+ packet_data[4],
+ PACKET_STORE_COLUMN_HEADERS,
+ headers,
+ PACKET_STORE_COLUMN_RAW_ARRIVAL_TIME,
+ arrival_time,
+ -1);
+ g_strfreev (packet_data);
+}
+
+static void
+on_ssdp_message (GSSDPClient *ssdp_client,
+ const gchar *from_ip,
+ gushort from_port,
+ _GSSDPMessageType type,
+ SoupMessageHeaders *headers,
+ gpointer user_data)
+{
+ GDateTime *arrival_time;
+ GSSDPDeviceSnifferMainWindow *self =
+ GSSDP_DEVICE_SNIFFER_MAIN_WINDOW (user_data);
+
+ if (type == _GSSDP_DISCOVERY_REQUEST)
+ return;
+
+ if (gtk_search_bar_get_search_mode (GTK_SEARCH_BAR (self->searchbar))) {
+ const char *filter =
+ gtk_entry_get_text (GTK_ENTRY (self->address_filter));
+ if (filter != NULL && strcmp (filter, from_ip) != 0)
+ return;
+ }
+
+ if (!self->capture)
+ return;
+
+ arrival_time = g_date_time_new_now_local ();
+ append_packet (self, from_ip, arrival_time, type, headers);
+ g_date_time_unref (arrival_time);
+}
+
+static gboolean
+find_device (GtkTreeModel *model, const char *uuid, GtkTreeIter *iter)
+{
+ gboolean found = FALSE;
+ gboolean more;
+
+ more = gtk_tree_model_get_iter_first (model, iter);
+ while (more) {
+ char *device_uuid;
+ gtk_tree_model_get (model,
+ iter,
+ 0, &device_uuid, -1);
+ found = g_strcmp0 (device_uuid, uuid) == 0;
+ g_free (device_uuid);
+
+ if (found)
+ break;
+ more = gtk_tree_model_iter_next (model, iter);
+ }
+
+ return found;
+}
+
+static void
+append_device (GtkWidget *treeview,
+ const char *uuid,
+ const char *first_notify,
+ const char *device_type,
+ const char *location)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
+
+ if (!find_device (model, uuid, &iter)) {
+ gtk_list_store_insert_with_values (GTK_LIST_STORE (model),
+ &iter, 0,
+ DEVICE_STORE_COLUMN_UUID, uuid,
+ DEVICE_STORE_COLUMN_FIRST_SEEN, first_notify,
+ DEVICE_STORE_COLUMN_LOCATION, location, -1);
+ }
+
+ if (device_type) {
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+ DEVICE_STORE_COLUMN_TYPE, device_type, -1);
+ }
+}
+
+static void
+resource_available_cb (GSSDPDeviceSnifferMainWindow *self,
+ const char *usn,
+ GList *locations,
+ gpointer user_data)
+{
+
+ char **usn_tokens;
+ char *uuid;
+ char *device_type = NULL;
+ GDateTime *current_time = NULL;
+ char *first_notify;
+
+ current_time = g_date_time_new_now_local ();
+ first_notify = g_date_time_format (current_time, "%R");
+ g_date_time_unref (current_time);
+
+ usn_tokens = g_strsplit (usn, "::", -1);
+ g_assert (usn_tokens != NULL && usn_tokens[0] != NULL);
+
+ uuid = usn_tokens[0] + 5; /* skip the prefix 'uuid:' */
+
+ if (usn_tokens[1] && strlen(usn_tokens[1]) != 0) {
+ char **urn_tokens;
+
+ urn_tokens = g_strsplit (usn_tokens[1], ":device:", -1);
+
+ if (urn_tokens[1])
+ device_type = g_strdup (urn_tokens[1]);
+ g_strfreev (urn_tokens);
+ }
+
+ /* Device Announcement */
+ append_device (self->device_treeview,
+ uuid,
+ first_notify,
+ device_type,
+ (char *) locations->data);
+
+ g_free (device_type);
+ g_free (first_notify);
+ g_strfreev (usn_tokens);
+}
+
+static void
+remove_device (GtkWidget *treeview, const char *uuid)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
+
+ if (find_device (model, uuid, &iter)) {
+ gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+ }
+}
+
+static void
+resource_unavailable_cb (GSSDPDeviceSnifferMainWindow *self,
+ const char *usn,
+ gpointer user_data)
+{
+ char **usn_tokens;
+ char *uuid;
+
+ usn_tokens = g_strsplit (usn, "::", -1);
+ g_assert (usn_tokens != NULL && usn_tokens[0] != NULL);
+ uuid = usn_tokens[0] + 5; /* skip the prefix 'uuid:' */
+
+ remove_device (self->device_treeview, uuid);
+
+ g_strfreev (usn_tokens);
+}
+
+static void
+main_window_constructed (GObject *object)
+{
+ if (G_OBJECT_CLASS (gssdp_device_sniffer_main_window_parent_class) != NULL)
+ G_OBJECT_CLASS (gssdp_device_sniffer_main_window_parent_class)
+ ->constructed (object);
+
+ GSSDPDeviceSnifferMainWindow *self =
+ GSSDP_DEVICE_SNIFFER_MAIN_WINDOW (object);
+ GError *error = NULL;
+
+ self->client = GSSDP_CLIENT (g_initable_new (GSSDP_TYPE_CLIENT,
+ NULL,
+ &error,
+ "address-family",
+ self->family,
+ "interface",
+ self->interface,
+ NULL));
+
+ if (error != NULL) {
+ g_critical ("Failed to create client: %s", error->message);
+ g_clear_error (&error);
+ // TODO: Show error dialog
+ return;
+ }
+
+ self->browser =
+ gssdp_resource_browser_new (self->client, GSSDP_ALL_RESOURCES);
+
+ g_signal_connect (self->client,
+ "message-received",
+ G_CALLBACK (on_ssdp_message),
+ self);
+ g_signal_connect_swapped (self->browser,
+ "resource-available",
+ G_CALLBACK (resource_available_cb),
+ self);
+ g_signal_connect_swapped (self->browser,
+ "resource-unavailable",
+ G_CALLBACK (resource_unavailable_cb),
+ self);
+
+ gssdp_resource_browser_set_active (self->browser, TRUE);
+}
+
+static void
+gssdp_device_sniffer_main_window_class_init (
+ GSSDPDeviceSnifferMainWindowClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (
+ widget_class,
+ "/org/gupnp/GSSDP/MainWindow.ui");
+ gtk_widget_class_bind_template_child (widget_class,
+ GSSDPDeviceSnifferMainWindow,
+ packet_treeview);
+ gtk_widget_class_bind_template_child (widget_class,
+ GSSDPDeviceSnifferMainWindow,
+ packet_textview);
+
+ gtk_widget_class_bind_template_child (widget_class,
+ GSSDPDeviceSnifferMainWindow,
+ capture_button_image);
+
+ gtk_widget_class_bind_template_child (widget_class,
+ GSSDPDeviceSnifferMainWindow,
+ device_treeview);
+
+ gtk_widget_class_bind_template_child (widget_class,
+ GSSDPDeviceSnifferMainWindow,
+ details_scrolled);
+
+ gtk_widget_class_bind_template_child(widget_class,
+ GSSDPDeviceSnifferMainWindow,
+ sniffer_context_menu);
+
+ gtk_widget_class_bind_template_child(widget_class,
+ GSSDPDeviceSnifferMainWindow,
+ searchbar);
+
+ gtk_widget_class_bind_template_child(widget_class,
+ GSSDPDeviceSnifferMainWindow,
+ address_filter);
+
+ gtk_widget_class_bind_template_child(widget_class,
+ GSSDPDeviceSnifferMainWindow,
+ searchbutton);
+
+
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->set_property = main_window_set_property;
+ object_class->get_property = main_window_get_property;
+ object_class->constructed = main_window_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ADDRESS_FAMILY,
+ g_param_spec_enum (
+ "address-family",
+ "Socket address familiy",
+ "The socket address familiy of the SSDP client",
+ G_TYPE_SOCKET_FAMILY,
+ G_SOCKET_FAMILY_INVALID,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_INTERFACE,
+ g_param_spec_string ("interface",
+ "Network interface",
+ "The network interface of the SSDP client",
+ NULL,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CAPTURE,
+ g_param_spec_boolean (
+ "capture",
+ "Packet capture state",
+ "Whether or not packet capturing is enabled",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+on_realize (GtkWidget *self, gpointer user_data)
+{
+ double w;
+ double h;
+#if GTK_CHECK_VERSION(3, 22, 0)
+ {
+ GdkWindow *window = gtk_widget_get_window (self);
+ GdkDisplay *display = gdk_display_get_default ();
+ GdkMonitor *monitor =
+ gdk_display_get_monitor_at_window (display, window);
+ GdkRectangle rectangle;
+
+ gdk_monitor_get_geometry (monitor, &rectangle);
+ w = rectangle.width * 0.75;
+ h = rectangle.height * 0.75;
+ }
+
+#else
+ w = gdk_screen_width () * 0.75;
+ h = gdk_screen_height () * 0.75;
+#endif
+
+ int window_width = CLAMP ((int) w, 10, 1000);
+ int window_height = CLAMP ((int) h, 10, 800);
+ gtk_window_set_default_size (GTK_WINDOW (self),
+ window_width,
+ window_height);
+}
+
+static void
+packet_header_to_string (const char *header_name,
+ const char *header_val,
+ GString **text)
+{
+ g_string_append_printf (*text, "%s: %s\n", header_name, header_val);
+}
+
+static void
+clear_textbuffer (GtkTextBuffer *textbuffer)
+{
+ GtkTextIter start, end;
+
+ gtk_text_buffer_get_bounds (textbuffer, &start, &end);
+ gtk_text_buffer_delete (textbuffer, &start, &end);
+}
+
+static void
+update_packet_details (GSSDPDeviceSnifferMainWindow *self,
+ const char *text,
+ int len)
+{
+ GtkTextBuffer *textbuffer;
+
+ textbuffer = gtk_text_view_get_buffer (
+ GTK_TEXT_VIEW (self->packet_textview));
+
+ clear_textbuffer (textbuffer);
+ gtk_text_buffer_insert_at_cursor (textbuffer, text, len);
+}
+
+static void
+display_packet (GSSDPDeviceSnifferMainWindow *self,
+ GDateTime *arrival_time,
+ SoupMessageHeaders *packet_headers)
+{
+ GString *text;
+ char *time = NULL;
+
+ time = g_date_time_format_iso8601 (arrival_time);
+ text = g_string_new ("Received on: ");
+ g_string_append (text, time);
+ g_string_append (text, "\nHeaders:\n\n");
+ g_free (time);
+
+ soup_message_headers_foreach (
+ packet_headers,
+ (SoupMessageHeadersForeachFunc) packet_header_to_string,
+ &text);
+
+ update_packet_details (self, text->str, text->len);
+ g_string_free (text, TRUE);
+}
+
+static void
+on_packet_selected (GtkTreeSelection *selection, gpointer user_data)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GDateTime *arrival_time;
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ SoupMessageHeaders *packet_headers;
+
+ gtk_tree_model_get (model,
+ &iter,
+ PACKET_STORE_COLUMN_HEADERS,
+ &packet_headers,
+ PACKET_STORE_COLUMN_RAW_ARRIVAL_TIME,
+ &arrival_time,
+ -1);
+ display_packet (GSSDP_DEVICE_SNIFFER_MAIN_WINDOW (user_data),
+ arrival_time,
+ packet_headers);
+ g_boxed_free (SOUP_TYPE_MESSAGE_HEADERS, packet_headers);
+ g_date_time_unref (arrival_time);
+ }
+
+ else
+ update_packet_details (
+ GSSDP_DEVICE_SNIFFER_MAIN_WINDOW (user_data),
+ "",
+ 0);
+}
+
+static void
+on_clear_capture (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GSSDPDeviceSnifferMainWindow *self =
+ GSSDP_DEVICE_SNIFFER_MAIN_WINDOW (user_data);
+
+ GtkTreeModel *model;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->packet_treeview));
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+}
+
+static void
+on_trigger_rescan (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GSSDPDeviceSnifferMainWindow *self =
+ GSSDP_DEVICE_SNIFFER_MAIN_WINDOW (user_data);
+
+ gssdp_resource_browser_rescan (self->browser);
+}
+
+static void
+on_about (GSimpleAction *action, GVariant *parameter, gpointer user_data)
+{
+ const char *AUTHORS[] = { "Zeeshan Ali (Khattak) <zeeshanak gnome org>",
+ NULL };
+ gtk_show_about_dialog (
+ GTK_WINDOW (user_data),
+ "copyright",
+ _ ("Copyright © 2007 Zeeshan Ali (Khattak)"),
+ "comments",
+ _ ("A Device Sniffer tool based on GSSDP framework. Inspired "
+ "by Intel Tools for UPnP."),
+ "authors",
+ AUTHORS,
+ // TRANSLATORS: Replace this string with your names, one per
+ // line
+ "translator_credits",
+ "translator-credits",
+ "license-type",
+ GTK_LICENSE_LGPL_2_1,
+ NULL);
+}
+
+static void
+on_set_address_filter (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GSSDPDeviceSnifferMainWindow *self =
+ GSSDP_DEVICE_SNIFFER_MAIN_WINDOW (user_data);
+
+ GtkTreeSelection *selection = NULL;
+ GtkTreeModel *model = NULL;
+ GtkTreeIter iter;
+ char *ip_filter;
+
+ selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (self->packet_treeview));
+ gtk_tree_selection_get_selected (selection, &model, &iter);
+ gtk_tree_model_get (model,
+ &iter,
+ PACKET_STORE_COLUMN_IP,
+ &ip_filter,
+ -1);
+ gtk_entry_set_text (GTK_ENTRY (self->address_filter), ip_filter);
+ g_free (ip_filter);
+
+ gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (self->searchbar), TRUE);
+}
+
+
+static GActionEntry actions[] = { { "clear-capture", on_clear_capture },
+ { "trigger-rescan", on_trigger_rescan },
+ { "about", on_about },
+ { "set-address-filter",
+ on_set_address_filter } };
+
+static void
+do_popup_menu (GSSDPDeviceSnifferMainWindow *self, GdkEventButton *event)
+{
+ gtk_menu_popup_at_pointer (GTK_MENU (self->context_menu),
+ (event != NULL) ? (GdkEvent *) event
+ : gtk_get_current_event ());
+}
+
+static gboolean
+on_popup_menu (GtkWidget *widget, gpointer user_data)
+{
+ GSSDPDeviceSnifferMainWindow *self =
+ GSSDP_DEVICE_SNIFFER_MAIN_WINDOW (user_data);
+
+ do_popup_menu (self, NULL);
+
+ return TRUE;
+}
+
+static gboolean
+on_button_release (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
+{
+ GSSDPDeviceSnifferMainWindow *self =
+ GSSDP_DEVICE_SNIFFER_MAIN_WINDOW (user_data);
+
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ if (event->type != GDK_BUTTON_RELEASE || event->button != 3) {
+ return FALSE;
+ }
+
+ selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (self->packet_treeview));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return FALSE;
+ }
+
+ do_popup_menu (self, event);
+
+ return TRUE;
+}
+
+static void
+gssdp_device_sniffer_main_window_init (GSSDPDeviceSnifferMainWindow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->context_menu =
+ gtk_menu_new_from_model (self->sniffer_context_menu);
+
+ gtk_menu_attach_to_widget(GTK_MENU (self->context_menu), self->packet_treeview, NULL);
+
+ g_signal_connect (G_OBJECT (self),
+ "realize",
+ G_CALLBACK (on_realize),
+ NULL);
+
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (self->packet_treeview));
+ g_signal_connect (G_OBJECT (selection),
+ "changed",
+ G_CALLBACK (on_packet_selected),
+ self);
+
+ g_signal_connect (G_OBJECT (self->packet_treeview),
+ "popup-menu",
+ G_CALLBACK (on_popup_menu),
+ self);
+
+ g_signal_connect (G_OBJECT (self->packet_treeview),
+ "button-release-event",
+ G_CALLBACK (on_button_release),
+ self);
+
+
+ GPropertyAction *action =
+ g_property_action_new ("toggle-capture", self, "capture");
+ g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (action));
+ action = g_property_action_new ("show-packet-details",
+ self->details_scrolled,
+ "visible");
+ g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (action));
+
+ g_action_map_add_action_entries (G_ACTION_MAP (self),
+ actions,
+ G_N_ELEMENTS (actions),
+ self);
+
+ g_object_bind_property (self->searchbutton,
+ "active",
+ self->searchbar,
+ "search-mode-enabled",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+}
diff --git a/tools/main-window.h b/tools/main-window.h
new file mode 100644
index 0000000..ba8877f
--- /dev/null
+++ b/tools/main-window.h
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GSSDP_DEVICE_SNIFFER_TYPE_MAIN_WINDOW \
+ (gssdp_device_sniffer_main_window_get_type ())
+
+G_DECLARE_FINAL_TYPE (GSSDPDeviceSnifferMainWindow,
+ gssdp_device_sniffer_main_window,
+ GSSDP_DEVICE_SNIFFER,
+ MAIN_WINDOW,
+ GtkApplicationWindow)
+
+G_END_DECLS
diff --git a/tools/main-window.ui b/tools/main-window.ui
new file mode 100644
index 0000000..13d5ed9
--- /dev/null
+++ b/tools/main-window.ui
@@ -0,0 +1,395 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.2 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkListStore" id="device_store">
+ <columns>
+ <!-- column-name uid -->
+ <column type="gchararray"/>
+ <!-- column-name first-seen -->
+ <column type="gchararray"/>
+ <!-- column-name device-type -->
+ <column type="gchararray"/>
+ <!-- column-name location -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="packet_store">
+ <columns>
+ <!-- column-name time -->
+ <column type="gchararray"/>
+ <!-- column-name source-address -->
+ <column type="gchararray"/>
+ <!-- column-name interface -->
+ <column type="gchararray"/>
+ <!-- column-name type -->
+ <column type="gchararray"/>
+ <!-- column-name information -->
+ <column type="gchararray"/>
+ <!-- column-name SoupMessageHeader1 -->
+ <column type="SoupMessageHeaders"/>
+ <!-- column-name GDateTime1 -->
+ <column type="GDateTime"/>
+ </columns>
+ </object>
+ <menu id="sniffer-window-menu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Show packet details</attribute>
+ <attribute name="action">win.show-packet-details</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_About</attribute>
+ <attribute name="action">win.about</attribute>
+ </item>
+ </section>
+ </menu>
+ <template class="GSSDPDeviceSnifferMainWindow" parent="GtkApplicationWindow">
+ <property name="can_focus">False</property>
+ <property name="can_focus">False</property>
+ <child>
+ <menu id="sniffer_context_menu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Set as filter</attribute>
+ <attribute name="action">win.set-address-filter</attribute>
+ </item>
+ </section>
+ </menu>
+ </child>
+ <child type="titlebar">
+ <object class="GtkHeaderBar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show_close_button">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Clear packet capture</property>
+ <property name="action_name">win.clear-capture</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">edit-clear-all-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Toggle packet capture</property>
+ <property name="active">True</property>
+ <property name="action_name">win.toggle-capture</property>
+ <child>
+ <object class="GtkImage" id="capture_button_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playback-stop-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Trigger a rescan of the network</property>
+ <property name="action_name">win.trigger-rescan</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">view-refresh-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="title">
+ <object class="GtkStackSwitcher">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="icon_size">0</property>
+ <property name="stack">stack1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="searchbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Enable content filter</property>
+ <property name="active">False</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">system-search-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="window-menu">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="menu-model">sniffer-window-menu</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">open-menu-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="stack1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="vbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkSearchBar" id="searchbar">
+ <property name="visible">True</property>
+ <property name="show_close_button">True</property>
+ <property name="search-mode-enabled">True</property>
+ <child>
+ <object class="GtkSearchEntry" id="address_filter">
+ <property name="visible">True</property>
+ <property name="max-width-chars">40</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="packet-scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="packet_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">packet_store</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection"/>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn">
+ <property name="title" translatable="yes">Time</property>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="markup">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn">
+ <property name="title" translatable="yes">Source Address</property>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="markup">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn">
+ <property name="title" translatable="yes">Interface</property>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="markup">2</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn">
+ <property name="title" translatable="yes">Packet Type</property>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="markup">3</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn">
+ <property name="title" translatable="yes">Packet Information</property>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="markup">4</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="details_scrolled">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTextView" id="packet_textview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="monospace">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">page0</property>
+ <property name="title" translatable="yes">Packets</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="device-details-scrolledwindow">
+ <property name="name">Bar</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="device_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">device_store</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection"/>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn">
+ <property name="title" translatable="yes">Unique Identifier</property>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn">
+ <property name="title" translatable="yes">First Notify</property>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn">
+ <property name="title" translatable="yes">Device Type</property>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">2</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn">
+ <property name="title" translatable="yes">Location</property>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">3</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">page1</property>
+ <property name="title" translatable="yes">Device history</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/tools/meson.build b/tools/meson.build
index bfd44d2..59c5e74 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -7,6 +7,8 @@ sniffer = executable(
'gssdp-device-sniffer',
[
'gssdp-device-sniffer.c',
+ 'main-window.c',
+ 'main-window.h',
resource
],
dependencies : [gssdp, gtk],
diff --git a/tools/window-menu.ui b/tools/window-menu.ui
index eae5745..a6bd31c 100644
--- a/tools/window-menu.ui
+++ b/tools/window-menu.ui
@@ -2,24 +2,6 @@
<!-- Generated with glade 3.22.0 -->
<interface>
<requires lib="gtk+" version="3.12"/>
- <menu id="sniffer-window-menu">
- <section>
- <item>
- <attribute name="label" translatable="yes">Show packet details</attribute>
- <attribute name="action">win.show-packet-details</attribute>
- </item>
- <item>
- <attribute name="label" translatable="yes">Address Filter…</attribute>
- <attribute name="action">win.show-address-filter</attribute>
- </item>
- </section>
- <section>
- <item>
- <attribute name="label" translatable="yes">_About</attribute>
- <attribute name="action">win.about</attribute>
- </item>
- </section>
- </menu>
<menu id="sniffer-context-menu">
<section>
<item>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]