chooser: pick the source type; each kind supplies its own connection form
This commit is contained in:
1 parent
059b660586
commit
14f1421b2b
2 files changed
+182
-87
No files matched your search
+171
-53
@@ -8,6 +8,7 @@
|
||||
#include "help_window.hpp"
|
||||
#include "layout_store.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "source/registry.hpp"
|
||||
// The app's shared chrome -- `ui_impl::menu_button()`/`menu_toggle()` and `install_css()` -- so the
|
||||
// chooser's help menu is the *same* menu the window's Help view is, not a second one that merely
|
||||
// looks like it. Heavy header, but a row shape copied here would be a row shape that drifts.
|
||||
@@ -19,6 +20,7 @@
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -32,10 +34,16 @@ namespace {
|
||||
/// group: it is the local address the join happens on, so storing one without the other would
|
||||
/// reconnect to the right group on the wrong path.
|
||||
void apply_source(Config& cfg, const KnownSource& src) {
|
||||
cfg.server = src.host;
|
||||
cfg.port = src.port;
|
||||
cfg.multicast = src.multicast;
|
||||
cfg.interface_addr = src.interface_addr;
|
||||
cfg.source_kind = src.kind;
|
||||
if (src.kind == "udps") {
|
||||
cfg.server = src.host;
|
||||
cfg.port = src.port;
|
||||
cfg.multicast = src.multicast;
|
||||
cfg.interface_addr = src.interface_addr;
|
||||
cfg.source_opts = SourceConfig();
|
||||
} else {
|
||||
cfg.source_opts = src.opts;
|
||||
}
|
||||
// The configuration's own startup settings. They are read *before* the model exists, which is the
|
||||
// only moment a buffer size can be applied at all -- and the reason they are stored per
|
||||
// configuration rather than only passed on the command line.
|
||||
@@ -105,10 +113,14 @@ struct Chooser {
|
||||
/// are typed: a switch is read with `adw_switch_row_get_active()` and not off a widget.
|
||||
GtkBuilder* form_ui = nullptr;
|
||||
AdwWindowTitle* form_title = nullptr;
|
||||
AdwEntryRow* form_server = nullptr;
|
||||
AdwSpinRow* form_port = nullptr;
|
||||
AdwEntryRow* form_group = nullptr;
|
||||
AdwEntryRow* form_iface = nullptr;
|
||||
/// The source-type dropdown and the group the chosen kind's rows live in. The rows themselves,
|
||||
/// and the closures that read and write them, are the kind's (`ConfigForm`); the chooser only
|
||||
/// swaps them when the dropdown moves.
|
||||
AdwComboRow* form_kind_row = nullptr;
|
||||
AdwPreferencesGroup* form_source_group = nullptr;
|
||||
std::vector<SourceKind*> form_kinds; ///< dropdown order
|
||||
ConfigForm form_cfg;
|
||||
std::vector<GtkWidget*> form_cfg_rows; ///< the rows currently in `form_source_group`
|
||||
GtkWidget* form_connect = nullptr;
|
||||
/// The configuration half of the form: what a source's *session* is called and the settings that
|
||||
/// can only be applied before the model exists. A new host needs all of them, and so does an
|
||||
@@ -373,7 +385,11 @@ void chooser_populate(Chooser& c) {
|
||||
// The *server*, even for a multicast session: that is the host the file is named after and
|
||||
// the one CONFIG is fetched from. Titling the row by the group would name it after something
|
||||
// that is not its identity.
|
||||
const std::string title = s.host + ":" + std::to_string(s.port) + " \u00b7 " + s.config;
|
||||
// A non-UDPS session whose kind is not registered keeps its pseudo-host as the title.
|
||||
SourceKind* sk = s.kind == "udps" ? nullptr : find_kind(s.kind);
|
||||
const std::string where = sk != nullptr ? sk->display_name() + " " + sk->identity(s.opts)
|
||||
: s.host + ":" + std::to_string(s.port);
|
||||
const std::string title = where + " \u00b7 " + s.config;
|
||||
GtkWidget* title_label = gtk_label_new(title.c_str());
|
||||
gtk_widget_set_halign(title_label, GTK_ALIGN_START);
|
||||
gtk_box_append(GTK_BOX(text), title_label);
|
||||
@@ -432,34 +448,69 @@ void form_close(Chooser& c) {
|
||||
if (c.form != nullptr) adw_dialog_close(c.form);
|
||||
}
|
||||
|
||||
void on_form_server_changed(GtkEditable* entry, gpointer user) {
|
||||
/// The kind selected in the dropdown, or null.
|
||||
SourceKind* form_selected_kind(const Chooser& c) {
|
||||
if (c.form_kind_row == nullptr) return nullptr;
|
||||
const guint i = adw_combo_row_get_selected(c.form_kind_row);
|
||||
return i < c.form_kinds.size() ? c.form_kinds[i] : nullptr;
|
||||
}
|
||||
|
||||
/// Swaps the Connection group's rows for `kind`'s form and fills them from `initial`. The
|
||||
/// connect button follows the form's own `valid()`: the one validation here is the kind's.
|
||||
void form_rebuild(Chooser& c, SourceKind& kind, const SourceConfig& initial) {
|
||||
for (GtkWidget* r : c.form_cfg_rows) adw_preferences_group_remove(c.form_source_group, r);
|
||||
c.form_cfg_rows.clear();
|
||||
c.form_cfg = kind.make_config_form();
|
||||
for (GtkWidget* r : c.form_cfg.rows) {
|
||||
adw_preferences_group_add(c.form_source_group, r);
|
||||
c.form_cfg_rows.push_back(r);
|
||||
}
|
||||
adw_preferences_group_set_description(
|
||||
c.form_source_group,
|
||||
c.form_cfg.description.empty() ? nullptr : c.form_cfg.description.c_str());
|
||||
c.form_cfg.write(initial);
|
||||
c.form_cfg.on_change([&c] {
|
||||
if (c.form_connect != nullptr) {
|
||||
gtk_widget_set_sensitive(c.form_connect, c.form_cfg.valid() ? TRUE : FALSE);
|
||||
}
|
||||
});
|
||||
if (c.form_connect != nullptr) {
|
||||
gtk_widget_set_sensitive(c.form_connect, c.form_cfg.valid() ? TRUE : FALSE);
|
||||
}
|
||||
if (c.form_cfg.focus != nullptr) gtk_widget_grab_focus(c.form_cfg.focus);
|
||||
}
|
||||
|
||||
void on_form_kind_changed(GObject*, GParamSpec*, gpointer user) {
|
||||
Chooser& c = *static_cast<Chooser*>(user);
|
||||
if (c.form_connect == nullptr) return;
|
||||
// The only validation here. A *malformed* address is the acquisition layer's to report -- it
|
||||
// already does, and the starvation notice covers a source that takes the socket and says
|
||||
// nothing -- so checking it twice would be two expressions of one rule.
|
||||
const char* text = gtk_editable_get_text(entry);
|
||||
gtk_widget_set_sensitive(c.form_connect, text != nullptr && text[0] != '\0');
|
||||
if (SourceKind* k = form_selected_kind(c)) form_rebuild(c, *k, SourceConfig());
|
||||
}
|
||||
|
||||
void on_form_connect(GtkButton*, gpointer user) {
|
||||
Chooser& c = *static_cast<Chooser*>(user);
|
||||
const char* server = gtk_editable_get_text(GTK_EDITABLE(c.form_server));
|
||||
if (server == nullptr || server[0] == '\0') return;
|
||||
const std::string host = server;
|
||||
const std::uint16_t port = static_cast<std::uint16_t>(adw_spin_row_get_value(c.form_port));
|
||||
const std::string group = gtk_editable_get_text(GTK_EDITABLE(c.form_group));
|
||||
const std::string iface = gtk_editable_get_text(GTK_EDITABLE(c.form_iface));
|
||||
SourceKind* kind = form_selected_kind(c);
|
||||
if (kind == nullptr || !c.form_cfg.valid()) return;
|
||||
const SourceConfig scfg = c.form_cfg.read();
|
||||
const bool udps = kind->id() == "udps";
|
||||
const std::string host = session_host(kind->id(), scfg);
|
||||
const std::uint16_t port = session_port(kind->id(), scfg);
|
||||
const std::string group = udps ? scfg.get("multicast") : std::string();
|
||||
const std::string iface = udps ? scfg.get("interface") : std::string();
|
||||
std::string name = gtk_editable_get_text(GTK_EDITABLE(c.form_name));
|
||||
const std::string note = gtk_editable_get_text(GTK_EDITABLE(c.form_note));
|
||||
const double buffer_mb = adw_spin_row_get_value(c.form_buffer);
|
||||
const bool relative = adw_switch_row_get_active(c.form_relative) != FALSE;
|
||||
if (name.empty()) name = "default";
|
||||
|
||||
c.cfg->server = host;
|
||||
c.cfg->port = port;
|
||||
c.cfg->multicast = group;
|
||||
c.cfg->interface_addr = iface;
|
||||
c.cfg->source_kind = kind->id();
|
||||
if (udps) {
|
||||
c.cfg->server = scfg.get("server");
|
||||
c.cfg->port = port;
|
||||
c.cfg->multicast = group;
|
||||
c.cfg->interface_addr = iface;
|
||||
c.cfg->source_opts = SourceConfig();
|
||||
} else {
|
||||
c.cfg->source_opts = scfg;
|
||||
}
|
||||
// The startup settings. Applied here because this runs *before* `ScopeModel` exists, which is the
|
||||
// only moment a buffer size can be applied at all -- and stored, so the next run gets them back.
|
||||
c.cfg->buffer_mb = buffer_mb;
|
||||
@@ -476,6 +527,8 @@ void on_form_connect(GtkButton*, gpointer user) {
|
||||
st.relative_time = relative;
|
||||
st.multicast = group;
|
||||
st.interface_addr = iface;
|
||||
st.source_kind = udps ? std::string() : kind->id();
|
||||
st.source_opts = udps ? SourceConfig() : scfg;
|
||||
|
||||
const bool editing = !c.edit_config.empty();
|
||||
const bool moved =
|
||||
@@ -527,10 +580,9 @@ void open_form(Chooser& c, int edit_index = -1) {
|
||||
c.form_ui = gtk_builder_new_from_resource("/org/udpscope/ui/source-form.ui");
|
||||
if (c.form_ui == nullptr) return;
|
||||
GObject* dialog = gtk_builder_get_object(c.form_ui, "source_form");
|
||||
c.form_server = ADW_ENTRY_ROW(gtk_builder_get_object(c.form_ui, "server_row"));
|
||||
c.form_port = ADW_SPIN_ROW(gtk_builder_get_object(c.form_ui, "port_row"));
|
||||
c.form_group = ADW_ENTRY_ROW(gtk_builder_get_object(c.form_ui, "group_row"));
|
||||
c.form_iface = ADW_ENTRY_ROW(gtk_builder_get_object(c.form_ui, "iface_row"));
|
||||
c.form_kind_row = ADW_COMBO_ROW(gtk_builder_get_object(c.form_ui, "kind_row"));
|
||||
c.form_source_group =
|
||||
ADW_PREFERENCES_GROUP(gtk_builder_get_object(c.form_ui, "source_group"));
|
||||
c.form_name = ADW_ENTRY_ROW(gtk_builder_get_object(c.form_ui, "name_row"));
|
||||
c.form_note = ADW_ENTRY_ROW(gtk_builder_get_object(c.form_ui, "note_row"));
|
||||
c.form_buffer = ADW_SPIN_ROW(gtk_builder_get_object(c.form_ui, "buffer_row"));
|
||||
@@ -539,8 +591,8 @@ void open_form(Chooser& c, int edit_index = -1) {
|
||||
c.form_connect = GTK_WIDGET(gtk_builder_get_object(c.form_ui, "form_apply"));
|
||||
// A missing id is the one mistake neither the build nor `GtkBuilder` sees, and every read below would
|
||||
// be a null row.
|
||||
if (dialog == nullptr || c.form_server == nullptr || c.form_port == nullptr ||
|
||||
c.form_group == nullptr || c.form_iface == nullptr || c.form_name == nullptr ||
|
||||
if (dialog == nullptr || c.form_kind_row == nullptr || c.form_source_group == nullptr ||
|
||||
c.form_name == nullptr ||
|
||||
c.form_note == nullptr || c.form_buffer == nullptr || c.form_relative == nullptr ||
|
||||
c.form_connect == nullptr) {
|
||||
g_clear_object(&c.form_ui);
|
||||
@@ -558,11 +610,6 @@ void open_form(Chooser& c, int edit_index = -1) {
|
||||
|
||||
// The values: the configuration being edited, or what the command line asked for. The hints live in
|
||||
// the rows' subtitles, so an empty field still says what empty means.
|
||||
gtk_editable_set_text(GTK_EDITABLE(c.form_server), src != nullptr ? src->host.c_str() : "127.0.0.1");
|
||||
adw_spin_row_set_value(c.form_port, src != nullptr ? src->port : 8000);
|
||||
gtk_editable_set_text(GTK_EDITABLE(c.form_group), src != nullptr ? src->multicast.c_str() : "");
|
||||
gtk_editable_set_text(GTK_EDITABLE(c.form_iface),
|
||||
src != nullptr ? src->interface_addr.c_str() : "");
|
||||
gtk_editable_set_text(GTK_EDITABLE(c.form_name), src != nullptr ? src->config.c_str() : "default");
|
||||
gtk_editable_set_text(GTK_EDITABLE(c.form_note), src != nullptr ? src->comment.c_str() : "");
|
||||
adw_spin_row_set_value(c.form_buffer, src != nullptr && src->buffer_mb > 0.0
|
||||
@@ -572,6 +619,37 @@ void open_form(Chooser& c, int edit_index = -1) {
|
||||
(src != nullptr ? src->relative_time : c.cfg->relative_time) ? TRUE
|
||||
: FALSE);
|
||||
|
||||
// The dropdown lists the registered kinds. A new host starts on the kind the command line named
|
||||
// (UDPS by default); an edit starts on the configuration's own kind and cannot change it --
|
||||
// disabled, not hidden, like every other control that does not apply.
|
||||
c.form_kinds = kinds();
|
||||
GtkStringList* names = gtk_string_list_new(nullptr);
|
||||
guint selected = 0;
|
||||
const std::string want_kind = src != nullptr ? src->kind : c.cfg->source_kind;
|
||||
for (guint i = 0; i < c.form_kinds.size(); ++i) {
|
||||
gtk_string_list_append(names, c.form_kinds[i]->display_name().c_str());
|
||||
if (c.form_kinds[i]->id() == want_kind) selected = i;
|
||||
}
|
||||
adw_combo_row_set_model(c.form_kind_row, G_LIST_MODEL(names));
|
||||
adw_combo_row_set_selected(c.form_kind_row, selected);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(c.form_kind_row), src == nullptr);
|
||||
g_object_unref(names);
|
||||
|
||||
SourceConfig initial;
|
||||
if (src != nullptr && src->kind == "udps") {
|
||||
initial.set("server", src->host);
|
||||
initial.set("port", std::to_string(src->port));
|
||||
if (!src->multicast.empty()) initial.set("multicast", src->multicast);
|
||||
if (!src->interface_addr.empty()) initial.set("interface", src->interface_addr);
|
||||
} else if (src != nullptr) {
|
||||
initial = src->opts;
|
||||
} else if (c.cfg->source_kind != "udps") {
|
||||
initial = c.cfg->source_opts;
|
||||
}
|
||||
if (!c.form_kinds.empty()) form_rebuild(c, *c.form_kinds[selected], initial);
|
||||
// Connected after the first rebuild: setting `selected` above must not rebuild a second time.
|
||||
g_signal_connect(c.form_kind_row, "notify::selected", G_CALLBACK(on_form_kind_changed), &c);
|
||||
|
||||
if (GObject* cancel = gtk_builder_get_object(c.form_ui, "form_cancel"); cancel != nullptr) {
|
||||
g_signal_connect(cancel, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
||||
form_close(*static_cast<Chooser*>(user));
|
||||
@@ -579,16 +657,22 @@ void open_form(Chooser& c, int edit_index = -1) {
|
||||
&c);
|
||||
}
|
||||
g_signal_connect(c.form_connect, "clicked", G_CALLBACK(on_form_connect), &c);
|
||||
g_signal_connect(c.form_server, "changed", G_CALLBACK(on_form_server_changed), &c);
|
||||
// `closed` clears the pointer. An `AdwDialog` emits that and not `close-request`, and it fires only
|
||||
// when the dialog really went away -- which is what the comment here used to be about.
|
||||
g_signal_connect(c.form, "closed", G_CALLBACK(+[](AdwDialog*, gpointer user) {
|
||||
static_cast<Chooser*>(user)->form = nullptr;
|
||||
Chooser& ch = *static_cast<Chooser*>(user);
|
||||
ch.form = nullptr;
|
||||
// The rows are gone with the dialog; dropping the closures that captured them
|
||||
// keeps a stale `read()` from ever being callable.
|
||||
ch.form_cfg = ConfigForm();
|
||||
ch.form_cfg_rows.clear();
|
||||
ch.form_kind_row = nullptr;
|
||||
ch.form_source_group = nullptr;
|
||||
}),
|
||||
&c);
|
||||
|
||||
adw_dialog_present(c.form, GTK_WIDGET(c.window));
|
||||
gtk_widget_grab_focus(GTK_WIDGET(c.form_server));
|
||||
if (c.form_cfg.focus != nullptr) gtk_widget_grab_focus(c.form_cfg.focus);
|
||||
}
|
||||
|
||||
/// What the pick probes report: the connection the chooser came back with. One helper rather than
|
||||
@@ -614,6 +698,15 @@ void report_pick(const Chooser& c) {
|
||||
(c.saved_config.empty() ? (c.known.empty() ? "-" : c.known.front().comment)
|
||||
: c.saved_note)
|
||||
.c_str());
|
||||
if (c.cfg->source_kind != "udps") {
|
||||
std::string opts;
|
||||
for (const auto& kv : c.cfg->source_opts.items()) {
|
||||
opts += (opts.empty() ? "" : ",") + kv.first + "=" + kv.second;
|
||||
}
|
||||
std::fprintf(stderr, "[sources] picked kind=%s opts=%s session=%s:%u\n",
|
||||
c.cfg->source_kind.c_str(), opts.c_str(), c.cfg->session_host().c_str(),
|
||||
c.cfg->session_port());
|
||||
}
|
||||
std::fflush(stderr);
|
||||
}
|
||||
|
||||
@@ -1039,22 +1132,42 @@ gboolean pick_probe(gpointer user) {
|
||||
open_form(c);
|
||||
const std::string s = spec;
|
||||
const std::size_t colon = s.rfind(':');
|
||||
gtk_editable_set_text(GTK_EDITABLE(c.form_server),
|
||||
colon == std::string::npos ? s.c_str() : s.substr(0, colon).c_str());
|
||||
if (colon != std::string::npos) {
|
||||
adw_spin_row_set_value(c.form_port, std::atof(s.c_str() + colon + 1));
|
||||
SourceConfig sc;
|
||||
if (const char* kspec = g_getenv("UDPSCOPE_PROBE_NEWHOST_KIND")) {
|
||||
// "kind" or "kind:k=v,k=v": pick the kind in the dropdown (which rebuilds the form), then
|
||||
// write the options into it.
|
||||
std::string ks = kspec;
|
||||
std::string opts;
|
||||
if (const std::size_t sep = ks.find(':'); sep != std::string::npos) {
|
||||
opts = ks.substr(sep + 1);
|
||||
ks = ks.substr(0, sep);
|
||||
}
|
||||
for (guint i = 0; i < c.form_kinds.size(); ++i) {
|
||||
if (c.form_kinds[i]->id() == ks) adw_combo_row_set_selected(c.form_kind_row, i);
|
||||
}
|
||||
for (std::size_t pos = 0; pos < opts.size();) {
|
||||
const std::size_t end = std::min(opts.find(',', pos), opts.size());
|
||||
sc.parse_assignment(opts.substr(pos, end - pos));
|
||||
pos = end + 1;
|
||||
}
|
||||
} else {
|
||||
sc.set("server", colon == std::string::npos ? s : s.substr(0, colon));
|
||||
if (colon != std::string::npos) sc.set("port", s.substr(colon + 1));
|
||||
}
|
||||
c.form_cfg.write(sc);
|
||||
gtk_editable_set_text(GTK_EDITABLE(c.form_name), "probe-host");
|
||||
gtk_editable_set_text(GTK_EDITABLE(c.form_note), "made by the new-host probe");
|
||||
adw_spin_row_set_value(c.form_buffer, 32);
|
||||
adw_switch_row_set_active(c.form_relative, TRUE);
|
||||
// 8/8: the form's row titles are mnemonics (`_Server`, `_Port`, `Multicast _group`, ...). The two
|
||||
// buttons already asked for it; the rows did not, and drew their underscores.
|
||||
std::fprintf(stderr, "[sources] form mnemonics=%d/8\n",
|
||||
(row_underlines(G_OBJECT(c.form_server)) + row_underlines(G_OBJECT(c.form_port)) +
|
||||
row_underlines(G_OBJECT(c.form_group)) + row_underlines(G_OBJECT(c.form_iface)) +
|
||||
row_underlines(G_OBJECT(c.form_name)) + row_underlines(G_OBJECT(c.form_note)) +
|
||||
row_underlines(G_OBJECT(c.form_buffer)) + row_underlines(G_OBJECT(c.form_relative))));
|
||||
// 9/9 for UDPS: the form's row titles are mnemonics (`Source _type`, `_Server`, `_Port`, ...). The
|
||||
// total follows the kind's row count: five rows are the chooser's, the rest the kind's own.
|
||||
int underlines = row_underlines(G_OBJECT(c.form_kind_row)) +
|
||||
row_underlines(G_OBJECT(c.form_name)) + row_underlines(G_OBJECT(c.form_note)) +
|
||||
row_underlines(G_OBJECT(c.form_buffer)) +
|
||||
row_underlines(G_OBJECT(c.form_relative));
|
||||
for (GtkWidget* r : c.form_cfg.rows) underlines += row_underlines(G_OBJECT(r));
|
||||
std::fprintf(stderr, "[sources] form mnemonics=%d/%zu\n", underlines,
|
||||
5 + c.form_cfg.rows.size());
|
||||
std::fprintf(stderr,
|
||||
"[sources] form: name='%s' note='%s' buffer=%.6g relative=%d "
|
||||
"connect_sensitive=%d\n",
|
||||
@@ -1196,8 +1309,13 @@ void loading_show(const Config& cfg) {
|
||||
std::string head;
|
||||
std::string sub;
|
||||
if (cfg.open_waveform.empty()) {
|
||||
head = "Connecting to " + cfg.server + ":" + std::to_string(cfg.port);
|
||||
if (!cfg.multicast.empty()) head += " (" + cfg.multicast + ")";
|
||||
SourceKind* kind = cfg.source_kind != "udps" ? find_kind(cfg.source_kind) : nullptr;
|
||||
if (kind != nullptr) {
|
||||
head = "Connecting to " + kind->display_name() + " " + kind->identity(cfg.source_config());
|
||||
} else {
|
||||
head = "Connecting to " + cfg.server + ":" + std::to_string(cfg.port);
|
||||
if (!cfg.multicast.empty()) head += " (" + cfg.multicast + ")";
|
||||
}
|
||||
char buf[128];
|
||||
std::snprintf(buf, sizeof(buf), "planning the %.0f MB sample buffer", cfg.buffer_mb);
|
||||
sub = buf;
|
||||
|
||||
+11
-34
@@ -2,7 +2,7 @@
|
||||
<!-- SPDX-License-Identifier: MIT -->
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!--
|
||||
The form a source is added or edited with: one dialog, two groups, eight rows.
|
||||
The form a source is added or edited with: one dialog, three groups, nine rows, the connection ones supplied by the chosen source kind.
|
||||
|
||||
A grid of labels and bare entries is what this was, and a grid is the wrong shape for it. Each row
|
||||
here is a whole row — a title, a subtitle under it where the hint used to be a placeholder, and the
|
||||
@@ -54,40 +54,17 @@
|
||||
<object class="AdwPreferencesPage">
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwComboRow" id="kind_row">
|
||||
<property name="title" translatable="yes">Source _type</property>
|
||||
<property name="use-underline">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="source_group">
|
||||
<property name="title" translatable="yes">Connection</property>
|
||||
<property name="description" translatable="yes">Leave the multicast group and the interface empty for unicast on the default route</property>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="server_row">
|
||||
<property name="title" translatable="yes">_Server</property>
|
||||
<property name="use-underline">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwSpinRow" id="port_row">
|
||||
<property name="title" translatable="yes">_Port</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="adjustment">
|
||||
<object class="GtkAdjustment">
|
||||
<property name="lower">1</property>
|
||||
<property name="upper">65535</property>
|
||||
<property name="step-increment">1</property>
|
||||
<property name="value">8000</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="group_row">
|
||||
<property name="title" translatable="yes">Multicast _group</property>
|
||||
<property name="use-underline">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="iface_row">
|
||||
<property name="title" translatable="yes">_Interface</property>
|
||||
<property name="use-underline">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
||||
Reference in new issue
Block a user