summary refs log tree commit diff
path: root/src/libnmt-newt
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2021-10-01 23:05:04 +0200
committerMichael Biebl <biebl@debian.org>2021-10-01 23:05:04 +0200
commite74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch)
tree3469f17ea9af91f7ff169b890633bda68b0cf76e /src/libnmt-newt
parentbfe522304da217296e2a61040f58e35ec5d6f3f2 (diff)
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'src/libnmt-newt')
-rw-r--r--src/libnmt-newt/meson.build32
-rw-r--r--src/libnmt-newt/nmt-newt-button-box.c358
-rw-r--r--src/libnmt-newt/nmt-newt-button-box.h46
-rw-r--r--src/libnmt-newt/nmt-newt-button.c232
-rw-r--r--src/libnmt-newt/nmt-newt-button.h37
-rw-r--r--src/libnmt-newt/nmt-newt-checkbox.c215
-rw-r--r--src/libnmt-newt/nmt-newt-checkbox.h37
-rw-r--r--src/libnmt-newt/nmt-newt-component.c293
-rw-r--r--src/libnmt-newt/nmt-newt-component.h41
-rw-r--r--src/libnmt-newt/nmt-newt-container.c233
-rw-r--r--src/libnmt-newt/nmt-newt-container.h43
-rw-r--r--src/libnmt-newt/nmt-newt-entry-numeric.c244
-rw-r--r--src/libnmt-newt/nmt-newt-entry-numeric.h39
-rw-r--r--src/libnmt-newt/nmt-newt-entry.c506
-rw-r--r--src/libnmt-newt/nmt-newt-entry.h54
-rw-r--r--src/libnmt-newt/nmt-newt-form.c739
-rw-r--r--src/libnmt-newt/nmt-newt-form.h48
-rw-r--r--src/libnmt-newt/nmt-newt-grid.c439
-rw-r--r--src/libnmt-newt/nmt-newt-grid.h48
-rw-r--r--src/libnmt-newt/nmt-newt-hacks.c85
-rw-r--r--src/libnmt-newt/nmt-newt-hacks.h22
-rw-r--r--src/libnmt-newt/nmt-newt-label.c303
-rw-r--r--src/libnmt-newt/nmt-newt-label.h44
-rw-r--r--src/libnmt-newt/nmt-newt-listbox.c518
-rw-r--r--src/libnmt-newt/nmt-newt-listbox.h50
-rw-r--r--src/libnmt-newt/nmt-newt-popup.c341
-rw-r--r--src/libnmt-newt/nmt-newt-popup.h44
-rw-r--r--src/libnmt-newt/nmt-newt-section.c422
-rw-r--r--src/libnmt-newt/nmt-newt-section.h40
-rw-r--r--src/libnmt-newt/nmt-newt-separator.c51
-rw-r--r--src/libnmt-newt/nmt-newt-separator.h35
-rw-r--r--src/libnmt-newt/nmt-newt-stack.c337
-rw-r--r--src/libnmt-newt/nmt-newt-stack.h41
-rw-r--r--src/libnmt-newt/nmt-newt-textbox.c279
-rw-r--r--src/libnmt-newt/nmt-newt-textbox.h42
-rw-r--r--src/libnmt-newt/nmt-newt-toggle-button.c214
-rw-r--r--src/libnmt-newt/nmt-newt-toggle-button.h39
-rw-r--r--src/libnmt-newt/nmt-newt-types.h31
-rw-r--r--src/libnmt-newt/nmt-newt-utils.c472
-rw-r--r--src/libnmt-newt/nmt-newt-utils.h32
-rw-r--r--src/libnmt-newt/nmt-newt-widget.c642
-rw-r--r--src/libnmt-newt/nmt-newt-widget.h85
-rw-r--r--src/libnmt-newt/nmt-newt.h26
43 files changed, 7879 insertions, 0 deletions
diff --git a/src/libnmt-newt/meson.build b/src/libnmt-newt/meson.build
new file mode 100644
index 00000000..9a07fd58
--- /dev/null
+++ b/src/libnmt-newt/meson.build
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+libnmt_newt = static_library(
+  'nmt-newt',
+  sources: files(
+    'nmt-newt-button-box.c',
+    'nmt-newt-button.c',
+    'nmt-newt-checkbox.c',
+    'nmt-newt-component.c',
+    'nmt-newt-container.c',
+    'nmt-newt-entry-numeric.c',
+    'nmt-newt-entry.c',
+    'nmt-newt-form.c',
+    'nmt-newt-grid.c',
+    'nmt-newt-hacks.c',
+    'nmt-newt-label.c',
+    'nmt-newt-listbox.c',
+    'nmt-newt-popup.c',
+    'nmt-newt-section.c',
+    'nmt-newt-separator.c',
+    'nmt-newt-stack.c',
+    'nmt-newt-textbox.c',
+    'nmt-newt-toggle-button.c',
+    'nmt-newt-utils.c',
+    'nmt-newt-widget.c',
+  ),
+  dependencies: [
+    libnm_dep,
+    newt_dep,
+    glib_dep,
+  ],
+)
diff --git a/src/libnmt-newt/nmt-newt-button-box.c b/src/libnmt-newt/nmt-newt-button-box.c
new file mode 100644
index 00000000..d86a154c
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-button-box.c
@@ -0,0 +1,358 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-button-box
+ * @short_description: A container for #NmtNewtButtons
+ *
+ * #NmtNewtButtonBox is a container for creating and holding
+ * #NmtNewtButtons.
+ *
+ * A button box can be either horizontally or vertically laid out, and
+ * has two sections within it: the "start" (left or top) and "end"
+ * (right or bottom). Buttons are added from left to right or top to bottom
+ * within each of the two sections.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-button-box.h"
+
+#include "nmt-newt-button.h"
+
+G_DEFINE_TYPE(NmtNewtButtonBox, nmt_newt_button_box, NMT_TYPE_NEWT_CONTAINER)
+
+#define NMT_NEWT_BUTTON_BOX_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_BUTTON_BOX, NmtNewtButtonBoxPrivate))
+
+typedef struct {
+    NmtNewtButtonBoxOrientation orientation;
+    GPtrArray *                 start_buttons, *end_buttons;
+} NmtNewtButtonBoxPrivate;
+
+enum {
+    PROP_0,
+
+    PROP_ORIENTATION,
+
+    LAST_PROP
+};
+
+/**
+ * NmtNewtButtonBoxOrientation:
+ * @NMT_NEWT_BUTTON_BOX_HORIZONTAL: horizontal
+ * @NMT_NEWT_BUTTON_BOX_VERTICAL: vertical
+ *
+ * The orientation of an #NmtNewtButtonBox
+ */
+
+/**
+ * nmt_newt_button_box_new:
+ * @orientation: the orientation
+ *
+ * Creates a new #NmtNewtButtonBox
+ *
+ * Returns: a new #NmtNewtButtonBox
+ */
+NmtNewtWidget *
+nmt_newt_button_box_new(NmtNewtButtonBoxOrientation orientation)
+{
+    return g_object_new(NMT_TYPE_NEWT_BUTTON_BOX, "orientation", orientation, NULL);
+}
+
+static void
+nmt_newt_button_box_init(NmtNewtButtonBox *bbox)
+{
+    NmtNewtButtonBoxPrivate *priv = NMT_NEWT_BUTTON_BOX_GET_PRIVATE(bbox);
+
+    priv->start_buttons = g_ptr_array_new();
+    priv->end_buttons   = g_ptr_array_new();
+}
+
+/**
+ * nmt_newt_button_box_add_start:
+ * @bbox: an #NmtNewtButtonBox
+ * @label: the label for the newt button
+ *
+ * Creates a new #NmtNewtButton with the given @label, adds it
+ * to the "start" section of @bbox, and returns the newly-created
+ * button.
+ *
+ * Returns: the newly-created button, already added to @bbox
+ */
+NmtNewtWidget *
+nmt_newt_button_box_add_start(NmtNewtButtonBox *bbox, const char *label)
+{
+    NmtNewtWidget *button;
+
+    button = nmt_newt_button_new(label);
+    nmt_newt_button_box_add_widget_start(bbox, button);
+    return button;
+}
+
+/**
+ * nmt_newt_button_box_add_widget_start:
+ * @bbox: an #NmtNewtButtonBox
+ * @widget: the #NmtNewtWidget to add
+ *
+ * Adds the given widget to the "start" section of @bbox.
+ */
+void
+nmt_newt_button_box_add_widget_start(NmtNewtButtonBox *bbox, NmtNewtWidget *widget)
+{
+    NmtNewtButtonBoxPrivate *priv = NMT_NEWT_BUTTON_BOX_GET_PRIVATE(bbox);
+
+    NMT_NEWT_CONTAINER_CLASS(nmt_newt_button_box_parent_class)
+        ->add(NMT_NEWT_CONTAINER(bbox), widget);
+    g_ptr_array_add(priv->start_buttons, widget);
+}
+
+/**
+ * nmt_newt_button_box_add_end:
+ * @bbox: an #NmtNewtButtonBox
+ * @label: the label for the newt button
+ *
+ * Creates a new #NmtNewtButton with the given @label, adds it
+ * to the "end" section of @bbox, and returns the newly-created
+ * button.
+ *
+ * Returns: the newly-created button, already added to @bbox
+ */
+NmtNewtWidget *
+nmt_newt_button_box_add_end(NmtNewtButtonBox *bbox, const char *label)
+{
+    NmtNewtWidget *button;
+
+    button = nmt_newt_button_new(label);
+    nmt_newt_button_box_add_widget_end(bbox, button);
+    return button;
+}
+
+/**
+ * nmt_newt_button_box_add_widget_end:
+ * @bbox: an #NmtNewtButtonBox
+ * @widget: the #NmtNewtWidget to add
+ *
+ * Adds the given widget to the "end" section of @bbox.
+ */
+void
+nmt_newt_button_box_add_widget_end(NmtNewtButtonBox *bbox, NmtNewtWidget *widget)
+{
+    NmtNewtButtonBoxPrivate *priv = NMT_NEWT_BUTTON_BOX_GET_PRIVATE(bbox);
+
+    NMT_NEWT_CONTAINER_CLASS(nmt_newt_button_box_parent_class)
+        ->add(NMT_NEWT_CONTAINER(bbox), widget);
+    g_ptr_array_add(priv->end_buttons, widget);
+}
+
+static void
+nmt_newt_button_box_remove(NmtNewtContainer *container, NmtNewtWidget *child)
+{
+    NmtNewtButtonBoxPrivate *priv = NMT_NEWT_BUTTON_BOX_GET_PRIVATE(container);
+    int                      i;
+
+    NMT_NEWT_CONTAINER_CLASS(nmt_newt_button_box_parent_class)->remove(container, child);
+
+    for (i = 0; i < priv->start_buttons->len; i++) {
+        if (priv->start_buttons->pdata[i] == (gpointer) child) {
+            g_ptr_array_remove_index(priv->start_buttons, i);
+            return;
+        }
+    }
+    for (i = 0; i < priv->end_buttons->len; i++) {
+        if (priv->end_buttons->pdata[i] == (gpointer) child) {
+            g_ptr_array_remove_index(priv->end_buttons, i);
+            return;
+        }
+    }
+}
+
+static void
+add_buttons(GPtrArray *buttons, GPtrArray *cos)
+{
+    NmtNewtWidget *child;
+    newtComponent *child_cos;
+    int            i, c;
+
+    for (i = 0; i < buttons->len; i++) {
+        child = buttons->pdata[i];
+
+        if (!nmt_newt_widget_get_visible(child))
+            continue;
+
+        child_cos = nmt_newt_widget_get_components(child);
+        for (c = 0; child_cos[c]; c++)
+            g_ptr_array_add(cos, child_cos[c]);
+        g_free(child_cos);
+    }
+}
+
+static newtComponent *
+nmt_newt_button_box_get_components(NmtNewtWidget *widget)
+{
+    NmtNewtButtonBoxPrivate *priv = NMT_NEWT_BUTTON_BOX_GET_PRIVATE(widget);
+    GPtrArray *              cos;
+
+    cos = g_ptr_array_new();
+    add_buttons(priv->start_buttons, cos);
+    add_buttons(priv->end_buttons, cos);
+    g_ptr_array_add(cos, NULL);
+
+    return (newtComponent *) g_ptr_array_free(cos, FALSE);
+}
+
+static void
+size_request_buttons(NmtNewtButtonBox *bbox, GPtrArray *buttons, int *width, int *height)
+{
+    NmtNewtButtonBoxPrivate *priv = NMT_NEWT_BUTTON_BOX_GET_PRIVATE(bbox);
+    int                      child_width, child_height;
+    int                      i;
+
+    for (i = 0; i < buttons->len; i++) {
+        NmtNewtWidget *child = buttons->pdata[i];
+
+        nmt_newt_widget_size_request(child, &child_width, &child_height);
+        if (priv->orientation == NMT_NEWT_BUTTON_BOX_HORIZONTAL) {
+            *width += child_width;
+            if (i > 0)
+                *width += 1;
+            *height = MAX(*height, child_height);
+        } else {
+            *height += child_height;
+            if (i > 0)
+                *height += 1;
+            *width = MAX(*width, child_width);
+        }
+    }
+}
+
+static void
+nmt_newt_button_box_size_request(NmtNewtWidget *widget, int *width, int *height)
+{
+    NmtNewtButtonBox *       bbox = NMT_NEWT_BUTTON_BOX(widget);
+    NmtNewtButtonBoxPrivate *priv = NMT_NEWT_BUTTON_BOX_GET_PRIVATE(widget);
+
+    *width = *height = 0;
+    size_request_buttons(bbox, priv->start_buttons, width, height);
+    size_request_buttons(bbox, priv->end_buttons, width, height);
+
+    if (priv->orientation == NMT_NEWT_BUTTON_BOX_HORIZONTAL)
+        *width += 1;
+    else
+        *height += 1;
+}
+
+static void
+nmt_newt_button_box_size_allocate(NmtNewtWidget *widget, int x, int y, int width, int height)
+{
+    NmtNewtButtonBoxPrivate *priv = NMT_NEWT_BUTTON_BOX_GET_PRIVATE(widget);
+    NmtNewtWidget *          child;
+    int                      child_x, child_y, child_width, child_height;
+    int                      i;
+
+    child_x = x;
+    child_y = y;
+    for (i = 0; i < priv->start_buttons->len; i++) {
+        child = priv->start_buttons->pdata[i];
+        nmt_newt_widget_size_request(child, &child_width, &child_height);
+
+        if (priv->orientation == NMT_NEWT_BUTTON_BOX_HORIZONTAL) {
+            nmt_newt_widget_size_allocate(child, child_x, child_y, child_width, child_height);
+            child_x += child_width + 1;
+        } else {
+            nmt_newt_widget_size_allocate(child, child_x, child_y, child_width, child_height);
+            child_y += child_height + 1;
+        }
+    }
+
+    if (priv->orientation == NMT_NEWT_BUTTON_BOX_HORIZONTAL)
+        child_x = x + width;
+    else
+        child_y = y + height;
+
+    for (i = priv->end_buttons->len - 1; i >= 0; i--) {
+        child = priv->end_buttons->pdata[i];
+        nmt_newt_widget_size_request(child, &child_width, &child_height);
+
+        if (priv->orientation == NMT_NEWT_BUTTON_BOX_HORIZONTAL) {
+            nmt_newt_widget_size_allocate(child,
+                                          child_x - child_width,
+                                          child_y,
+                                          child_width,
+                                          child_height);
+            child_x -= child_width + 1;
+        } else {
+            nmt_newt_widget_size_allocate(child,
+                                          child_x,
+                                          child_y - child_height,
+                                          child_width,
+                                          child_height);
+            child_y -= child_height + 1;
+        }
+    }
+}
+
+static void
+nmt_newt_button_box_set_property(GObject *     object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec *  pspec)
+{
+    NmtNewtButtonBoxPrivate *priv = NMT_NEWT_BUTTON_BOX_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_ORIENTATION:
+        priv->orientation = g_value_get_int(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_button_box_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtButtonBoxPrivate *priv = NMT_NEWT_BUTTON_BOX_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_ORIENTATION:
+        g_value_set_int(value, priv->orientation);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_button_box_class_init(NmtNewtButtonBoxClass *bbox_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(bbox_class);
+    NmtNewtWidgetClass *   widget_class    = NMT_NEWT_WIDGET_CLASS(bbox_class);
+    NmtNewtContainerClass *container_class = NMT_NEWT_CONTAINER_CLASS(bbox_class);
+
+    g_type_class_add_private(bbox_class, sizeof(NmtNewtButtonBoxPrivate));
+
+    object_class->get_property = nmt_newt_button_box_get_property;
+    object_class->set_property = nmt_newt_button_box_set_property;
+
+    widget_class->get_components = nmt_newt_button_box_get_components;
+    widget_class->size_request   = nmt_newt_button_box_size_request;
+    widget_class->size_allocate  = nmt_newt_button_box_size_allocate;
+
+    container_class->remove = nmt_newt_button_box_remove;
+
+    g_object_class_install_property(
+        object_class,
+        PROP_ORIENTATION,
+        g_param_spec_int("orientation",
+                         "",
+                         "",
+                         0,
+                         G_MAXINT,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-button-box.h b/src/libnmt-newt/nmt-newt-button-box.h
new file mode 100644
index 00000000..6d17778b
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-button-box.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_BUTTON_BOX_H
+#define NMT_NEWT_BUTTON_BOX_H
+
+#include "nmt-newt-grid.h"
+
+#define NMT_TYPE_NEWT_BUTTON_BOX (nmt_newt_button_box_get_type())
+#define NMT_NEWT_BUTTON_BOX(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_BUTTON_BOX, NmtNewtButtonBox))
+#define NMT_NEWT_BUTTON_BOX_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_BUTTON_BOX, NmtNewtButtonBoxClass))
+#define NMT_IS_NEWT_BUTTON_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_BUTTON_BOX))
+#define NMT_IS_NEWT_BUTTON_BOX_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_BUTTON_BOX))
+#define NMT_NEWT_BUTTON_BOX_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_BUTTON_BOX, NmtNewtButtonBoxClass))
+
+struct _NmtNewtButtonBox {
+    NmtNewtContainer parent;
+};
+
+typedef struct {
+    NmtNewtContainerClass parent;
+
+} NmtNewtButtonBoxClass;
+
+GType nmt_newt_button_box_get_type(void);
+
+typedef enum {
+    NMT_NEWT_BUTTON_BOX_HORIZONTAL,
+    NMT_NEWT_BUTTON_BOX_VERTICAL
+} NmtNewtButtonBoxOrientation;
+
+NmtNewtWidget *nmt_newt_button_box_new(NmtNewtButtonBoxOrientation orientation);
+
+NmtNewtWidget *nmt_newt_button_box_add_start(NmtNewtButtonBox *bbox, const char *label);
+NmtNewtWidget *nmt_newt_button_box_add_end(NmtNewtButtonBox *bbox, const char *label);
+
+void nmt_newt_button_box_add_widget_start(NmtNewtButtonBox *bbox, NmtNewtWidget *widget);
+void nmt_newt_button_box_add_widget_end(NmtNewtButtonBox *bbox, NmtNewtWidget *widget);
+
+#endif /* NMT_NEWT_BUTTON_BOX_H */
diff --git a/src/libnmt-newt/nmt-newt-button.c b/src/libnmt-newt/nmt-newt-button.c
new file mode 100644
index 00000000..974d05bd
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-button.c
@@ -0,0 +1,232 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-button
+ * @short_description: Push buttons
+ *
+ * #NmtNewtButton implements a button widget.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-button.h"
+#include "nmt-newt-utils.h"
+
+G_DEFINE_TYPE(NmtNewtButton, nmt_newt_button, NMT_TYPE_NEWT_COMPONENT)
+
+#define NMT_NEWT_BUTTON_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_BUTTON, NmtNewtButtonPrivate))
+
+typedef struct {
+    char *label;
+} NmtNewtButtonPrivate;
+
+enum {
+    PROP_0,
+    PROP_LABEL,
+
+    LAST_PROP
+};
+
+enum {
+    CLICKED,
+
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/**
+ * nmt_newt_button_new:
+ * @label: the (initial) button label
+ *
+ * Creates a new button.
+ *
+ * Returns: a new #NmtNewtButton
+ */
+NmtNewtWidget *
+nmt_newt_button_new(const char *label)
+{
+    return g_object_new(NMT_TYPE_NEWT_BUTTON, "label", label, NULL);
+}
+
+/**
+ * nmt_newt_button_set_label:
+ * @button: an #NmtNewtButton
+ * @label: the new label
+ *
+ * Updates @button's label.
+ */
+void
+nmt_newt_button_set_label(NmtNewtButton *button, const char *label)
+{
+    NmtNewtButtonPrivate *priv = NMT_NEWT_BUTTON_GET_PRIVATE(button);
+
+    if (!g_strcmp0(priv->label, label))
+        return;
+
+    g_free(priv->label);
+    priv->label = g_strdup(label);
+    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(button));
+}
+
+/**
+ * nmt_newt_button_get_label:
+ * @button: an #NmtNewtButton
+ *
+ * Gets @button's label.
+ *
+ * Returns: @button's label.
+ */
+const char *
+nmt_newt_button_get_label(NmtNewtButton *button)
+{
+    NmtNewtButtonPrivate *priv = NMT_NEWT_BUTTON_GET_PRIVATE(button);
+
+    return priv->label;
+}
+
+static void
+nmt_newt_button_init(NmtNewtButton *button)
+{}
+
+static void
+nmt_newt_button_finalize(GObject *object)
+{
+    NmtNewtButtonPrivate *priv = NMT_NEWT_BUTTON_GET_PRIVATE(object);
+
+    g_free(priv->label);
+
+    G_OBJECT_CLASS(nmt_newt_button_parent_class)->finalize(object);
+}
+
+static newtComponent
+nmt_newt_button_build_component(NmtNewtComponent *component, gboolean sensitive)
+{
+    NmtNewtButtonPrivate *priv = NMT_NEWT_BUTTON_GET_PRIVATE(component);
+    newtComponent         co;
+    gs_free char *        label_lc = NULL;
+
+    if (sensitive) {
+        label_lc = nmt_newt_locale_from_utf8(priv->label);
+        co       = newtCompactButton(-1, -1, label_lc);
+    } else {
+        gs_free char *label = NULL;
+
+        label    = g_strdup_printf(" <%s>", priv->label);
+        label_lc = nmt_newt_locale_from_utf8(label);
+        co       = newtLabel(-1, -1, label_lc);
+        newtLabelSetColors(co, NMT_NEWT_COLORSET_DISABLED_BUTTON);
+    }
+
+    return co;
+}
+
+static void
+nmt_newt_button_size_request(NmtNewtWidget *widget, int *width, int *height)
+{
+    NMT_NEWT_WIDGET_CLASS(nmt_newt_button_parent_class)->size_request(widget, width, height);
+
+    /* remove the automatically-added left padding */
+    (*width)--;
+}
+
+static void
+nmt_newt_button_size_allocate(NmtNewtWidget *widget, int x, int y, int width, int height)
+{
+    /* account for the automatically-added left padding */
+    x--;
+    width++;
+
+    NMT_NEWT_WIDGET_CLASS(nmt_newt_button_parent_class)->size_allocate(widget, x, y, width, height);
+}
+
+static void
+nmt_newt_button_activated(NmtNewtWidget *widget)
+{
+    g_signal_emit(widget, signals[CLICKED], 0);
+
+    NMT_NEWT_WIDGET_CLASS(nmt_newt_button_parent_class)->activated(widget);
+}
+
+static void
+nmt_newt_button_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    switch (prop_id) {
+    case PROP_LABEL:
+        nmt_newt_button_set_label(NMT_NEWT_BUTTON(object), g_value_get_string(value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_button_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtButtonPrivate *priv = NMT_NEWT_BUTTON_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_LABEL:
+        g_value_set_string(value, priv->label);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_button_class_init(NmtNewtButtonClass *button_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(button_class);
+    NmtNewtComponentClass *component_class = NMT_NEWT_COMPONENT_CLASS(button_class);
+    NmtNewtWidgetClass *   widget_class    = NMT_NEWT_WIDGET_CLASS(button_class);
+
+    g_type_class_add_private(button_class, sizeof(NmtNewtButtonPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_button_set_property;
+    object_class->get_property = nmt_newt_button_get_property;
+    object_class->finalize     = nmt_newt_button_finalize;
+
+    widget_class->size_request  = nmt_newt_button_size_request;
+    widget_class->size_allocate = nmt_newt_button_size_allocate;
+    widget_class->activated     = nmt_newt_button_activated;
+
+    component_class->build_component = nmt_newt_button_build_component;
+
+    /* signals */
+
+    /**
+     * NmtNewtButton::clicked:
+     * @button: the #NmtNewtButton
+     *
+     * Emitted when the button is clicked.
+     */
+    signals[CLICKED] = g_signal_new("clicked",
+                                    G_OBJECT_CLASS_TYPE(object_class),
+                                    G_SIGNAL_RUN_FIRST,
+                                    0,
+                                    NULL,
+                                    NULL,
+                                    NULL,
+                                    G_TYPE_NONE,
+                                    0);
+
+    /* properties */
+
+    /**
+     * NmtNewtButton:label:
+     *
+     * The button's label
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_LABEL,
+        g_param_spec_string("label", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-button.h b/src/libnmt-newt/nmt-newt-button.h
new file mode 100644
index 00000000..12649600
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-button.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_BUTTON_H
+#define NMT_NEWT_BUTTON_H
+
+#include "nmt-newt-component.h"
+
+#define NMT_TYPE_NEWT_BUTTON (nmt_newt_button_get_type())
+#define NMT_NEWT_BUTTON(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_BUTTON, NmtNewtButton))
+#define NMT_NEWT_BUTTON_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_BUTTON, NmtNewtButtonClass))
+#define NMT_IS_NEWT_BUTTON(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_BUTTON))
+#define NMT_IS_NEWT_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_BUTTON))
+#define NMT_NEWT_BUTTON_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_BUTTON, NmtNewtButtonClass))
+
+struct _NmtNewtButton {
+    NmtNewtComponent parent;
+};
+
+typedef struct {
+    NmtNewtComponentClass parent;
+
+} NmtNewtButtonClass;
+
+GType nmt_newt_button_get_type(void);
+
+NmtNewtWidget *nmt_newt_button_new(const char *label);
+
+void        nmt_newt_button_set_label(NmtNewtButton *button, const char *label);
+const char *nmt_newt_button_get_label(NmtNewtButton *button);
+
+#endif /* NMT_NEWT_BUTTON_H */
diff --git a/src/libnmt-newt/nmt-newt-checkbox.c b/src/libnmt-newt/nmt-newt-checkbox.c
new file mode 100644
index 00000000..cf825e08
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-checkbox.c
@@ -0,0 +1,215 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-checkbox
+ * @short_description: Checkboxes
+ *
+ * #NmtNewtCheckbox implements a checkbox widget.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-checkbox.h"
+#include "nmt-newt-utils.h"
+
+G_DEFINE_TYPE(NmtNewtCheckbox, nmt_newt_checkbox, NMT_TYPE_NEWT_COMPONENT)
+
+#define NMT_NEWT_CHECKBOX_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_CHECKBOX, NmtNewtCheckboxPrivate))
+
+typedef struct {
+    char *   label_lc;
+    gboolean active;
+} NmtNewtCheckboxPrivate;
+
+enum {
+    PROP_0,
+    PROP_LABEL,
+    PROP_ACTIVE,
+
+    LAST_PROP
+};
+
+#define CHECKBOX_INACTIVE ' '
+#define CHECKBOX_ACTIVE   'X'
+#define CHECKBOX_STATES   " X"
+
+/**
+ * nmt_newt_checkbox_new:
+ * @label: the (initial) checkbox label
+ *
+ * Creates a new checkbox.
+ *
+ * Returns: a new #NmtNewtCheckbox
+ */
+NmtNewtWidget *
+nmt_newt_checkbox_new(const char *label)
+{
+    return g_object_new(NMT_TYPE_NEWT_CHECKBOX, "label", label, NULL);
+}
+
+/**
+ * nmt_newt_checkbox_set_active:
+ * @checkbox: an #NmtNewtCheckbox
+ * @active: whether @checkbox should be checked
+ *
+ * Updates @checkbox's checked state
+ */
+void
+nmt_newt_checkbox_set_active(NmtNewtCheckbox *checkbox, gboolean active)
+{
+    NmtNewtCheckboxPrivate *priv = NMT_NEWT_CHECKBOX_GET_PRIVATE(checkbox);
+    newtComponent           co;
+
+    active = !!active;
+    if (active == priv->active)
+        return;
+
+    priv->active = active;
+
+    co = nmt_newt_component_get_component(NMT_NEWT_COMPONENT(checkbox));
+    if (co)
+        newtCheckboxSetValue(co, priv->active ? CHECKBOX_ACTIVE : CHECKBOX_INACTIVE);
+
+    g_object_notify(G_OBJECT(checkbox), "active");
+}
+
+/**
+ * nmt_newt_checkbox_get_active:
+ * @checkbox: an #NmtNewtCheckbox
+ *
+ * Gets @checkbox's checked state
+ *
+ * Returns: @checkbox's checked state
+ */
+gboolean
+nmt_newt_checkbox_get_active(NmtNewtCheckbox *checkbox)
+{
+    NmtNewtCheckboxPrivate *priv = NMT_NEWT_CHECKBOX_GET_PRIVATE(checkbox);
+
+    return priv->active;
+}
+
+static void
+nmt_newt_checkbox_init(NmtNewtCheckbox *checkbox)
+{}
+
+static void
+nmt_newt_checkbox_finalize(GObject *object)
+{
+    NmtNewtCheckboxPrivate *priv = NMT_NEWT_CHECKBOX_GET_PRIVATE(object);
+
+    g_free(priv->label_lc);
+
+    G_OBJECT_CLASS(nmt_newt_checkbox_parent_class)->finalize(object);
+}
+
+static void
+checkbox_toggled_callback(newtComponent co, void *checkbox)
+{
+    NmtNewtCheckboxPrivate *priv = NMT_NEWT_CHECKBOX_GET_PRIVATE(checkbox);
+    gboolean                active;
+
+    active = (newtCheckboxGetValue(co) == CHECKBOX_ACTIVE);
+    if (active != priv->active) {
+        priv->active = active;
+        g_object_notify(checkbox, "active");
+    }
+}
+
+static newtComponent
+nmt_newt_checkbox_build_component(NmtNewtComponent *component, gboolean sensitive)
+{
+    NmtNewtCheckboxPrivate *priv = NMT_NEWT_CHECKBOX_GET_PRIVATE(component);
+    newtComponent           co;
+
+    co = newtCheckbox(-1,
+                      -1,
+                      priv->label_lc,
+                      priv->active ? CHECKBOX_ACTIVE : CHECKBOX_INACTIVE,
+                      CHECKBOX_STATES,
+                      NULL);
+    if (!sensitive)
+        newtCheckboxSetFlags(co, NEWT_FLAG_DISABLED, NEWT_FLAGS_SET);
+    newtComponentAddCallback(co, checkbox_toggled_callback, component);
+    return co;
+}
+
+static void
+nmt_newt_checkbox_set_property(GObject *     object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec *  pspec)
+{
+    NmtNewtCheckbox *       checkbox = NMT_NEWT_CHECKBOX(object);
+    NmtNewtCheckboxPrivate *priv     = NMT_NEWT_CHECKBOX_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_LABEL:
+        g_free(priv->label_lc);
+        priv->label_lc = nmt_newt_locale_from_utf8(g_value_get_string(value));
+        break;
+    case PROP_ACTIVE:
+        nmt_newt_checkbox_set_active(checkbox, g_value_get_boolean(value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_checkbox_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtCheckboxPrivate *priv = NMT_NEWT_CHECKBOX_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_LABEL:
+        g_value_take_string(value, nmt_newt_locale_to_utf8(priv->label_lc));
+        break;
+    case PROP_ACTIVE:
+        g_value_set_boolean(value, priv->active);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_checkbox_class_init(NmtNewtCheckboxClass *checkbox_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(checkbox_class);
+    NmtNewtComponentClass *component_class = NMT_NEWT_COMPONENT_CLASS(checkbox_class);
+
+    g_type_class_add_private(checkbox_class, sizeof(NmtNewtCheckboxPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_checkbox_set_property;
+    object_class->get_property = nmt_newt_checkbox_get_property;
+    object_class->finalize     = nmt_newt_checkbox_finalize;
+
+    component_class->build_component = nmt_newt_checkbox_build_component;
+
+    /**
+     * NmtNewtCheckbox:label:
+     *
+     * The checkbox's label
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_LABEL,
+        g_param_spec_string("label", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtCheckbox:active:
+     *
+     * The checkbox's checked state
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_ACTIVE,
+        g_param_spec_boolean("active", "", "", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-checkbox.h b/src/libnmt-newt/nmt-newt-checkbox.h
new file mode 100644
index 00000000..c33c086a
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-checkbox.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_CHECKBOX_H
+#define NMT_NEWT_CHECKBOX_H
+
+#include "nmt-newt-component.h"
+
+#define NMT_TYPE_NEWT_CHECKBOX (nmt_newt_checkbox_get_type())
+#define NMT_NEWT_CHECKBOX(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_CHECKBOX, NmtNewtCheckbox))
+#define NMT_NEWT_CHECKBOX_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_CHECKBOX, NmtNewtCheckboxClass))
+#define NMT_IS_NEWT_CHECKBOX(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_CHECKBOX))
+#define NMT_IS_NEWT_CHECKBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_CHECKBOX))
+#define NMT_NEWT_CHECKBOX_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_CHECKBOX, NmtNewtCheckboxClass))
+
+struct _NmtNewtCheckbox {
+    NmtNewtComponent parent;
+};
+
+typedef struct {
+    NmtNewtComponentClass parent;
+
+} NmtNewtCheckboxClass;
+
+GType nmt_newt_checkbox_get_type(void);
+
+NmtNewtWidget *nmt_newt_checkbox_new(const char *label);
+
+void     nmt_newt_checkbox_set_active(NmtNewtCheckbox *checkbox, gboolean active);
+gboolean nmt_newt_checkbox_get_active(NmtNewtCheckbox *checkbox);
+
+#endif /* NMT_NEWT_CHECKBOX_H */
diff --git a/src/libnmt-newt/nmt-newt-component.c b/src/libnmt-newt/nmt-newt-component.c
new file mode 100644
index 00000000..8d7a9005
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-component.c
@@ -0,0 +1,293 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-component
+ * @short_description: Base class for widgets that wrap #newtComponents
+ *
+ * #NmtNewtComponent is the abstract class for #NmtNewtWidgets that
+ * wrap a (single) #newtComponent.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-component.h"
+#include "nmt-newt-form.h"
+#include "nmt-newt-hacks.h"
+
+G_DEFINE_ABSTRACT_TYPE(NmtNewtComponent, nmt_newt_component, NMT_TYPE_NEWT_WIDGET)
+
+#define NMT_NEWT_COMPONENT_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_COMPONENT, NmtNewtComponentPrivate))
+
+typedef struct {
+    newtComponent co;
+    gboolean      own_component;
+    gboolean      sensitive;
+} NmtNewtComponentPrivate;
+
+enum {
+    PROP_0,
+
+    PROP_COMPONENT,
+    PROP_SENSITIVE,
+
+    LAST_PROP
+};
+
+static void
+nmt_newt_component_init(NmtNewtComponent *component)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(component);
+
+    priv->sensitive = TRUE;
+}
+
+static void
+nmt_newt_component_unrealize(NmtNewtWidget *widget)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);
+
+    if (!priv->co)
+        return;
+
+    newtComponentAddCallback(priv->co, NULL, NULL);
+    newtComponentAddDestroyCallback(priv->co, NULL, NULL);
+
+    if (priv->own_component)
+        newtComponentDestroy(priv->co);
+    priv->co = NULL;
+}
+
+static void
+component_destroy_callback(newtComponent co, void *component)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(component);
+
+    priv->own_component = FALSE;
+    nmt_newt_widget_unrealize(component);
+    nmt_newt_widget_needs_rebuild(component);
+}
+
+static void
+nmt_newt_component_realize(NmtNewtWidget *widget)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);
+
+    nmt_newt_component_unrealize(widget);
+    priv->co = NMT_NEWT_COMPONENT_GET_CLASS(widget)->build_component(NMT_NEWT_COMPONENT(widget),
+                                                                     priv->sensitive);
+    priv->own_component = TRUE;
+    if (!priv->sensitive)
+        newtComponentTakesFocus(priv->co, FALSE);
+    newtComponentAddDestroyCallback(priv->co, component_destroy_callback, widget);
+}
+
+static newtComponent *
+nmt_newt_component_get_components(NmtNewtWidget *widget)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);
+    newtComponent *          cos;
+
+    priv->own_component = FALSE;
+    cos                 = g_new0(newtComponent, 2);
+    cos[0]              = priv->co;
+    return cos;
+}
+
+static NmtNewtWidget *
+nmt_newt_component_find_component(NmtNewtWidget *widget, newtComponent co)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);
+
+    if (co == priv->co)
+        return widget;
+    else
+        return NULL;
+}
+
+static void
+nmt_newt_component_size_request(NmtNewtWidget *widget, int *width, int *height)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);
+
+    newtComponentGetSize(priv->co, width, height);
+}
+
+static void
+nmt_newt_component_size_allocate(NmtNewtWidget *widget, int x, int y, int width, int height)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);
+    newtGrid                 grid;
+
+    /* You can't directly place a newtComponent, so we create a newtGrid,
+     * position the component within that, and then place the grid.
+     */
+    grid = newtCreateGrid(1, 1);
+    newtGridSetField(grid,
+                     0,
+                     0,
+                     NEWT_GRID_COMPONENT,
+                     priv->co,
+                     x,
+                     y,
+                     0,
+                     0,
+                     NEWT_ANCHOR_LEFT | NEWT_ANCHOR_TOP,
+                     0);
+    newtGridPlace(grid, 0, 0);
+    newtGridFree(grid, FALSE);
+}
+
+static newtComponent
+nmt_newt_component_get_focus_component(NmtNewtWidget *widget)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(widget);
+
+    return priv->co;
+}
+
+/**
+ * nmt_newt_component_get_component:
+ * @component: an #NmtNewtComponent
+ *
+ * A simpler version of nmt_newt_widget_get_components() for the
+ * single-component case. Also, unlike
+ * nmt_newt_widget_get_component(), this does not realize the widget
+ * if it isn't already realized. FIXME: why?
+ *
+ * Returns: @component's #newtComponent
+ */
+newtComponent
+nmt_newt_component_get_component(NmtNewtComponent *component)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(component);
+
+    return priv->co;
+}
+
+/**
+ * nmt_newt_component_get_sensitive:
+ * @component: an #NmtNewtComponent
+ *
+ * Gets @component's #NmtNewtComponent:sensitive property, indicating
+ * whether the widget is available for manipulation. Insensitive
+ * components will be skipped over in the keyboard tab chain, and may
+ * be displayed differently.
+ *
+ * Returns: @component's #NmtNewtComponent:sensitive property
+ */
+gboolean
+nmt_newt_component_get_sensitive(NmtNewtComponent *component)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(component);
+
+    return priv->sensitive;
+}
+
+/**
+ * nmt_newt_component_set_sensitive:
+ * @component: an #NmtNewtComponent
+ * @sensitive: whether @component should be sensitive
+ *
+ * Sets @component's #NmtNewtComponent:sensitive property.
+ */
+void
+nmt_newt_component_set_sensitive(NmtNewtComponent *component, gboolean sensitive)
+{
+    NmtNewtComponentPrivate *priv = NMT_NEWT_COMPONENT_GET_PRIVATE(component);
+
+    sensitive = !!sensitive;
+    if (priv->sensitive == sensitive)
+        return;
+
+    priv->sensitive = sensitive;
+    g_object_notify(G_OBJECT(component), "sensitive");
+    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(component));
+}
+
+static void
+nmt_newt_component_set_property(GObject *     object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec *  pspec)
+{
+    NmtNewtComponent *component = NMT_NEWT_COMPONENT(object);
+
+    switch (prop_id) {
+    case PROP_SENSITIVE:
+        nmt_newt_component_set_sensitive(component, g_value_get_boolean(value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_component_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtComponent *component = NMT_NEWT_COMPONENT(object);
+
+    switch (prop_id) {
+    case PROP_COMPONENT:
+        g_value_set_pointer(value, nmt_newt_component_get_component(component));
+        break;
+    case PROP_SENSITIVE:
+        g_value_set_boolean(value, nmt_newt_component_get_sensitive(component));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_component_class_init(NmtNewtComponentClass *component_class)
+{
+    GObjectClass *      object_class = G_OBJECT_CLASS(component_class);
+    NmtNewtWidgetClass *widget_class = NMT_NEWT_WIDGET_CLASS(component_class);
+
+    g_type_class_add_private(component_class, sizeof(NmtNewtComponentPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_component_set_property;
+    object_class->get_property = nmt_newt_component_get_property;
+
+    widget_class->realize             = nmt_newt_component_realize;
+    widget_class->unrealize           = nmt_newt_component_unrealize;
+    widget_class->get_components      = nmt_newt_component_get_components;
+    widget_class->find_component      = nmt_newt_component_find_component;
+    widget_class->size_request        = nmt_newt_component_size_request;
+    widget_class->size_allocate       = nmt_newt_component_size_allocate;
+    widget_class->get_focus_component = nmt_newt_component_get_focus_component;
+
+    /* properties */
+
+    /**
+     * NmtNewtComponent:component:
+     *
+     * The component's #newtComponent
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_COMPONENT,
+        g_param_spec_pointer("component", "", "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtComponent:sensitive:
+     *
+     * Whether the component is sensitive. Insensitive components will
+     * be skipped over in the keyboard tab chain, and may be displayed
+     * differently.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_SENSITIVE,
+        g_param_spec_boolean("sensitive",
+                             "",
+                             "",
+                             TRUE,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-component.h b/src/libnmt-newt/nmt-newt-component.h
new file mode 100644
index 00000000..54164c54
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-component.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_COMPONENT_H
+#define NMT_NEWT_COMPONENT_H
+
+#include "nmt-newt-widget.h"
+
+#define NMT_TYPE_NEWT_COMPONENT (nmt_newt_component_get_type())
+#define NMT_NEWT_COMPONENT(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_COMPONENT, NmtNewtComponent))
+#define NMT_NEWT_COMPONENT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_COMPONENT, NmtNewtComponentClass))
+#define NMT_IS_NEWT_COMPONENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_COMPONENT))
+#define NMT_IS_NEWT_COMPONENT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_COMPONENT))
+#define NMT_NEWT_COMPONENT_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_COMPONENT, NmtNewtComponentClass))
+
+struct _NmtNewtComponent {
+    NmtNewtWidget parent;
+};
+
+typedef struct {
+    NmtNewtWidgetClass parent;
+
+    /* methods */
+    newtComponent (*build_component)(NmtNewtComponent *component, gboolean sensitive);
+
+} NmtNewtComponentClass;
+
+GType nmt_newt_component_get_type(void);
+
+newtComponent nmt_newt_component_get_component(NmtNewtComponent *component);
+
+gboolean nmt_newt_component_get_sensitive(NmtNewtComponent *component);
+void     nmt_newt_component_set_sensitive(NmtNewtComponent *component, gboolean sensitive);
+
+#endif /* NMT_NEWT_COMPONENT_H */
diff --git a/src/libnmt-newt/nmt-newt-container.c b/src/libnmt-newt/nmt-newt-container.c
new file mode 100644
index 00000000..4cc8db24
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-container.c
@@ -0,0 +1,233 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-container
+ * @short_description: Base class for containers
+ *
+ * #NmtNewtContainer is the base class for #NmtNewtWidgets that
+ * contain other widgets.
+ *
+ * #NmtNewtGrid is the most generic container type.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-container.h"
+
+#include "nmt-newt-component.h"
+
+G_DEFINE_ABSTRACT_TYPE(NmtNewtContainer, nmt_newt_container, NMT_TYPE_NEWT_WIDGET)
+
+#define NMT_NEWT_CONTAINER_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_CONTAINER, NmtNewtContainerPrivate))
+
+typedef struct {
+    GPtrArray *children;
+
+} NmtNewtContainerPrivate;
+
+static void child_needs_rebuild(NmtNewtWidget *widget, gpointer user_data);
+
+static void
+nmt_newt_container_init(NmtNewtContainer *container)
+{
+    NmtNewtContainerPrivate *priv = NMT_NEWT_CONTAINER_GET_PRIVATE(container);
+
+    priv->children = g_ptr_array_new();
+}
+
+static void
+nmt_newt_container_finalize(GObject *object)
+{
+    NmtNewtContainer *       container = NMT_NEWT_CONTAINER(object);
+    NmtNewtContainerPrivate *priv      = NMT_NEWT_CONTAINER_GET_PRIVATE(object);
+
+    while (priv->children->len)
+        nmt_newt_container_remove(container, priv->children->pdata[0]);
+
+    G_OBJECT_CLASS(nmt_newt_container_parent_class)->finalize(object);
+}
+
+static void
+nmt_newt_container_realize(NmtNewtWidget *widget)
+{
+    NmtNewtContainerPrivate *priv = NMT_NEWT_CONTAINER_GET_PRIVATE(widget);
+    int                      i;
+
+    for (i = 0; i < priv->children->len; i++)
+        nmt_newt_widget_realize(priv->children->pdata[i]);
+}
+
+static void
+nmt_newt_container_unrealize(NmtNewtWidget *widget)
+{
+    NmtNewtContainerPrivate *priv = NMT_NEWT_CONTAINER_GET_PRIVATE(widget);
+    int                      i;
+
+    for (i = 0; i < priv->children->len; i++)
+        nmt_newt_widget_unrealize(priv->children->pdata[i]);
+}
+
+static void
+child_needs_rebuild(NmtNewtWidget *widget, gpointer user_data)
+{
+    NmtNewtWidget *container = user_data;
+
+    nmt_newt_widget_needs_rebuild(container);
+}
+
+static void
+nmt_newt_container_real_child_validity_changed(NmtNewtContainer *container, NmtNewtWidget *widget)
+{
+    NmtNewtContainerPrivate *priv;
+    int                      i;
+
+    if (widget) {
+        if (!nmt_newt_widget_get_visible(widget))
+            return;
+        if (!nmt_newt_widget_get_valid(widget)) {
+            nmt_newt_widget_set_valid(NMT_NEWT_WIDGET(container), FALSE);
+            return;
+        }
+    }
+
+    priv = NMT_NEWT_CONTAINER_GET_PRIVATE(container);
+    for (i = 0; i < priv->children->len; i++) {
+        widget = priv->children->pdata[i];
+        if (nmt_newt_widget_get_visible(widget) && !nmt_newt_widget_get_valid(widget)) {
+            nmt_newt_widget_set_valid(NMT_NEWT_WIDGET(container), FALSE);
+            return;
+        }
+    }
+
+    nmt_newt_widget_set_valid(NMT_NEWT_WIDGET(container), TRUE);
+}
+
+static void
+nmt_newt_container_child_validity_changed(NmtNewtContainer *container, NmtNewtWidget *widget)
+{
+    NMT_NEWT_CONTAINER_GET_CLASS(container)->child_validity_changed(container, widget);
+}
+
+static void
+child_validity_notify(GObject *object, GParamSpec *pspec, gpointer container)
+{
+    nmt_newt_container_child_validity_changed(container, NMT_NEWT_WIDGET(object));
+}
+
+static void
+nmt_newt_container_real_add(NmtNewtContainer *container, NmtNewtWidget *widget)
+{
+    NmtNewtContainerPrivate *priv = NMT_NEWT_CONTAINER_GET_PRIVATE(container);
+
+    g_signal_connect(widget, "needs-rebuild", G_CALLBACK(child_needs_rebuild), container);
+    g_signal_connect(widget, "notify::valid", G_CALLBACK(child_validity_notify), container);
+    g_ptr_array_add(priv->children, g_object_ref_sink(widget));
+    nmt_newt_widget_set_parent(widget, NMT_NEWT_WIDGET(container));
+
+    nmt_newt_container_child_validity_changed(container, widget);
+    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(container));
+}
+
+static void
+nmt_newt_container_real_remove(NmtNewtContainer *container, NmtNewtWidget *widget)
+{
+    NmtNewtContainerPrivate *priv = NMT_NEWT_CONTAINER_GET_PRIVATE(container);
+    int                      i;
+
+    for (i = 0; i < priv->children->len; i++) {
+        if (widget == priv->children->pdata[i]) {
+            g_ptr_array_remove_index(priv->children, i);
+            g_signal_handlers_disconnect_by_func(widget,
+                                                 G_CALLBACK(child_needs_rebuild),
+                                                 container);
+            g_signal_handlers_disconnect_by_func(widget,
+                                                 G_CALLBACK(child_validity_notify),
+                                                 container);
+            nmt_newt_widget_set_parent(widget, NULL);
+            g_object_unref(widget);
+
+            nmt_newt_container_child_validity_changed(container, NULL);
+            nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(container));
+            return;
+        }
+    }
+}
+
+/**
+ * nmt_newt_container_remove:
+ * @container: the #NmtNewtContainer
+ * @widget: the child to remove
+ *
+ * Removes @widget from @container.
+ *
+ * Note that there is not a corresponding
+ * <literal>nmt_newt_container_add ()</literal>; you must use
+ * container-type-specific methods to add widgets to containers.
+ */
+void
+nmt_newt_container_remove(NmtNewtContainer *container, NmtNewtWidget *widget)
+{
+    NMT_NEWT_CONTAINER_GET_CLASS(container)->remove(container, widget);
+}
+
+static NmtNewtWidget *
+nmt_newt_container_find_component(NmtNewtWidget *widget, newtComponent co)
+{
+    NmtNewtContainerPrivate *priv = NMT_NEWT_CONTAINER_GET_PRIVATE(widget);
+    NmtNewtWidget *          found, *child;
+    int                      i;
+
+    for (i = 0; i < priv->children->len; i++) {
+        child = priv->children->pdata[i];
+
+        found = nmt_newt_widget_find_component(child, co);
+        if (found)
+            return found;
+    }
+
+    return NULL;
+}
+
+/**
+ * nmt_newt_container_get_children:
+ * @container: an #NmtNewtContainer
+ *
+ * Gets a list of @container's children.
+ *
+ * Returns: (transfer full): a list of @container's children.
+ */
+GSList *
+nmt_newt_container_get_children(NmtNewtContainer *container)
+{
+    NmtNewtContainerPrivate *priv = NMT_NEWT_CONTAINER_GET_PRIVATE(container);
+    GSList *                 ret;
+    int                      i;
+
+    for (i = 0, ret = NULL; i < priv->children->len; i++)
+        ret = g_slist_prepend(ret, g_object_ref(priv->children->pdata[i]));
+    return g_slist_reverse(ret);
+}
+
+static void
+nmt_newt_container_class_init(NmtNewtContainerClass *container_class)
+{
+    GObjectClass *      object_class = G_OBJECT_CLASS(container_class);
+    NmtNewtWidgetClass *widget_class = NMT_NEWT_WIDGET_CLASS(container_class);
+
+    g_type_class_add_private(container_class, sizeof(NmtNewtContainerPrivate));
+
+    /* virtual methods */
+    object_class->finalize = nmt_newt_container_finalize;
+
+    widget_class->realize        = nmt_newt_container_realize;
+    widget_class->unrealize      = nmt_newt_container_unrealize;
+    widget_class->find_component = nmt_newt_container_find_component;
+
+    container_class->add                    = nmt_newt_container_real_add;
+    container_class->remove                 = nmt_newt_container_real_remove;
+    container_class->child_validity_changed = nmt_newt_container_real_child_validity_changed;
+}
diff --git a/src/libnmt-newt/nmt-newt-container.h b/src/libnmt-newt/nmt-newt-container.h
new file mode 100644
index 00000000..02585294
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-container.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_CONTAINER_H
+#define NMT_NEWT_CONTAINER_H
+
+#include "nmt-newt-widget.h"
+
+#define NMT_TYPE_NEWT_CONTAINER (nmt_newt_container_get_type())
+#define NMT_NEWT_CONTAINER(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_CONTAINER, NmtNewtContainer))
+#define NMT_NEWT_CONTAINER_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_CONTAINER, NmtNewtContainerClass))
+#define NMT_IS_NEWT_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_CONTAINER))
+#define NMT_IS_NEWT_CONTAINER_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_CONTAINER))
+#define NMT_NEWT_CONTAINER_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_CONTAINER, NmtNewtContainerClass))
+
+struct _NmtNewtContainer {
+    NmtNewtWidget parent;
+};
+
+typedef struct {
+    NmtNewtWidgetClass parent;
+
+    /* methods */
+    void (*add)(NmtNewtContainer *container, NmtNewtWidget *child);
+    void (*remove)(NmtNewtContainer *container, NmtNewtWidget *child);
+
+    void (*child_validity_changed)(NmtNewtContainer *container, NmtNewtWidget *child);
+
+} NmtNewtContainerClass;
+
+GType nmt_newt_container_get_type(void);
+
+void nmt_newt_container_remove(NmtNewtContainer *container, NmtNewtWidget *widget);
+
+GSList *nmt_newt_container_get_children(NmtNewtContainer *container);
+
+#endif /* NMT_NEWT_CONTAINER_H */
diff --git a/src/libnmt-newt/nmt-newt-entry-numeric.c b/src/libnmt-newt/nmt-newt-entry-numeric.c
new file mode 100644
index 00000000..7e8edd6c
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-entry-numeric.c
@@ -0,0 +1,244 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-entry-numeric
+ * @short_description: Numeric text entry
+ *
+ * #NmtNewtEntryNumeric implements a numeric-only #NmtNewtEntry.
+ *
+ * #NmtNewtEntryNumeric provides its own #NmtNewtEntryFilter and
+ * #NmtNewtEntryValidator functions, so you should not set your own.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <stdlib.h>
+
+#include "nmt-newt-entry-numeric.h"
+
+G_DEFINE_TYPE(NmtNewtEntryNumeric, nmt_newt_entry_numeric, NMT_TYPE_NEWT_ENTRY)
+
+#define NMT_NEWT_ENTRY_NUMERIC_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_ENTRY_NUMERIC, NmtNewtEntryNumericPrivate))
+
+typedef struct {
+    gint64 min, max;
+    bool   optional;
+} NmtNewtEntryNumericPrivate;
+
+enum {
+    PROP_0,
+    PROP_MINIMUM,
+    PROP_MAXIMUM,
+    PROP_OPTIONAL,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_newt_entry_numeric_new:
+ * @width: the entry's width in characters
+ * @min: the minimum valid value
+ * @max: the maximum valid value
+ *
+ * Creates a new #NmtNewtEntryNumeric, accepting values in the
+ * indicated range.
+ *
+ * Returns: a new #NmtNewtEntryNumeric
+ */
+NmtNewtWidget *
+nmt_newt_entry_numeric_new(int width, gint64 min, gint64 max)
+{
+    return nmt_newt_entry_numeric_new_full(width, min, max, FALSE);
+}
+
+/**
+ * nmt_newt_entry_numeric_new_full:
+ * @width: the entry's width in characters
+ * @min: the minimum valid value
+ * @max: the maximum valid value
+ * @optional: whether an empty entry is valid
+ *
+ * Creates a new #NmtNewtEntryNumeric, accepting values in the
+ * indicated range.
+ *
+ * Returns: a new #NmtNewtEntryNumeric
+ */
+NmtNewtWidget *
+nmt_newt_entry_numeric_new_full(int width, gint64 min, gint64 max, gboolean optional)
+{
+    return g_object_new(NMT_TYPE_NEWT_ENTRY_NUMERIC,
+                        "width",
+                        width,
+                        "minimum",
+                        min,
+                        "maximum",
+                        max,
+                        "optional",
+                        optional,
+                        NULL);
+}
+
+static gboolean
+newt_entry_numeric_filter(NmtNewtEntry *entry,
+                          const char *  text,
+                          int           ch,
+                          int           position,
+                          gpointer      user_data)
+{
+    NmtNewtEntryNumericPrivate *priv = NMT_NEWT_ENTRY_NUMERIC_GET_PRIVATE(entry);
+
+    if (g_ascii_isdigit(ch))
+        return TRUE;
+
+    if (ch == '-' && position == 0 && priv->min < 0)
+        return TRUE;
+
+    return FALSE;
+}
+
+static gboolean
+newt_entry_numeric_validate(NmtNewtEntry *entry, const char *text, gpointer user_data)
+{
+    NmtNewtEntryNumericPrivate *priv = NMT_NEWT_ENTRY_NUMERIC_GET_PRIVATE(entry);
+    gint64                      val;
+
+    if (!*text)
+        return priv->optional ? TRUE : FALSE;
+
+    val = _nm_utils_ascii_str_to_int64(text, 10, priv->min, priv->max, G_MAXINT64);
+    return val != G_MAXINT64 || errno == 0;
+}
+
+static void
+nmt_newt_entry_numeric_init(NmtNewtEntryNumeric *entry)
+{
+    nmt_newt_entry_set_filter(NMT_NEWT_ENTRY(entry), newt_entry_numeric_filter, NULL);
+    nmt_newt_entry_set_validator(NMT_NEWT_ENTRY(entry), newt_entry_numeric_validate, NULL);
+}
+
+static void
+nmt_newt_entry_numeric_constructed(GObject *object)
+{
+    NmtNewtEntryNumericPrivate *priv = NMT_NEWT_ENTRY_NUMERIC_GET_PRIVATE(object);
+
+    if (!*nmt_newt_entry_get_text(NMT_NEWT_ENTRY(object))) {
+        char buf[32];
+
+        g_snprintf(buf, sizeof(buf), "%lld", (long long) priv->min);
+        nmt_newt_entry_set_text(NMT_NEWT_ENTRY(object), buf);
+    }
+
+    G_OBJECT_CLASS(nmt_newt_entry_numeric_parent_class)->constructed(object);
+}
+
+static void
+nmt_newt_entry_numeric_set_property(GObject *     object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec *  pspec)
+{
+    NmtNewtEntryNumericPrivate *priv = NMT_NEWT_ENTRY_NUMERIC_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MINIMUM:
+        priv->min = g_value_get_int64(value);
+        break;
+    case PROP_MAXIMUM:
+        priv->max = g_value_get_int64(value);
+        break;
+    case PROP_OPTIONAL:
+        priv->optional = g_value_get_boolean(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_entry_numeric_get_property(GObject *   object,
+                                    guint       prop_id,
+                                    GValue *    value,
+                                    GParamSpec *pspec)
+{
+    NmtNewtEntryNumericPrivate *priv = NMT_NEWT_ENTRY_NUMERIC_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MINIMUM:
+        g_value_set_int64(value, priv->min);
+        break;
+    case PROP_MAXIMUM:
+        g_value_set_int64(value, priv->max);
+        break;
+    case PROP_OPTIONAL:
+        g_value_set_boolean(value, priv->optional);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_entry_numeric_class_init(NmtNewtEntryNumericClass *entry_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(entry_class);
+
+    g_type_class_add_private(entry_class, sizeof(NmtNewtEntryNumericPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_newt_entry_numeric_constructed;
+    object_class->set_property = nmt_newt_entry_numeric_set_property;
+    object_class->get_property = nmt_newt_entry_numeric_get_property;
+
+    /**
+     * NmtNewtEntryNumeric:minimum:
+     *
+     * The minimum #NmtNewtWidget:valid value for the entry. If this
+     * is non-negative, then the entry will not allow negative numbers
+     * to be entered.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_MINIMUM,
+        g_param_spec_int64("minimum",
+                           "",
+                           "",
+                           G_MININT64,
+                           G_MAXINT64,
+                           0,
+                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtEntryNumeric:maximum:
+     *
+     * The maximum #NmtNewtWidget:valid value for the entry.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_MAXIMUM,
+        g_param_spec_int64("maximum",
+                           "",
+                           "",
+                           G_MININT64,
+                           G_MAXINT64,
+                           G_MAXINT64,
+                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtEntryNumeric:optional:
+     *
+     * If %TRUE, allow empty string to indicate some default value.
+     * It means the property is optional and can be left at the default
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_OPTIONAL,
+        g_param_spec_boolean("optional",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-entry-numeric.h b/src/libnmt-newt/nmt-newt-entry-numeric.h
new file mode 100644
index 00000000..a10e4c5b
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-entry-numeric.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_ENTRY_NUMERIC_H
+#define NMT_NEWT_ENTRY_NUMERIC_H
+
+#include "nmt-newt-entry.h"
+
+#define NMT_TYPE_NEWT_ENTRY_NUMERIC (nmt_newt_entry_numeric_get_type())
+#define NMT_NEWT_ENTRY_NUMERIC(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_ENTRY_NUMERIC, NmtNewtEntryNumeric))
+#define NMT_NEWT_ENTRY_NUMERIC_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_ENTRY_NUMERIC, NmtNewtEntryNumericClass))
+#define NMT_IS_NEWT_ENTRY_NUMERIC(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_ENTRY_NUMERIC))
+#define NMT_IS_NEWT_ENTRY_NUMERIC_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_ENTRY_NUMERIC))
+#define NMT_NEWT_ENTRY_NUMERIC_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_ENTRY_NUMERIC, NmtNewtEntryNumericClass))
+
+struct _NmtNewtEntryNumeric {
+    NmtNewtEntry parent;
+};
+
+typedef struct {
+    NmtNewtEntryClass parent;
+
+} NmtNewtEntryNumericClass;
+
+GType nmt_newt_entry_numeric_get_type(void);
+
+NmtNewtWidget *nmt_newt_entry_numeric_new(int width, gint64 min, gint64 max);
+
+NmtNewtWidget *
+nmt_newt_entry_numeric_new_full(int width, gint64 min, gint64 max, gboolean optional);
+
+#endif /* NMT_NEWT_ENTRY_NUMERIC_H */
diff --git a/src/libnmt-newt/nmt-newt-entry.c b/src/libnmt-newt/nmt-newt-entry.c
new file mode 100644
index 00000000..76a96df2
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-entry.c
@@ -0,0 +1,506 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-entry
+ * @short_description: Text entries
+ *
+ * #NmtNewtEntry implements entry widgets, with optional filtering and
+ * validation.
+ *
+ * See also #NmtNewtEntryNumeric, for numeric-only entries.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-entry.h"
+#include "nmt-newt-form.h"
+#include "nmt-newt-hacks.h"
+#include "nmt-newt-utils.h"
+
+G_DEFINE_TYPE(NmtNewtEntry, nmt_newt_entry, NMT_TYPE_NEWT_COMPONENT)
+
+#define NMT_NEWT_ENTRY_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_ENTRY, NmtNewtEntryPrivate))
+
+typedef struct {
+    int               width;
+    NmtNewtEntryFlags flags;
+    char *            text;
+    int               last_cursor_pos;
+    guint             idle_update;
+
+    NmtNewtEntryFilter filter;
+    gpointer           filter_data;
+
+    NmtNewtEntryValidator validator;
+    gpointer              validator_data;
+} NmtNewtEntryPrivate;
+
+enum {
+    PROP_0,
+    PROP_TEXT,
+    PROP_WIDTH,
+    PROP_FLAGS,
+    PROP_PASSWORD,
+
+    LAST_PROP
+};
+
+/**
+ * NmtNewtEntryFlags:
+ * @NMT_NEWT_ENTRY_NOSCROLL: the entry content should not scroll left
+ *   and right
+ * @NMT_NEWT_ENTRY_PASSWORD: the entry should show '*'s instead of its
+ *   actual contents
+ * @NMT_NEWT_ENTRY_NONEMPTY: the entry should be considered not
+ *   #NmtNewtWidget:valid if it is empty.
+ *
+ * Flags describing an #NmtNewtEntry
+ */
+
+/**
+ * nmt_newt_entry_new:
+ * @width: the width in characters for the entry
+ * @flags: flags describing the entry
+ *
+ * Creates a new #NmtNewtEntry.
+ *
+ * Returns: a new #NmtNewtEntry
+ */
+NmtNewtWidget *
+nmt_newt_entry_new(int width, NmtNewtEntryFlags flags)
+{
+    return g_object_new(NMT_TYPE_NEWT_ENTRY, "width", width, "flags", flags, NULL);
+}
+
+/**
+ * NmtNewtEntryFilter:
+ * @entry: the #NmtNewtEntry
+ * @text: the current contents of @entry
+ * @ch: the character just typed
+ * @position: the position of the cursor in @entry
+ * @user_data: the data passed to nmt_newt_entry_set_filter()
+ *
+ * Callback function used to filter the contents of an entry.
+ *
+ * Returns: %TRUE if @ch should be accepted, %FALSE if not
+ */
+
+/**
+ * nmt_newt_entry_set_filter:
+ * @entry: an #NmtNewtEntry
+ * @filter: the function to use to filter the entry
+ * @user_data: data for @filter
+ *
+ * Sets a #NmtNewtEntryFilter on @entry, to allow filtering out
+ * certain characters from the entry.
+ *
+ * Note that @filter will only be called for printable characters (eg,
+ * not for cursor-control characters or the like), and that it will
+ * only be called for user input, not, eg, for
+ * nmt_newt_entry_set_text().
+ */
+void
+nmt_newt_entry_set_filter(NmtNewtEntry *entry, NmtNewtEntryFilter filter, gpointer user_data)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(entry);
+
+    priv->filter      = filter;
+    priv->filter_data = user_data;
+}
+
+static void
+nmt_newt_entry_check_valid(NmtNewtEntry *entry)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(entry);
+    gboolean             valid;
+
+    if ((priv->flags & NMT_NEWT_ENTRY_NONEMPTY) && *priv->text == '\0')
+        valid = FALSE;
+    else if (priv->validator)
+        valid = !!priv->validator(entry, priv->text, priv->validator_data);
+    else
+        valid = TRUE;
+
+    nmt_newt_widget_set_valid(NMT_NEWT_WIDGET(entry), valid);
+}
+
+/**
+ * NmtNewtEntryValidator:
+ * @entry: the #NmtNewtEntry
+ * @text: the current contents of @entry
+ * @user_data: the data passed to nmt_newt_entry_set_validator()
+ *
+ * Callback function used to validate the contents of an entry.
+ *
+ * Returns: whether the entry is #NmtNewtWidget:valid
+ */
+
+/**
+ * nmt_newt_entry_set_validator:
+ * @entry: an #NmtNewtEntry
+ * @validator: the function to use to validate the entry
+ * @user_data: data for @validator
+ *
+ * Sets a #NmtNewtEntryValidator on @entry, to allow validation of
+ * the entry contents. If @validator returns %FALSE, then the entry
+ * will not be considered #NmtNewtWidget:valid.
+ */
+void
+nmt_newt_entry_set_validator(NmtNewtEntry *        entry,
+                             NmtNewtEntryValidator validator,
+                             gpointer              user_data)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(entry);
+
+    priv->validator      = validator;
+    priv->validator_data = user_data;
+
+    nmt_newt_entry_check_valid(entry);
+}
+
+static void
+nmt_newt_entry_set_text_internal(NmtNewtEntry *entry, const char *text, newtComponent co)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(entry);
+
+    if (!text)
+        text = "";
+
+    if (!strcmp(priv->text, text))
+        return;
+
+    g_free(priv->text);
+    priv->text = g_strdup(text);
+
+    if (co) {
+        char *text_lc;
+
+        text_lc = priv->text ? nmt_newt_locale_from_utf8(priv->text) : NULL;
+        newtEntrySet(co, text_lc, TRUE);
+        g_free(text_lc);
+        priv->last_cursor_pos = -1;
+    }
+
+    g_object_freeze_notify(G_OBJECT(entry));
+    nmt_newt_entry_check_valid(entry);
+    g_object_notify(G_OBJECT(entry), "text");
+    g_object_thaw_notify(G_OBJECT(entry));
+}
+
+/**
+ * nmt_newt_entry_set_text:
+ * @entry: an #NmtNewtEntry
+ * @text: the new text
+ *
+ * Updates @entry's text. Note that this skips the entry's
+ * #NmtNewtEntryFilter, but will cause its #NmtNewtEntryValidator to
+ * be re-run.
+ */
+void
+nmt_newt_entry_set_text(NmtNewtEntry *entry, const char *text)
+{
+    newtComponent co;
+
+    co = nmt_newt_component_get_component(NMT_NEWT_COMPONENT(entry));
+    nmt_newt_entry_set_text_internal(entry, text, co);
+}
+
+/**
+ * nmt_newt_entry_get_text:
+ * @entry: an #NmtNewtEntry
+ *
+ * Gets @entry's text
+ *
+ * Returns: @entry's text
+ */
+const char *
+nmt_newt_entry_get_text(NmtNewtEntry *entry)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(entry);
+
+    return priv->text;
+}
+
+/**
+ * nmt_newt_entry_set_width:
+ * @entry: an #NmtNewtEntpry
+ * @widget: the new width
+ *
+ * Updates @entry's width
+ */
+void
+nmt_newt_entry_set_width(NmtNewtEntry *entry, int width)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(entry);
+
+    if (priv->width == width)
+        return;
+
+    priv->width = width;
+    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(entry));
+
+    g_object_notify(G_OBJECT(entry), "width");
+}
+
+/**
+ * nmt_newt_entry_get_width:
+ * @entry: an #NmtNewtEntry
+ *
+ * Gets @entry's width
+ *
+ * Returns: @entry's width
+ */
+int
+nmt_newt_entry_get_width(NmtNewtEntry *entry)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(entry);
+
+    return priv->width;
+}
+
+static void
+nmt_newt_entry_init(NmtNewtEntry *entry)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(entry);
+
+    priv->text            = g_strdup("");
+    priv->last_cursor_pos = -1;
+}
+
+static void
+nmt_newt_entry_constructed(GObject *object)
+{
+    nmt_newt_entry_check_valid(NMT_NEWT_ENTRY(object));
+
+    G_OBJECT_CLASS(nmt_newt_entry_parent_class)->constructed(object);
+}
+
+static void
+nmt_newt_entry_finalize(GObject *object)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(object);
+
+    g_free(priv->text);
+    if (priv->idle_update)
+        g_source_remove(priv->idle_update);
+
+    G_OBJECT_CLASS(nmt_newt_entry_parent_class)->finalize(object);
+}
+
+static gboolean
+idle_update_entry(gpointer entry)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(entry);
+    newtComponent        co   = nmt_newt_component_get_component(entry);
+    char *               text;
+
+    priv->idle_update = 0;
+    if (!co)
+        return FALSE;
+
+    priv->last_cursor_pos = newtEntryGetCursorPosition(co);
+
+    text = nmt_newt_locale_to_utf8(newtEntryGetValue(co));
+    nmt_newt_entry_set_text_internal(entry, text, NULL);
+    g_free(text);
+
+    return FALSE;
+}
+
+static int
+entry_filter(newtComponent entry, void *self, int ch, int cursor)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(self);
+
+    if (g_ascii_isprint(ch)) {
+        if (priv->filter) {
+            char *text = nmt_newt_locale_to_utf8(newtEntryGetValue(entry));
+
+            if (!priv->filter(self, text, ch, cursor, priv->filter_data)) {
+                g_free(text);
+                return 0;
+            }
+            g_free(text);
+        }
+    }
+
+    if (!priv->idle_update)
+        priv->idle_update = g_idle_add(idle_update_entry, self);
+    return ch;
+}
+
+static guint
+convert_flags(NmtNewtEntryFlags flags)
+{
+    guint newt_flags = NEWT_FLAG_RETURNEXIT;
+
+    if (!(flags & NMT_NEWT_ENTRY_NOSCROLL))
+        newt_flags |= NEWT_FLAG_SCROLL;
+    if (flags & NMT_NEWT_ENTRY_PASSWORD)
+        newt_flags |= NEWT_FLAG_PASSWORD;
+
+    return newt_flags;
+}
+
+static newtComponent
+nmt_newt_entry_build_component(NmtNewtComponent *component, gboolean sensitive)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(component);
+    newtComponent        co;
+    char *               text_lc;
+    int                  flags;
+
+    flags = convert_flags(priv->flags);
+    if (!sensitive)
+        flags |= NEWT_FLAG_DISABLED;
+
+    text_lc = priv->text ? nmt_newt_locale_from_utf8(priv->text) : NULL;
+    co      = newtEntry(-1, -1, text_lc, priv->width, NULL, flags);
+    g_free(text_lc);
+
+    if (priv->last_cursor_pos != -1)
+        newtEntrySetCursorPosition(co, priv->last_cursor_pos);
+
+    newtEntrySetFilter(co, entry_filter, component);
+    return co;
+}
+
+static void
+nmt_newt_entry_activated(NmtNewtWidget *widget)
+{
+    NmtNewtEntryPrivate *priv = NMT_NEWT_ENTRY_GET_PRIVATE(widget);
+
+    if (priv->idle_update) {
+        g_source_remove(priv->idle_update);
+        idle_update_entry(widget);
+    }
+
+    NMT_NEWT_WIDGET_CLASS(nmt_newt_entry_parent_class)->activated(widget);
+}
+
+static void
+nmt_newt_entry_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtNewtEntry *       entry = NMT_NEWT_ENTRY(object);
+    NmtNewtEntryPrivate *priv  = NMT_NEWT_ENTRY_GET_PRIVATE(entry);
+
+    switch (prop_id) {
+    case PROP_TEXT:
+        nmt_newt_entry_set_text(entry, g_value_get_string(value));
+        break;
+    case PROP_WIDTH:
+        nmt_newt_entry_set_width(entry, g_value_get_int(value));
+        break;
+    case PROP_FLAGS:
+        priv->flags = g_value_get_uint(value);
+        nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(entry));
+        break;
+    case PROP_PASSWORD:
+        if (g_value_get_boolean(value))
+            priv->flags |= NMT_NEWT_ENTRY_PASSWORD;
+        else
+            priv->flags &= ~NMT_NEWT_ENTRY_PASSWORD;
+        nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(entry));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_entry_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtEntry *       entry = NMT_NEWT_ENTRY(object);
+    NmtNewtEntryPrivate *priv  = NMT_NEWT_ENTRY_GET_PRIVATE(entry);
+
+    switch (prop_id) {
+    case PROP_TEXT:
+        g_value_set_string(value, nmt_newt_entry_get_text(entry));
+        break;
+    case PROP_WIDTH:
+        g_value_set_int(value, priv->width);
+        break;
+    case PROP_FLAGS:
+        g_value_set_uint(value, priv->flags);
+        break;
+    case PROP_PASSWORD:
+        g_value_set_boolean(value, (priv->flags & NMT_NEWT_ENTRY_PASSWORD) != 0);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_entry_class_init(NmtNewtEntryClass *entry_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(entry_class);
+    NmtNewtWidgetClass *   widget_class    = NMT_NEWT_WIDGET_CLASS(entry_class);
+    NmtNewtComponentClass *component_class = NMT_NEWT_COMPONENT_CLASS(entry_class);
+
+    g_type_class_add_private(entry_class, sizeof(NmtNewtEntryPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_newt_entry_constructed;
+    object_class->set_property = nmt_newt_entry_set_property;
+    object_class->get_property = nmt_newt_entry_get_property;
+    object_class->finalize     = nmt_newt_entry_finalize;
+
+    widget_class->activated = nmt_newt_entry_activated;
+
+    component_class->build_component = nmt_newt_entry_build_component;
+
+    /**
+     * NmtNewtEntry:text
+     *
+     * The entry's text
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_TEXT,
+        g_param_spec_string("text", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtEntry:width
+     *
+     * The entry's width in characters
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_WIDTH,
+        g_param_spec_int("width", "", "", -1, 80, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtEntry:flags
+     *
+     * The entry's #NmtNewtEntryFlags
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_FLAGS,
+        g_param_spec_uint("flags",
+                          "",
+                          "",
+                          0,
+                          0xFFFF,
+                          0,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtEntry:password
+     *
+     * %TRUE if #NmtNewtEntry:flags contains %NMT_NEWT_ENTRY_PASSWORD,
+     * %FALSE if not.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_PASSWORD,
+        g_param_spec_boolean("password",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-entry.h b/src/libnmt-newt/nmt-newt-entry.h
new file mode 100644
index 00000000..da1a9aee
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-entry.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_ENTRY_H
+#define NMT_NEWT_ENTRY_H
+
+#include "nmt-newt-component.h"
+
+#define NMT_TYPE_NEWT_ENTRY (nmt_newt_entry_get_type())
+#define NMT_NEWT_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_ENTRY, NmtNewtEntry))
+#define NMT_NEWT_ENTRY_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_ENTRY, NmtNewtEntryClass))
+#define NMT_IS_NEWT_ENTRY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_ENTRY))
+#define NMT_IS_NEWT_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_ENTRY))
+#define NMT_NEWT_ENTRY_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_ENTRY, NmtNewtEntryClass))
+
+struct _NmtNewtEntry {
+    NmtNewtComponent parent;
+};
+
+typedef struct {
+    NmtNewtComponentClass parent;
+
+} NmtNewtEntryClass;
+
+GType nmt_newt_entry_get_type(void);
+
+typedef gboolean (
+    *NmtNewtEntryFilter)(NmtNewtEntry *, const char *text, int ch, int position, gpointer);
+typedef gboolean (*NmtNewtEntryValidator)(NmtNewtEntry *, const char *text, gpointer);
+
+typedef enum {
+    NMT_NEWT_ENTRY_NOSCROLL = (1 << 0),
+    NMT_NEWT_ENTRY_PASSWORD = (1 << 1),
+    NMT_NEWT_ENTRY_NONEMPTY = (1 << 2)
+} NmtNewtEntryFlags;
+
+NmtNewtWidget *nmt_newt_entry_new(int width, NmtNewtEntryFlags flags);
+
+void nmt_newt_entry_set_filter(NmtNewtEntry *entry, NmtNewtEntryFilter filter, gpointer user_data);
+void nmt_newt_entry_set_validator(NmtNewtEntry *        entry,
+                                  NmtNewtEntryValidator validator,
+                                  gpointer              user_data);
+
+void        nmt_newt_entry_set_text(NmtNewtEntry *entry, const char *text);
+const char *nmt_newt_entry_get_text(NmtNewtEntry *entry);
+
+void nmt_newt_entry_set_width(NmtNewtEntry *entry, int width);
+int  nmt_newt_entry_get_width(NmtNewtEntry *entry);
+
+#endif /* NMT_NEWT_ENTRY_H */
diff --git a/src/libnmt-newt/nmt-newt-form.c b/src/libnmt-newt/nmt-newt-form.c
new file mode 100644
index 00000000..825f7cfe
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-form.c
@@ -0,0 +1,739 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-form
+ * @short_description: The top-level NmtNewt widget
+ *
+ * #NmtNewtForm is the top-level widget that contains and presents a
+ * "form" (aka dialog) to the user.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "nmt-newt-form.h"
+#include "nmt-newt-button.h"
+#include "nmt-newt-grid.h"
+#include "nmt-newt-utils.h"
+
+G_DEFINE_TYPE(NmtNewtForm, nmt_newt_form, NMT_TYPE_NEWT_CONTAINER)
+
+#define NMT_NEWT_FORM_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_FORM, NmtNewtFormPrivate))
+
+typedef struct {
+    newtComponent  form;
+    NmtNewtWidget *content;
+
+    guint    x, y, width, height;
+    guint    padding;
+    gboolean fixed_x, fixed_y;
+    gboolean fixed_width, fixed_height;
+    char *   title_lc;
+
+    gboolean       dirty, escape_exits;
+    NmtNewtWidget *focus;
+#ifdef HAVE_NEWTFORMGETSCROLLPOSITION
+    int scroll_position = 0;
+#endif
+} NmtNewtFormPrivate;
+
+enum {
+    PROP_0,
+    PROP_TITLE,
+    PROP_FULLSCREEN,
+    PROP_FULLSCREEN_VERTICAL,
+    PROP_FULLSCREEN_HORIZONTAL,
+    PROP_X,
+    PROP_Y,
+    PROP_WIDTH,
+    PROP_HEIGHT,
+    PROP_PADDING,
+    PROP_ESCAPE_EXITS,
+
+    LAST_PROP
+};
+
+enum {
+    QUIT,
+
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+static void nmt_newt_form_redraw(NmtNewtForm *form);
+
+/**
+ * nmt_newt_form_new:
+ * @title: (allow-none): the form title
+ *
+ * Creates a new form, which will be shown centered on the screen.
+ * Compare nmt_newt_form_new_fullscreen(). You can also position a
+ * form manually by setting its #NmtNewtForm:x and #NmtNewtForm:y
+ * properties at construct time, and/or by setting
+ * #NmtNewtForm:fullscreen, #NmtNewtform:fullscreen-horizontal, or
+ * #NmtNewtForm:fullscreen-vertical.
+ *
+ * If @title is NULL, the form will have no title.
+ *
+ * Returns: a new #NmtNewtForm
+ */
+NmtNewtForm *
+nmt_newt_form_new(const char *title)
+{
+    return g_object_new(NMT_TYPE_NEWT_FORM, "title", title, NULL);
+}
+
+/**
+ * nmt_newt_form_new_fullscreen:
+ * @title: (allow-none): the form title
+ *
+ * Creates a new fullscreen form. Compare nmt_newt_form_new().
+ *
+ * If @title is NULL, the form will have no title.
+ *
+ * Returns: a new #NmtNewtForm
+ */
+NmtNewtForm *
+nmt_newt_form_new_fullscreen(const char *title)
+{
+    return g_object_new(NMT_TYPE_NEWT_FORM, "title", title, "fullscreen", TRUE, NULL);
+}
+
+static void
+nmt_newt_form_init(NmtNewtForm *form)
+{
+    g_object_ref_sink(form);
+}
+
+static void
+nmt_newt_form_finalize(GObject *object)
+{
+    NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE(object);
+
+    g_free(priv->title_lc);
+    g_clear_object(&priv->focus);
+
+    G_OBJECT_CLASS(nmt_newt_form_parent_class)->finalize(object);
+}
+
+static void
+nmt_newt_form_needs_rebuild(NmtNewtWidget *widget)
+{
+    NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE(widget);
+
+    if (!priv->dirty) {
+        priv->dirty = TRUE;
+        nmt_newt_form_redraw(NMT_NEWT_FORM(widget));
+    }
+}
+
+static void
+nmt_newt_form_remove(NmtNewtContainer *container, NmtNewtWidget *widget)
+{
+    NmtNewtFormPrivate *   priv         = NMT_NEWT_FORM_GET_PRIVATE(container);
+    NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS(nmt_newt_form_parent_class);
+
+    g_return_if_fail(widget == priv->content);
+
+    parent_class->remove(container, widget);
+    priv->content = NULL;
+}
+
+/**
+ * nmt_newt_form_set_content:
+ * @form: the #NmtNewtForm
+ * @content: the form's content
+ *
+ * Sets @form's content to be @content.
+ */
+void
+nmt_newt_form_set_content(NmtNewtForm *form, NmtNewtWidget *content)
+{
+    NmtNewtFormPrivate *   priv         = NMT_NEWT_FORM_GET_PRIVATE(form);
+    NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS(nmt_newt_form_parent_class);
+
+    if (priv->content)
+        nmt_newt_form_remove(NMT_NEWT_CONTAINER(form), priv->content);
+
+    priv->content = content;
+
+    if (priv->content)
+        parent_class->add(NMT_NEWT_CONTAINER(form), content);
+}
+
+static void
+nmt_newt_form_build(NmtNewtForm *form)
+{
+    NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE(form);
+    int                 screen_height, screen_width, form_height, form_width;
+    newtComponent *     cos;
+    int                 i;
+
+    priv->dirty = FALSE;
+    nmt_newt_widget_realize(NMT_NEWT_WIDGET(form));
+
+    nmt_newt_widget_size_request(priv->content, &form_width, &form_height);
+    newtGetScreenSize(&screen_width, &screen_height);
+
+    if (!priv->fixed_width)
+        priv->width = MIN(form_width + 2 * priv->padding, screen_width - 2);
+    if (!priv->fixed_height)
+        priv->height = MIN(form_height + 2 * priv->padding, screen_height - 2);
+
+    if (!priv->fixed_x)
+        priv->x = (screen_width - form_width) / 2;
+    if (!priv->fixed_y)
+        priv->y = (screen_height - form_height) / 2;
+
+    nmt_newt_widget_size_allocate(priv->content,
+                                  priv->padding,
+                                  priv->padding,
+                                  priv->width - 2 * priv->padding,
+                                  priv->height - 2 * priv->padding);
+
+    if (priv->height - 2 * priv->padding < form_height) {
+        newtComponent scroll_bar = newtVerticalScrollbar(priv->width - 1,
+                                                         0,
+                                                         priv->height,
+                                                         NEWT_COLORSET_WINDOW,
+                                                         NEWT_COLORSET_ACTCHECKBOX);
+
+        priv->form = newtForm(scroll_bar, NULL, NEWT_FLAG_NOF12);
+        newtFormAddComponent(priv->form, scroll_bar);
+        newtFormSetHeight(priv->form, priv->height - 2);
+    } else
+        priv->form = newtForm(NULL, NULL, NEWT_FLAG_NOF12);
+
+    if (priv->escape_exits)
+        newtFormAddHotKey(priv->form, NEWT_KEY_ESCAPE);
+
+    cos = nmt_newt_widget_get_components(priv->content);
+    for (i = 0; cos[i]; i++)
+        newtFormAddComponent(priv->form, cos[i]);
+    g_free(cos);
+
+    if (priv->focus) {
+        newtComponent fco;
+
+        fco = nmt_newt_widget_get_focus_component(priv->focus);
+        if (fco)
+            newtFormSetCurrent(priv->form, fco);
+    }
+#ifdef HAVE_NEWTFORMGETSCROLLPOSITION
+    if (priv->scroll_position)
+        newtFormSetScrollPosition(priv->form, priv->scroll_position);
+#endif
+
+    newtOpenWindow(priv->x, priv->y, priv->width, priv->height, priv->title_lc);
+}
+
+static void
+nmt_newt_form_destroy(NmtNewtForm *form)
+{
+    NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE(form);
+
+#ifdef HAVE_NEWTFORMGETSCROLLPOSITION
+    priv->scroll_position = newtFormGetScrollPosition(priv->form);
+#endif
+
+    newtFormDestroy(priv->form);
+    priv->form = NULL;
+    newtPopWindowNoRefresh();
+
+    nmt_newt_widget_unrealize(NMT_NEWT_WIDGET(form));
+}
+
+/* A "normal" newt program would call newtFormRun() to run newt's main loop
+ * and process events. But we want to let GLib's main loop control the program
+ * (eg, so libnm can process D-Bus notifications). So we call this function
+ * to run a single iteration of newt's main loop (or rather, to run newt's
+ * main loop for 1ms) whenever there are events for newt to process (redrawing
+ * or keypresses).
+ */
+static void
+nmt_newt_form_iterate(NmtNewtForm *form)
+{
+    NmtNewtFormPrivate *  priv = NMT_NEWT_FORM_GET_PRIVATE(form);
+    NmtNewtWidget *       focus;
+    struct newtExitStruct es;
+
+    if (priv->dirty) {
+        nmt_newt_form_destroy(form);
+        nmt_newt_form_build(form);
+    }
+
+    newtFormSetTimer(priv->form, 1);
+    newtFormRun(priv->form, &es);
+
+    if (es.reason == NEWT_EXIT_HOTKEY || es.reason == NEWT_EXIT_ERROR) {
+        /* The user hit Esc or there was an error. */
+        g_clear_object(&priv->focus);
+        nmt_newt_form_quit(form);
+        return;
+    }
+
+    if (es.reason == NEWT_EXIT_COMPONENT) {
+        /* The user hit Return/Space on a component; update the form focus
+         * to point that component, and activate it.
+         */
+        focus = nmt_newt_widget_find_component(priv->content, es.u.co);
+        if (focus) {
+            nmt_newt_form_set_focus(form, focus);
+            nmt_newt_widget_activated(focus);
+        }
+    } else {
+        /* The 1ms timer ran out. Update focus but don't do anything else. */
+        focus = nmt_newt_widget_find_component(priv->content, newtFormGetCurrent(priv->form));
+        if (focus)
+            nmt_newt_form_set_focus(form, focus);
+    }
+}
+
+/* @form_stack keeps track of all currently-displayed forms, from top to bottom.
+ * @keypress_source is the global stdin-monitoring GSource. When it triggers,
+ * nmt_newt_form_keypress_callback() iterates the top-most form, so it can
+ * process the keypress.
+ */
+static GSList * form_stack;
+static GSource *keypress_source;
+
+static gboolean
+nmt_newt_form_keypress_callback(int fd, GIOCondition condition, gpointer user_data)
+{
+    g_return_val_if_fail(form_stack != NULL, FALSE);
+
+    nmt_newt_form_iterate(form_stack->data);
+    return TRUE;
+}
+
+static gboolean
+nmt_newt_form_timeout_callback(gpointer user_data)
+{
+    if (form_stack)
+        nmt_newt_form_iterate(form_stack->data);
+    return FALSE;
+}
+
+static void
+nmt_newt_form_redraw(NmtNewtForm *form)
+{
+    g_timeout_add(0, nmt_newt_form_timeout_callback, NULL);
+}
+
+static void
+nmt_newt_form_real_show(NmtNewtForm *form)
+{
+    if (!keypress_source) {
+        keypress_source = nm_g_unix_fd_source_new(STDIN_FILENO,
+                                                  G_IO_IN,
+                                                  G_PRIORITY_DEFAULT,
+                                                  nmt_newt_form_keypress_callback,
+                                                  NULL,
+                                                  NULL);
+        g_source_set_can_recurse(keypress_source, TRUE);
+        g_source_attach(keypress_source, NULL);
+    }
+
+    nmt_newt_form_build(form);
+    form_stack = g_slist_prepend(form_stack, g_object_ref(form));
+    nmt_newt_form_redraw(form);
+}
+
+/**
+ * nmt_newt_form_show:
+ * @form: an #NmtNewtForm
+ *
+ * Displays @form and begins running it asynchronously in the default
+ * #GMainContext. If another form is currently running, it will remain
+ * visible in the background, but will not be able to receive keyboard
+ * input until @form exits.
+ *
+ * Call nmt_newt_form_quit() to quit the form.
+ */
+void
+nmt_newt_form_show(NmtNewtForm *form)
+{
+    NMT_NEWT_FORM_GET_CLASS(form)->show(form);
+}
+
+/**
+ * nmt_newt_form_run_sync:
+ * @form: an #NmtNewtForm
+ *
+ * Displays @form as with nmt_newt_form_show(), but then iterates the
+ * #GMainContext internally until @form exits.
+ *
+ * Returns: the widget whose activation caused @form to exit, or
+ *   %NULL if it was not caused by a widget. FIXME: this exit value is
+ *   sort of weird and may not be 100% accurate anyway.
+ */
+NmtNewtWidget *
+nmt_newt_form_run_sync(NmtNewtForm *form)
+{
+    NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE(form);
+
+    nmt_newt_form_show(form);
+    while (priv->form)
+        g_main_context_iteration(NULL, TRUE);
+
+    return priv->focus;
+}
+
+/**
+ * nmt_newt_form_quit:
+ * @form: an #NmtNewtForm
+ *
+ * Causes @form to exit.
+ */
+void
+nmt_newt_form_quit(NmtNewtForm *form)
+{
+    NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE(form);
+
+    g_return_if_fail(priv->form != NULL);
+
+    nmt_newt_form_destroy(form);
+
+    form_stack = g_slist_remove(form_stack, form);
+
+    if (form_stack)
+        nmt_newt_form_iterate(form_stack->data);
+    else
+        nm_clear_g_source_inst(&keypress_source);
+
+    g_signal_emit(form, signals[QUIT], 0);
+    g_object_unref(form);
+}
+
+/**
+ * nmt_newt_form_set_focus:
+ * @form: an #NmtNewtForm
+ * @widget: the widget to focus
+ *
+ * Focuses @widget in @form.
+ */
+void
+nmt_newt_form_set_focus(NmtNewtForm *form, NmtNewtWidget *widget)
+{
+    NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE(form);
+
+    g_return_if_fail(priv->form != NULL);
+
+    if (priv->focus == widget)
+        return;
+
+    if (priv->focus)
+        g_object_unref(priv->focus);
+    priv->focus = widget;
+    if (priv->focus)
+        g_object_ref(priv->focus);
+}
+
+static void
+nmt_newt_form_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE(object);
+    int                 screen_width, screen_height;
+
+    switch (prop_id) {
+    case PROP_TITLE:
+        if (g_value_get_string(value)) {
+            priv->title_lc = nmt_newt_locale_from_utf8(g_value_get_string(value));
+        } else
+            priv->title_lc = NULL;
+        break;
+    case PROP_FULLSCREEN:
+        if (g_value_get_boolean(value)) {
+            newtGetScreenSize(&screen_width, &screen_height);
+            priv->x = priv->y = 2;
+            priv->fixed_x = priv->fixed_y = TRUE;
+            priv->width                   = screen_width - 4;
+            priv->height                  = screen_height - 4;
+            priv->fixed_width = priv->fixed_height = TRUE;
+        }
+        break;
+    case PROP_FULLSCREEN_VERTICAL:
+        if (g_value_get_boolean(value)) {
+            newtGetScreenSize(&screen_width, &screen_height);
+            priv->y            = 2;
+            priv->fixed_y      = TRUE;
+            priv->height       = screen_height - 4;
+            priv->fixed_height = TRUE;
+        }
+        break;
+    case PROP_FULLSCREEN_HORIZONTAL:
+        if (g_value_get_boolean(value)) {
+            newtGetScreenSize(&screen_width, &screen_height);
+            priv->x           = 2;
+            priv->fixed_x     = TRUE;
+            priv->width       = screen_width - 4;
+            priv->fixed_width = TRUE;
+        }
+        break;
+    case PROP_X:
+        if (g_value_get_uint(value)) {
+            priv->x       = g_value_get_uint(value);
+            priv->fixed_x = TRUE;
+        }
+        break;
+    case PROP_Y:
+        if (g_value_get_uint(value)) {
+            priv->y       = g_value_get_uint(value);
+            priv->fixed_y = TRUE;
+        }
+        break;
+    case PROP_WIDTH:
+        if (g_value_get_uint(value)) {
+            priv->width       = g_value_get_uint(value);
+            priv->fixed_width = TRUE;
+        }
+        break;
+    case PROP_HEIGHT:
+        if (g_value_get_uint(value)) {
+            priv->height       = g_value_get_uint(value);
+            priv->fixed_height = TRUE;
+        }
+        break;
+    case PROP_PADDING:
+        priv->padding = g_value_get_uint(value);
+        break;
+    case PROP_ESCAPE_EXITS:
+        priv->escape_exits = g_value_get_boolean(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_form_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_TITLE:
+        if (priv->title_lc) {
+            g_value_take_string(value, nmt_newt_locale_to_utf8(priv->title_lc));
+        } else
+            g_value_set_string(value, NULL);
+        break;
+    case PROP_X:
+        g_value_set_uint(value, priv->x);
+        break;
+    case PROP_Y:
+        g_value_set_uint(value, priv->y);
+        break;
+    case PROP_WIDTH:
+        g_value_set_uint(value, priv->width);
+        break;
+    case PROP_HEIGHT:
+        g_value_set_uint(value, priv->height);
+        break;
+    case PROP_PADDING:
+        g_value_set_uint(value, priv->padding);
+        break;
+    case PROP_ESCAPE_EXITS:
+        g_value_set_boolean(value, priv->escape_exits);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_form_class_init(NmtNewtFormClass *form_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(form_class);
+    NmtNewtContainerClass *container_class = NMT_NEWT_CONTAINER_CLASS(form_class);
+    NmtNewtWidgetClass *   widget_class    = NMT_NEWT_WIDGET_CLASS(form_class);
+
+    g_type_class_add_private(form_class, sizeof(NmtNewtFormPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_form_set_property;
+    object_class->get_property = nmt_newt_form_get_property;
+    object_class->finalize     = nmt_newt_form_finalize;
+
+    widget_class->needs_rebuild = nmt_newt_form_needs_rebuild;
+
+    container_class->remove = nmt_newt_form_remove;
+
+    form_class->show = nmt_newt_form_real_show;
+
+    /* signals */
+
+    /**
+     * NmtNewtForm::quit:
+     * @form: the #NmtNewtForm
+     *
+     * Emitted when the form quits.
+     */
+    signals[QUIT] = g_signal_new("quit",
+                                 G_OBJECT_CLASS_TYPE(object_class),
+                                 G_SIGNAL_RUN_FIRST,
+                                 G_STRUCT_OFFSET(NmtNewtFormClass, quit),
+                                 NULL,
+                                 NULL,
+                                 NULL,
+                                 G_TYPE_NONE,
+                                 0);
+
+    /**
+     * NmtNewtForm:title:
+     *
+     * The form's title. If non-%NULL, this will be displayed above
+     * the form in its border.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_TITLE,
+        g_param_spec_string("title",
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+    /**
+     * NmtNewtForm:fullscreen:
+     *
+     * If %TRUE, the form will fill the entire "screen" (ie, terminal
+     * window).
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_FULLSCREEN,
+        g_param_spec_boolean("fullscreen",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+    /**
+     * NmtNewtForm:fullscreen-vertical:
+     *
+     * If %TRUE, the form will fill the entire "screen" (ie, terminal
+     * window) vertically, but not necessarily horizontally.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_FULLSCREEN_VERTICAL,
+        g_param_spec_boolean("fullscreen-vertical",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+    /**
+     * NmtNewtForm:fullscreen-horizontal:
+     *
+     * If %TRUE, the form will fill the entire "screen" (ie, terminal
+     * window) horizontally, but not necessarily vertically.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_FULLSCREEN_HORIZONTAL,
+        g_param_spec_boolean("fullscreen-horizontal",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+    /**
+     * NmtNewtForm:x:
+     *
+     * The form's x coordinate. By default, the form will be centered
+     * on the screen.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_X,
+        g_param_spec_uint("x",
+                          "",
+                          "",
+                          0,
+                          G_MAXUINT,
+                          0,
+                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+    /**
+     * NmtNewtForm:y:
+     *
+     * The form's y coordinate. By default, the form will be centered
+     * on the screen.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_Y,
+        g_param_spec_uint("y",
+                          "",
+                          "",
+                          0,
+                          G_MAXUINT,
+                          0,
+                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+    /**
+     * NmtNewtForm:width:
+     *
+     * The form's width. By default, this will be determined by the
+     * width of the form's content.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_WIDTH,
+        g_param_spec_uint("width",
+                          "",
+                          "",
+                          0,
+                          G_MAXUINT,
+                          0,
+                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+    /**
+     * NmtNewtForm:height:
+     *
+     * The form's height. By default, this will be determined by the
+     * height of the form's content.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_HEIGHT,
+        g_param_spec_uint("height",
+                          "",
+                          "",
+                          0,
+                          G_MAXUINT,
+                          0,
+                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+    /**
+     * NmtNewtForm:padding:
+     *
+     * The padding between the form's content and its border.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_PADDING,
+        g_param_spec_uint("padding",
+                          "",
+                          "",
+                          0,
+                          G_MAXUINT,
+                          1,
+                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+    /**
+     * NmtNewtForm:escape-exits:
+     *
+     * If %TRUE, then hitting the Escape key will cause the form to
+     * exit.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_ESCAPE_EXITS,
+        g_param_spec_boolean("escape-exits",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+}
diff --git a/src/libnmt-newt/nmt-newt-form.h b/src/libnmt-newt/nmt-newt-form.h
new file mode 100644
index 00000000..55fb583b
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-form.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_FORM_H
+#define NMT_NEWT_FORM_H
+
+#include "nmt-newt-container.h"
+
+#define NMT_TYPE_NEWT_FORM (nmt_newt_form_get_type())
+#define NMT_NEWT_FORM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_FORM, NmtNewtForm))
+#define NMT_NEWT_FORM_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_FORM, NmtNewtFormClass))
+#define NMT_IS_NEWT_FORM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_FORM))
+#define NMT_IS_NEWT_FORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_FORM))
+#define NMT_NEWT_FORM_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_FORM, NmtNewtFormClass))
+
+struct _NmtNewtForm {
+    NmtNewtContainer parent;
+};
+
+typedef struct {
+    NmtNewtContainerClass parent;
+
+    /* signals */
+    void (*quit)(NmtNewtForm *form);
+
+    /* methods */
+    void (*show)(NmtNewtForm *form);
+
+} NmtNewtFormClass;
+
+GType nmt_newt_form_get_type(void);
+
+NmtNewtForm *nmt_newt_form_new(const char *title);
+NmtNewtForm *nmt_newt_form_new_fullscreen(const char *title);
+
+void nmt_newt_form_set_content(NmtNewtForm *form, NmtNewtWidget *content);
+
+void           nmt_newt_form_show(NmtNewtForm *form);
+NmtNewtWidget *nmt_newt_form_run_sync(NmtNewtForm *form);
+void           nmt_newt_form_quit(NmtNewtForm *form);
+
+void nmt_newt_form_set_focus(NmtNewtForm *form, NmtNewtWidget *widget);
+
+#endif /* NMT_NEWT_FORM_H */
diff --git a/src/libnmt-newt/nmt-newt-grid.c b/src/libnmt-newt/nmt-newt-grid.c
new file mode 100644
index 00000000..a68a496b
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-grid.c
@@ -0,0 +1,439 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-grid
+ * @short_description: Grid container
+ *
+ * #NmtNewtGrid is the most general-purpose container widget in NmtNewt.
+ *
+ * An #NmtNewtGrid consists of a number of rows and columns. There is
+ * no pre-established maximum row or columns. Rather, rows and columns
+ * exist if and only if there are widgets in them.
+ *
+ * The width of each column is the width of the widest widget in that
+ * column, and the height of each row is the height of the tallest
+ * widget in that row. Empty rows and empty columns take up no space,
+ * so a grid with a single widget at 0,0 would look exactly the same
+ * if the widget was at 5,10 instead.
+ *
+ * If a widget's cell ends up being larger than the widget's requested
+ * size, then by default the widget will be centered in its cell.
+ * However, this can be modified by changing its #NmtNewtGridFlags.
+ * FIXME: the FILL/ANCHOR flags can be implemented in #NmtNewtWidget
+ * and so should move there. Less clear about the EXPAND flags, which
+ * must be implemented by the container...
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-grid.h"
+
+G_DEFINE_TYPE(NmtNewtGrid, nmt_newt_grid, NMT_TYPE_NEWT_CONTAINER)
+
+#define NMT_NEWT_GRID_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_GRID, NmtNewtGridPrivate))
+
+typedef struct {
+    NmtNewtWidget *  widget;
+    int              x, y;
+    NmtNewtGridFlags flags;
+    int              req_height, req_width;
+} NmtNewtGridChild;
+
+typedef struct {
+    GArray *  children;
+    int       max_x, max_y;
+    int *     row_heights, *col_widths;
+    gboolean *expand_rows, *expand_cols;
+    int       n_expand_rows, n_expand_cols;
+    int       req_height, req_width;
+} NmtNewtGridPrivate;
+
+/**
+ * nmt_newt_grid_new:
+ *
+ * Creates a new #NmtNewtGrid
+ *
+ * Returns: a new #NmtNewtGrid
+ */
+NmtNewtWidget *
+nmt_newt_grid_new(void)
+{
+    return g_object_new(NMT_TYPE_NEWT_GRID, NULL);
+}
+
+static void
+nmt_newt_grid_init(NmtNewtGrid *grid)
+{
+    NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE(grid);
+
+    priv->children = g_array_new(FALSE, FALSE, sizeof(NmtNewtGridChild));
+}
+
+static void
+nmt_newt_grid_finalize(GObject *object)
+{
+    NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE(object);
+
+    g_array_unref(priv->children);
+    nm_clear_g_free(&priv->row_heights);
+    nm_clear_g_free(&priv->col_widths);
+    nm_clear_g_free(&priv->expand_rows);
+    nm_clear_g_free(&priv->expand_cols);
+
+    G_OBJECT_CLASS(nmt_newt_grid_parent_class)->finalize(object);
+}
+
+static int
+child_sort_func(gconstpointer a, gconstpointer b)
+{
+    NmtNewtGridChild *child_a = (NmtNewtGridChild *) a;
+    NmtNewtGridChild *child_b = (NmtNewtGridChild *) b;
+
+    if (child_a->y != child_b->y)
+        return child_a->y - child_b->y;
+    else
+        return child_a->x - child_b->x;
+}
+
+static newtComponent *
+nmt_newt_grid_get_components(NmtNewtWidget *widget)
+{
+    NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE(widget);
+    NmtNewtGridChild *  children;
+    GPtrArray *         cos;
+    newtComponent *     child_cos;
+    int                 i, c;
+
+    g_array_sort(priv->children, child_sort_func);
+    children = (NmtNewtGridChild *) priv->children->data;
+
+    cos = g_ptr_array_new();
+
+    for (i = 0; i < priv->children->len; i++) {
+        if (!nmt_newt_widget_get_visible(children[i].widget))
+            continue;
+
+        child_cos = nmt_newt_widget_get_components(children[i].widget);
+        for (c = 0; child_cos[c]; c++)
+            g_ptr_array_add(cos, child_cos[c]);
+        g_free(child_cos);
+    }
+    g_ptr_array_add(cos, NULL);
+
+    return (newtComponent *) g_ptr_array_free(cos, FALSE);
+}
+
+static void
+nmt_newt_grid_size_request(NmtNewtWidget *widget, int *width, int *height)
+{
+    NmtNewtGrid *       grid     = NMT_NEWT_GRID(widget);
+    NmtNewtGridPrivate *priv     = NMT_NEWT_GRID_GET_PRIVATE(grid);
+    NmtNewtGridChild *  children = (NmtNewtGridChild *) priv->children->data;
+    int                 row, col, i;
+
+    g_free(priv->row_heights);
+    g_free(priv->col_widths);
+    g_free(priv->expand_rows);
+    g_free(priv->expand_cols);
+
+    priv->row_heights   = g_new0(int, priv->max_y + 1);
+    priv->col_widths    = g_new0(int, priv->max_x + 1);
+    priv->expand_rows   = g_new0(gboolean, priv->max_y + 1);
+    priv->expand_cols   = g_new0(gboolean, priv->max_x + 1);
+    priv->n_expand_rows = priv->n_expand_cols = 0;
+
+    for (row = 0; row < priv->max_y + 1; row++) {
+        for (col = 0; col < priv->max_x + 1; col++) {
+            for (i = 0; i < priv->children->len; i++) {
+                if (children[i].x != col || children[i].y != row)
+                    continue;
+                if (!nmt_newt_widget_get_visible(children[i].widget))
+                    continue;
+
+                nmt_newt_widget_size_request(children[i].widget,
+                                             &children[i].req_width,
+                                             &children[i].req_height);
+                if (children[i].req_height > priv->row_heights[row])
+                    priv->row_heights[row] = children[i].req_height;
+                if (children[i].req_width > priv->col_widths[col])
+                    priv->col_widths[col] = children[i].req_width;
+
+                if ((children[i].flags & NMT_NEWT_GRID_EXPAND_X)
+                    && !priv->expand_cols[children[i].x]) {
+                    priv->expand_cols[children[i].x] = TRUE;
+                    priv->n_expand_cols++;
+                }
+                if ((children[i].flags & NMT_NEWT_GRID_EXPAND_Y)
+                    && !priv->expand_rows[children[i].y]) {
+                    priv->expand_rows[children[i].y] = TRUE;
+                    priv->n_expand_rows++;
+                }
+            }
+        }
+    }
+
+    priv->req_height = priv->req_width = 0;
+    for (row = 0; row < priv->max_y + 1; row++)
+        priv->req_height += priv->row_heights[row];
+    for (col = 0; col < priv->max_x + 1; col++)
+        priv->req_width += priv->col_widths[col];
+
+    *height = priv->req_height;
+    *width  = priv->req_width;
+}
+
+static void
+nmt_newt_grid_size_allocate(NmtNewtWidget *widget, int x, int y, int width, int height)
+{
+    NmtNewtGridPrivate *priv     = NMT_NEWT_GRID_GET_PRIVATE(widget);
+    NmtNewtGridChild *  children = (NmtNewtGridChild *) priv->children->data, *child;
+    int                 i, row, col;
+    int                 child_x, child_y, child_width, child_height;
+    int                 extra, extra_all, extra_some;
+
+    extra = width - priv->req_width;
+    if (extra > 0 && priv->n_expand_cols) {
+        extra_all  = extra / priv->n_expand_cols;
+        extra_some = extra % priv->n_expand_cols;
+
+        for (col = 0; col < priv->max_x + 1; col++) {
+            if (!priv->expand_cols[col])
+                continue;
+            priv->col_widths[col] += extra_all;
+            if (extra_some) {
+                priv->col_widths[col]++;
+                extra_some--;
+            }
+        }
+    }
+
+    extra = height - priv->req_height;
+    if (extra > 0 && priv->n_expand_rows) {
+        extra_all  = extra / priv->n_expand_rows;
+        extra_some = extra % priv->n_expand_rows;
+
+        for (row = 0; row < priv->max_y + 1; row++) {
+            if (!priv->expand_rows[row])
+                continue;
+            priv->row_heights[row] += extra_all;
+            if (extra_some) {
+                priv->row_heights[row]++;
+                extra_some--;
+            }
+        }
+    }
+
+    for (i = 0; i < priv->children->len; i++) {
+        child = &children[i];
+        if (!nmt_newt_widget_get_visible(child->widget))
+            continue;
+
+        child_x = x;
+        for (col = 0; col < child->x; col++)
+            child_x += priv->col_widths[col];
+
+        if ((child->flags & NMT_NEWT_GRID_FILL_X) == NMT_NEWT_GRID_FILL_X) {
+            child_width = priv->col_widths[child->x];
+        } else {
+            child_width = child->req_width;
+            if (child->flags & NMT_NEWT_GRID_ANCHOR_RIGHT)
+                child_x += priv->col_widths[child->x] - child->req_width;
+            else if (!(child->flags & NMT_NEWT_GRID_ANCHOR_LEFT))
+                child_x += (priv->col_widths[child->x] - child->req_width) / 2;
+        }
+
+        child_y = y;
+        for (row = 0; row < child->y; row++)
+            child_y += priv->row_heights[row];
+
+        if ((child->flags & NMT_NEWT_GRID_FILL_Y) == NMT_NEWT_GRID_FILL_Y) {
+            child_height = priv->row_heights[child->y];
+        } else {
+            child_height = child->req_height;
+            if (child->flags & NMT_NEWT_GRID_ANCHOR_BOTTOM)
+                child_y += priv->row_heights[child->y] - child->req_height;
+            else if (!(child->flags & NMT_NEWT_GRID_ANCHOR_TOP))
+                child_y += (priv->row_heights[child->y] - child->req_height) / 2;
+        }
+
+        nmt_newt_widget_size_allocate(child->widget, child_x, child_y, child_width, child_height);
+    }
+}
+
+static void
+nmt_newt_grid_find_size(NmtNewtGrid *grid)
+{
+    NmtNewtGridPrivate *priv     = NMT_NEWT_GRID_GET_PRIVATE(grid);
+    NmtNewtGridChild *  children = (NmtNewtGridChild *) priv->children->data;
+    int                 i;
+
+    priv->max_x = priv->max_y = 0;
+    for (i = 0; i < priv->children->len; i++) {
+        if (children[i].x > priv->max_x)
+            priv->max_x = children[i].x;
+        if (children[i].y > priv->max_y)
+            priv->max_y = children[i].y;
+    }
+}
+
+/**
+ * nmt_newt_grid_add:
+ * @grid: an #NmtNewtGrid
+ * @widget: the widget to add
+ * @x: x coordinate
+ * @y: y coordinate
+ *
+ * Adds @widget to @grid at @x, @y. See the discussion above for more
+ * details of exactly how this works.
+ */
+void
+nmt_newt_grid_add(NmtNewtGrid *grid, NmtNewtWidget *widget, int x, int y)
+{
+    NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE(grid);
+    NmtNewtGridChild    child;
+
+    NMT_NEWT_CONTAINER_CLASS(nmt_newt_grid_parent_class)->add(NMT_NEWT_CONTAINER(grid), widget);
+
+    memset(&child, 0, sizeof(child));
+    child.widget = widget;
+    child.x      = x;
+    child.y      = y;
+    child.flags  = NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y;
+    g_array_append_val(priv->children, child);
+
+    if (x > priv->max_x)
+        priv->max_x = x;
+    if (y > priv->max_y)
+        priv->max_y = y;
+}
+
+static int
+find_child(NmtNewtGrid *grid, NmtNewtWidget *widget)
+{
+    NmtNewtGridPrivate *priv     = NMT_NEWT_GRID_GET_PRIVATE(grid);
+    NmtNewtGridChild *  children = (NmtNewtGridChild *) priv->children->data;
+    int                 i;
+
+    for (i = 0; i < priv->children->len; i++) {
+        if (children[i].widget == widget)
+            return i;
+    }
+
+    return -1;
+}
+
+static void
+nmt_newt_grid_remove(NmtNewtContainer *container, NmtNewtWidget *widget)
+{
+    NmtNewtGrid *       grid = NMT_NEWT_GRID(container);
+    NmtNewtGridPrivate *priv = NMT_NEWT_GRID_GET_PRIVATE(grid);
+    int                 i;
+
+    i = find_child(grid, widget);
+    if (i != -1) {
+        g_array_remove_index(priv->children, i);
+        nmt_newt_grid_find_size(grid);
+    }
+
+    NMT_NEWT_CONTAINER_CLASS(nmt_newt_grid_parent_class)->remove(container, widget);
+}
+
+/**
+ * nmt_newt_grid_move:
+ * @grid: an #NmtNewtGrid
+ * @widget: a child of @grid
+ * @x: x coordinate
+ * @y: y coordinate
+ *
+ * Moves @widget to the given new coordinates.
+ */
+void
+nmt_newt_grid_move(NmtNewtGrid *grid, NmtNewtWidget *widget, int x, int y)
+{
+    NmtNewtGridPrivate *priv     = NMT_NEWT_GRID_GET_PRIVATE(grid);
+    NmtNewtGridChild *  children = (NmtNewtGridChild *) priv->children->data;
+    int                 i;
+
+    i = find_child(grid, widget);
+    if (i != -1 && (children[i].x != x || children[i].y != y)) {
+        children[i].x = x;
+        children[i].y = y;
+        nmt_newt_grid_find_size(grid);
+        nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(grid));
+    }
+}
+
+/**
+ * NmtNewtGridFlags:
+ * @NMT_NEWT_GRID_EXPAND_X: The widget's cell should expand
+ *   horizontally if the grid as a whole is given more width than
+ *   it requested.
+ * @NMT_NEWT_GRID_EXPAND_Y: The widget's cell should expand
+ *   vertically if the grid as a whole is given more height than
+ *   it requested.
+ * @NMT_NEWT_GRID_ANCHOR_LEFT: If the widget's cell is wider than
+ *   the widget requested, the widget should be anchored to the
+ *   left of its cell rather than being centered.
+ * @NMT_NEWT_GRID_ANCHOR_RIGHT: If the widget's cell is wider than
+ *   the widget requested, the widget should be anchored to the
+ *   right of its cell rather than being centered.
+ * @NMT_NEWT_GRID_FILL_X: If the widget's cell is wider than
+ *   the widget requested, the widget should be allocated the
+ *   full width of the cell; this is equivalent to specifying
+ *   both %NMT_NEWT_GRID_ANCHOR_LEFT and %NMT_NEWT_GRID_ANCHOR_RIGHT.
+ * @NMT_NEWT_GRID_ANCHOR_TOP: If the widget's cell is taller than
+ *   the widget requested, the widget should be anchored to the
+ *   top of its cell rather than being centered.
+ * @NMT_NEWT_GRID_ANCHOR_BOTTOM: If the widget's cell is taller than
+ *   the widget requested, the widget should be anchored to the
+ *   bottom of its cell rather than being centered.
+ * @NMT_NEWT_GRID_FILL_Y: If the widget's cell is taller than
+ *   the widget requested, the widget should be allocated the
+ *   full height of the cell; this is equivalent to specifying
+ *   both %NMT_NEWT_GRID_ANCHOR_TOP and %NMT_NEWT_GRID_ANCHOR_BOTTOM.
+ *
+ * Flags describing how a widget is placed within its grid cell.
+ */
+
+/**
+ * nmt_newt_grid_set_flags:
+ * @grid: an #NmtNewtGrid
+ * @widget: a child of @grid
+ * @flags: #NmtNewtGridFlags for @widget
+ *
+ * Sets the #NmtNewtGridFlags on @widget
+ */
+void
+nmt_newt_grid_set_flags(NmtNewtGrid *grid, NmtNewtWidget *widget, NmtNewtGridFlags flags)
+{
+    NmtNewtGridPrivate *priv     = NMT_NEWT_GRID_GET_PRIVATE(grid);
+    NmtNewtGridChild *  children = (NmtNewtGridChild *) priv->children->data;
+    int                 i;
+
+    i = find_child(grid, widget);
+    if (i != -1)
+        children[i].flags = flags;
+}
+
+static void
+nmt_newt_grid_class_init(NmtNewtGridClass *grid_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(grid_class);
+    NmtNewtWidgetClass *   widget_class    = NMT_NEWT_WIDGET_CLASS(grid_class);
+    NmtNewtContainerClass *container_class = NMT_NEWT_CONTAINER_CLASS(grid_class);
+
+    g_type_class_add_private(grid_class, sizeof(NmtNewtGridPrivate));
+
+    /* virtual methods */
+    object_class->finalize = nmt_newt_grid_finalize;
+
+    widget_class->get_components = nmt_newt_grid_get_components;
+    widget_class->size_request   = nmt_newt_grid_size_request;
+    widget_class->size_allocate  = nmt_newt_grid_size_allocate;
+
+    container_class->remove = nmt_newt_grid_remove;
+}
diff --git a/src/libnmt-newt/nmt-newt-grid.h b/src/libnmt-newt/nmt-newt-grid.h
new file mode 100644
index 00000000..7aac18bc
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-grid.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_GRID_H
+#define NMT_NEWT_GRID_H
+
+#include "nmt-newt-container.h"
+
+#define NMT_TYPE_NEWT_GRID (nmt_newt_grid_get_type())
+#define NMT_NEWT_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_GRID, NmtNewtGrid))
+#define NMT_NEWT_GRID_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_GRID, NmtNewtGridClass))
+#define NMT_IS_NEWT_GRID(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_GRID))
+#define NMT_IS_NEWT_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_GRID))
+#define NMT_NEWT_GRID_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_GRID, NmtNewtGridClass))
+
+struct _NmtNewtGrid {
+    NmtNewtContainer parent;
+};
+
+typedef struct {
+    NmtNewtContainerClass parent;
+
+} NmtNewtGridClass;
+
+GType nmt_newt_grid_get_type(void);
+
+typedef enum {
+    NMT_NEWT_GRID_EXPAND_X      = (1 << 0),
+    NMT_NEWT_GRID_EXPAND_Y      = (1 << 1),
+    NMT_NEWT_GRID_ANCHOR_LEFT   = (1 << 2),
+    NMT_NEWT_GRID_ANCHOR_RIGHT  = (1 << 3),
+    NMT_NEWT_GRID_FILL_X        = NMT_NEWT_GRID_ANCHOR_LEFT | NMT_NEWT_GRID_ANCHOR_RIGHT,
+    NMT_NEWT_GRID_ANCHOR_TOP    = (1 << 4),
+    NMT_NEWT_GRID_ANCHOR_BOTTOM = (1 << 5),
+    NMT_NEWT_GRID_FILL_Y        = NMT_NEWT_GRID_ANCHOR_TOP | NMT_NEWT_GRID_ANCHOR_BOTTOM,
+} NmtNewtGridFlags;
+
+NmtNewtWidget *nmt_newt_grid_new(void);
+
+void nmt_newt_grid_add(NmtNewtGrid *grid, NmtNewtWidget *widget, int x, int y);
+void nmt_newt_grid_move(NmtNewtGrid *grid, NmtNewtWidget *widget, int x, int y);
+void nmt_newt_grid_set_flags(NmtNewtGrid *grid, NmtNewtWidget *widget, NmtNewtGridFlags flags);
+
+#endif /* NMT_NEWT_GRID_H */
diff --git a/src/libnmt-newt/nmt-newt-hacks.c b/src/libnmt-newt/nmt-newt-hacks.c
new file mode 100644
index 00000000..40114a29
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-hacks.c
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-hacks
+ * @short_description: Hacks!
+ *
+ * This contains hacky cheating implementations of certain newt
+ * functions that were added after 0.52.15.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-hacks.h"
+
+#if !defined(HAVE_NEWTCOMPONENTGETSIZE) || !defined(HAVE_NEWTENTRYGETCURSORPOSITION)
+struct newtComponent_0_52_15_struct_hack {
+    int height, width;
+    int top, left;
+    int takesFocus;
+    int isMapped;
+
+    struct componentOps *ops;
+
+    newtCallback callback;
+    void *       callbackData;
+
+    newtCallback destroyCallback;
+    void *       destroyCallbackData;
+
+    void *data;
+};
+#endif
+
+#ifndef HAVE_NEWTCOMPONENTGETSIZE
+void
+newtComponentGetSize(newtComponent component, int *width, int *height)
+{
+    struct newtComponent_0_52_15_struct_hack *hack = (void *) component;
+
+    *width  = hack->width;
+    *height = hack->height;
+}
+
+void
+newtComponentGetPosition(newtComponent component, int *left, int *top)
+{
+    struct newtComponent_0_52_15_struct_hack *hack = (void *) component;
+
+    *left = hack->left;
+    *top  = hack->top;
+}
+#endif
+
+#ifndef HAVE_NEWTENTRYGETCURSORPOSITION
+struct newtEntry_0_52_15_struct_hack {
+    int          flags;
+    char *       buf;
+    const char **resultPtr;
+    int          bufAlloced;
+    int          bufUsed;
+    int          cursorPosition;
+    /* ... */
+};
+
+int
+newtEntryGetCursorPosition(newtComponent component)
+{
+    struct newtComponent_0_52_15_struct_hack *co_hack    = (void *) component;
+    struct newtEntry_0_52_15_struct_hack *    entry_hack = co_hack->data;
+
+    return entry_hack->cursorPosition;
+}
+
+void
+newtEntrySetCursorPosition(newtComponent component, int position)
+{
+    struct newtComponent_0_52_15_struct_hack *co_hack    = (void *) component;
+    struct newtEntry_0_52_15_struct_hack *    entry_hack = co_hack->data;
+
+    entry_hack->cursorPosition = position;
+}
+#endif
diff --git a/src/libnmt-newt/nmt-newt-hacks.h b/src/libnmt-newt/nmt-newt-hacks.h
new file mode 100644
index 00000000..92b376f9
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-hacks.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_HACKS_H
+#define NMT_NEWT_HACKS_H
+
+#include <newt.h>
+
+#ifndef HAVE_NEWTCOMPONENTGETSIZE
+void newtComponentGetSize(newtComponent component, int *width, int *height);
+
+void newtComponentGetPosition(newtComponent component, int *left, int *top);
+#endif
+
+#ifndef HAVE_NEWTENTRYGETCURSORPOSITION
+int  newtEntryGetCursorPosition(newtComponent component);
+void newtEntrySetCursorPosition(newtComponent component, int position);
+#endif
+
+#endif /* NMT_NEWT_HACKS_H */
diff --git a/src/libnmt-newt/nmt-newt-label.c b/src/libnmt-newt/nmt-newt-label.c
new file mode 100644
index 00000000..32c7809d
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-label.c
@@ -0,0 +1,303 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-label
+ * @short_description: Labels
+ *
+ * #NmtNewtLabel implements a single-line label.
+ *
+ * See also #NmtNewtTextbox, for multiline.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-label.h"
+
+#include "nmt-newt-utils.h"
+
+G_DEFINE_TYPE(NmtNewtLabel, nmt_newt_label, NMT_TYPE_NEWT_COMPONENT)
+
+#define NMT_NEWT_LABEL_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_LABEL, NmtNewtLabelPrivate))
+
+typedef struct {
+    char *            text;
+    NmtNewtLabelStyle style;
+    gboolean          highlight;
+} NmtNewtLabelPrivate;
+
+enum {
+    PROP_0,
+    PROP_TEXT,
+    PROP_STYLE,
+    PROP_HIGHLIGHT,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_newt_label_new:
+ * @text: the initial label text
+ *
+ * Creates a new #NmtNewtLabel
+ *
+ * Returns: a new #NmtNewtLabel
+ */
+NmtNewtWidget *
+nmt_newt_label_new(const char *text)
+{
+    return g_object_new(NMT_TYPE_NEWT_LABEL, "text", text, NULL);
+}
+
+/**
+ * nmt_newt_label_set_text:
+ * @label: an #NmtNewtLabel
+ * @text: the new text
+ *
+ * Updates @label's text.
+ */
+void
+nmt_newt_label_set_text(NmtNewtLabel *label, const char *text)
+{
+    NmtNewtLabelPrivate *priv = NMT_NEWT_LABEL_GET_PRIVATE(label);
+
+    if (!g_strcmp0(priv->text, text))
+        return;
+
+    g_free(priv->text);
+    priv->text = g_strdup(text);
+
+    g_object_notify(G_OBJECT(label), "text");
+    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(label));
+}
+
+/**
+ * nmt_newt_label_get_text:
+ * @label: an #NmtNewtLabel
+ *
+ * Gets @label's text
+ *
+ * Returns: @label's text
+ */
+const char *
+nmt_newt_label_get_text(NmtNewtLabel *label)
+{
+    NmtNewtLabelPrivate *priv = NMT_NEWT_LABEL_GET_PRIVATE(label);
+
+    return priv->text;
+}
+
+/**
+ * NmtNewtLabelStyle:
+ * @NMT_NEWT_LABEL_NORMAL: a normal label
+ * @NMT_NEWT_LABEL_PLAIN: a "plain-looking" label
+ *
+ * The label style. Normal labels are blue. Plain labels are black,
+ * making them look more like they are text in their own right rather
+ * than just being a label for something else.
+ */
+
+/**
+ * nmt_newt_label_set_style:
+ * @label: an #NmtNewtLabel
+ * @style: the #NmtNewtLabelStyle
+ *
+ * Sets the style of @label
+ */
+void
+nmt_newt_label_set_style(NmtNewtLabel *label, NmtNewtLabelStyle style)
+{
+    NmtNewtLabelPrivate *priv = NMT_NEWT_LABEL_GET_PRIVATE(label);
+
+    if (priv->style == style)
+        return;
+
+    priv->style = style;
+    g_object_notify(G_OBJECT(label), "style");
+    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(label));
+}
+
+/**
+ * nmt_newt_label_get_style:
+ * @label: an #NmtNewtLabel
+ *
+ * Gets the style of @label
+ *
+ * Returns: the style of @label
+ */
+NmtNewtLabelStyle
+nmt_newt_label_get_style(NmtNewtLabel *label)
+{
+    NmtNewtLabelPrivate *priv = NMT_NEWT_LABEL_GET_PRIVATE(label);
+
+    return priv->style;
+}
+
+/**
+ * nmt_newt_label_set_highlight:
+ * @label: an #NmtNewtLabel
+ * @highlight: %TRUE if @label should be highlighted
+ *
+ * Sets whether @label is highlighted. Highlighted labels are red;
+ * this is generally used to highlight invalid widgets.
+ */
+void
+nmt_newt_label_set_highlight(NmtNewtLabel *label, gboolean highlight)
+{
+    NmtNewtLabelPrivate *priv = NMT_NEWT_LABEL_GET_PRIVATE(label);
+
+    highlight = !!highlight;
+    if (priv->highlight == highlight)
+        return;
+
+    priv->highlight = highlight;
+    g_object_notify(G_OBJECT(label), "highlight");
+    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(label));
+}
+
+/**
+ * nmt_newt_label_get_highlight:
+ * @label: an #NmtNewtLabel
+ *
+ * Gets whether @label is highlighted.
+ *
+ * Returns: whether @label is highlighted.
+ */
+gboolean
+nmt_newt_label_get_highlight(NmtNewtLabel *label)
+{
+    NmtNewtLabelPrivate *priv = NMT_NEWT_LABEL_GET_PRIVATE(label);
+
+    return priv->highlight;
+}
+
+static void
+nmt_newt_label_init(NmtNewtLabel *label)
+{}
+
+static void
+nmt_newt_label_finalize(GObject *object)
+{
+    NmtNewtLabelPrivate *priv = NMT_NEWT_LABEL_GET_PRIVATE(object);
+
+    g_free(priv->text);
+
+    G_OBJECT_CLASS(nmt_newt_label_parent_class)->finalize(object);
+}
+
+static newtComponent
+nmt_newt_label_build_component(NmtNewtComponent *component, gboolean sensitive)
+{
+    NmtNewtLabelPrivate *priv = NMT_NEWT_LABEL_GET_PRIVATE(component);
+    newtComponent        co;
+    char *               text_lc;
+
+    text_lc = nmt_newt_locale_from_utf8(priv->text);
+    co      = newtLabel(-1, -1, text_lc);
+    g_free(text_lc);
+
+    if (priv->highlight)
+        newtLabelSetColors(co, NMT_NEWT_COLORSET_BAD_LABEL);
+    else if (priv->style == NMT_NEWT_LABEL_PLAIN)
+        newtLabelSetColors(co, NMT_NEWT_COLORSET_PLAIN_LABEL);
+
+    return co;
+}
+
+static void
+nmt_newt_label_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtNewtLabel *label = NMT_NEWT_LABEL(object);
+
+    switch (prop_id) {
+    case PROP_TEXT:
+        nmt_newt_label_set_text(label, g_value_get_string(value));
+        break;
+    case PROP_STYLE:
+        nmt_newt_label_set_style(label, g_value_get_int(value));
+        break;
+    case PROP_HIGHLIGHT:
+        nmt_newt_label_set_highlight(label, g_value_get_boolean(value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_label_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtLabelPrivate *priv = NMT_NEWT_LABEL_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_TEXT:
+        g_value_set_string(value, priv->text);
+        break;
+    case PROP_STYLE:
+        g_value_set_int(value, priv->style);
+        break;
+    case PROP_HIGHLIGHT:
+        g_value_set_boolean(value, priv->highlight);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_label_class_init(NmtNewtLabelClass *label_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(label_class);
+    NmtNewtComponentClass *component_class = NMT_NEWT_COMPONENT_CLASS(label_class);
+
+    g_type_class_add_private(label_class, sizeof(NmtNewtLabelPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_label_set_property;
+    object_class->get_property = nmt_newt_label_get_property;
+    object_class->finalize     = nmt_newt_label_finalize;
+
+    component_class->build_component = nmt_newt_label_build_component;
+
+    /**
+     * NmtNewtLabel:text:
+     *
+     * The label's text
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_TEXT,
+        g_param_spec_string("text", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtLabel:style:
+     *
+     * The label's #NmtNewtLabelStyle
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_STYLE,
+                                    g_param_spec_int("style",
+                                                     "",
+                                                     "",
+                                                     0,
+                                                     G_MAXINT,
+                                                     0,
+                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtLabel:highlight:
+     *
+     * Whether the label is highlighted.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_HIGHLIGHT,
+        g_param_spec_boolean("highlight",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-label.h b/src/libnmt-newt/nmt-newt-label.h
new file mode 100644
index 00000000..ff2e471b
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-label.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_LABEL_H
+#define NMT_NEWT_LABEL_H
+
+#include "nmt-newt-component.h"
+
+#define NMT_TYPE_NEWT_LABEL (nmt_newt_label_get_type())
+#define NMT_NEWT_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_LABEL, NmtNewtLabel))
+#define NMT_NEWT_LABEL_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_LABEL, NmtNewtLabelClass))
+#define NMT_IS_NEWT_LABEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_LABEL))
+#define NMT_IS_NEWT_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_LABEL))
+#define NMT_NEWT_LABEL_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_LABEL, NmtNewtLabelClass))
+
+struct _NmtNewtLabel {
+    NmtNewtComponent parent;
+};
+
+typedef struct {
+    NmtNewtComponentClass parent;
+
+} NmtNewtLabelClass;
+
+GType nmt_newt_label_get_type(void);
+
+typedef enum { NMT_NEWT_LABEL_NORMAL, NMT_NEWT_LABEL_PLAIN } NmtNewtLabelStyle;
+
+NmtNewtWidget *nmt_newt_label_new(const char *text);
+
+void        nmt_newt_label_set_text(NmtNewtLabel *label, const char *text);
+const char *nmt_newt_label_get_text(NmtNewtLabel *label);
+
+void              nmt_newt_label_set_style(NmtNewtLabel *label, NmtNewtLabelStyle style);
+NmtNewtLabelStyle nmt_newt_label_get_style(NmtNewtLabel *label);
+
+void     nmt_newt_label_set_highlight(NmtNewtLabel *label, gboolean highlight);
+gboolean nmt_newt_label_get_highlight(NmtNewtLabel *label);
+
+#endif /* NMT_NEWT_LABEL_H */
diff --git a/src/libnmt-newt/nmt-newt-listbox.c b/src/libnmt-newt/nmt-newt-listbox.c
new file mode 100644
index 00000000..cfe23647
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-listbox.c
@@ -0,0 +1,518 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-listbox
+ * @short_description: Single-choice listboxes
+ *
+ * #NmtNewtListbox implements a single-choice listbox.
+ *
+ * A listbox has some number of rows, each associated with an
+ * arbitrary pointer value. The pointer values do not need to be
+ * unique, but some APIs will not be usable if they aren't. You
+ * can also cause rows with %NULL keys to be treated specially.
+ *
+ * The listbox will emit #NmtNewtWidget::activate when the user
+ * presses Return on a selection.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-listbox.h"
+#include "nmt-newt-form.h"
+#include "nmt-newt-utils.h"
+
+G_DEFINE_TYPE(NmtNewtListbox, nmt_newt_listbox, NMT_TYPE_NEWT_COMPONENT)
+
+#define NMT_NEWT_LISTBOX_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_LISTBOX, NmtNewtListboxPrivate))
+
+typedef struct {
+    int                 height, alloc_height, width;
+    gboolean            fixed_height;
+    NmtNewtListboxFlags flags;
+
+    GPtrArray *entries;
+    GPtrArray *keys;
+
+    int      active;
+    gpointer active_key;
+    gboolean skip_null_keys;
+
+} NmtNewtListboxPrivate;
+
+enum {
+    PROP_0,
+    PROP_HEIGHT,
+    PROP_FLAGS,
+    PROP_ACTIVE,
+    PROP_ACTIVE_KEY,
+    PROP_SKIP_NULL_KEYS,
+
+    LAST_PROP
+};
+
+/**
+ * NmtNewtListboxFlags:
+ * @NMT_NEWT_LISTBOX_SCROLL: the listbox should have a scroll bar.
+ * @NMT_NEWT_LISTBOX_BORDER: the listbox should have a border around it.
+ *
+ * Flags describing an #NmtNewtListbox
+ */
+
+/**
+ * nmt_newt_listbox_new:
+ * @height: the height of the listbox, or -1 for no fixed height
+ * @flags: the listbox flags
+ *
+ * Creates a new #NmtNewtListbox
+ *
+ * Returns: a new #NmtNewtListbox
+ */
+NmtNewtWidget *
+nmt_newt_listbox_new(int height, NmtNewtListboxFlags flags)
+{
+    return g_object_new(NMT_TYPE_NEWT_LISTBOX, "height", height, "flags", flags, NULL);
+}
+
+/**
+ * nmt_newt_listbox_append:
+ * @listbox: an #NmtNewtListbox
+ * @entry: the text for the new row
+ * @key: (allow-none): the key associated with @entry
+ *
+ * Adds a row to @listbox.
+ */
+void
+nmt_newt_listbox_append(NmtNewtListbox *listbox, const char *entry, gpointer key)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(listbox);
+
+    g_ptr_array_add(priv->entries, nmt_newt_locale_from_utf8(entry));
+    g_ptr_array_add(priv->keys, key);
+    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(listbox));
+}
+
+/**
+ * nmt_newt_listbox_clear:
+ * @listbox: an #NmtNewtListbox
+ *
+ * Clears the contents of @listbox.
+ */
+void
+nmt_newt_listbox_clear(NmtNewtListbox *listbox)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(listbox);
+
+    g_ptr_array_set_size(priv->entries, 0);
+    g_ptr_array_set_size(priv->keys, 0);
+
+    priv->active     = -1;
+    priv->active_key = NULL;
+
+    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(listbox));
+}
+
+/**
+ * nmt_newt_listbox_set_active:
+ * @listbox: an #NmtNewtListbox
+ * @active: the row to make active
+ *
+ * Sets @active to be the currently-selected row in @listbox,
+ * scrolling it into view if needed.
+ */
+void
+nmt_newt_listbox_set_active(NmtNewtListbox *listbox, int active)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(listbox);
+
+    if (active == priv->active)
+        return;
+
+    g_return_if_fail(active >= 0 && active < priv->entries->len);
+    g_return_if_fail(!priv->skip_null_keys || priv->keys->pdata[active]);
+
+    priv->active     = active;
+    priv->active_key = priv->keys->pdata[active];
+
+    g_object_notify(G_OBJECT(listbox), "active");
+    g_object_notify(G_OBJECT(listbox), "active-key");
+}
+
+/**
+ * nmt_newt_listbox_set_active_key:
+ * @listbox: an #NmtNewtListbox
+ * @active_key: the key for the row to make active
+ *
+ * Selects the (first) row in @listbox with @active_key as its key,
+ * scrolling it into view if needed.
+ */
+void
+nmt_newt_listbox_set_active_key(NmtNewtListbox *listbox, gpointer active_key)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(listbox);
+    int                    i;
+
+    if (active_key == priv->active_key)
+        return;
+
+    g_return_if_fail(!priv->skip_null_keys || active_key);
+
+    for (i = 0; i < priv->keys->len; i++) {
+        if (priv->keys->pdata[i] == active_key) {
+            priv->active     = i;
+            priv->active_key = active_key;
+
+            g_object_notify(G_OBJECT(listbox), "active");
+            g_object_notify(G_OBJECT(listbox), "active-key");
+            return;
+        }
+    }
+}
+
+/**
+ * nmt_newt_listbox_get_active:
+ * @listbox: an #NmtNewtListbox
+ *
+ * Gets the currently-selected row in @listbox.
+ *
+ * Returns: the currently-selected row in @listbox.
+ */
+int
+nmt_newt_listbox_get_active(NmtNewtListbox *listbox)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(listbox);
+
+    return priv->active;
+}
+
+/**
+ * nmt_newt_listbox_get_active_key:
+ * @listbox: an #NmtNewtListbox
+ *
+ * Gets the key of the currently-selected row in @listbox.
+ *
+ * Returns: the key of the currently-selected row in @listbox.
+ */
+gpointer
+nmt_newt_listbox_get_active_key(NmtNewtListbox *listbox)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(listbox);
+
+    return priv->active_key;
+}
+
+/**
+ * nmt_newt_listbox_set_height:
+ * @listbox: an #NmtNewtListbox
+ * @height: the new height, or -1 for no fixed height
+ *
+ * Updates @listbox's height.
+ */
+void
+nmt_newt_listbox_set_height(NmtNewtListbox *listbox, int height)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(listbox);
+
+    priv->height       = height;
+    priv->fixed_height = priv->height != 0;
+    g_object_notify(G_OBJECT(listbox), "height");
+}
+
+static void
+nmt_newt_listbox_init(NmtNewtListbox *listbox)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(listbox);
+
+    priv->entries = g_ptr_array_new_with_free_func(g_free);
+    priv->keys    = g_ptr_array_new();
+
+    priv->active = -1;
+}
+
+static void
+nmt_newt_listbox_finalize(GObject *object)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(object);
+
+    g_ptr_array_unref(priv->entries);
+    g_ptr_array_unref(priv->keys);
+
+    G_OBJECT_CLASS(nmt_newt_listbox_parent_class)->finalize(object);
+}
+
+static void
+nmt_newt_listbox_size_request(NmtNewtWidget *widget, int *width, int *height)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(widget);
+
+    NMT_NEWT_WIDGET_CLASS(nmt_newt_listbox_parent_class)->size_request(widget, width, height);
+
+    priv->alloc_height = -1;
+    if (!priv->fixed_height)
+        *height = 1;
+    priv->width = *width;
+}
+
+static void
+nmt_newt_listbox_size_allocate(NmtNewtWidget *widget, int x, int y, int width, int height)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(widget);
+
+    if (width > priv->width) {
+        newtListboxSetWidth(nmt_newt_component_get_component(NMT_NEWT_COMPONENT(widget)), width);
+    }
+
+    NMT_NEWT_WIDGET_CLASS(nmt_newt_listbox_parent_class)
+        ->size_allocate(widget, x, y, width, height);
+
+    priv->alloc_height = height;
+
+    if (!priv->fixed_height && height != priv->height) {
+        priv->height = height;
+        nmt_newt_widget_needs_rebuild(widget);
+    }
+}
+
+static void
+update_active_internal(NmtNewtListbox *listbox, int new_active)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(listbox);
+
+    if (priv->active == new_active)
+        return;
+    if (new_active >= priv->keys->len)
+        return;
+
+    if (priv->skip_null_keys && !priv->keys->pdata[new_active]) {
+        if (new_active > priv->active) {
+            while (new_active < priv->entries->len && !priv->keys->pdata[new_active])
+                new_active++;
+        } else {
+            while (new_active >= 0 && !priv->keys->pdata[new_active])
+                new_active--;
+        }
+
+        if (new_active < 0 || new_active >= priv->entries->len || !priv->keys->pdata[new_active]) {
+            g_assert(priv->active >= 0 && priv->active < priv->entries->len);
+            return;
+        }
+    }
+
+    nmt_newt_listbox_set_active(listbox, new_active);
+}
+
+static void
+selection_changed_callback(newtComponent co, void *user_data)
+{
+    NmtNewtListbox *       listbox = user_data;
+    NmtNewtListboxPrivate *priv    = NMT_NEWT_LISTBOX_GET_PRIVATE(listbox);
+    int                    new_active;
+
+    new_active = GPOINTER_TO_UINT(newtListboxGetCurrent(co));
+    update_active_internal(listbox, new_active);
+
+    if (priv->active != new_active)
+        newtListboxSetCurrent(co, priv->active);
+}
+
+static guint
+convert_flags(NmtNewtListboxFlags flags)
+{
+    guint newt_flags = NEWT_FLAG_RETURNEXIT;
+
+    if (flags & NMT_NEWT_LISTBOX_SCROLL)
+        newt_flags |= NEWT_FLAG_SCROLL;
+    if (flags & NMT_NEWT_LISTBOX_BORDER)
+        newt_flags |= NEWT_FLAG_BORDER;
+
+    return newt_flags;
+}
+
+static newtComponent
+nmt_newt_listbox_build_component(NmtNewtComponent *component, gboolean sensitive)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(component);
+    newtComponent          co;
+    int                    i, active;
+
+    if (priv->active == -1)
+        update_active_internal(NMT_NEWT_LISTBOX(component), 0);
+    active = priv->active;
+
+    co = newtListbox(-1, -1, priv->height, convert_flags(priv->flags));
+    newtComponentAddCallback(co, selection_changed_callback, component);
+
+    for (i = 0; i < priv->entries->len; i++) {
+        newtListboxAppendEntry(co, priv->entries->pdata[i], GUINT_TO_POINTER(i));
+        if (active == -1 && priv->keys->pdata[i] == priv->active_key)
+            active = i;
+    }
+
+    if (active != -1)
+        newtListboxSetCurrent(co, active);
+
+    return co;
+}
+
+static void
+nmt_newt_listbox_activated(NmtNewtWidget *widget)
+{
+    NmtNewtListbox *listbox = NMT_NEWT_LISTBOX(widget);
+    newtComponent   co      = nmt_newt_component_get_component(NMT_NEWT_COMPONENT(widget));
+
+    nmt_newt_listbox_set_active(listbox, GPOINTER_TO_UINT(newtListboxGetCurrent(co)));
+
+    NMT_NEWT_WIDGET_CLASS(nmt_newt_listbox_parent_class)->activated(widget);
+}
+
+static void
+nmt_newt_listbox_set_property(GObject *     object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec *  pspec)
+{
+    NmtNewtListbox *       listbox = NMT_NEWT_LISTBOX(object);
+    NmtNewtListboxPrivate *priv    = NMT_NEWT_LISTBOX_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_HEIGHT:
+        priv->height       = g_value_get_int(value);
+        priv->fixed_height = (priv->height != 0);
+        break;
+    case PROP_FLAGS:
+        priv->flags = g_value_get_uint(value);
+        break;
+    case PROP_ACTIVE:
+        nmt_newt_listbox_set_active(listbox, g_value_get_int(value));
+        break;
+    case PROP_ACTIVE_KEY:
+        nmt_newt_listbox_set_active_key(listbox, g_value_get_pointer(value));
+        break;
+    case PROP_SKIP_NULL_KEYS:
+        priv->skip_null_keys = g_value_get_boolean(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_listbox_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtListboxPrivate *priv = NMT_NEWT_LISTBOX_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_HEIGHT:
+        g_value_set_int(value, priv->height);
+        break;
+    case PROP_FLAGS:
+        g_value_set_uint(value, priv->flags);
+        break;
+    case PROP_ACTIVE:
+        g_value_set_int(value, priv->active);
+        break;
+    case PROP_ACTIVE_KEY:
+        g_value_set_pointer(value, priv->active_key);
+        break;
+    case PROP_SKIP_NULL_KEYS:
+        g_value_set_boolean(value, priv->skip_null_keys);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_listbox_class_init(NmtNewtListboxClass *listbox_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(listbox_class);
+    NmtNewtWidgetClass *   widget_class    = NMT_NEWT_WIDGET_CLASS(listbox_class);
+    NmtNewtComponentClass *component_class = NMT_NEWT_COMPONENT_CLASS(listbox_class);
+
+    g_type_class_add_private(listbox_class, sizeof(NmtNewtListboxPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_listbox_set_property;
+    object_class->get_property = nmt_newt_listbox_get_property;
+    object_class->finalize     = nmt_newt_listbox_finalize;
+
+    widget_class->size_request  = nmt_newt_listbox_size_request;
+    widget_class->size_allocate = nmt_newt_listbox_size_allocate;
+    widget_class->activated     = nmt_newt_listbox_activated;
+
+    component_class->build_component = nmt_newt_listbox_build_component;
+
+    /* properties */
+
+    /**
+     * NmtNewtListbox:height:
+     *
+     * The listbox's height, or -1 if it has no fixed height.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_HEIGHT,
+                                    g_param_spec_int("height",
+                                                     "",
+                                                     "",
+                                                     -1,
+                                                     255,
+                                                     -1,
+                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtListbox:flags:
+     *
+     * The listbox's #NmtNewtListboxFlags.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_FLAGS,
+        g_param_spec_uint("flags",
+                          "",
+                          "",
+                          0,
+                          0xFFFF,
+                          0,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtListbox:active:
+     *
+     * The currently-selected row.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_ACTIVE,
+                                    g_param_spec_int("active",
+                                                     "",
+                                                     "",
+                                                     0,
+                                                     G_MAXINT,
+                                                     0,
+                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtListbox:active-key:
+     *
+     * The key of the currently-selected row.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_ACTIVE_KEY,
+        g_param_spec_pointer("active-key", "", "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtListbox:skip-null-keys:
+     *
+     * If %TRUE, rows with %NULL key values will be skipped over when
+     * navigating the list with the arrow keys.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_SKIP_NULL_KEYS,
+        g_param_spec_boolean("skip-null-keys",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-listbox.h b/src/libnmt-newt/nmt-newt-listbox.h
new file mode 100644
index 00000000..602e3380
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-listbox.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_LISTBOX_H
+#define NMT_NEWT_LISTBOX_H
+
+#include "nmt-newt-component.h"
+
+#define NMT_TYPE_NEWT_LISTBOX (nmt_newt_listbox_get_type())
+#define NMT_NEWT_LISTBOX(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_LISTBOX, NmtNewtListbox))
+#define NMT_NEWT_LISTBOX_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_LISTBOX, NmtNewtListboxClass))
+#define NMT_IS_NEWT_LISTBOX(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_LISTBOX))
+#define NMT_IS_NEWT_LISTBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_LISTBOX))
+#define NMT_NEWT_LISTBOX_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_LISTBOX, NmtNewtListboxClass))
+
+struct _NmtNewtListbox {
+    NmtNewtComponent parent;
+};
+
+typedef struct {
+    NmtNewtComponentClass parent;
+
+} NmtNewtListboxClass;
+
+GType nmt_newt_listbox_get_type(void);
+
+typedef enum {
+    NMT_NEWT_LISTBOX_SCROLL = (1 << 0),
+    NMT_NEWT_LISTBOX_BORDER = (1 << 1)
+} NmtNewtListboxFlags;
+
+NmtNewtWidget *nmt_newt_listbox_new(int height, NmtNewtListboxFlags flags);
+
+void nmt_newt_listbox_set_height(NmtNewtListbox *listbox, int height);
+
+void nmt_newt_listbox_append(NmtNewtListbox *listbox, const char *entry, gpointer key);
+void nmt_newt_listbox_clear(NmtNewtListbox *listbox);
+
+void nmt_newt_listbox_set_active(NmtNewtListbox *listbox, int active);
+void nmt_newt_listbox_set_active_key(NmtNewtListbox *listbox, gpointer active_key);
+
+int      nmt_newt_listbox_get_active(NmtNewtListbox *listbox);
+gpointer nmt_newt_listbox_get_active_key(NmtNewtListbox *listbox);
+
+#endif /* NMT_NEWT_LISTBOX_H */
diff --git a/src/libnmt-newt/nmt-newt-popup.c b/src/libnmt-newt/nmt-newt-popup.c
new file mode 100644
index 00000000..1822c9d9
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-popup.c
@@ -0,0 +1,341 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-popup
+ * @short_description: Pop-up menus
+ *
+ * #NmtNewtPopup implements a pop-up menu. When inactive, they appear
+ * the same as #NmtNewtButtons, displaying the label from the
+ * #NmtNewtPopup:active entry. When activated, they pop up a temporary
+ * #NmtNewtForm containing an #NmtNewtListbox to select from.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-popup.h"
+#include "nmt-newt-form.h"
+#include "nmt-newt-hacks.h"
+#include "nmt-newt-listbox.h"
+#include "nmt-newt-utils.h"
+
+G_DEFINE_TYPE(NmtNewtPopup, nmt_newt_popup, NMT_TYPE_NEWT_BUTTON)
+
+#define NMT_NEWT_POPUP_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_POPUP, NmtNewtPopupPrivate))
+
+typedef struct {
+    GArray *entries;
+    int     active;
+} NmtNewtPopupPrivate;
+
+enum {
+    PROP_0,
+    PROP_ACTIVE,
+    PROP_ACTIVE_ID,
+
+    LAST_PROP
+};
+
+/**
+ * NmtNewtPopupEntry:
+ * @label: the user-visible label for the entry
+ * @id: the internal ID of the entry
+ *
+ * A single entry in a pop-up menu.
+ */
+
+/**
+ * nmt_newt_popup_new:
+ * @entries: an array of #NmtNewtPopupEntry, terminated by an
+ *   entry with a %NULL label
+ *
+ * Creates a new #NmtNewtPopup with the given entries.
+ *
+ * Returns: a new #NmtNewtPopup
+ */
+NmtNewtWidget *
+nmt_newt_popup_new(NmtNewtPopupEntry *entries)
+{
+    NmtNewtWidget *      widget;
+    NmtNewtPopupPrivate *priv;
+    int                  i;
+
+    widget = g_object_new(NMT_TYPE_NEWT_POPUP, NULL);
+    priv   = NMT_NEWT_POPUP_GET_PRIVATE(widget);
+
+    for (i = 0; entries[i].label; i++) {
+        NmtNewtPopupEntry entry;
+
+        entry.label = nmt_newt_locale_from_utf8(_(entries[i].label));
+        entry.id    = g_strdup(entries[i].id);
+        g_array_append_val(priv->entries, entry);
+    }
+
+    return widget;
+}
+
+static void
+popup_entry_clear_func(NmtNewtPopupEntry *entry)
+{
+    g_free(entry->label);
+    g_free(entry->id);
+}
+
+static void
+nmt_newt_popup_init(NmtNewtPopup *popup)
+{
+    NmtNewtPopupPrivate *priv = NMT_NEWT_POPUP_GET_PRIVATE(popup);
+
+    priv->entries = g_array_sized_new(FALSE, FALSE, sizeof(NmtNewtPopupEntry), 10);
+    g_array_set_clear_func(priv->entries, (GDestroyNotify) popup_entry_clear_func);
+}
+
+static void
+nmt_newt_popup_finalize(GObject *object)
+{
+    NmtNewtPopupPrivate *priv = NMT_NEWT_POPUP_GET_PRIVATE(object);
+
+    g_array_unref(priv->entries);
+
+    G_OBJECT_CLASS(nmt_newt_popup_parent_class)->finalize(object);
+}
+
+static newtComponent
+nmt_newt_popup_build_component(NmtNewtComponent *component, gboolean sensitive)
+{
+    NmtNewtPopupPrivate *priv    = NMT_NEWT_POPUP_GET_PRIVATE(component);
+    NmtNewtPopupEntry *  entries = (NmtNewtPopupEntry *) priv->entries->data;
+
+    nmt_newt_button_set_label(NMT_NEWT_BUTTON(component), entries[priv->active].label);
+    return NMT_NEWT_COMPONENT_CLASS(nmt_newt_popup_parent_class)
+        ->build_component(component, sensitive);
+}
+
+static void
+nmt_newt_popup_activated(NmtNewtWidget *widget)
+{
+    NmtNewtPopupPrivate *priv    = NMT_NEWT_POPUP_GET_PRIVATE(widget);
+    NmtNewtPopupEntry *  entries = (NmtNewtPopupEntry *) priv->entries->data;
+    NmtNewtForm *        form;
+    NmtNewtWidget *      listbox, *ret;
+    int                  button_x, button_y;
+    int                  window_x, window_y;
+    int                  list_w, list_h;
+    int                  i, active;
+
+    listbox = nmt_newt_listbox_new(priv->entries->len, 0);
+    nmt_newt_widget_set_exit_on_activate(listbox, TRUE);
+    for (i = 0; i < priv->entries->len; i++)
+        nmt_newt_listbox_append(NMT_NEWT_LISTBOX(listbox), entries[i].label, NULL);
+    nmt_newt_listbox_set_active(NMT_NEWT_LISTBOX(listbox), priv->active);
+    nmt_newt_widget_set_padding(listbox, 1, 0, 1, 0);
+
+    nmt_newt_widget_size_request(listbox, &list_w, &list_h);
+
+    g_object_get(nmt_newt_widget_get_form(widget), "x", &window_x, "y", &window_y, NULL);
+    newtComponentGetPosition(nmt_newt_component_get_component(NMT_NEWT_COMPONENT(widget)),
+                             &button_x,
+                             &button_y);
+    /* (window_x + button_x) is the screen X coordinate of the newtComponent. A
+     * newtButton labelled "Foo" is rendered as " <Foo>" (with a preceding
+     * space), so the "F" is at (window_x + button_x + 2). We've added 1 column
+     * of padding to the left of the listbox, so we need to position the popup
+     * at (window_x + button_x + 1) in order for its text to be aligned with the
+     * button's text. (The x and y coordinates given to NmtNewtForm are the
+     * coordinates of the top left of the window content, ignoring the border
+     * graphics.)
+     */
+    window_x += button_x + 1;
+    window_y += button_y - priv->active;
+
+    form = g_object_new(NMT_TYPE_NEWT_FORM,
+                        "x",
+                        window_x,
+                        "y",
+                        window_y,
+                        "width",
+                        list_w,
+                        "height",
+                        list_h,
+                        "padding",
+                        0,
+                        "escape-exits",
+                        TRUE,
+                        NULL);
+    nmt_newt_form_set_content(form, listbox);
+
+    ret = nmt_newt_form_run_sync(form);
+    if (ret == listbox)
+        active = nmt_newt_listbox_get_active(NMT_NEWT_LISTBOX(listbox));
+    else
+        active = priv->active;
+
+    g_object_unref(form);
+
+    if (active != priv->active) {
+        priv->active = active;
+        g_object_notify(G_OBJECT(widget), "active");
+        g_object_notify(G_OBJECT(widget), "active-id");
+        nmt_newt_widget_needs_rebuild(widget);
+    }
+
+    NMT_NEWT_WIDGET_CLASS(nmt_newt_popup_parent_class)->activated(widget);
+}
+
+/**
+ * nmt_newt_popup_get_active:
+ * @popup: a #NmtNewtPopup
+ *
+ * Gets the index of the active entry in @popup.
+ *
+ * Returns: the index of the active entry in @popup.
+ */
+int
+nmt_newt_popup_get_active(NmtNewtPopup *popup)
+{
+    NmtNewtPopupPrivate *priv = NMT_NEWT_POPUP_GET_PRIVATE(popup);
+
+    return priv->active;
+}
+
+/**
+ * nmt_newt_popup_set_active:
+ * @popup: a #NmtNewtPopup
+ * @active: the index of the new active entry
+ *
+ * Sets the active entry in @popup.
+ */
+void
+nmt_newt_popup_set_active(NmtNewtPopup *popup, int active)
+{
+    NmtNewtPopupPrivate *priv = NMT_NEWT_POPUP_GET_PRIVATE(popup);
+
+    active = CLAMP(active, 0, priv->entries->len - 1);
+
+    if (active != priv->active) {
+        priv->active = active;
+        g_object_notify(G_OBJECT(popup), "active");
+        g_object_notify(G_OBJECT(popup), "active-id");
+    }
+}
+
+/**
+ * nmt_newt_popup_get_active_id:
+ * @popup: a #NmtNewtPopup
+ *
+ * Gets the textual ID of the active entry in @popup.
+ *
+ * Returns: the ID of the active entry in @popup.
+ */
+const char *
+nmt_newt_popup_get_active_id(NmtNewtPopup *popup)
+{
+    NmtNewtPopupPrivate *priv    = NMT_NEWT_POPUP_GET_PRIVATE(popup);
+    NmtNewtPopupEntry *  entries = (NmtNewtPopupEntry *) priv->entries->data;
+
+    return entries[priv->active].id;
+}
+
+/**
+ * nmt_newt_popup_set_active_id:
+ * @popup: a #NmtNewtPopup
+ * @active_id: the ID of the new active entry
+ *
+ * Sets the active entry in @popup.
+ */
+void
+nmt_newt_popup_set_active_id(NmtNewtPopup *popup, const char *active_id)
+{
+    NmtNewtPopupPrivate *priv    = NMT_NEWT_POPUP_GET_PRIVATE(popup);
+    NmtNewtPopupEntry *  entries = (NmtNewtPopupEntry *) priv->entries->data;
+    int                  i;
+
+    for (i = 0; i < priv->entries->len; i++) {
+        if (!g_strcmp0(active_id, entries[i].id)) {
+            nmt_newt_popup_set_active(popup, i);
+            return;
+        }
+    }
+}
+
+static void
+nmt_newt_popup_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtNewtPopup *popup = NMT_NEWT_POPUP(object);
+
+    switch (prop_id) {
+    case PROP_ACTIVE:
+        nmt_newt_popup_set_active(popup, g_value_get_uint(value));
+        break;
+    case PROP_ACTIVE_ID:
+        nmt_newt_popup_set_active_id(popup, g_value_get_string(value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_popup_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtPopup *popup = NMT_NEWT_POPUP(object);
+
+    switch (prop_id) {
+    case PROP_ACTIVE:
+        g_value_set_uint(value, nmt_newt_popup_get_active(popup));
+        break;
+    case PROP_ACTIVE_ID:
+        g_value_set_string(value, nmt_newt_popup_get_active_id(popup));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_popup_class_init(NmtNewtPopupClass *popup_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(popup_class);
+    NmtNewtWidgetClass *   widget_class    = NMT_NEWT_WIDGET_CLASS(popup_class);
+    NmtNewtComponentClass *component_class = NMT_NEWT_COMPONENT_CLASS(popup_class);
+
+    g_type_class_add_private(popup_class, sizeof(NmtNewtPopupPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_popup_set_property;
+    object_class->get_property = nmt_newt_popup_get_property;
+    object_class->finalize     = nmt_newt_popup_finalize;
+
+    widget_class->activated = nmt_newt_popup_activated;
+
+    component_class->build_component = nmt_newt_popup_build_component;
+
+    /**
+     * NmtNewtPopup:active:
+     *
+     * The index of the currently-active entry.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_ACTIVE,
+                                    g_param_spec_uint("active",
+                                                      "",
+                                                      "",
+                                                      0,
+                                                      G_MAXUINT,
+                                                      0,
+                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtPopup:active-id:
+     *
+     * The textual ID of the currently-active entry.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_ACTIVE_ID,
+        g_param_spec_string("active-id", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-popup.h b/src/libnmt-newt/nmt-newt-popup.h
new file mode 100644
index 00000000..15e247b4
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-popup.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_POPUP_H
+#define NMT_NEWT_POPUP_H
+
+#include "nmt-newt-button.h"
+
+#define NMT_TYPE_NEWT_POPUP (nmt_newt_popup_get_type())
+#define NMT_NEWT_POPUP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_POPUP, NmtNewtPopup))
+#define NMT_NEWT_POPUP_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_POPUP, NmtNewtPopupClass))
+#define NMT_IS_NEWT_POPUP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_POPUP))
+#define NMT_IS_NEWT_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_POPUP))
+#define NMT_NEWT_POPUP_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_POPUP, NmtNewtPopupClass))
+
+struct _NmtNewtPopup {
+    NmtNewtButton parent;
+};
+
+typedef struct {
+    NmtNewtButtonClass parent;
+
+} NmtNewtPopupClass;
+
+GType nmt_newt_popup_get_type(void);
+
+typedef struct {
+    char *label;
+    char *id;
+} NmtNewtPopupEntry;
+
+NmtNewtWidget *nmt_newt_popup_new(NmtNewtPopupEntry *entries);
+
+int  nmt_newt_popup_get_active(NmtNewtPopup *popup);
+void nmt_newt_popup_set_active(NmtNewtPopup *popup, int active);
+
+const char *nmt_newt_popup_get_active_id(NmtNewtPopup *popup);
+void        nmt_newt_popup_set_active_id(NmtNewtPopup *popup, const char *active_id);
+
+#endif /* NMT_NEWT_POPUP_H */
diff --git a/src/libnmt-newt/nmt-newt-section.c b/src/libnmt-newt/nmt-newt-section.c
new file mode 100644
index 00000000..fa73ea4a
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-section.c
@@ -0,0 +1,422 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-section
+ * @short_description: A collapsible section
+ *
+ * #NmtNewtSection is a container with two children; the header and
+ * the body. The header is always visible, but the body is only
+ * visible when the container is #NmtNewtSection:open.
+ *
+ * Note that there is no default way to open and close an
+ * #NmtNewtSection. You need to implement this yourself. (Eg, by
+ * binding the #NmtToggleButton:active property of an #NmtToggleButton
+ * in the section's header to the section's #NmtNewtSection:open
+ * property.)
+ *
+ * In addition to the header and body, the #NmtNewtSection also
+ * optionally draws a border along the left side, indicating the
+ * extent of the section.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-section.h"
+
+#include "nmt-newt-grid.h"
+#include "nmt-newt-label.h"
+#include "nmt-newt-utils.h"
+
+G_DEFINE_TYPE(NmtNewtSection, nmt_newt_section, NMT_TYPE_NEWT_CONTAINER)
+
+#define NMT_NEWT_SECTION_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_SECTION, NmtNewtSectionPrivate))
+
+typedef struct {
+    NmtNewtWidget *header;
+    int            hheight_req, hwidth_req;
+
+    NmtNewtWidget *body;
+    int            bheight_req, bwidth_req;
+
+    gboolean       show_border;
+    NmtNewtWidget *border_grid;
+    NmtNewtWidget *border_open_label;
+    NmtNewtWidget *border_closed_label;
+    NmtNewtWidget *border_end_label;
+    GPtrArray *    border_line_labels;
+
+    gboolean open;
+} NmtNewtSectionPrivate;
+
+static char *closed_glyph, *open_glyph, *line_glyph, *end_glyph;
+
+enum {
+    PROP_0,
+
+    PROP_SHOW_BORDER,
+    PROP_OPEN,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_newt_section_new:
+ * @show_border: whether to show the border on the side of the section
+ *
+ * Creates a new #NmtNewtSection
+ *
+ * Returns: a new #NmtNewtSection
+ */
+NmtNewtWidget *
+nmt_newt_section_new(gboolean show_border)
+{
+    return g_object_new(NMT_TYPE_NEWT_SECTION, "show-border", show_border, NULL);
+}
+
+static void
+nmt_newt_section_init(NmtNewtSection *section)
+{
+    NmtNewtSectionPrivate *priv         = NMT_NEWT_SECTION_GET_PRIVATE(section);
+    NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS(nmt_newt_section_parent_class);
+
+    priv->show_border = TRUE;
+
+    priv->border_grid = nmt_newt_grid_new();
+    parent_class->add(NMT_NEWT_CONTAINER(section), priv->border_grid);
+
+    priv->border_open_label = nmt_newt_label_new(open_glyph);
+    nmt_newt_widget_set_visible(priv->border_open_label, FALSE);
+    nmt_newt_grid_add(NMT_NEWT_GRID(priv->border_grid), priv->border_open_label, 0, 0);
+
+    priv->border_closed_label = nmt_newt_label_new(closed_glyph);
+    nmt_newt_grid_add(NMT_NEWT_GRID(priv->border_grid), priv->border_closed_label, 0, 0);
+
+    priv->border_end_label = nmt_newt_label_new(end_glyph);
+    nmt_newt_widget_set_visible(priv->border_open_label, FALSE);
+    nmt_newt_grid_add(NMT_NEWT_GRID(priv->border_grid), priv->border_end_label, 0, 1);
+
+    priv->border_line_labels = g_ptr_array_new();
+}
+
+static void
+nmt_newt_section_finalize(GObject *object)
+{
+    NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE(object);
+
+    g_ptr_array_unref(priv->border_line_labels);
+
+    G_OBJECT_CLASS(nmt_newt_section_parent_class)->finalize(object);
+}
+
+/**
+ * nmt_newt_section_set_header:
+ * @section: an #NmtNewtSection
+ * @header: the header widget
+ *
+ * Sets @section's header widget.
+ */
+void
+nmt_newt_section_set_header(NmtNewtSection *section, NmtNewtWidget *header)
+{
+    NmtNewtSectionPrivate *priv         = NMT_NEWT_SECTION_GET_PRIVATE(section);
+    NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS(nmt_newt_section_parent_class);
+    NmtNewtContainer *     container    = NMT_NEWT_CONTAINER(section);
+
+    if (priv->header)
+        parent_class->remove(container, priv->header);
+    priv->header = header;
+    parent_class->add(container, header);
+}
+
+/**
+ * nmt_newt_section_get_header:
+ * @section: an #NmtNewtSection
+ *
+ * Gets @section's header widget.
+ *
+ * Returns: @section's header widget.
+ */
+NmtNewtWidget *
+nmt_newt_section_get_header(NmtNewtSection *section)
+{
+    NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE(section);
+
+    return priv->header;
+}
+
+/**
+ * nmt_newt_section_set_body:
+ * @section: an #NmtNewtSection
+ * @body: the body widget
+ *
+ * Sets @section's body widget.
+ */
+void
+nmt_newt_section_set_body(NmtNewtSection *section, NmtNewtWidget *body)
+{
+    NmtNewtSectionPrivate *priv         = NMT_NEWT_SECTION_GET_PRIVATE(section);
+    NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS(nmt_newt_section_parent_class);
+    NmtNewtContainer *     container    = NMT_NEWT_CONTAINER(section);
+
+    if (priv->body)
+        parent_class->remove(container, priv->body);
+    priv->body = body;
+    parent_class->add(container, body);
+}
+
+/**
+ * nmt_newt_section_get_body:
+ * @section: an #NmtNewtSection
+ *
+ * Gets @section's body widget.
+ *
+ * Returns: @section's body widget.
+ */
+NmtNewtWidget *
+nmt_newt_section_get_body(NmtNewtSection *section)
+{
+    NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE(section);
+
+    return priv->body;
+}
+
+static void
+nmt_newt_section_remove(NmtNewtContainer *container, NmtNewtWidget *widget)
+{
+    NmtNewtSection *       section      = NMT_NEWT_SECTION(container);
+    NmtNewtSectionPrivate *priv         = NMT_NEWT_SECTION_GET_PRIVATE(section);
+    NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS(nmt_newt_section_parent_class);
+
+    if (widget == priv->header)
+        priv->header = NULL;
+    else if (widget == priv->body)
+        priv->body = NULL;
+    else if (widget == priv->border_grid)
+        priv->border_grid = NULL;
+
+    parent_class->remove(container, widget);
+}
+
+static newtComponent *
+nmt_newt_section_get_components(NmtNewtWidget *widget)
+{
+    NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE(widget);
+    newtComponent *        child_cos;
+    GPtrArray *            cos;
+    int                    i;
+
+    g_return_val_if_fail(priv->header != NULL && priv->body != NULL, NULL);
+
+    cos = g_ptr_array_new();
+
+    if (priv->show_border) {
+        child_cos = nmt_newt_widget_get_components(priv->border_grid);
+        for (i = 0; child_cos[i]; i++)
+            g_ptr_array_add(cos, child_cos[i]);
+        g_free(child_cos);
+    }
+
+    child_cos = nmt_newt_widget_get_components(priv->header);
+    for (i = 0; child_cos[i]; i++)
+        g_ptr_array_add(cos, child_cos[i]);
+    g_free(child_cos);
+
+    if (priv->open) {
+        child_cos = nmt_newt_widget_get_components(priv->body);
+        for (i = 0; child_cos[i]; i++)
+            g_ptr_array_add(cos, child_cos[i]);
+        g_free(child_cos);
+    }
+
+    g_ptr_array_add(cos, NULL);
+    return (newtComponent *) g_ptr_array_free(cos, FALSE);
+}
+
+static void
+nmt_newt_section_size_request(NmtNewtWidget *widget, int *width, int *height)
+{
+    NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE(widget);
+    int                    w_ignore, h_ignore;
+
+    g_return_if_fail(priv->header != NULL && priv->body != NULL);
+
+    if (priv->show_border)
+        nmt_newt_widget_size_request(priv->border_grid, &w_ignore, &h_ignore);
+    nmt_newt_widget_size_request(priv->header, &priv->hwidth_req, &priv->hheight_req);
+    nmt_newt_widget_size_request(priv->body, &priv->bwidth_req, &priv->bheight_req);
+
+    *width = MAX(priv->hwidth_req, priv->bwidth_req) + 2;
+    if (priv->open)
+        *height = priv->hheight_req + priv->bheight_req + (priv->show_border ? 1 : 0);
+    else
+        *height = priv->hheight_req;
+}
+
+static void
+adjust_border_for_allocation(NmtNewtSectionPrivate *priv, int height)
+{
+    int i;
+
+    /* We have to use a series of one-line labels rather than a multi-line
+     * textbox, because newt will hide any component that's partially offscreen,
+     * but we want the on-screen portion of the border to show even if part of
+     * it is offscreen.
+     */
+
+    if (height == 1) {
+        nmt_newt_widget_set_visible(priv->border_closed_label, TRUE);
+        nmt_newt_widget_set_visible(priv->border_open_label, FALSE);
+        for (i = 0; i < priv->border_line_labels->len; i++)
+            nmt_newt_widget_set_visible(priv->border_line_labels->pdata[i], FALSE);
+        nmt_newt_widget_set_visible(priv->border_end_label, FALSE);
+    } else {
+        nmt_newt_widget_set_visible(priv->border_closed_label, FALSE);
+        nmt_newt_widget_set_visible(priv->border_open_label, TRUE);
+        for (i = 0; i < height - 2; i++) {
+            if (i >= priv->border_line_labels->len) {
+                NmtNewtWidget *label;
+
+                label = nmt_newt_label_new(line_glyph);
+                g_ptr_array_add(priv->border_line_labels, label);
+                nmt_newt_grid_add(NMT_NEWT_GRID(priv->border_grid), label, 0, i + 1);
+            } else
+                nmt_newt_widget_set_visible(priv->border_line_labels->pdata[i], TRUE);
+        }
+        nmt_newt_widget_set_visible(priv->border_end_label, TRUE);
+        nmt_newt_grid_move(NMT_NEWT_GRID(priv->border_grid), priv->border_end_label, 0, height - 1);
+    }
+}
+
+static void
+nmt_newt_section_size_allocate(NmtNewtWidget *widget, int x, int y, int width, int height)
+{
+    NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE(widget);
+
+    if (priv->show_border) {
+        int w_ignore, h_ignore;
+
+        adjust_border_for_allocation(priv, height);
+        nmt_newt_widget_size_request(priv->border_grid, &w_ignore, &h_ignore);
+        nmt_newt_widget_size_allocate(priv->border_grid, x, y, 1, height);
+        nmt_newt_widget_size_allocate(priv->header, x + 2, y, width, priv->hheight_req);
+    } else
+        nmt_newt_widget_size_allocate(priv->header, x, y, width, priv->hheight_req);
+
+    if (priv->open) {
+        nmt_newt_widget_size_allocate(priv->body,
+                                      x + 2,
+                                      y + priv->hheight_req,
+                                      width,
+                                      height - priv->hheight_req);
+    }
+}
+
+static void
+nmt_newt_section_set_property(GObject *     object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec *  pspec)
+{
+    NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_SHOW_BORDER:
+        priv->show_border = g_value_get_boolean(value);
+        nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(object));
+        break;
+    case PROP_OPEN:
+        priv->open = g_value_get_boolean(value);
+        nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(object));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_section_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtSectionPrivate *priv = NMT_NEWT_SECTION_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_SHOW_BORDER:
+        g_value_set_boolean(value, priv->show_border);
+        break;
+    case PROP_OPEN:
+        g_value_set_boolean(value, priv->open);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_section_class_init(NmtNewtSectionClass *section_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(section_class);
+    NmtNewtWidgetClass *   widget_class    = NMT_NEWT_WIDGET_CLASS(section_class);
+    NmtNewtContainerClass *container_class = NMT_NEWT_CONTAINER_CLASS(section_class);
+
+    g_type_class_add_private(section_class, sizeof(NmtNewtSectionPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_section_set_property;
+    object_class->get_property = nmt_newt_section_get_property;
+    object_class->finalize     = nmt_newt_section_finalize;
+
+    widget_class->get_components = nmt_newt_section_get_components;
+    widget_class->size_request   = nmt_newt_section_size_request;
+    widget_class->size_allocate  = nmt_newt_section_size_allocate;
+
+    container_class->remove = nmt_newt_section_remove;
+
+    /* properties */
+
+    /**
+     * NmtNewtSection:show-border:
+     *
+     * %TRUE if the section should show a border along the left side.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_SHOW_BORDER,
+        g_param_spec_boolean("show-border",
+                             "",
+                             "",
+                             TRUE,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * NmtNewtSection:open:
+     *
+     * %TRUE if the section is open (ie, its body is visible), %FALSE
+     * if not.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_OPEN,
+        g_param_spec_boolean("open", "", "", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+    /* globals */
+    closed_glyph = nmt_newt_locale_from_utf8("\342\225\220"); /* ═ */
+    open_glyph   = nmt_newt_locale_from_utf8("\342\225\244"); /* ╤ */
+    line_glyph   = nmt_newt_locale_from_utf8("\342\224\202"); /* │ */
+    end_glyph    = nmt_newt_locale_from_utf8("\342\224\224"); /* └ */
+    if (!*closed_glyph || !*open_glyph || !*line_glyph || !*end_glyph) {
+        g_free(closed_glyph);
+        g_free(open_glyph);
+        g_free(line_glyph);
+        g_free(end_glyph);
+
+        closed_glyph = g_strdup("-");
+        open_glyph   = g_strdup("+");
+        line_glyph   = g_strdup("|");
+        end_glyph    = g_strdup("\\");
+    }
+}
diff --git a/src/libnmt-newt/nmt-newt-section.h b/src/libnmt-newt/nmt-newt-section.h
new file mode 100644
index 00000000..c3b9f909
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-section.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_SECTION_H
+#define NMT_NEWT_SECTION_H
+
+#include "nmt-newt-container.h"
+
+#define NMT_TYPE_NEWT_SECTION (nmt_newt_section_get_type())
+#define NMT_NEWT_SECTION(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_SECTION, NmtNewtSection))
+#define NMT_NEWT_SECTION_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_SECTION, NmtNewtSectionClass))
+#define NMT_IS_NEWT_SECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_SECTION))
+#define NMT_IS_NEWT_SECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_SECTION))
+#define NMT_NEWT_SECTION_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_SECTION, NmtNewtSectionClass))
+
+struct _NmtNewtSection {
+    NmtNewtContainer parent;
+};
+
+typedef struct {
+    NmtNewtContainerClass parent;
+
+} NmtNewtSectionClass;
+
+GType nmt_newt_section_get_type(void);
+
+NmtNewtWidget *nmt_newt_section_new(gboolean show_border);
+
+void           nmt_newt_section_set_header(NmtNewtSection *section, NmtNewtWidget *header);
+NmtNewtWidget *nmt_newt_section_get_header(NmtNewtSection *section);
+
+void           nmt_newt_section_set_body(NmtNewtSection *section, NmtNewtWidget *body);
+NmtNewtWidget *nmt_newt_section_get_body(NmtNewtSection *section);
+
+#endif /* NMT_NEWT_SECTION_H */
diff --git a/src/libnmt-newt/nmt-newt-separator.c b/src/libnmt-newt/nmt-newt-separator.c
new file mode 100644
index 00000000..70c505ca
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-separator.c
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-separator
+ * @short_description: Separator
+ *
+ * #NmtNewtSeparator is just a blank label, which is used in a few places
+ * where a widget is needed but none is desired, or to add blank space
+ * between widgets in containers that don't implement padding.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-separator.h"
+
+G_DEFINE_TYPE(NmtNewtSeparator, nmt_newt_separator, NMT_TYPE_NEWT_COMPONENT)
+
+/**
+ * nmt_newt_separator_new:
+ *
+ * Creates a new #NmtNewtSeparator.
+ *
+ * Returns: a new #NmtNewtSeparator
+ */
+NmtNewtWidget *
+nmt_newt_separator_new(void)
+{
+    return g_object_new(NMT_TYPE_NEWT_SEPARATOR, NULL);
+}
+
+static void
+nmt_newt_separator_init(NmtNewtSeparator *separator)
+{}
+
+static newtComponent
+nmt_newt_separator_build_component(NmtNewtComponent *component, gboolean sensitive)
+{
+    return newtLabel(-1, -1, " ");
+}
+
+static void
+nmt_newt_separator_class_init(NmtNewtSeparatorClass *separator_class)
+{
+    NmtNewtComponentClass *component_class = NMT_NEWT_COMPONENT_CLASS(separator_class);
+
+    /* virtual methods */
+    component_class->build_component = nmt_newt_separator_build_component;
+}
diff --git a/src/libnmt-newt/nmt-newt-separator.h b/src/libnmt-newt/nmt-newt-separator.h
new file mode 100644
index 00000000..0aa0fd0e
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-separator.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_SEPARATOR_H
+#define NMT_NEWT_SEPARATOR_H
+
+#include "nmt-newt-component.h"
+
+#define NMT_TYPE_NEWT_SEPARATOR (nmt_newt_separator_get_type())
+#define NMT_NEWT_SEPARATOR(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_SEPARATOR, NmtNewtSeparator))
+#define NMT_NEWT_SEPARATOR_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_SEPARATOR, NmtNewtSeparatorClass))
+#define NMT_IS_NEWT_SEPARATOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_SEPARATOR))
+#define NMT_IS_NEWT_SEPARATOR_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_SEPARATOR))
+#define NMT_NEWT_SEPARATOR_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_SEPARATOR, NmtNewtSeparatorClass))
+
+struct _NmtNewtSeparator {
+    NmtNewtComponent parent;
+};
+
+typedef struct {
+    NmtNewtComponentClass parent;
+
+} NmtNewtSeparatorClass;
+
+GType nmt_newt_separator_get_type(void);
+
+NmtNewtWidget *nmt_newt_separator_new(void);
+
+#endif /* NMT_NEWT_SEPARATOR_H */
diff --git a/src/libnmt-newt/nmt-newt-stack.c b/src/libnmt-newt/nmt-newt-stack.c
new file mode 100644
index 00000000..e0fdb3b9
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-stack.c
@@ -0,0 +1,337 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-stack
+ * @short_description: A stack of alternative widgets
+ *
+ * #NmtNewtStack implements a stack of widgets, only one of which is
+ * visible at any time.
+ *
+ * The height and width of the widget is determined only by its
+ * visible child. Likewise, the widget's #NmtNewtWidget:valid is
+ * determined only by the validity of its visible child, not its other
+ * children.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-stack.h"
+
+G_DEFINE_TYPE(NmtNewtStack, nmt_newt_stack, NMT_TYPE_NEWT_CONTAINER)
+
+#define NMT_NEWT_STACK_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_STACK, NmtNewtStackPrivate))
+
+typedef struct {
+    GPtrArray *children;
+    GPtrArray *ids;
+
+    guint active;
+} NmtNewtStackPrivate;
+
+enum {
+    PROP_0,
+    PROP_ACTIVE,
+    PROP_ACTIVE_ID,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_newt_stack_new:
+ *
+ * Creates a new #NmtNewtStack
+ *
+ * Returns: a new #NmtNewtStack
+ */
+NmtNewtWidget *
+nmt_newt_stack_new(void)
+{
+    return g_object_new(NMT_TYPE_NEWT_STACK, NULL);
+}
+
+static void
+nmt_newt_stack_init(NmtNewtStack *stack)
+{
+    NmtNewtStackPrivate *priv = NMT_NEWT_STACK_GET_PRIVATE(stack);
+
+    priv->children = g_ptr_array_new();
+    priv->ids      = g_ptr_array_new_with_free_func(g_free);
+}
+
+static void
+nmt_newt_stack_finalize(GObject *object)
+{
+    NmtNewtStackPrivate *priv = NMT_NEWT_STACK_GET_PRIVATE(object);
+
+    g_ptr_array_unref(priv->children);
+    g_ptr_array_unref(priv->ids);
+
+    G_OBJECT_CLASS(nmt_newt_stack_parent_class)->finalize(object);
+}
+
+static newtComponent *
+nmt_newt_stack_get_components(NmtNewtWidget *widget)
+{
+    NmtNewtStackPrivate *priv = NMT_NEWT_STACK_GET_PRIVATE(widget);
+
+    if (priv->active > priv->children->len)
+        return NULL;
+
+    return nmt_newt_widget_get_components(priv->children->pdata[priv->active]);
+}
+
+static void
+nmt_newt_stack_size_request(NmtNewtWidget *widget, int *width, int *height)
+{
+    NmtNewtStack *       stack = NMT_NEWT_STACK(widget);
+    NmtNewtStackPrivate *priv  = NMT_NEWT_STACK_GET_PRIVATE(stack);
+    int                  i, child_width, child_height;
+
+    if (priv->active > priv->children->len) {
+        *width = *height = 0;
+        return;
+    }
+
+    /* We size-request all pages so that embedded NmtPageGrids will
+     * participate in their size-grouping (so that switching pages
+     * won't result in the column widths changing).
+     */
+    for (i = 0; i < priv->children->len; i++) {
+        nmt_newt_widget_size_request(priv->children->pdata[i], &child_width, &child_height);
+        if (i == priv->active) {
+            *width  = child_width;
+            *height = child_height;
+        }
+    }
+}
+
+static void
+nmt_newt_stack_size_allocate(NmtNewtWidget *widget, int x, int y, int width, int height)
+{
+    NmtNewtStackPrivate *priv = NMT_NEWT_STACK_GET_PRIVATE(widget);
+
+    if (priv->active > priv->children->len)
+        return;
+
+    nmt_newt_widget_size_allocate(priv->children->pdata[priv->active], x, y, width, height);
+}
+
+/**
+ * nmt_newt_stack_add:
+ * @stack: an #NmtNewtStack
+ * @id: the ID for the new page
+ * @widget: the widget to add
+ *
+ * Adds @widget to @stack with the given @id.
+ */
+void
+nmt_newt_stack_add(NmtNewtStack *stack, const char *id, NmtNewtWidget *widget)
+{
+    NmtNewtStackPrivate *priv = NMT_NEWT_STACK_GET_PRIVATE(stack);
+
+    g_ptr_array_add(priv->children, widget);
+    g_ptr_array_add(priv->ids, g_strdup(id));
+
+    NMT_NEWT_CONTAINER_CLASS(nmt_newt_stack_parent_class)->add(NMT_NEWT_CONTAINER(stack), widget);
+}
+
+static void
+nmt_newt_stack_remove(NmtNewtContainer *container, NmtNewtWidget *widget)
+{
+    NmtNewtStack *       stack = NMT_NEWT_STACK(container);
+    NmtNewtStackPrivate *priv  = NMT_NEWT_STACK_GET_PRIVATE(stack);
+    int                  i;
+
+    NMT_NEWT_CONTAINER_CLASS(nmt_newt_stack_parent_class)->remove(container, widget);
+
+    for (i = 0; i < priv->children->len; i++) {
+        if (priv->children->pdata[i] == widget) {
+            g_ptr_array_remove_index(priv->children, i);
+            g_ptr_array_remove_index(priv->ids, i);
+            return;
+        }
+    }
+}
+
+static void
+nmt_newt_stack_child_validity_changed(NmtNewtContainer *container, NmtNewtWidget *widget)
+{
+    NmtNewtStackPrivate *priv = NMT_NEWT_STACK_GET_PRIVATE(container);
+
+    if (priv->active > priv->children->len)
+        return;
+
+    if (priv->children->pdata[priv->active] == (gpointer) widget) {
+        NMT_NEWT_CONTAINER_CLASS(nmt_newt_stack_parent_class)
+            ->child_validity_changed(container, widget);
+    }
+}
+
+/**
+ * nmt_newt_stack_set_active:
+ * @stack: an #NmtNewtStack
+ * @active: the index of the new active page
+ *
+ * Sets the active page on @stack to @active.
+ */
+void
+nmt_newt_stack_set_active(NmtNewtStack *stack, guint active)
+{
+    NmtNewtStackPrivate *priv = NMT_NEWT_STACK_GET_PRIVATE(stack);
+
+    if (priv->active == active)
+        return;
+
+    priv->active = active;
+    g_object_notify(G_OBJECT(stack), "active");
+    g_object_notify(G_OBJECT(stack), "active-id");
+    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(stack));
+}
+
+/**
+ * nmt_newt_stack_get_active:
+ * @stack: an #NmtNewtStack
+ *
+ * Gets the index of the active page on @stack
+ *
+ * Returns: the index of the active page on @stack
+ */
+guint
+nmt_newt_stack_get_active(NmtNewtStack *stack)
+{
+    NmtNewtStackPrivate *priv = NMT_NEWT_STACK_GET_PRIVATE(stack);
+
+    return priv->active;
+}
+
+/**
+ * nmt_newt_stack_set_active_id:
+ * @stack: an #NmtNewtStack
+ * @active_id: the ID of the new active page
+ *
+ * Sets the active page on @stack to @active_id.
+ */
+void
+nmt_newt_stack_set_active_id(NmtNewtStack *stack, const char *id)
+{
+    NmtNewtStackPrivate *priv = NMT_NEWT_STACK_GET_PRIVATE(stack);
+    int                  i;
+
+    if (!g_strcmp0(priv->ids->pdata[priv->active], id))
+        return;
+
+    for (i = 0; i < priv->ids->len; i++) {
+        if (!g_strcmp0(priv->ids->pdata[i], id)) {
+            priv->active = i;
+            g_object_notify(G_OBJECT(stack), "active");
+            g_object_notify(G_OBJECT(stack), "active-id");
+            nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(stack));
+            return;
+        }
+    }
+}
+
+/**
+ * nmt_newt_stack_get_active_id:
+ * @stack: an #NmtNewtStack
+ *
+ * Gets the ID of the active page on @stack
+ *
+ * Returns: the ID of the active page on @stack
+ */
+const char *
+nmt_newt_stack_get_active_id(NmtNewtStack *stack)
+{
+    NmtNewtStackPrivate *priv = NMT_NEWT_STACK_GET_PRIVATE(stack);
+
+    if (priv->active > priv->children->len)
+        return NULL;
+
+    return priv->ids->pdata[priv->active];
+}
+
+static void
+nmt_newt_stack_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtNewtStack *stack = NMT_NEWT_STACK(object);
+
+    switch (prop_id) {
+    case PROP_ACTIVE:
+        nmt_newt_stack_set_active(stack, g_value_get_uint(value));
+        break;
+    case PROP_ACTIVE_ID:
+        nmt_newt_stack_set_active_id(stack, g_value_get_string(value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_stack_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtStack *stack = NMT_NEWT_STACK(object);
+
+    switch (prop_id) {
+    case PROP_ACTIVE:
+        g_value_set_uint(value, nmt_newt_stack_get_active(stack));
+        break;
+    case PROP_ACTIVE_ID:
+        g_value_set_string(value, nmt_newt_stack_get_active_id(stack));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_stack_class_init(NmtNewtStackClass *stack_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(stack_class);
+    NmtNewtWidgetClass *   widget_class    = NMT_NEWT_WIDGET_CLASS(stack_class);
+    NmtNewtContainerClass *container_class = NMT_NEWT_CONTAINER_CLASS(stack_class);
+
+    g_type_class_add_private(stack_class, sizeof(NmtNewtStackPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_stack_set_property;
+    object_class->get_property = nmt_newt_stack_get_property;
+    object_class->finalize     = nmt_newt_stack_finalize;
+
+    widget_class->get_components = nmt_newt_stack_get_components;
+    widget_class->size_request   = nmt_newt_stack_size_request;
+    widget_class->size_allocate  = nmt_newt_stack_size_allocate;
+
+    container_class->remove                 = nmt_newt_stack_remove;
+    container_class->child_validity_changed = nmt_newt_stack_child_validity_changed;
+
+    /**
+     * NmtNewtStack:active:
+     *
+     * The index of the active page
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_ACTIVE,
+                                    g_param_spec_uint("active",
+                                                      "",
+                                                      "",
+                                                      0,
+                                                      G_MAXUINT,
+                                                      0,
+                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtStack:active-id:
+     *
+     * The ID of the active page
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_ACTIVE_ID,
+        g_param_spec_string("active-id", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-stack.h b/src/libnmt-newt/nmt-newt-stack.h
new file mode 100644
index 00000000..ca6ad203
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-stack.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_STACK_H
+#define NMT_NEWT_STACK_H
+
+#include "nmt-newt-container.h"
+
+#define NMT_TYPE_NEWT_STACK (nmt_newt_stack_get_type())
+#define NMT_NEWT_STACK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_STACK, NmtNewtStack))
+#define NMT_NEWT_STACK_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_STACK, NmtNewtStackClass))
+#define NMT_IS_NEWT_STACK(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_STACK))
+#define NMT_IS_NEWT_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_STACK))
+#define NMT_NEWT_STACK_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_STACK, NmtNewtStackClass))
+
+struct _NmtNewtStack {
+    NmtNewtContainer parent;
+};
+
+typedef struct {
+    NmtNewtContainerClass parent;
+
+} NmtNewtStackClass;
+
+GType nmt_newt_stack_get_type(void);
+
+NmtNewtWidget *nmt_newt_stack_new(void);
+
+void nmt_newt_stack_add(NmtNewtStack *stack, const char *id, NmtNewtWidget *widget);
+
+void  nmt_newt_stack_set_active(NmtNewtStack *stack, guint active);
+guint nmt_newt_stack_get_active(NmtNewtStack *stack);
+
+void        nmt_newt_stack_set_active_id(NmtNewtStack *stack, const char *id);
+const char *nmt_newt_stack_get_active_id(NmtNewtStack *stack);
+
+#endif /* NMT_NEWT_STACK_H */
diff --git a/src/libnmt-newt/nmt-newt-textbox.c b/src/libnmt-newt/nmt-newt-textbox.c
new file mode 100644
index 00000000..128e539b
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-textbox.c
@@ -0,0 +1,279 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-textbox
+ * @short_description: Multi-line text box
+ *
+ * #NmtNewtTextbox implements a multi-line text, optionally with
+ * word-wrapping.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-textbox.h"
+
+#include "nmt-newt-utils.h"
+
+G_DEFINE_TYPE(NmtNewtTextbox, nmt_newt_textbox, NMT_TYPE_NEWT_COMPONENT)
+
+#define NMT_NEWT_TEXTBOX_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_TEXTBOX, NmtNewtTextboxPrivate))
+
+typedef struct {
+    int                 wrap_width;
+    NmtNewtTextboxFlags flags;
+
+    char *text;
+    int   width, height;
+} NmtNewtTextboxPrivate;
+
+enum {
+    PROP_0,
+    PROP_TEXT,
+    PROP_FLAGS,
+    PROP_WRAP_WIDTH,
+
+    LAST_PROP
+};
+
+/**
+ * NmtNewtTextboxFlags:
+ * @NMT_NEWT_TEXTBOX_SCROLLABLE: the textbox should be scrollable.
+ * @NMT_NEWT_TEXTBOX_SET_BACKGROUND: the textbox should have a
+ *   white background
+ *
+ * Flags for an #NmtNewtTextbox
+ */
+
+/**
+ * nmt_newt_textbox_new:
+ * @flags: the textbox's flags
+ * @wrap_width: width in characters at which to word-wrap, or
+ *   0 to not wrap.
+ *
+ * Creates a new #NmtNewtTextbox.
+ *
+ * Returns: a new #NmtNewtTextbox
+ */
+NmtNewtWidget *
+nmt_newt_textbox_new(NmtNewtTextboxFlags flags, int wrap_width)
+{
+    return g_object_new(NMT_TYPE_NEWT_TEXTBOX, "flags", flags, "wrap-width", wrap_width, NULL);
+}
+
+/**
+ * nmt_newt_textbox_get_text:
+ * @textbox: an #NmtNewtTextbox
+ *
+ * Gets @textbox's text
+ *
+ * Returns: @textbox's text
+ */
+void
+nmt_newt_textbox_set_text(NmtNewtTextbox *textbox, const char *text)
+{
+    NmtNewtTextboxPrivate *priv = NMT_NEWT_TEXTBOX_GET_PRIVATE(textbox);
+    char **                lines;
+    int                    i, width;
+
+    if (!text)
+        text = "";
+    if (!strcmp(priv->text, text))
+        return;
+
+    g_free(priv->text);
+    priv->text = g_strdup(text);
+
+    priv->width = priv->height = 0;
+    lines                      = g_strsplit(priv->text, "\n", -1);
+    for (i = 0; lines[i]; i++) {
+        width = nmt_newt_text_width(lines[i]);
+        if (width > priv->width)
+            priv->width = width;
+    }
+    g_free(lines);
+    priv->height = MIN(i, 1);
+
+    g_object_notify(G_OBJECT(textbox), "text");
+    nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(textbox));
+}
+
+/**
+ * nmt_newt_textbox_get_text:
+ * @textbox: an #NmtNewtTextbox
+ *
+ * Gets @textbox's text
+ *
+ * Returns: @textbox's text
+ */
+const char *
+nmt_newt_textbox_get_text(NmtNewtTextbox *textbox)
+{
+    NmtNewtTextboxPrivate *priv = NMT_NEWT_TEXTBOX_GET_PRIVATE(textbox);
+
+    return priv->text;
+}
+
+static void
+nmt_newt_textbox_init(NmtNewtTextbox *textbox)
+{
+    NmtNewtTextboxPrivate *priv = NMT_NEWT_TEXTBOX_GET_PRIVATE(textbox);
+
+    priv->text = g_strdup("");
+}
+
+static void
+nmt_newt_textbox_finalize(GObject *object)
+{
+    NmtNewtTextboxPrivate *priv = NMT_NEWT_TEXTBOX_GET_PRIVATE(object);
+
+    g_free(priv->text);
+
+    G_OBJECT_CLASS(nmt_newt_textbox_parent_class)->finalize(object);
+}
+
+static guint
+convert_flags(NmtNewtTextboxFlags flags)
+{
+    guint newt_flags = 0;
+
+    if (flags & NMT_NEWT_TEXTBOX_SCROLLABLE)
+        newt_flags |= NEWT_FLAG_SCROLL;
+
+    return newt_flags;
+}
+
+static newtComponent
+nmt_newt_textbox_build_component(NmtNewtComponent *component, gboolean sensitive)
+{
+    NmtNewtTextboxPrivate *priv = NMT_NEWT_TEXTBOX_GET_PRIVATE(component);
+    newtComponent          co;
+    const char *           text;
+    char *                 text_lc;
+
+    text = priv->text;
+    if (!*text)
+        text = "\n";
+
+    text_lc = nmt_newt_locale_from_utf8(text);
+    if (priv->wrap_width > 0) {
+        co = newtTextboxReflowed(-1, -1, text_lc, priv->wrap_width, 0, 0, 0);
+    } else {
+        co = newtTextbox(-1, -1, priv->width, priv->height, convert_flags(priv->flags));
+        newtTextboxSetText(co, text_lc);
+    }
+    g_free(text_lc);
+
+    if (priv->flags & NMT_NEWT_TEXTBOX_SET_BACKGROUND)
+        newtTextboxSetColors(co,
+                             NMT_NEWT_COLORSET_TEXTBOX_WITH_BACKGROUND,
+                             NEWT_COLORSET_ACTTEXTBOX);
+
+    return co;
+}
+
+static void
+nmt_newt_textbox_set_property(GObject *     object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec *  pspec)
+{
+    NmtNewtTextbox *       textbox = NMT_NEWT_TEXTBOX(object);
+    NmtNewtTextboxPrivate *priv    = NMT_NEWT_TEXTBOX_GET_PRIVATE(textbox);
+
+    switch (prop_id) {
+    case PROP_TEXT:
+        nmt_newt_textbox_set_text(textbox, g_value_get_string(value));
+        break;
+    case PROP_FLAGS:
+        priv->flags = g_value_get_uint(value);
+        break;
+    case PROP_WRAP_WIDTH:
+        priv->wrap_width = g_value_get_int(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_textbox_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtTextboxPrivate *priv = NMT_NEWT_TEXTBOX_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_TEXT:
+        g_value_set_string(value, priv->text);
+        break;
+    case PROP_FLAGS:
+        g_value_set_uint(value, priv->flags);
+        break;
+    case PROP_WRAP_WIDTH:
+        g_value_set_int(value, priv->wrap_width);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_textbox_class_init(NmtNewtTextboxClass *textbox_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(textbox_class);
+    NmtNewtComponentClass *component_class = NMT_NEWT_COMPONENT_CLASS(textbox_class);
+
+    g_type_class_add_private(textbox_class, sizeof(NmtNewtTextboxPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_textbox_set_property;
+    object_class->get_property = nmt_newt_textbox_get_property;
+    object_class->finalize     = nmt_newt_textbox_finalize;
+
+    component_class->build_component = nmt_newt_textbox_build_component;
+
+    /**
+     * NmtNewtTextbox:text:
+     *
+     * The textbox's text
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_TEXT,
+        g_param_spec_string("text", "", "", "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtTextbox:flags:
+     *
+     * The textbox's flags
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_FLAGS,
+        g_param_spec_uint("flags",
+                          "",
+                          "",
+                          0,
+                          G_MAXUINT,
+                          0,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtTextbox:wrap-width:
+     *
+     * The width in characters at which the textbox's text
+     * will wrap, or 0 if it does not wrap.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_WRAP_WIDTH,
+        g_param_spec_int("wrap-width",
+                         "",
+                         "",
+                         0,
+                         G_MAXINT,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-textbox.h b/src/libnmt-newt/nmt-newt-textbox.h
new file mode 100644
index 00000000..f589d62c
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-textbox.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_TEXTBOX_H
+#define NMT_NEWT_TEXTBOX_H
+
+#include "nmt-newt-component.h"
+
+#define NMT_TYPE_NEWT_TEXTBOX (nmt_newt_textbox_get_type())
+#define NMT_NEWT_TEXTBOX(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_TEXTBOX, NmtNewtTextbox))
+#define NMT_NEWT_TEXTBOX_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_TEXTBOX, NmtNewtTextboxClass))
+#define NMT_IS_NEWT_TEXTBOX(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_TEXTBOX))
+#define NMT_IS_NEWT_TEXTBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_TEXTBOX))
+#define NMT_NEWT_TEXTBOX_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_TEXTBOX, NmtNewtTextboxClass))
+
+struct _NmtNewtTextbox {
+    NmtNewtComponent parent;
+};
+
+typedef struct {
+    NmtNewtComponentClass parent;
+
+} NmtNewtTextboxClass;
+
+GType nmt_newt_textbox_get_type(void);
+
+typedef enum {
+    NMT_NEWT_TEXTBOX_SCROLLABLE     = (1 << 0),
+    NMT_NEWT_TEXTBOX_SET_BACKGROUND = (1 << 1)
+} NmtNewtTextboxFlags;
+
+NmtNewtWidget *nmt_newt_textbox_new(NmtNewtTextboxFlags flags, int wrap_width);
+
+void        nmt_newt_textbox_set_text(NmtNewtTextbox *textbox, const char *text);
+const char *nmt_newt_textbox_get_text(NmtNewtTextbox *textbox);
+
+#endif /* NMT_NEWT_TEXTBOX_H */
diff --git a/src/libnmt-newt/nmt-newt-toggle-button.c b/src/libnmt-newt/nmt-newt-toggle-button.c
new file mode 100644
index 00000000..c48931e5
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-toggle-button.c
@@ -0,0 +1,214 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-toggle-button
+ * @short_description: Toggle buttons
+ *
+ * #NmtNewtToggleButton implements a two-state toggle button.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-toggle-button.h"
+
+G_DEFINE_TYPE(NmtNewtToggleButton, nmt_newt_toggle_button, NMT_TYPE_NEWT_BUTTON)
+
+#define NMT_NEWT_TOGGLE_BUTTON_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_TOGGLE_BUTTON, NmtNewtToggleButtonPrivate))
+
+typedef struct {
+    char *   on_label, *off_label;
+    gboolean active;
+} NmtNewtToggleButtonPrivate;
+
+enum {
+    PROP_0,
+    PROP_ON_LABEL,
+    PROP_OFF_LABEL,
+    PROP_ACTIVE,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_newt_toggle_button_new:
+ * @on_label: the button's label when it is in the "on" state
+ * @off_label: the button's label when it is in the "off" state
+ *
+ * Creates a new #NmtNewtToggleButton
+ *
+ * Returns: a new #NmtNewtToggleButton
+ */
+NmtNewtWidget *
+nmt_newt_toggle_button_new(const char *on_label, const char *off_label)
+{
+    return g_object_new(NMT_TYPE_NEWT_TOGGLE_BUTTON,
+                        "on-label",
+                        on_label,
+                        "off-label",
+                        off_label,
+                        NULL);
+}
+
+/**
+ * nmt_newt_toggle_button_get_active:
+ * @button: an #NmtNewtToggleButton
+ *
+ * Gets whether @button is currently "on" or "off"
+ *
+ * Returns: whether @button is currently "on" (%TRUE) or "off" (%FALSE)
+ */
+gboolean
+nmt_newt_toggle_button_get_active(NmtNewtToggleButton *button)
+{
+    NmtNewtToggleButtonPrivate *priv = NMT_NEWT_TOGGLE_BUTTON_GET_PRIVATE(button);
+
+    return priv->active;
+}
+
+/**
+ * nmt_newt_toggle_button_set_active:
+ * @button: an #NmtNewtToggleButton
+ * @active: whether @button should be "on" or "off"
+ *
+ * Sets whether @button is currently "on" or "off"
+ */
+void
+nmt_newt_toggle_button_set_active(NmtNewtToggleButton *button, gboolean active)
+{
+    NmtNewtToggleButtonPrivate *priv = NMT_NEWT_TOGGLE_BUTTON_GET_PRIVATE(button);
+
+    if (priv->active == active)
+        return;
+
+    priv->active = active;
+    g_object_set(G_OBJECT(button), "label", active ? priv->on_label : priv->off_label, NULL);
+    g_object_notify(G_OBJECT(button), "active");
+}
+
+static void
+nmt_newt_toggle_button_init(NmtNewtToggleButton *button)
+{}
+
+static void
+nmt_newt_toggle_button_finalize(GObject *object)
+{
+    NmtNewtToggleButtonPrivate *priv = NMT_NEWT_TOGGLE_BUTTON_GET_PRIVATE(object);
+
+    g_free(priv->on_label);
+    g_free(priv->off_label);
+
+    G_OBJECT_CLASS(nmt_newt_toggle_button_parent_class)->finalize(object);
+}
+
+static void
+nmt_newt_toggle_button_activated(NmtNewtWidget *widget)
+{
+    NmtNewtToggleButton *button = NMT_NEWT_TOGGLE_BUTTON(widget);
+
+    nmt_newt_toggle_button_set_active(button, !nmt_newt_toggle_button_get_active(button));
+
+    NMT_NEWT_WIDGET_CLASS(nmt_newt_toggle_button_parent_class)->activated(widget);
+}
+
+static void
+nmt_newt_toggle_button_set_property(GObject *     object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec *  pspec)
+{
+    NmtNewtToggleButtonPrivate *priv = NMT_NEWT_TOGGLE_BUTTON_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_ON_LABEL:
+        g_free(priv->on_label);
+        priv->on_label = g_value_dup_string(value);
+        if (priv->active)
+            g_object_set(object, "label", priv->on_label, NULL);
+        break;
+    case PROP_OFF_LABEL:
+        g_free(priv->off_label);
+        priv->off_label = g_value_dup_string(value);
+        if (!priv->active)
+            g_object_set(object, "label", priv->off_label, NULL);
+        break;
+    case PROP_ACTIVE:
+        priv->active = g_value_get_boolean(value);
+        g_object_set(object, "label", priv->active ? priv->on_label : priv->off_label, NULL);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_toggle_button_get_property(GObject *   object,
+                                    guint       prop_id,
+                                    GValue *    value,
+                                    GParamSpec *pspec)
+{
+    NmtNewtToggleButtonPrivate *priv = NMT_NEWT_TOGGLE_BUTTON_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_ON_LABEL:
+        g_value_set_string(value, priv->on_label);
+        break;
+    case PROP_OFF_LABEL:
+        g_value_set_string(value, priv->off_label);
+        break;
+    case PROP_ACTIVE:
+        g_value_set_boolean(value, priv->active);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_toggle_button_class_init(NmtNewtToggleButtonClass *button_class)
+{
+    GObjectClass *      object_class = G_OBJECT_CLASS(button_class);
+    NmtNewtWidgetClass *widget_class = NMT_NEWT_WIDGET_CLASS(button_class);
+
+    g_type_class_add_private(button_class, sizeof(NmtNewtToggleButtonPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_toggle_button_set_property;
+    object_class->get_property = nmt_newt_toggle_button_get_property;
+    object_class->finalize     = nmt_newt_toggle_button_finalize;
+
+    widget_class->activated = nmt_newt_toggle_button_activated;
+
+    /**
+     * NmtNewtToggleButton:on-label:
+     *
+     * The label the button displays when it is "on".
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_ON_LABEL,
+        g_param_spec_string("on-label", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtToggleButton:off-label:
+     *
+     * The label the button displays when it is "off".
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_OFF_LABEL,
+        g_param_spec_string("off-label", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtToggleButton:active:
+     *
+     * Whether the button is currently "on" (%TRUE) or "off" (%FALSE)
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_ACTIVE,
+        g_param_spec_boolean("active", "", "", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-toggle-button.h b/src/libnmt-newt/nmt-newt-toggle-button.h
new file mode 100644
index 00000000..bdaa445d
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-toggle-button.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_TOGGLE_BUTTON_H
+#define NMT_NEWT_TOGGLE_BUTTON_H
+
+#include "nmt-newt-button.h"
+
+#define NMT_TYPE_NEWT_TOGGLE_BUTTON (nmt_newt_toggle_button_get_type())
+#define NMT_NEWT_TOGGLE_BUTTON(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_TOGGLE_BUTTON, NmtNewtToggleButton))
+#define NMT_NEWT_TOGGLE_BUTTON_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_TOGGLE_BUTTON, NmtNewtToggleButtonClass))
+#define NMT_IS_NEWT_TOGGLE_BUTTON(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_TOGGLE_BUTTON))
+#define NMT_IS_NEWT_TOGGLE_BUTTON_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_TOGGLE_BUTTON))
+#define NMT_NEWT_TOGGLE_BUTTON_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_TOGGLE_BUTTON, NmtNewtToggleButtonClass))
+
+struct _NmtNewtToggleButton {
+    NmtNewtButton parent;
+};
+
+typedef struct {
+    NmtNewtButtonClass parent;
+
+} NmtNewtToggleButtonClass;
+
+GType nmt_newt_toggle_button_get_type(void);
+
+NmtNewtWidget *nmt_newt_toggle_button_new(const char *on_label, const char *off_label);
+
+gboolean nmt_newt_toggle_button_get_active(NmtNewtToggleButton *button);
+void     nmt_newt_toggle_button_set_active(NmtNewtToggleButton *button, gboolean active);
+
+#endif /* NMT_NEWT_TOGGLE_BUTTON_H */
diff --git a/src/libnmt-newt/nmt-newt-types.h b/src/libnmt-newt/nmt-newt-types.h
new file mode 100644
index 00000000..daeb7c24
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-types.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_TYPES_H
+#define NMT_NEWT_TYPES_H
+
+#include <newt.h>
+
+typedef struct _NmtNewtButton        NmtNewtButton;
+typedef struct _NmtNewtButtonBox     NmtNewtButtonBox;
+typedef struct _NmtNewtCheckbox      NmtNewtCheckbox;
+typedef struct _NmtNewtComponent     NmtNewtComponent;
+typedef struct _NmtNewtContainer     NmtNewtContainer;
+typedef struct _NmtNewtEntry         NmtNewtEntry;
+typedef struct _NmtNewtEntryNumeric  NmtNewtEntryNumeric;
+typedef struct _NmtNewtForm          NmtNewtForm;
+typedef struct _NmtNewtGrid          NmtNewtGrid;
+typedef struct _NmtNewtLabel         NmtNewtLabel;
+typedef struct _NmtNewtListbox       NmtNewtListbox;
+typedef struct _NmtNewtPopup         NmtNewtPopup;
+typedef struct _NmtNewtSection       NmtNewtSection;
+typedef struct _NmtNewtSectionBorder NmtNewtSectionBorder;
+typedef struct _NmtNewtSeparator     NmtNewtSeparator;
+typedef struct _NmtNewtStack         NmtNewtStack;
+typedef struct _NmtNewtTextbox       NmtNewtTextbox;
+typedef struct _NmtNewtToggleButton  NmtNewtToggleButton;
+typedef struct _NmtNewtWidget        NmtNewtWidget;
+
+#endif /* NMT_NEWT_COMPONENT_H */
diff --git a/src/libnmt-newt/nmt-newt-utils.c b/src/libnmt-newt/nmt-newt-utils.c
new file mode 100644
index 00000000..480c8194
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-utils.c
@@ -0,0 +1,472 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-utils
+ * @short_description: Utility functions
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-utils.h"
+
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#include "libnm-glib-aux/nm-io-utils.h"
+
+static void
+nmt_newt_dialog_g_log_handler(const char *   log_domain,
+                              GLogLevelFlags log_level,
+                              const char *   message,
+                              gpointer       user_data)
+{
+    const char *  level_name;
+    char *        full_message;
+    int           screen_width, screen_height;
+    newtComponent text, ok, form;
+    newtGrid      grid;
+
+    g_assert(!(log_level & G_LOG_FLAG_RECURSION));
+
+    if (log_level & G_LOG_LEVEL_DEBUG)
+        return;
+
+    switch (log_level & G_LOG_LEVEL_MASK) {
+    case G_LOG_LEVEL_ERROR:
+        level_name = "ERROR";
+        break;
+    case G_LOG_LEVEL_CRITICAL:
+        level_name = "CRITICAL";
+        break;
+    case G_LOG_LEVEL_WARNING:
+        level_name = "WARNING";
+        break;
+    case G_LOG_LEVEL_MESSAGE:
+        level_name = "Message";
+        break;
+    default:
+        level_name = NULL;
+    }
+
+    full_message = g_strdup_printf("%s%s%s%s%s",
+                                   log_domain ?: "",
+                                   log_domain && level_name ? " " : "",
+                                   level_name ?: "",
+                                   log_domain || level_name ? ": " : "",
+                                   message);
+
+    /* newtWinMessage() wraps the window too narrowly by default, so
+     * we don't want to use that. But we intentionally avoid using any
+     * NmtNewt classes, to avoid possible error recursion.
+     */
+
+    newtGetScreenSize(&screen_width, &screen_height);
+    text = newtTextboxReflowed(-1, -1, full_message, MAX(70, screen_width - 10), 0, 0, 0);
+    g_free(full_message);
+
+    ok = newtButton(-1, -1, "OK");
+
+    grid = newtCreateGrid(1, 2);
+    newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, text, 0, 0, 0, 0, 0, 0);
+    newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, ok, 0, 1, 0, 0, NEWT_ANCHOR_RIGHT, 0);
+
+    newtGridWrappedWindow(grid, (char *) (level_name ?: ""));
+    newtGridFree(grid, TRUE);
+
+    form = newtForm(NULL, NULL, 0);
+    newtFormAddComponents(form, text, ok, NULL);
+    newtRunForm(form);
+    newtFormDestroy(form);
+    newtPopWindow();
+}
+
+static void
+nmt_newt_basic_g_log_handler(const char *   log_domain,
+                             GLogLevelFlags log_level,
+                             const char *   message,
+                             gpointer       user_data)
+{
+    newtSuspend();
+    g_log_default_handler(log_domain, log_level, message, NULL);
+    newtResume();
+}
+
+static void
+nmt_newt_suspend_callback(gpointer user_data)
+{
+    newtSuspend();
+    kill(getpid(), SIGTSTP);
+    newtResume();
+}
+
+static void
+_newtSetColor(int colorset, const char *fg, const char *bg)
+{
+    newtSetColor(colorset, (char *) fg, (char *) bg);
+}
+
+/**
+ * nmt_newt_parse_colors:
+ * @s: buffer with color settings
+ * @is_newt: boolean indicating if buffer s
+ *           contains NEWT (true) or NMT (false) color setting
+ *
+ * Parses content of buffer s and sets color accordingly
+ * with newtSetColor()
+ */
+static void
+nmt_newt_parse_colors(const char *s, bool is_newt)
+{
+    gs_free const char **lines = NULL;
+    size_t               i;
+
+    lines = nm_utils_strsplit_set(s, ";:\n\r\t ");
+
+    if (!lines)
+        return;
+
+    for (i = 0; lines[i]; i++) {
+        const char *name;
+        const char *fg;
+        const char *bg;
+        char *      parsed_s;
+
+        parsed_s = (char *) lines[i];
+        name     = parsed_s;
+
+        if (!(parsed_s = strchr(parsed_s, '=')) || !*parsed_s)
+            continue;
+
+        *parsed_s = '\0';
+        fg        = ++parsed_s;
+
+        if (!(parsed_s = strchr(parsed_s, ',')) || !*parsed_s)
+            continue;
+
+        *parsed_s = '\0';
+
+        bg = ++parsed_s;
+
+        if (is_newt) {
+            if (nm_streq(name, "checkbox"))
+                _newtSetColor(NEWT_COLORSET_CHECKBOX, fg, bg);
+        } else {
+            if (nm_streq(name, "badLabel")) {
+                _newtSetColor(NMT_NEWT_COLORSET_BAD_LABEL, fg, bg);
+            } else if (nm_streq(name, "plainLabel")) {
+                _newtSetColor(NMT_NEWT_COLORSET_PLAIN_LABEL, fg, bg);
+            } else if (nm_streq(name, "disabledButton")) {
+                _newtSetColor(NMT_NEWT_COLORSET_DISABLED_BUTTON, fg, bg);
+            } else if (nm_streq(name, "textboxWithBackground")) {
+                _newtSetColor(NMT_NEWT_COLORSET_TEXTBOX_WITH_BACKGROUND, fg, bg);
+            }
+        }
+    }
+}
+
+/**
+ * nmt_newt_nit_colors:
+ * @is_newt: boolean indicating if the function is looking for NEWT or NMT env var
+ *
+ * Looks for enviroment variables for aditional
+ * color set up in nmtui
+ */
+static void
+nmt_newt_init_colors(gboolean is_newt)
+{
+    const char *  colors;
+    gs_free char *file_content = NULL;
+
+    colors = getenv(is_newt ? "NEWT_COLORS" : "NMT_NEWT_COLORS");
+
+    if (!colors) {
+        const char *file_name;
+
+        file_name = getenv(is_newt ? "NEWT_COLORS_FILE" : "NMT_NEWT_COLORS_FILE");
+
+        if (file_name && file_name[0] != '\0'
+            && (nm_utils_file_get_contents(-1,
+                                           file_name,
+                                           16384,
+                                           NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+                                           &file_content,
+                                           NULL,
+                                           NULL,
+                                           NULL))) {
+            colors = file_content;
+        }
+    }
+
+    nmt_newt_parse_colors(colors, is_newt);
+}
+
+/**
+libnm-client-aux-extern * nmt_newt_init:
+ *
+ * Wrapper for newtInit() that also does some nmt-newt-internal setup.
+ * This should be called once, before any other nmt-newt functions.
+ */
+void
+nmt_newt_init(void)
+{
+    newtInit();
+    newtCls();
+
+    newtSetColor(NEWT_COLORSET_CHECKBOX, "black", "lightgray");
+    newtSetColor(NMT_NEWT_COLORSET_BAD_LABEL, "red", "lightgray");
+    newtSetColor(NMT_NEWT_COLORSET_PLAIN_LABEL, "black", "lightgray");
+    newtSetColor(NMT_NEWT_COLORSET_DISABLED_BUTTON, "blue", "lightgray");
+    newtSetColor(NMT_NEWT_COLORSET_TEXTBOX_WITH_BACKGROUND, "black", "white");
+
+    nmt_newt_init_colors(TRUE);
+    nmt_newt_init_colors(FALSE);
+
+    if (g_getenv("NMTUI_DEBUG"))
+        g_log_set_default_handler(nmt_newt_dialog_g_log_handler, NULL);
+    else
+        g_log_set_default_handler(nmt_newt_basic_g_log_handler, NULL);
+
+    newtSetSuspendCallback(nmt_newt_suspend_callback, NULL);
+}
+
+/**
+ * nmt_newt_finished:
+ *
+ * Wrapper for newtFinished(). Should be called at the end of the program.
+ */
+void
+nmt_newt_finished(void)
+{
+    newtFinished();
+    g_log_set_default_handler(g_log_default_handler, NULL);
+}
+
+/**
+ * nmt_newt_message_dialog:
+ * @message: a printf()-style message format
+ * @...: arguments
+ *
+ * Displays the given message in a dialog box with a single "OK"
+ * button, and returns after the user clicks "OK".
+ */
+void
+nmt_newt_message_dialog(const char *message, ...)
+{
+    va_list ap;
+    char *  msg, *msg_lc, *ok_lc;
+
+    va_start(ap, message);
+    msg = g_strdup_vprintf(message, ap);
+    va_end(ap);
+
+    msg_lc = nmt_newt_locale_from_utf8(msg);
+    ok_lc  = nmt_newt_locale_from_utf8(_("OK"));
+    newtWinMessage(NULL, ok_lc, "%s", msg_lc);
+
+    g_free(ok_lc);
+    g_free(msg_lc);
+    g_free(msg);
+}
+
+/**
+ * nmt_newt_choice_dialog:
+ * @button1: the label for the first button
+ * @button2: the label for the second button
+ * @message: a printf()-style message format
+ * @...: arguments
+ *
+ * Displays the given message in a dialog box with two buttons with
+ * the indicated labels, and waits for the user to click one.
+ *
+ * Returns: which button was clicked: 0 for @button1 or 1 for @button2
+ */
+int
+nmt_newt_choice_dialog(const char *button1, const char *button2, const char *message, ...)
+{
+    va_list ap;
+    char *  msg, *msg_lc, *button1_lc, *button2_lc;
+    int     choice;
+
+    va_start(ap, message);
+    msg = g_strdup_vprintf(message, ap);
+    va_end(ap);
+
+    msg_lc     = nmt_newt_locale_from_utf8(msg);
+    button1_lc = nmt_newt_locale_from_utf8(button1);
+    button2_lc = nmt_newt_locale_from_utf8(button2);
+    choice     = newtWinChoice(NULL, button1_lc, button2_lc, "%s", msg_lc);
+
+    g_free(button1_lc);
+    g_free(button2_lc);
+    g_free(msg_lc);
+    g_free(msg);
+
+    return choice;
+}
+
+/**
+ * nmt_newt_locale_to_utf8:
+ * @str_lc: a string in the user's locale encoding
+ *
+ * Convenience wrapper around g_locale_to_utf8().
+ *
+ * Note that libnewt works in terms of the user's locale character
+ * set, NOT UTF-8, so all strings received from libnewt must be
+ * converted back to UTF-8 before being returned to the caller or used
+ * in other APIs.
+ *
+ * Returns: @str_lc, converted to UTF-8.
+ */
+char *
+nmt_newt_locale_to_utf8(const char *str_lc)
+{
+    char *str_utf8;
+
+    str_utf8 = g_locale_to_utf8(str_lc, -1, NULL, NULL, NULL);
+    if (!str_utf8)
+        str_utf8 = g_strdup("");
+    return str_utf8;
+}
+
+/**
+ * nmt_newt_locale_from_utf8:
+ * @str_utf8: a UTF-8 string
+ *
+ * Convenience wrapper around g_locale_from_utf8().
+ *
+ * Note that libnewt works in terms of the user's locale character
+ * set, NOT UTF-8, so all strings from nmt-newt must be converted to
+ * locale encoding before being passed to libnewt.
+ *
+ * Returns: @str_utf8, converted to the user's locale encoding.
+ */
+char *
+nmt_newt_locale_from_utf8(const char *str_utf8)
+{
+    char *str_lc;
+
+    str_lc = g_locale_from_utf8(str_utf8, -1, NULL, NULL, NULL);
+    if (!str_lc)
+        str_lc = g_strdup("");
+    return str_lc;
+}
+
+/**
+ * nmt_newt_text_width
+ * @str: a UTF-8 string
+ *
+ * Computes the width (in terminal columns) of @str.
+ *
+ * Returns: the width of @str
+ */
+int
+nmt_newt_text_width(const char *str)
+{
+    int      width;
+    gunichar ch;
+
+    for (width = 0; *str; str = g_utf8_next_char(str)) {
+        ch = g_utf8_get_char(str);
+
+        /* Based on _vte_iso2022_unichar_width */
+        if (G_LIKELY(ch < 0x80))
+            width += 1;
+        else if (G_UNLIKELY(g_unichar_iszerowidth(ch)))
+            width += 0;
+        else if (G_UNLIKELY(g_unichar_iswide(ch)))
+            width += 2;
+        else
+            width += 1;
+    }
+
+    return width;
+}
+
+/**
+ * nmt_newt_edit_string:
+ * @data: data to edit
+ *
+ * libnewt does not have a multi-line editable text component, so
+ * nmt-newt provides this function instead, which will open the user's
+ * editor to edit a file containing the given @data (ensuring that the
+ * current screen state is saved before starting the editor and
+ * restored after it returns).
+ *
+ * Returns: the edited data, or %NULL if an error occurred.
+ */
+char *
+nmt_newt_edit_string(const char *data)
+{
+    gssize  len, nwrote;
+    char *  filename, *argv[3];
+    GError *error = NULL;
+    int     fd, status;
+    char *  new_data = NULL;
+
+    fd = g_file_open_tmp("XXXXXX.json", &filename, &error);
+    if (fd == -1) {
+        nmt_newt_message_dialog(_("Could not create temporary file: %s"), error->message);
+        g_error_free(error);
+        return NULL;
+    }
+
+    len = data ? strlen(data) : 0;
+    while (len) {
+        do
+            nwrote = write(fd, data, len);
+        while (nwrote == -1 && errno == EINTR);
+
+        len -= nwrote;
+        data += nwrote;
+    }
+    nm_close(fd);
+
+    argv[0] = (char *) g_getenv("VISUAL");
+    if (!argv[0])
+        argv[0] = (char *) g_getenv("EDITOR");
+    if (!argv[0])
+        argv[0] = (char *) "vi";
+    argv[1] = filename;
+    argv[2] = NULL;
+
+    newtSuspend();
+    g_spawn_sync(NULL,
+                 argv,
+                 NULL,
+                 G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN,
+                 NULL,
+                 NULL,
+                 NULL,
+                 NULL,
+                 &status,
+                 &error);
+    newtResume();
+
+    if (error) {
+        nmt_newt_message_dialog(_("Could not create temporary file: %s"), error->message);
+        g_error_free(error);
+        goto done;
+    }
+
+    if (!g_spawn_check_exit_status(status, &error)) {
+        nmt_newt_message_dialog(_("Editor failed: %s"), error->message);
+        g_error_free(error);
+        goto done;
+    }
+
+    if (!g_file_get_contents(filename, &new_data, NULL, &error)) {
+        nmt_newt_message_dialog(_("Could not re-read file: %s"), error->message);
+        g_error_free(error);
+        goto done;
+    }
+
+done:
+    unlink(filename);
+    g_free(filename);
+
+    return new_data;
+}
diff --git a/src/libnmt-newt/nmt-newt-utils.h b/src/libnmt-newt/nmt-newt-utils.h
new file mode 100644
index 00000000..04089f9f
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-utils.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_UTILS_H
+#define NMT_NEWT_UTILS_H
+
+#include <newt.h>
+
+void nmt_newt_init(void);
+void nmt_newt_finished(void);
+
+typedef enum {
+    NMT_NEWT_COLORSET_BAD_LABEL = NEWT_COLORSET_CUSTOM(0),
+    NMT_NEWT_COLORSET_PLAIN_LABEL,
+    NMT_NEWT_COLORSET_DISABLED_BUTTON,
+    NMT_NEWT_COLORSET_TEXTBOX_WITH_BACKGROUND
+} NmtNewtColorsets;
+
+char *nmt_newt_locale_to_utf8(const char *str_lc);
+char *nmt_newt_locale_from_utf8(const char *str_utf8);
+
+int nmt_newt_text_width(const char *str);
+
+void nmt_newt_message_dialog(const char *message, ...) _nm_printf(1, 2);
+int  nmt_newt_choice_dialog(const char *button1, const char *button2, const char *message, ...)
+    _nm_printf(3, 4);
+
+char *nmt_newt_edit_string(const char *data);
+
+#endif /* NMT_NEWT_UTILS_H */
diff --git a/src/libnmt-newt/nmt-newt-widget.c b/src/libnmt-newt/nmt-newt-widget.c
new file mode 100644
index 00000000..8b5647aa
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-widget.c
@@ -0,0 +1,642 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-newt-widget
+ * @short_description: Base TUI Widget class
+ *
+ * #NmtNewtWidget is the abstract base class for nmt-newt. All widgets
+ * inherit from one of its two subclasses: #NmtNewtComponent, for
+ * widgets that wrap a (single) #newtComponent, and #NmtNewtContainer,
+ * for widgets consisting of multiple components. See those classes
+ * for more details.
+ *
+ * With the exception of #NmtNewtForm, all widgets start out with a
+ * floating reference, which will be sunk by the container they are
+ * added to. #NmtNewtForm is the "top-level" widget type, and so does
+ * not have a floating reference.
+ *
+ * FIXME: need RTL support
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-newt-widget.h"
+#include "nmt-newt-form.h"
+
+G_DEFINE_ABSTRACT_TYPE(NmtNewtWidget, nmt_newt_widget, G_TYPE_INITIALLY_UNOWNED)
+
+#define NMT_NEWT_WIDGET_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_NEWT_WIDGET, NmtNewtWidgetPrivate))
+
+typedef struct {
+    NmtNewtWidget *parent;
+    gboolean       visible, realized, valid;
+    gboolean       exit_on_activate;
+
+    int pad_left, pad_top, pad_right, pad_bottom;
+} NmtNewtWidgetPrivate;
+
+enum {
+    PROP_0,
+
+    PROP_PARENT,
+    PROP_VISIBLE,
+    PROP_VALID,
+    PROP_EXIT_ON_ACTIVATE,
+
+    LAST_PROP
+};
+
+enum {
+    NEEDS_REBUILD,
+    ACTIVATED,
+
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+static void
+nmt_newt_widget_init(NmtNewtWidget *widget)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    priv->visible = TRUE;
+    priv->valid   = TRUE;
+}
+
+static void
+nmt_newt_widget_finalize(GObject *object)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(object);
+
+    nmt_newt_widget_unrealize(NMT_NEWT_WIDGET(object));
+    g_clear_object(&priv->parent);
+
+    G_OBJECT_CLASS(nmt_newt_widget_parent_class)->finalize(object);
+}
+
+/**
+ * nmt_newt_widget_realize:
+ * @widget: an #NmtNewtWidget
+ *
+ * "Realizes" @widget. That is, creates #newtComponents corresponding
+ * to @widget and its children.
+ *
+ * You should not need to call this yourself; an #NmtNewtForm will
+ * cause its children to be realized and unrealized as needed.
+ */
+void
+nmt_newt_widget_realize(NmtNewtWidget *widget)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    if (!priv->realized) {
+        NMT_NEWT_WIDGET_GET_CLASS(widget)->realize(widget);
+        priv->realized = TRUE;
+    }
+}
+
+/**
+ * nmt_newt_widget_unrealize:
+ * @widget: an #NmtNewtWidget
+ *
+ * "Unrealizes" @widget, destroying its #newtComponents.
+ *
+ * You should not need to call this yourself; an #NmtNewtForm will
+ * cause its children to be realized and unrealized as needed.
+ */
+void
+nmt_newt_widget_unrealize(NmtNewtWidget *widget)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    if (priv->realized) {
+        NMT_NEWT_WIDGET_GET_CLASS(widget)->unrealize(widget);
+        priv->realized = FALSE;
+    }
+}
+
+/**
+ * nmt_newt_widget_get_realized:
+ * @widget: an #NmtNewtWidget
+ *
+ * Checks if @widget is realized or not.
+ *
+ * Returns: whether @widget is realized.
+ */
+gboolean
+nmt_newt_widget_get_realized(NmtNewtWidget *widget)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    return priv->realized;
+}
+
+/**
+ * nmt_newt_widget_get_components:
+ * @widget: an #NmtNewtWidget
+ *
+ * Gets the #newtComponents that make up @widget, if @widget is
+ * visible. If @widget has not yet been realized, it will be realized
+ * first.
+ *
+ * If this function is called on a widget, then the widget will assume
+ * that someone else is now responsible for destroying the components,
+ * and so it will not destroy them itself when the widget is
+ * destroyed. Normally, components will end up being destroyed by the
+ * #NmtNewtForm they are added to.
+ *
+ * Returns: a %NULL-terminated array of components, in focus-chain
+ *   order. You must free the array with g_free() when you are done
+ *   with it.
+ */
+newtComponent *
+nmt_newt_widget_get_components(NmtNewtWidget *widget)
+{
+    if (nmt_newt_widget_get_visible(widget)) {
+        nmt_newt_widget_realize(widget);
+        return NMT_NEWT_WIDGET_GET_CLASS(widget)->get_components(widget);
+    } else
+        return NULL;
+}
+
+/**
+ * nmt_newt_widget_find_component:
+ * @widget: an #NmtNewtWidget
+ * @co: a #newtComponent
+ *
+ * Finds the widget inside @widget that owns @co.
+ *
+ * Return value: @co's owner, or %NULL if it was not found.
+ */
+NmtNewtWidget *
+nmt_newt_widget_find_component(NmtNewtWidget *widget, newtComponent co)
+{
+    return NMT_NEWT_WIDGET_GET_CLASS(widget)->find_component(widget, co);
+}
+
+/**
+ * nmt_newt_widget_set_padding:
+ * @widget: an #NmtNewtWidget
+ * @pad_left: padding on the left of @widget
+ * @pad_top: padding on the top of @widget
+ * @pad_right: padding on the right of @widget
+ * @pad_bottom: padding on the bottom of @widget
+ *
+ * Sets the padding on @widget.
+ */
+void
+nmt_newt_widget_set_padding(NmtNewtWidget *widget,
+                            int            pad_left,
+                            int            pad_top,
+                            int            pad_right,
+                            int            pad_bottom)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    priv->pad_left   = pad_left;
+    priv->pad_top    = pad_top;
+    priv->pad_right  = pad_right;
+    priv->pad_bottom = pad_bottom;
+}
+
+/**
+ * nmt_newt_widget_size_request:
+ * @widget: an #NmtNewtWidget
+ * @width: (out): on output, the widget's requested width
+ * @height: (out): on output, the widget's requested height
+ *
+ * Asks @widget for its requested size. If @widget is not visible,
+ * this will return 0, 0. If @widget has not yet been realized, it
+ * will be realized first.
+ */
+void
+nmt_newt_widget_size_request(NmtNewtWidget *widget, int *width, int *height)
+{
+    if (nmt_newt_widget_get_visible(widget)) {
+        NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+        nmt_newt_widget_realize(widget);
+        NMT_NEWT_WIDGET_GET_CLASS(widget)->size_request(widget, width, height);
+
+        *width += priv->pad_left + priv->pad_right;
+        *height += priv->pad_top + priv->pad_bottom;
+    } else
+        *width = *height = 0;
+}
+
+/**
+ * nmt_newt_widget_size_allocate:
+ * @widget: an #NmtNewtWidget
+ * @x: the widget's (absolute) X coordinate
+ * @y: the widget's (absolute) Y coordinate
+ * @width: the widget's allocated width
+ * @height: the widget's allocated height
+ *
+ * Positions @widget at the given coordinates, with the given size. If
+ * @widget is not visible, this has no effect. If @widget has not yet
+ * been realized, it will be realized first.
+ *
+ * @x and @y are absolute coordinates (ie, relative to the screen /
+ * terminal window, not relative to @widget's parent).
+ *
+ * In general, the results are undefined if @width or @height is less
+ * than the widget's requested size. If @width or @height is larger
+ * than the requested size, most #NmtNewtComponents will ignore the
+ * extra space, but some components and most containers will expand to
+ * fit.
+ */
+void
+nmt_newt_widget_size_allocate(NmtNewtWidget *widget, int x, int y, int width, int height)
+{
+    if (nmt_newt_widget_get_visible(widget)) {
+        NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+        nmt_newt_widget_realize(widget);
+        x += priv->pad_left;
+        y += priv->pad_top;
+        width -= priv->pad_left + priv->pad_right;
+        height -= priv->pad_top + priv->pad_bottom;
+
+        NMT_NEWT_WIDGET_GET_CLASS(widget)->size_allocate(widget, x, y, width, height);
+    }
+}
+
+/**
+ * nmt_newt_widget_get_focus_component:
+ * @widget: an #NmtNewtWidget
+ *
+ * Gets the #newtComponent that should be given the keyboard focus when
+ * @widget is focused.
+ *
+ * Returns: the #newtComponent to focus, or %NULL if @widget can't
+ *   take the focus.
+ */
+newtComponent
+nmt_newt_widget_get_focus_component(NmtNewtWidget *widget)
+{
+    if (!NMT_NEWT_WIDGET_GET_CLASS(widget)->get_focus_component)
+        return NULL;
+
+    return NMT_NEWT_WIDGET_GET_CLASS(widget)->get_focus_component(widget);
+}
+
+static void
+nmt_newt_widget_real_activated(NmtNewtWidget *widget)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    if (priv->exit_on_activate)
+        nmt_newt_form_quit(nmt_newt_widget_get_form(widget));
+}
+
+/**
+ * nmt_newt_widget_activated:
+ * @widget: an #NmtNewtWidget
+ *
+ * Tells @widget that its #newtComponent has been activated (ie, the
+ * user hit "Return" on it) and emits #NmtNewtWidget::activated.
+ *
+ * If #NmtNewtWidget:exit-on-activate is set on @widget, then this
+ * will call nmt_newt_form_quit() on the widget's form.
+ */
+void
+nmt_newt_widget_activated(NmtNewtWidget *widget)
+{
+    g_signal_emit(widget, signals[ACTIVATED], 0);
+}
+
+/**
+ * nmt_newt_widget_get_exit_on_activate:
+ * @widget: an #NmtNewtWidget
+ *
+ * Gets @widget's #NmtNewtWidget:exit-on-activate flag, qv.
+ *
+ * Returns: @widget's #NmtNewtWidget:exit-on-activate flag
+ */
+gboolean
+nmt_newt_widget_get_exit_on_activate(NmtNewtWidget *widget)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    return priv->exit_on_activate;
+}
+
+/**
+ * nmt_newt_widget_set_exit_on_activate:
+ * @widget: an #NmtNewtWidget
+ * @exit_on_activate: whether @widget should exit on activate.
+ *
+ * Sets @widget's #NmtNewtWidget:exit-on-activate flag, qv.
+ */
+void
+nmt_newt_widget_set_exit_on_activate(NmtNewtWidget *widget, gboolean exit_on_activate)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    exit_on_activate = !!exit_on_activate;
+    if (priv->exit_on_activate != exit_on_activate) {
+        priv->exit_on_activate = exit_on_activate;
+        g_object_notify(G_OBJECT(widget), "exit-on-activate");
+    }
+}
+
+/**
+ * nmt_newt_widget_get_visible:
+ * @widget: an #NmtNewtWidget
+ *
+ * Gets @widget's #NmtNewtWidget:visible flag, qv.
+ *
+ * Returns: @widget's #NmtNewtWidget:visible flag
+ */
+gboolean
+nmt_newt_widget_get_visible(NmtNewtWidget *widget)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    return priv->visible;
+}
+
+/**
+ * nmt_newt_widget_set_visible:
+ * @widget: an #NmtNewtWidget
+ * @visible: whether @widget should be visible
+ *
+ * Sets @widget's #NmtNewtWidget:visible flag, qv.
+ */
+void
+nmt_newt_widget_set_visible(NmtNewtWidget *widget, gboolean visible)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    visible = !!visible;
+    if (priv->visible != visible) {
+        priv->visible = visible;
+        g_object_notify(G_OBJECT(widget), "visible");
+        nmt_newt_widget_needs_rebuild(widget);
+    }
+}
+
+/**
+ * nmt_newt_widget_set_parent:
+ * @widget: an #NmtNewtWidget
+ * @parent: @widget's parent
+ *
+ * Sets @widget's parent to @parent. This is used internally by
+ * #NmtNewtContainer implementations; you must use an appropriate
+ * container-specific method to actually add a widget to a container.
+ */
+void
+nmt_newt_widget_set_parent(NmtNewtWidget *widget, NmtNewtWidget *parent)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    g_clear_object(&priv->parent);
+    priv->parent = parent ? g_object_ref(parent) : NULL;
+    g_object_notify(G_OBJECT(widget), "parent");
+}
+
+/**
+ * nmt_newt_widget_get_parent:
+ * @widget: an #NmtNewtWidget
+ *
+ * Gets @widget's parent
+ *
+ * Returns: @widget's parent
+ */
+NmtNewtWidget *
+nmt_newt_widget_get_parent(NmtNewtWidget *widget)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    return priv->parent;
+}
+
+/**
+ * nmt_newt_widget_get_form:
+ * @widget: an #NmtNewtWidget
+ *
+ * Gets @widget's top-level form.
+ *
+ * Returns: @widget's #NmtNewtForm
+ */
+NmtNewtForm *
+nmt_newt_widget_get_form(NmtNewtWidget *widget)
+{
+    while (widget) {
+        if (NMT_IS_NEWT_FORM(widget))
+            return NMT_NEWT_FORM(widget);
+        widget = nmt_newt_widget_get_parent(widget);
+    }
+
+    return NULL;
+}
+
+/**
+ * nmt_newt_widget_get_valid:
+ * @widget: an #NmtNewtWidget
+ *
+ * Gets @widget's #NmtNewtWidget:valid flag, indicating whether its
+ * content is valid.
+ *
+ * Returns: @widget's #NmtNewtWidget:valid flag
+ */
+gboolean
+nmt_newt_widget_get_valid(NmtNewtWidget *widget)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    return priv->valid;
+}
+
+/**
+ * nmt_newt_widget_set_valid:
+ * @widget: an #NmtNewtWidget
+ * @valid: whether @widget is valid
+ *
+ * Sets @widget's #NmtNewtWidget:valid flag, indicating whether its
+ * content is valid.
+ *
+ * This method should be considered "protected"; if you change it, the
+ * widget implementation will likely just change it back at some
+ * point.
+ */
+void
+nmt_newt_widget_set_valid(NmtNewtWidget *widget, gboolean valid)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(widget);
+
+    valid = !!valid;
+    if (priv->valid == valid)
+        return;
+
+    priv->valid = valid;
+    g_object_notify(G_OBJECT(widget), "valid");
+}
+
+/**
+ * nmt_newt_widget_needs_rebuilds:
+ * @widget: an #NmtNewtWidget
+ *
+ * Marks @widget as needing to be "rebuilt" (ie, re-realized). This is
+ * called automatically in some cases (such as when adding a widget to
+ * or removing it from a container). #NmtNewtComponent implementations
+ * should also call this if they need to make some change that can
+ * only be done by destroying their current #newtComponent and
+ * creating a new one.
+ */
+void
+nmt_newt_widget_needs_rebuild(NmtNewtWidget *widget)
+{
+    g_signal_emit(widget, signals[NEEDS_REBUILD], 0);
+}
+
+static void
+nmt_newt_widget_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtNewtWidget *widget = NMT_NEWT_WIDGET(object);
+
+    switch (prop_id) {
+    case PROP_PARENT:
+        nmt_newt_widget_set_parent(widget, g_value_get_object(value));
+        break;
+    case PROP_VISIBLE:
+        nmt_newt_widget_set_visible(widget, g_value_get_boolean(value));
+        break;
+    case PROP_EXIT_ON_ACTIVATE:
+        nmt_newt_widget_set_exit_on_activate(widget, g_value_get_boolean(value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_widget_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtNewtWidgetPrivate *priv = NMT_NEWT_WIDGET_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_PARENT:
+        g_value_set_object(value, priv->parent);
+        break;
+    case PROP_VISIBLE:
+        g_value_set_boolean(value, priv->visible);
+        break;
+    case PROP_VALID:
+        g_value_set_boolean(value, priv->valid);
+        break;
+    case PROP_EXIT_ON_ACTIVATE:
+        g_value_set_boolean(value, priv->exit_on_activate);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_newt_widget_class_init(NmtNewtWidgetClass *widget_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(widget_class);
+
+    g_type_class_add_private(widget_class, sizeof(NmtNewtWidgetPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_newt_widget_set_property;
+    object_class->get_property = nmt_newt_widget_get_property;
+    object_class->finalize     = nmt_newt_widget_finalize;
+
+    widget_class->activated = nmt_newt_widget_real_activated;
+
+    /* signals */
+
+    /**
+     * NmtNewtWidget::needs-rebuild:
+     * @widget: the #NmtNewtWidget
+     *
+     * Emitted when nmt_newt_widget_need_rebuild() is called on @widget
+     * or any of its children. This signal propagates up the container
+     * hierarchy, eventually reaching the top-level #NmtNewtForm.
+     */
+    signals[NEEDS_REBUILD] = g_signal_new("needs-rebuild",
+                                          G_OBJECT_CLASS_TYPE(object_class),
+                                          G_SIGNAL_RUN_FIRST,
+                                          G_STRUCT_OFFSET(NmtNewtWidgetClass, needs_rebuild),
+                                          NULL,
+                                          NULL,
+                                          NULL,
+                                          G_TYPE_NONE,
+                                          0);
+
+    /**
+     * NmtNewtWidget::activated:
+     * @widget: the #NmtNewtWidget
+     *
+     * Emitted when the widget's #newtComponent is activated.
+     */
+    signals[ACTIVATED] = g_signal_new("activated",
+                                      G_OBJECT_CLASS_TYPE(object_class),
+                                      G_SIGNAL_RUN_FIRST,
+                                      G_STRUCT_OFFSET(NmtNewtWidgetClass, activated),
+                                      NULL,
+                                      NULL,
+                                      NULL,
+                                      G_TYPE_NONE,
+                                      0);
+
+    /* properties */
+
+    /**
+     * NmtNewtWidget:parent:
+     *
+     * The widget's parent widget, or %NULL if it has no parent.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_PARENT,
+                                    g_param_spec_object("parent",
+                                                        "",
+                                                        "",
+                                                        NMT_TYPE_NEWT_WIDGET,
+                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtWidget:visible:
+     *
+     * Whether the widget is visible. Invisible widgets do not get
+     * realized or sized.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_VISIBLE,
+        g_param_spec_boolean("visible", "", "", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtWidget:valid:
+     *
+     * Whether the widget's content is considered valid. Components
+     * determine their own validity. A container, by default, is
+     * considered valid if all of its children are valid.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_VALID,
+        g_param_spec_boolean("valid", "", "", TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtNewtWidget:exit-on-activate:
+     *
+     * If %TRUE, the widget will call nmt_newt_form_quit() on its form
+     * when it is activated.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_EXIT_ON_ACTIVATE,
+        g_param_spec_boolean("exit-on-activate",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/libnmt-newt/nmt-newt-widget.h b/src/libnmt-newt/nmt-newt-widget.h
new file mode 100644
index 00000000..70b62d29
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt-widget.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_WIDGET_H
+#define NMT_NEWT_WIDGET_H
+
+#include "nmt-newt-types.h"
+
+#define NMT_TYPE_NEWT_WIDGET (nmt_newt_widget_get_type())
+#define NMT_NEWT_WIDGET(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_NEWT_WIDGET, NmtNewtWidget))
+#define NMT_NEWT_WIDGET_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_NEWT_WIDGET, NmtNewtWidgetClass))
+#define NMT_IS_NEWT_WIDGET(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_NEWT_WIDGET))
+#define NMT_IS_NEWT_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_NEWT_WIDGET))
+#define NMT_NEWT_WIDGET_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_NEWT_WIDGET, NmtNewtWidgetClass))
+
+struct _NmtNewtWidget {
+    GInitiallyUnowned parent;
+};
+
+typedef struct {
+    GInitiallyUnownedClass parent;
+
+    /* signals */
+    void (*needs_rebuild)(NmtNewtWidget *widget);
+    void (*activated)(NmtNewtWidget *widget);
+
+    /* methods */
+    void (*realize)(NmtNewtWidget *widget);
+    void (*unrealize)(NmtNewtWidget *widget);
+
+    newtComponent *(*get_components)(NmtNewtWidget *widget);
+    NmtNewtWidget *(*find_component)(NmtNewtWidget *widget, newtComponent co);
+
+    void (*size_request)(NmtNewtWidget *widget, int *width, int *height);
+    void (*size_allocate)(NmtNewtWidget *widget, int x, int y, int width, int height);
+
+    newtComponent (*get_focus_component)(NmtNewtWidget *widget);
+
+} NmtNewtWidgetClass;
+
+GType nmt_newt_widget_get_type(void);
+
+void     nmt_newt_widget_realize(NmtNewtWidget *widget);
+void     nmt_newt_widget_unrealize(NmtNewtWidget *widget);
+gboolean nmt_newt_widget_get_realized(NmtNewtWidget *widget);
+
+newtComponent *nmt_newt_widget_get_components(NmtNewtWidget *widget);
+
+void nmt_newt_widget_set_padding(NmtNewtWidget *widget,
+                                 int            pad_left,
+                                 int            pad_top,
+                                 int            pad_right,
+                                 int            pad_bottom);
+
+void nmt_newt_widget_size_request(NmtNewtWidget *widget, int *width, int *height);
+void nmt_newt_widget_size_allocate(NmtNewtWidget *widget, int x, int y, int width, int height);
+
+void           nmt_newt_widget_set_parent(NmtNewtWidget *widget, NmtNewtWidget *parent);
+NmtNewtWidget *nmt_newt_widget_get_parent(NmtNewtWidget *widget);
+
+NmtNewtForm *nmt_newt_widget_get_form(NmtNewtWidget *widget);
+
+gboolean nmt_newt_widget_get_visible(NmtNewtWidget *widget);
+void     nmt_newt_widget_set_visible(NmtNewtWidget *widget, gboolean visible);
+
+newtComponent nmt_newt_widget_get_focus_component(NmtNewtWidget *widget);
+
+void     nmt_newt_widget_activated(NmtNewtWidget *widget);
+gboolean nmt_newt_widget_get_exit_on_activate(NmtNewtWidget *widget);
+void     nmt_newt_widget_set_exit_on_activate(NmtNewtWidget *widget, gboolean exit_on_activate);
+
+gboolean nmt_newt_widget_get_valid(NmtNewtWidget *widget);
+
+NmtNewtWidget *nmt_newt_widget_find_component(NmtNewtWidget *widget, newtComponent co);
+
+/* protected */
+void nmt_newt_widget_needs_rebuild(NmtNewtWidget *widget);
+void nmt_newt_widget_set_valid(NmtNewtWidget *widget, gboolean valid);
+
+#endif /* NMT_NEWT_WIDGET_H */
diff --git a/src/libnmt-newt/nmt-newt.h b/src/libnmt-newt/nmt-newt.h
new file mode 100644
index 00000000..984c65e2
--- /dev/null
+++ b/src/libnmt-newt/nmt-newt.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_NEWT_H
+#define NMT_NEWT_H
+
+#include "nmt-newt-button.h"
+#include "nmt-newt-button-box.h"
+#include "nmt-newt-checkbox.h"
+#include "nmt-newt-entry.h"
+#include "nmt-newt-entry-numeric.h"
+#include "nmt-newt-form.h"
+#include "nmt-newt-grid.h"
+#include "nmt-newt-label.h"
+#include "nmt-newt-listbox.h"
+#include "nmt-newt-popup.h"
+#include "nmt-newt-section.h"
+#include "nmt-newt-separator.h"
+#include "nmt-newt-stack.h"
+#include "nmt-newt-textbox.h"
+#include "nmt-newt-toggle-button.h"
+#include "nmt-newt-utils.h"
+
+#endif /* NMT_NEWT_H */