Implement recording

parent f2f34a4e
Pipeline #27433 passed with stage
in 4 minutes and 20 seconds
#include "bustle-record-address-dialog.h"
struct _BustleRecordAddressDialog
{
GtkDialog parent_instance;
GtkEntry *recordAddressEntry;
GtkButton *recordAddressRecord;
};
G_DEFINE_TYPE (BustleRecordAddressDialog, bustle_record_address_dialog, GTK_TYPE_DIALOG)
BustleRecordAddressDialog *
bustle_record_address_dialog_new (GtkWindow *parent)
{
return g_object_new (BUSTLE_TYPE_RECORD_ADDRESS_DIALOG,
"transient-for", parent,
NULL);
}
static void
bustle_record_address_dialog_finalize (GObject *object)
{
BustleRecordAddressDialog *self = (BustleRecordAddressDialog *)object;
g_debug ("%s: %p", G_STRFUNC, self);
G_OBJECT_CLASS (bustle_record_address_dialog_parent_class)->finalize (object);
}
static void
bustle_record_address_dialog_class_init (BustleRecordAddressDialogClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = bustle_record_address_dialog_finalize;
gtk_widget_class_set_template_from_resource (widget_class,
"/org/freedesktop/Bustle/bustle-record-address-dialog.ui");
gtk_widget_class_bind_template_child (widget_class, BustleRecordAddressDialog, recordAddressEntry);
gtk_widget_class_bind_template_child (widget_class, BustleRecordAddressDialog, recordAddressRecord);
}
static void
entry_changed_cb (GtkEditable *editable,
gpointer user_data)
{
BustleRecordAddressDialog *self = BUSTLE_RECORD_ADDRESS_DIALOG (user_data);
GtkEntry *entry = GTK_ENTRY (editable);
const gchar *text = gtk_entry_get_text (entry);
g_autoptr(GError) local_error = NULL;
gboolean ok = text && *text && g_dbus_is_supported_address (text, &local_error);
gtk_widget_set_sensitive (GTK_WIDGET (self->recordAddressRecord), ok);
if (ok)
{
gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, NULL);
}
else if (text && *text)
{
gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, local_error->message);
}
}
static void
bustle_record_address_dialog_init (BustleRecordAddressDialog *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
g_signal_connect_object (self->recordAddressEntry, "changed",
G_CALLBACK (entry_changed_cb), self, 0);
entry_changed_cb (GTK_EDITABLE (self->recordAddressEntry), self);
}
static void
response_cb (GtkDialog *dialog,
gint response_id,
gpointer user_data)
{
BustleRecordAddressDialog *self = BUSTLE_RECORD_ADDRESS_DIALOG (dialog);
g_autoptr(GTask) task = G_TASK (user_data);
if (response_id == GTK_RESPONSE_ACCEPT)
g_task_return_pointer (task,
g_strdup (gtk_entry_get_text (self->recordAddressEntry)),
g_free);
else
g_cancellable_cancel (g_task_get_cancellable (task));
g_signal_handlers_disconnect_by_data (dialog, task);
gtk_widget_destroy (GTK_WIDGET (dialog));
}
void
bustle_record_address_dialog_run_async (BustleRecordAddressDialog *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = g_task_new (self, cancellable, callback, user_data);
g_signal_connect (self, "response",
G_CALLBACK (response_cb), g_steal_pointer (&task));
gtk_widget_show_all (GTK_WIDGET (self));
}
gchar *
bustle_record_address_dialog_run_finish (BustleRecordAddressDialog *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (BUSTLE_IS_RECORD_ADDRESS_DIALOG (self), NULL);
g_return_val_if_fail (g_task_is_valid (result, self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
#pragma once
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define BUSTLE_TYPE_RECORD_ADDRESS_DIALOG (bustle_record_address_dialog_get_type())
G_DECLARE_FINAL_TYPE (BustleRecordAddressDialog, bustle_record_address_dialog, BUSTLE, RECORD_ADDRESS_DIALOG, GtkDialog)
BustleRecordAddressDialog *bustle_record_address_dialog_new (GtkWindow *parent);
void bustle_record_address_dialog_run_async (BustleRecordAddressDialog *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gchar *bustle_record_address_dialog_run_finish (BustleRecordAddressDialog *self,
GAsyncResult *result,
GError **error);
G_END_DECLS
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<template class="BustleRecordAddressDialog" parent="GtkDialog">
<property name="title" translatable="yes">Record Address</property>
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="recordAddressCancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="recordAddressRecord">
<property name="label" translatable="yes">_Record</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Address:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="recordAddressEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="activates_default">True</property>
<property name="width_chars">60</property>
<property name="placeholder_text" translatable="yes" comments="Just translate the &quot;e.g.&quot;; leave the rest untouched.">e.g. unix:abstract=/tmp/dbus-E5RlEB5Tzu,guid= b1c1921b62283b7b612b57305b20cc28</property>
<property name="input_hints">GTK_INPUT_HINT_NO_SPELLCHECK | GTK_INPUT_HINT_NO_EMOJI | GTK_INPUT_HINT_NONE</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">recordAddressCancel</action-widget>
<action-widget response="-3">recordAddressRecord</action-widget>
</action-widgets>
</template>
</interface>
......@@ -11,7 +11,8 @@ open_cb (GApplication *application,
{
for (int i = 0; i < n_files; i++)
{
bustle_window_new (GTK_APPLICATION (application), files[i]);
BustleWindow *window = bustle_window_new (GTK_APPLICATION (application));
bustle_window_load_file (window, files[i]);
}
}
......@@ -19,9 +20,7 @@ static void
activate_cb (GApplication *application,
gpointer user_data)
{
g_autoptr(GFile) file = g_file_new_for_path ("/home/wjt/src/bustle/oh-no.pcap");
bustle_window_new (GTK_APPLICATION (application), file);
bustle_window_new (GTK_APPLICATION (application));
}
gint
......
......@@ -6,20 +6,41 @@
#include "bustle-model.h"
#include "bustle-name-model.h"
#include "bustle-pcap-reader.h"
#include "bustle-record-address-dialog.h"
#include "pcap-monitor.h"
struct _BustleWindow
{
GtkApplicationWindow parent_instance;
GFile *file;
BustlePcapMonitor *monitor;
BustleModel *model;
guint messages_logged;
/* Timestamp of the first message in @model, or 0 if it is empty. */
gint64 first_ts;
GtkStack *diagramOrNot;
GtkScrolledWindow *diagramScrolledWindow;
/* Error stuff */
GtkInfoBar *errorBar;
GtkLabel *errorBarTitle;
GtkLabel *errorBarDetails;
/* Menu stuff */
GtkMenuButton *headerRecord;
GtkMenuButton *headerStop;
/* TODO: move to actions */
GtkMenuItem *recordSession;
GtkMenuItem *recordSystem;
GtkMenuItem *recordAddress;
GtkSpinner *headerSpinner;
GtkLabel *headerTitle;
GtkLabel *headerSubtitle;
/* Details stuff */
/* TODO: move to separate widget */
GtkGrid *detailsGrid;
......@@ -38,11 +59,15 @@ struct _BustleWindow
G_DEFINE_TYPE (BustleWindow, bustle_window, GTK_TYPE_APPLICATION_WINDOW)
static void bustle_window_show_model (BustleWindow *self);
static void bustle_window_show_error (BustleWindow *self,
const gchar *title,
const GError *error);
static gboolean bustle_window_load_file (BustleWindow *self,
GError **error);
static void record_cb (GtkMenuItem *item,
gpointer user_data);
static void stop_cb (GtkButton *item,
gpointer user_data);
typedef enum {
PROP_FILE = 1,
......@@ -52,15 +77,12 @@ typedef enum {
static GParamSpec *properties [N_PROPS];
BustleWindow *
bustle_window_new (GtkApplication *application,
GFile *file)
bustle_window_new (GtkApplication *application)
{
g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);
g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
return g_object_new (BUSTLE_TYPE_WINDOW,
"application", application,
"file", file,
NULL);
}
......@@ -76,24 +98,22 @@ static void
bustle_window_constructed (GObject *object)
{
BustleWindow *self = (BustleWindow *)object;
g_autoptr(GError) error = NULL;
G_OBJECT_CLASS (bustle_window_parent_class)->constructed (object);
gtk_widget_show (GTK_WIDGET (self));
self->model = bustle_model_new ();
bustle_window_show_model (self);
g_signal_connect_object (self->errorBar, "response",
G_CALLBACK (error_bar_response_cb), self,
0);
g_assert (self->file != NULL);
if (!bustle_window_load_file (self, &error))
{
g_autofree gchar *title = g_strdup_printf (_("Could not read ‘%s’."),
g_file_peek_path (self->file));
g_signal_connect (self->recordSession, "activate", G_CALLBACK (record_cb), self);
g_signal_connect (self->recordSystem, "activate", G_CALLBACK (record_cb), self);
g_signal_connect (self->recordAddress, "activate", G_CALLBACK (record_cb), self);
g_signal_connect (self->headerStop, "clicked", G_CALLBACK (stop_cb), self);
bustle_window_show_error (self, title, error);
}
gtk_widget_show (GTK_WIDGET (self));
}
static void
......@@ -102,6 +122,8 @@ bustle_window_finalize (GObject *object)
BustleWindow *self = (BustleWindow *)object;
g_clear_object (&self->file);
g_clear_object (&self->monitor);
g_clear_object (&self->model);
G_OBJECT_CLASS (bustle_window_parent_class)->finalize (object);
}
......@@ -162,8 +184,7 @@ bustle_window_class_init (BustleWindowClass *klass)
"File",
"File",
G_TYPE_FILE,
(G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
(G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_FILE,
properties [PROP_FILE]);
......@@ -177,6 +198,16 @@ bustle_window_class_init (BustleWindowClass *klass)
gtk_widget_class_bind_template_child (widget_class, BustleWindow, errorBarTitle);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, errorBarDetails);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, headerRecord);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, headerStop);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, recordSession);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, recordSystem);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, recordAddress);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, headerSpinner);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, headerTitle);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, headerSubtitle);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, detailsGrid);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, detailsType);
gtk_widget_class_bind_template_child (widget_class, BustleWindow, detailsSender);
......@@ -204,14 +235,14 @@ timestamp_data_func (GtkTreeViewColumn *tree_column,
GtkTreeIter *iter,
gpointer data)
{
gint64 *first_ts = data;
BustleWindow *self = BUSTLE_WINDOW (data);
gint64 timestamp_usec;
g_autofree gchar *text = NULL;
gtk_tree_model_get (tree_model, iter,
BUSTLE_MODEL_COLUMN_TIMESTAMP_USEC, &timestamp_usec,
-1);
text = g_strdup_printf ("%.3fs", (double) (timestamp_usec - *first_ts) / G_USEC_PER_SEC);
text = g_strdup_printf ("+%.3fs", (double) (timestamp_usec - self->first_ts) / G_USEC_PER_SEC);
g_object_set (cell, "text", text, NULL);
}
......@@ -494,16 +525,12 @@ selection_changed_cb (GtkTreeSelection *selection,
}
static void
bustle_window_show_model (BustleWindow *self,
GtkTreeModel *model)
bustle_window_show_model (BustleWindow *self)
{
g_autoptr(GtkTreePath) path = gtk_tree_path_new_from_string ("0");
GtkTreeIter iter;
gint64 *first_ts = g_new (gint64, 1);
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_model_get (model, &iter, BUSTLE_MODEL_COLUMN_TIMESTAMP_USEC, first_ts, -1);
/*
*/
GtkTreeModel *model = bustle_model_get_tree_model (self->model);
GtkWidget *tree_view = gtk_tree_view_new_with_model (model);
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
......@@ -519,7 +546,7 @@ bustle_window_show_model (BustleWindow *self,
column = gtk_tree_view_column_new_with_attributes ("Timestamp", renderer, NULL);
gtk_tree_view_column_set_cell_data_func (column, renderer,
timestamp_data_func,
g_steal_pointer (&first_ts), g_free);
self, NULL);
gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
renderer = gtk_cell_renderer_text_new ();
......@@ -559,13 +586,26 @@ bustle_window_show_model (BustleWindow *self,
selection_changed_cb (selection, self);
gtk_container_add (GTK_CONTAINER (self->diagramScrolledWindow), tree_view);
gtk_stack_set_visible_child_name (self->diagramOrNot, "CanvasPage");
gtk_widget_show (tree_view);
}
static void
bustle_window_add_message (BustleWindow *self,
gint64 ts,
GDBusMessage *message)
{
if (self->first_ts == 0)
{
self->first_ts = ts;
gtk_stack_set_visible_child_name (self->diagramOrNot, "CanvasPage");
}
bustle_model_add_message (self->model, ts, message);
}
static gboolean
bustle_window_load_file (BustleWindow *self,
GError **error)
bustle_window_do_load_file (BustleWindow *self,
GError **error)
{
g_assert (self->file != NULL);
......@@ -574,8 +614,6 @@ bustle_window_load_file (BustleWindow *self,
if (reader == NULL)
return FALSE;
g_autoptr(BustleModel) model = bustle_model_new ();
gint64 ts;
g_autoptr(GDBusMessage) message = NULL;
......@@ -584,11 +622,10 @@ bustle_window_load_file (BustleWindow *self,
if (message == NULL)
{
/* EOF */
bustle_window_show_model (self, bustle_model_get_tree_model (model));
return TRUE;
}
bustle_model_add_message (model, ts, message);
bustle_window_add_message (self, ts, message);
}
return FALSE;
......@@ -615,3 +652,227 @@ bustle_window_show_error (BustleWindow *self,
/* TODO: use :revealed, modulo https://gitlab.gnome.org/GNOME/gtk/issues/1165 */
gtk_widget_show (GTK_WIDGET (self->errorBar));
}
void
bustle_window_load_file (BustleWindow *self,
GFile *file)
{
g_return_if_fail (BUSTLE_IS_WINDOW (self));
g_return_if_fail (G_IS_FILE (file));
g_return_if_fail (self->file == NULL);
self->file = g_object_ref (file);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]);
g_autoptr(GError) local_error = NULL;
if (!bustle_window_do_load_file (self, &local_error))
{
g_autofree gchar *title = g_strdup_printf (_("Could not read ‘%s’."),
g_file_peek_path (self->file));
bustle_window_show_error (self, title, local_error);
}
}
static void
message_logged_cb (BustlePcapMonitor *monitor,
long timestamp_sec,
long timestamp_usec,
const guint8 *blob,
gsize len,
gpointer user_data)
{
BustleWindow *self = BUSTLE_WINDOW (user_data);
gint64 timestamp = G_USEC_PER_SEC * timestamp_sec + timestamp_usec;
g_autoptr(GError) local_error = NULL;
g_autoptr(GDBusMessage) message = NULL;
message = g_dbus_message_new_from_blob ((guchar *) blob, len,
G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING,
&local_error);
if (message == NULL)
{
bustle_window_show_error (self,
_("Could not decode recorded message."),
local_error);
bustle_pcap_monitor_stop (self->monitor);
}
else
{
bustle_window_add_message (self, timestamp, message);
self->messages_logged++;
g_autofree gchar *subtitle = g_strdup_printf (_("%u messages"),
self->messages_logged);
gtk_label_set_text (self->headerSubtitle, subtitle);
}
}
static void
stopped_cb (BustlePcapMonitor *monitor,
guint domain,
gint code,
const gchar *message,
gpointer user_data)
{
BustleWindow *self = BUSTLE_WINDOW (user_data);
g_assert (domain != 0);
g_assert (domain <= G_MAXUINT32);
g_autoptr(GError) local_error = g_error_new_literal ((GQuark) domain, code, message);
gtk_spinner_stop (self->headerSpinner);
gtk_widget_show (GTK_WIDGET (self->headerRecord));
gtk_widget_hide (GTK_WIDGET (self->headerStop));
if (self->first_ts == 0)
{
gtk_stack_set_visible_child_name (self->diagramOrNot, "InstructionsPage");
g_clear_object (&self->file);
}
if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
bustle_window_show_error (self,
_("Recording failed."),
local_error);
g_signal_handlers_disconnect_by_data (self->monitor, self);
g_clear_object (&self->monitor);
}
static void
bustle_window_start_recording (BustleWindow *self,
GBusType bus_type,
const gchar *address)
{
g_assert (BUSTLE_IS_WINDOW (self));
g_assert ((bus_type == G_BUS_TYPE_NONE) == (address != NULL));
g_assert (self->file == NULL);
g_assert (self->monitor == NULL);
g_autoptr(GDateTime) now = g_date_time_new_now_local ();
g_autofree gchar *basename = g_date_time_format (now,
"%F %H-%M-%S.p