diff --git a/birb/birbfilter.h b/birb/birbfilter.h
new file mode 100644
--- /dev/null
+++ b/birb/birbfilter.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 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_FILTER_H
+#define BIRB_FILTER_H
+
+#include <gio/gio.h>
+
+#include "birbversion.h"
+
+G_BEGIN_DECLS
+
+#define BIRB_TYPE_FILTER (birb_filter_get_type())
+
+/**
+ * BirbFilter:
+ *
+ * A BirbFilter is used to check if an item matches the filter.
+ *
+ * Since: 0.2
+ */
+BIRB_AVAILABLE_IN_0_2
+G_DECLARE_DERIVABLE_TYPE(BirbFilter, birb_filter, BIRB, FILTER, GObject)
+
+struct _BirbFilterClass {
+    /*< private >*/
+    GObjectClass parent;
+
+    /*< public >*/
+    gboolean (*matches)(BirbFilter *filter, GObject *item);
+
+    /*< private >*/
+    gpointer reserved[4];
+};
+
+/**
+ * birb_filter_changed:
+ * @filter: The instance.
+ *
+ * Emits [signal@Filter::changed].
+ *
+ * This should be called by sub classes when the filter changes.
+ *
+ * Since: 0.2
+ */
+BIRB_AVAILABLE_IN_0_2
+void birb_filter_changed(BirbFilter *filter);
+
+/**
+ * birb_filter_matches:
+ * @filter: The instance.
+ * @item: The item.
+ *
+ * Checks if @item matches what @filter is looking for.
+ *
+ * Returns: %TRUE if the @item matches, otherwise %FALSE.
+ *
+ * Since: 0.2
+ */
+BIRB_AVAILABLE_IN_0_2
+gboolean birb_filter_matches(BirbFilter *filter, GObject *item);
+
+G_END_DECLS
+
+#endif /* BIRB_FILTER_H */
# HG changeset patch
# Node ID 934b85479d1a
# Parent  76c83a79e528
diff --git a/birb/birbfilter.c b/birb/birbfilter.c
new file mode 100644
--- /dev/null
+++ b/birb/birbfilter.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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 "birbfilter.h"
+
+enum {
+    SIG_CHANGED,
+    N_SIGNALS,
+};
+static guint signals[N_SIGNALS] = {0, };
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_TYPE(BirbFilter, birb_filter, G_TYPE_OBJECT)
+
+static void
+birb_filter_init(G_GNUC_UNUSED BirbFilter *filter) {
+}
+
+static void
+birb_filter_class_init(BirbFilterClass *klass) {
+    /**
+     * BirbFilter::changed:
+     *
+     * Emitted when the filter changes in a way that filter should be performed
+     * again.
+     *
+     * Sub classes should call [method@Filter.changed] to emit this signal.
+     *
+     * Since: 0.1
+     */
+    signals[SIG_CHANGED] = g_signal_new_class_handler(
+        "changed",
+        G_OBJECT_CLASS_TYPE(klass),
+        G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        G_TYPE_NONE,
+        0);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+void
+birb_filter_changed(BirbFilter *filter) {
+    g_return_if_fail(BIRB_IS_FILTER(filter));
+
+    g_signal_emit(filter, signals[SIG_CHANGED], 0);
+}
+
+gboolean
+birb_filter_matches(BirbFilter *filter, GObject *item) {
+    BirbFilterClass *klass = NULL;
+
+    g_return_val_if_fail(BIRB_IS_FILTER(filter), FALSE);
+
+    klass = BIRB_FILTER_GET_CLASS(filter);
+    if(klass != NULL && klass->matches != NULL) {
+        return klass->matches(filter, item);
+    }
+
+    return FALSE;
+}
diff --git a/birb/birbversion.h b/birb/birbversion.h
--- a/birb/birbversion.h
+++ b/birb/birbversion.h
@@ -148,6 +148,30 @@
 #define BIRB_AVAILABLE_TYPE_IN_0_1
 #endif
 
+/**
+ * BIRB_VERSION_0_2:
+ *
+ * A macro that evaluates to the 0.2 version of birb, in a format that can be
+ * used by the C pre-processor.
+ *
+ * Since: 0.2
+ */
+#define BIRB_VERSION_0_2 (G_ENCODE_VERSION(0, 2))
+
+#if BIRB_VERSION_MAX_ALLOWED < BIRB_VERSION_0_2
+#define BIRB_AVAILABLE_IN_0_2 BIRB_UNAVAILABLE(0, 2)
+#define BIRB_AVAILABLE_STATIC_INLINE_IN_0_2 BIRB_UNAVAILABLE_STATIC_INLINE(0, 2)
+#define BIRB_AVAILABLE_MACRO_IN_0_2 BIRB_UNAVAILABLE_MACRO(0, 2)
+#define BIRB_AVAILABLE_ENUMERATOR_IN_0_2 BIRB_UNAVAILABLE_ENUMERATOR(0, 2)
+#define BIRB_AVAILABLE_TYPE_IN_0_2 BIRB_UNAVAILABLE_TYPE(0, 2)
+#else
+#define BIRB_AVAILABLE_IN_0_2 _BIRB_EXTERN
+#define BIRB_AVAILABLE_STATIC_INLINE_IN_0_2
+#define BIRB_AVAILABLE_MACRO_IN_0_2
+#define BIRB_AVAILABLE_ENUMERATOR_IN_0_2
+#define BIRB_AVAILABLE_TYPE_IN_0_2
+#endif
+
 G_BEGIN_DECLS
 
 /**
diff --git a/birb/meson.build b/birb/meson.build
--- a/birb/meson.build
+++ b/birb/meson.build
@@ -3,6 +3,7 @@
 BIRB_SOURCES = [
   'birbactionmenu.c',
   'birbdatetime.c',
+  'birbfilter.c',
   'birbqueuedoutputstream.c',
   'birbversion.c',
 ]
@@ -10,6 +11,7 @@
 BIRB_HEADERS = [
   'birbactionmenu.h',
   'birbdatetime.h',
+  'birbfilter.h',
   'birbqueuedoutputstream.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
@@ -1,6 +1,7 @@
 PROGRAMS = [
   'action_menu',
   'date_time',
+  'filter',
   'queued_output_stream',
 ]
 
diff --git a/birb/tests/test_filter.c b/birb/tests/test_filter.c
new file mode 100644
--- /dev/null
+++ b/birb/tests/test_filter.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 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>
+
+/******************************************************************************
+ * Changed Tests
+ *****************************************************************************/
+static void
+test_filter_changed_cb(BirbFilter *filter, gpointer data) {
+	guint *counter = data;
+
+	g_assert_true(BIRB_IS_FILTER(filter));
+
+	*counter = *counter + 1;
+}
+
+static void
+test_filter_changed(void) {
+	BirbFilter *filter = NULL;
+	guint counter = 0;
+
+	filter = g_object_new(BIRB_TYPE_FILTER, NULL);
+	g_assert_true(BIRB_IS_FILTER(filter));
+
+	g_signal_connect(filter, "changed", G_CALLBACK(test_filter_changed_cb),
+	                 &counter);
+
+	birb_filter_changed(filter);
+	g_assert_cmpuint(counter, ==, 1);
+
+	g_assert_finalize_object(filter);
+}
+
+/******************************************************************************
+ * Matches tests
+ *****************************************************************************/
+static gboolean
+test_filter_matches_impl(BirbFilter *filter, GObject *item) {
+	gpointer ret = NULL;
+
+	g_assert_true(BIRB_IS_FILTER(filter));
+	g_assert_true(G_IS_OBJECT(item));
+
+	ret = g_object_get_data(G_OBJECT(filter), "matches");
+
+	return GPOINTER_TO_INT(ret);
+}
+
+static void
+test_filter_matches(gconstpointer data) {
+	BirbFilter *filter = NULL;
+	BirbFilterClass *klass = NULL;
+	GObject *item = NULL;
+	gboolean actual = FALSE;
+	gboolean expected = GPOINTER_TO_INT(data);
+
+	filter = g_object_new(BIRB_TYPE_FILTER, NULL);
+	g_assert_true(BIRB_IS_FILTER(filter));
+
+	klass = BIRB_FILTER_GET_CLASS(filter);
+	klass->matches = test_filter_matches_impl;
+
+	g_object_set_data(G_OBJECT(filter), "matches", (gpointer)data);
+
+	item = g_object_new(G_TYPE_OBJECT, NULL);
+
+	actual = birb_filter_matches(filter, item);
+	if(expected) {
+		g_assert_true(actual);
+	} else {
+		g_assert_false(actual);
+	}
+
+	g_assert_finalize_object(item);
+	g_assert_finalize_object(filter);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+int
+main(int argc, char **argv) {
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_set_nonfatal_assertions();
+
+	g_test_add_func("/filter/changed", test_filter_changed);
+	g_test_add_data_func("/filter/matches/true", GINT_TO_POINTER(TRUE),
+	                     test_filter_matches);
+	g_test_add_data_func("/filter/matches/false", GINT_TO_POINTER(FALSE),
+	                     test_filter_matches);
+
+	return g_test_run();
+}
diff --git a/meson.build b/meson.build
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('birb', 'c',
-  version : '0.1.1-dev',
+  version : '0.2.0-dev',
   meson_version : '>=1.0.0',
   default_options : ['c_std=c17', 'warning_level=2'])
 
