Index: uspace/lib/tbarcfg/include/ipc/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/include/ipc/tbarcfg.h	(revision ee3b28a97324351b0acfb1161a2ed94632eab95c)
+++ uspace/lib/tbarcfg/include/ipc/tbarcfg.h	(revision ee3b28a97324351b0acfb1161a2ed94632eab95c)
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2024 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libtbarcfg
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBTBARCFG_IPC_TBARCFG_H
+#define LIBTBARCFG_IPC_TBARCFG_H
+
+#include <ipc/common.h>
+
+typedef enum {
+	TBARCFG_NOTIFY_NOTIFY = IPC_FIRST_USER_METHOD
+} tbarcfg_notify_request_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/tbarcfg/include/tbarcfg/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/include/tbarcfg/tbarcfg.h	(revision e63e74a596d3dab316c056385061a0ffc336c62b)
+++ uspace/lib/tbarcfg/include/tbarcfg/tbarcfg.h	(revision ee3b28a97324351b0acfb1161a2ed94632eab95c)
@@ -42,4 +42,6 @@
 #include <types/tbarcfg/tbarcfg.h>
 
+#define TBARCFG_NOTIFY_DEFAULT "tbarcfg-notif"
+
 extern errno_t tbarcfg_create(const char *, tbarcfg_t **);
 extern errno_t tbarcfg_open(const char *, tbarcfg_t **);
@@ -63,4 +65,8 @@
 extern errno_t smenu_entry_move_up(smenu_entry_t *);
 extern errno_t smenu_entry_move_down(smenu_entry_t *);
+extern errno_t tbarcfg_listener_create(const char *, void (*)(void *),
+    void *, tbarcfg_listener_t **);
+extern void tbarcfg_listener_destroy(tbarcfg_listener_t *);
+extern errno_t tbarcfg_notify(const char *);
 
 #endif
Index: uspace/lib/tbarcfg/include/types/tbarcfg/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/include/types/tbarcfg/tbarcfg.h	(revision e63e74a596d3dab316c056385061a0ffc336c62b)
+++ uspace/lib/tbarcfg/include/types/tbarcfg/tbarcfg.h	(revision ee3b28a97324351b0acfb1161a2ed94632eab95c)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -43,4 +43,7 @@
 typedef struct smenu_entry smenu_entry_t;
 
+struct tbarcfg_listener;
+typedef struct tbarcfg_listener tbarcfg_listener_t;
+
 #endif
 
Index: uspace/lib/tbarcfg/private/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/private/tbarcfg.h	(revision e63e74a596d3dab316c056385061a0ffc336c62b)
+++ uspace/lib/tbarcfg/private/tbarcfg.h	(revision ee3b28a97324351b0acfb1161a2ed94632eab95c)
@@ -71,4 +71,12 @@
 };
 
+/** Taskbar configuration listener */
+typedef struct tbarcfg_listener {
+	/** Notification callback */
+	void (*cb)(void *);
+	/** Callback argument */
+	void *arg;
+} tbarcfg_listener_t;
+
 extern errno_t smenu_entry_new(tbarcfg_t *, sif_node_t *, const char *,
     const char *, bool, smenu_entry_t **);
Index: uspace/lib/tbarcfg/src/tbarcfg.c
===================================================================
--- uspace/lib/tbarcfg/src/tbarcfg.c	(revision e63e74a596d3dab316c056385061a0ffc336c62b)
+++ uspace/lib/tbarcfg/src/tbarcfg.c	(revision ee3b28a97324351b0acfb1161a2ed94632eab95c)
@@ -34,10 +34,17 @@
  */
 
+#include <async.h>
 #include <errno.h>
 #include <sif.h>
+#include <ipc/tbarcfg.h>
+#include <loc.h>
+#include <task.h>
 #include <tbarcfg/tbarcfg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <str.h>
 #include "../private/tbarcfg.h"
+
+static void tbarcfg_notify_conn(ipc_call_t *, void *);
 
 /** Create taskbar configuration.
@@ -784,4 +791,176 @@
 }
 
+/** Create taskbar configuration listener.
+ *
+ * Listens for taskbar configuration change notifications.
+ *
+ * @param nchan Notification channel (TBARCFG_NOTIFY_DEFAULT)
+ * @param rlst Place to store pointer to new listener
+ * @return EOK on success or an error code
+ */
+errno_t tbarcfg_listener_create(const char *nchan, void (*cb)(void *),
+    void *arg, tbarcfg_listener_t **rlst)
+{
+	tbarcfg_listener_t *lst;
+	service_id_t svcid = 0;
+	loc_srv_t *srv = NULL;
+	task_id_t taskid;
+	char *svcname = NULL;
+	category_id_t catid;
+	port_id_t port;
+	int rv;
+	errno_t rc;
+
+	lst = calloc(1, sizeof(tbarcfg_listener_t));
+	if (lst == NULL)
+		return ENOMEM;
+
+	lst->cb = cb;
+	lst->arg = arg;
+
+	rc = async_create_port(INTERFACE_TBARCFG_NOTIFY,
+	    tbarcfg_notify_conn, (void *)lst, &port);
+	if (rc != EOK)
+		goto error;
+
+	rc = loc_server_register("tbarcfg-listener", &srv);
+	if (rc != EOK)
+		goto error;
+
+	taskid = task_get_id();
+
+	rv = asprintf(&svcname, "tbarcfg/%u", (unsigned)taskid);
+	if (rv < 0) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	rc = loc_service_register(srv, svcname, &svcid);
+	if (rc != EOK)
+		goto error;
+
+	rc = loc_category_get_id(nchan, &catid, 0);
+	if (rc != EOK)
+		goto error;
+
+	rc = loc_service_add_to_cat(srv, svcid, catid);
+	if (rc != EOK)
+		goto error;
+
+	*rlst = lst;
+	return EOK;
+error:
+	if (svcid != 0)
+		loc_service_unregister(srv, svcid);
+	if (srv != NULL)
+		loc_server_unregister(srv);
+	if (svcname != NULL)
+		free(svcname);
+	return rc;
+}
+
+/** Destroy taskbar configuration listener.
+ *
+ * @param lst Listener
+ */
+void tbarcfg_listener_destroy(tbarcfg_listener_t *lst)
+{
+	free(lst);
+}
+
+/** Send taskbar configuration notification to a particular service ID.
+ *
+ * @param svcid Service ID
+ * @return EOK on success or an error code
+ */
+static errno_t tbarcfg_notify_svc(service_id_t svcid)
+{
+	async_sess_t *sess;
+	async_exch_t *exch;
+	errno_t rc;
+
+	sess = loc_service_connect(svcid, INTERFACE_TBARCFG_NOTIFY, 0);
+	if (sess == NULL)
+		return EIO;
+
+	exch = async_exchange_begin(sess);
+	rc = async_req_0_0(exch, TBARCFG_NOTIFY_NOTIFY);
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_hangup(sess);
+		return rc;
+	}
+
+	async_exchange_end(exch);
+	async_hangup(sess);
+	return EOK;
+}
+
+/** Send taskbar configuration change notification.
+ *
+ * @param nchan Notification channel (TBARCFG_NOTIFY_DEFAULT)
+ */
+errno_t tbarcfg_notify(const char *nchan)
+{
+	errno_t rc;
+	category_id_t catid;
+	service_id_t *svcs = NULL;
+	size_t count, i;
+
+	rc = loc_category_get_id(nchan, &catid, 0);
+	if (rc != EOK)
+		return rc;
+
+	rc = loc_category_get_svcs(catid, &svcs, &count);
+	if (rc != EOK)
+		return rc;
+
+	for (i = 0; i < count; i++) {
+		rc = tbarcfg_notify_svc(svcs[i]);
+		if (rc != EOK)
+			goto error;
+	}
+
+	free(svcs);
+	return EOK;
+error:
+	free(svcs);
+	return rc;
+}
+
+/** Taskbar configuration connection handler.
+ *
+ * @param icall Initial call
+ * @param arg Argument (tbarcfg_listener_t *)
+ */
+static void tbarcfg_notify_conn(ipc_call_t *icall, void *arg)
+{
+	tbarcfg_listener_t *lst = (tbarcfg_listener_t *)arg;
+
+	/* Accept the connection */
+	async_accept_0(icall);
+
+	while (true) {
+		ipc_call_t call;
+		async_get_call(&call);
+		sysarg_t method = ipc_get_imethod(&call);
+
+		if (!method) {
+			/* The other side has hung up */
+			async_answer_0(&call, EOK);
+			return;
+		}
+
+		switch (method) {
+		case TBARCFG_NOTIFY_NOTIFY:
+			lst->cb(lst->arg);
+			async_answer_0(&call, EOK);
+			break;
+		default:
+			async_answer_0(&call, EINVAL);
+		}
+	}
+}
+
 /** @}
  */
Index: uspace/lib/tbarcfg/test/tbarcfg.c
===================================================================
--- uspace/lib/tbarcfg/test/tbarcfg.c	(revision e63e74a596d3dab316c056385061a0ffc336c62b)
+++ uspace/lib/tbarcfg/test/tbarcfg.c	(revision ee3b28a97324351b0acfb1161a2ed94632eab95c)
@@ -30,4 +30,5 @@
 #include <pcut/pcut.h>
 #include <tbarcfg/tbarcfg.h>
+#include <stdbool.h>
 #include <stdio.h>
 
@@ -35,4 +36,10 @@
 
 PCUT_TEST_SUITE(tbarcfg);
+
+typedef struct {
+	bool notified;
+} tbarcfg_test_resp_t;
+
+static void test_cb(void *);
 
 /** Creating, opening and closing taskbar configuration */
@@ -554,3 +561,32 @@
 }
 
+/** Notifications can be delivered from tbarcfg_notify() to a listener. */
+PCUT_TEST(notify)
+{
+	errno_t rc;
+	tbarcfg_listener_t *lst;
+	tbarcfg_test_resp_t test_resp;
+
+	test_resp.notified = false;
+
+	printf("create listener resp=%p\n", (void *)&test_resp);
+	rc = tbarcfg_listener_create(TBARCFG_NOTIFY_DEFAULT,
+	    test_cb, &test_resp, &lst);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_TRUE(test_resp.notified);
+	tbarcfg_listener_destroy(lst);
+}
+
+static void test_cb(void *arg)
+{
+	tbarcfg_test_resp_t *resp = (tbarcfg_test_resp_t *)arg;
+
+	printf("test_cb: executing resp=%p\n", (void *)resp);
+	resp->notified = true;
+}
+
 PCUT_EXPORT(tbarcfg);
Index: uspace/lib/ui/src/menuentry.c
===================================================================
--- uspace/lib/ui/src/menuentry.c	(revision e63e74a596d3dab316c056385061a0ffc336c62b)
+++ uspace/lib/ui/src/menuentry.c	(revision ee3b28a97324351b0acfb1161a2ed94632eab95c)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -145,5 +145,20 @@
 		return;
 
+	mentry->menu->total_h -= ui_menu_entry_height(mentry);
+	/* NOTE: max_caption_w/max_shortcut_w not updated (speed) */
+
 	list_remove(&mentry->lentries);
+
+	/*
+	 * If we emptied the menu, reset accumulated dims so they
+	 * can be correctly calculated when (if) the menu is
+	 * re-populated.
+	 */
+	if (list_empty(&mentry->menu->entries)) {
+		mentry->menu->total_h = 0;
+		mentry->menu->max_caption_w = 0;
+		mentry->menu->max_shortcut_w = 0;
+	}
+
 	free(mentry->caption);
 	free(mentry);
