# HG changeset patch
# Node ID 382572fd150dd255cc985180b8ad48248a41cf3c
# Parent  9d1d6768735ef925203d38e3796e1e5c070926db
diff --git a/birb/birbstringobject.c b/birb/birbstringobject.c
new file mode 100644
--- /dev/null
+++ b/birb/birbstringobject.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023-2025 Birb Developers
+ *
+ * Birb is the legal property of its developers, whose names are too
+ * numerous to list here. Please refer to the AUTHORS file distributed
+ * with this source distribution
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "birbstringobject.h"
+
+struct _BirbStringObject {
+	GObject parent;
+
+	char *string;
+};
+
+enum {
+	PROP_0,
+	PROP_STRING,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_FINAL_TYPE(BirbStringObject, birb_string_object, G_TYPE_OBJECT)
+
+static void
+birb_string_object_finalize(GObject *obj) {
+	BirbStringObject *str_obj = BIRB_STRING_OBJECT(obj);
+
+	g_clear_pointer(&str_obj->string, g_free);
+
+	G_OBJECT_CLASS(birb_string_object_parent_class)->finalize(obj);
+}
+
+static void
+birb_string_object_get_property(GObject *obj, guint param_id, GValue *value,
+                                GParamSpec *pspec)
+{
+	BirbStringObject *str_obj = BIRB_STRING_OBJECT(obj);
+
+	switch(param_id) {
+	case PROP_STRING:
+		g_value_set_string(value, birb_string_object_get_string(str_obj));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
+birb_string_object_set_property(GObject *obj, guint param_id,
+                                const GValue *value, GParamSpec *pspec)
+{
+	BirbStringObject *str_obj = BIRB_STRING_OBJECT(obj);
+
+	switch(param_id) {
+	case PROP_STRING:
+		birb_string_object_set_string(str_obj, g_value_get_string(value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
+birb_string_object_init(G_GNUC_UNUSED BirbStringObject *str_obj) {
+}
+
+static void
+birb_string_object_class_init(BirbStringObjectClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->finalize = birb_string_object_finalize;
+	obj_class->get_property = birb_string_object_get_property;
+	obj_class->set_property = birb_string_object_set_property;
+
+	/**
+	 * BirbStringObject:string:
+	 *
+	 * The string that is being wrapped.
+	 *
+	 * Since: 0.3
+	 */
+	properties[PROP_STRING] = g_param_spec_string(
+		"string", NULL, NULL,
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+const char *
+birb_string_object_get_string(BirbStringObject *obj) {
+	g_return_val_if_fail(BIRB_IS_STRING_OBJECT(obj), NULL);
+
+	return obj->string;
+}
+
+BirbStringObject *
+birb_string_object_new(const char *str) {
+	g_return_val_if_fail(str != NULL, NULL);
+	g_return_val_if_fail(str[0] != '\0', NULL);
+
+	return g_object_new(BIRB_TYPE_STRING_OBJECT, "string", str, NULL);
+}
+
+void
+birb_string_object_set_string(BirbStringObject *obj, const char *str) {
+	g_return_if_fail(BIRB_IS_STRING_OBJECT(obj));
+	g_return_if_fail(str != NULL);
+	g_return_if_fail(str[0] != '\0');
+
+	if(g_set_str(&obj->string, str)) {
+		g_object_notify_by_pspec(G_OBJECT(obj), properties[PROP_STRING]);
+	}
+}
diff --git a/birb/birbstringobject.h b/birb/birbstringobject.h
new file mode 100644
--- /dev/null
+++ b/birb/birbstringobject.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023-2025 Birb Developers
+ *
+ * Birb is the legal property of its developers, whose names are too
+ * numerous to list here. Please refer to the AUTHORS file distributed
+ * with this source distribution
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(BIRB_GLOBAL_HEADER_INSIDE) && !defined(BIRB_COMPILATION)
+# error "only <birb.h> may be included directly"
+#endif
+
+#ifndef BIRB_STRING_OBJECT_H
+#define BIRB_STRING_OBJECT_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "birbversion.h"
+
+G_BEGIN_DECLS
+
+#define BIRB_TYPE_STRING_OBJECT (birb_string_object_get_type())
+
+/**
+ * BirbStringObject:
+ *
+ * A BirbStringObject is a simple [class@GObject.Object] that holds a string.
+ *
+ * It was created to hold strings in a [iface@Gio.ListModel].
+ *
+ * Since: 0.3
+ */
+BIRB_AVAILABLE_IN_0_3
+G_DECLARE_FINAL_TYPE(BirbStringObject, birb_string_object, BIRB, STRING_OBJECT,
+                     GObject)
+
+/**
+ * birb_string_object_get_string:
+ *
+ * Gets the string.
+ *
+ * Returns: The string.
+ *
+ * Since: 0.3
+ */
+BIRB_AVAILABLE_IN_0_3
+const char *birb_string_object_get_string(BirbStringObject *obj);
+
+/**
+ * birb_string_object_new:
+ * @str: the string
+ *
+ * Creates a new action menu.
+ *
+ * Returns: (transfer full): The new instance.
+ *
+ * Since: 0.3
+ */
+BIRB_AVAILABLE_IN_0_3
+BirbStringObject *birb_string_object_new(const char *str);
+
+/**
+ * birb_string_object_set_string:
+ * @str: the new string
+ *
+ * Sets the string that the object contains.
+ *
+ * Since: 0.3
+ */
+BIRB_AVAILABLE_IN_0_3
+void birb_string_object_set_string(BirbStringObject *obj, const char *str);
+
+G_END_DECLS
+
+#endif /* BIRB_STRING_OBJECT_H */
diff --git a/birb/meson.build b/birb/meson.build
--- a/birb/meson.build
+++ b/birb/meson.build
@@ -6,6 +6,7 @@
   'birbdatetime.c',
   'birbfilter.c',
   'birbqueuedoutputstream.c',
+  'birbstringobject.c',
   'birbversion.c',
 ]
 
@@ -15,6 +16,7 @@
   'birbdatetime.h',
   'birbfilter.h',
   'birbqueuedoutputstream.h',
+  'birbstringobject.h',
   'birbversion.h',
 ]
 
diff --git a/birb/tests/meson.build b/birb/tests/meson.build
--- a/birb/tests/meson.build
+++ b/birb/tests/meson.build
@@ -4,6 +4,7 @@
   'date_time',
   'filter',
   'queued_output_stream',
+  'string_object',
 ]
 
 foreach PROGRAM : PROGRAMS
diff --git a/birb/tests/test_string_object.c b/birb/tests/test_string_object.c
new file mode 100644
--- /dev/null
+++ b/birb/tests/test_string_object.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023-2025 Birb Developers
+ *
+ * Birb is the legal property of its developers, whose names are too
+ * numerous to list here. Please refer to the AUTHORS file distributed
+ * with this source distribution
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <glib.h>
+
+#include <birb.h>
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+test_birb_string_object_property_counter(GObject *obj,
+                                         G_GNUC_UNUSED GParamSpec *pspec,
+                                         gpointer data)
+{
+	guint *counter = data;
+
+	g_assert_true(BIRB_IS_STRING_OBJECT(obj));
+
+	*counter = *counter + 1;
+}
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_birb_string_object_new(void) {
+	BirbStringObject *obj = NULL;
+
+	obj = birb_string_object_new("birdy");
+	g_assert_true(BIRB_IS_STRING_OBJECT(obj));
+
+	g_assert_finalize_object(obj);
+}
+
+static void
+test_birb_string_object_properties(void) {
+	BirbStringObject *obj = NULL;
+	char *str = NULL;
+	guint counter = 0;
+
+	obj = g_object_new(
+		BIRB_TYPE_STRING_OBJECT,
+		"string", "birdy",
+		NULL);
+
+	counter = 0;
+	g_signal_connect(obj, "notify",
+	                 G_CALLBACK(test_birb_string_object_property_counter),
+	                 &counter);
+
+	g_object_get(
+		G_OBJECT(obj),
+		"string", &str,
+		NULL);
+
+	/* Make sure we didn't notify of anything. */
+	g_assert_cmpuint(counter, ==, 0);
+
+	g_assert_cmpstr(str, ==, "birdy");
+	g_clear_pointer(&str, g_free);
+
+	/* Now set our writable properties and verify that they only notify on
+	 * change.
+	 */
+	counter = 0;
+
+	birb_string_object_set_string(obj, "birdy");
+	birb_string_object_set_string(obj, "birdy the bird");
+
+	g_assert_cmpuint(counter, ==, 1);
+
+	/* And we're done. */
+	g_assert_finalize_object(obj);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+int
+main(int argc, char **argv) {
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_set_nonfatal_assertions();
+
+	g_test_add_func("/birb/string-object/new", test_birb_string_object_new);
+	g_test_add_func("/birb/string-object/properties",
+	                test_birb_string_object_properties);
+
+	return g_test_run();
+}
