Index: uspace/app/calculator/calculator.c
===================================================================
--- uspace/app/calculator/calculator.c	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/app/calculator/calculator.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -887,5 +887,5 @@
 	}
 
-	rc = ui_menu_bar_create(ui_res, &calc.menubar);
+	rc = ui_menu_bar_create(ui, ui_res, &calc.menubar);
 	if (rc != EOK) {
 		printf("Error creating menu bar.\n");
Index: uspace/app/uidemo/uidemo.c
===================================================================
--- uspace/app/uidemo/uidemo.c	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/app/uidemo/uidemo.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -331,5 +331,5 @@
 	}
 
-	rc = ui_menu_bar_create(ui_res, &demo.mbar);
+	rc = ui_menu_bar_create(ui, ui_res, &demo.mbar);
 	if (rc != EOK) {
 		printf("Error creating menu bar.\n");
Index: uspace/lib/ui/include/types/ui/popup.h
===================================================================
--- uspace/lib/ui/include/types/ui/popup.h	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
+++ uspace/lib/ui/include/types/ui/popup.h	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2021 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 libui
+ * @{
+ */
+/**
+ * @file Popup window
+ */
+
+#ifndef _UI_TYPES_POPUP_H
+#define _UI_TYPES_POPUP_H
+
+#include <errno.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+
+struct ui_popup;
+typedef struct ui_popup ui_popup_t;
+
+/** Popup window parameters */
+typedef struct {
+	/** Popup rectangle */
+	gfx_rect_t rect;
+	/** Placement rectangle close to which popup should be placed */
+	gfx_rect_t place;
+} ui_popup_params_t;
+
+/** Popup callbacks */
+typedef struct ui_popup_cb {
+	void (*close)(ui_popup_t *, void *);
+	void (*kbd)(ui_popup_t *, void *, kbd_event_t *);
+	errno_t (*paint)(ui_popup_t *, void *);
+	void (*pos)(ui_popup_t *, void *, pos_event_t *);
+} ui_popup_cb_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/menu.h
===================================================================
--- uspace/lib/ui/include/ui/menu.h	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/include/ui/menu.h	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -50,6 +50,7 @@
 extern const char *ui_menu_caption(ui_menu_t *);
 extern void ui_menu_get_rect(ui_menu_t *, gfx_coord2_t *, gfx_rect_t *);
+extern errno_t ui_menu_open(ui_menu_t *, gfx_rect_t *);
+extern void ui_menu_close(ui_menu_t *);
 extern errno_t ui_menu_paint(ui_menu_t *, gfx_coord2_t *);
-extern errno_t ui_menu_unpaint(ui_menu_t *);
 extern ui_evclaim_t ui_menu_pos_event(ui_menu_t *, gfx_coord2_t *,
     pos_event_t *);
Index: uspace/lib/ui/include/ui/menubar.h
===================================================================
--- uspace/lib/ui/include/ui/menubar.h	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/include/ui/menubar.h	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -44,6 +44,7 @@
 #include <types/ui/event.h>
 #include <types/ui/resource.h>
+#include <types/ui/ui.h>
 
-extern errno_t ui_menu_bar_create(ui_resource_t *,
+extern errno_t ui_menu_bar_create(ui_t *, ui_resource_t *,
     ui_menu_bar_t **);
 extern void ui_menu_bar_destroy(ui_menu_bar_t *);
Index: uspace/lib/ui/include/ui/popup.h
===================================================================
--- uspace/lib/ui/include/ui/popup.h	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
+++ uspace/lib/ui/include/ui/popup.h	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2021 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 libui
+ * @{
+ */
+/**
+ * @file Popup window
+ */
+
+#ifndef _UI_POPUP_H
+#define _UI_POPUP_H
+
+#include <errno.h>
+#include <gfx/context.h>
+#include <types/ui/control.h>
+#include <types/ui/ui.h>
+#include <types/ui/popup.h>
+#include <types/ui/resource.h>
+
+extern void ui_popup_params_init(ui_popup_params_t *);
+extern errno_t ui_popup_create(ui_t *, ui_popup_params_t *,
+    ui_popup_t **);
+extern void ui_popup_set_cb(ui_popup_t *, ui_popup_cb_t *, void *);
+extern void ui_popup_destroy(ui_popup_t *);
+extern void ui_popup_add(ui_popup_t *, ui_control_t *);
+extern void ui_popup_remove(ui_popup_t *, ui_control_t *);
+extern ui_resource_t *ui_popup_get_res(ui_popup_t *);
+extern gfx_context_t *ui_popup_get_gc(ui_popup_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/meson.build
===================================================================
--- uspace/lib/ui/meson.build	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/meson.build	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -42,4 +42,5 @@
 	'src/paint.c',
 	'src/pbutton.c',
+	'src/popup.c',
 	'src/rbutton.c',
 	'src/resource.c',
@@ -64,4 +65,5 @@
 	'test/paint.c',
 	'test/pbutton.c',
+	'test/popup.c',
 	'test/rbutton.c',
 	'test/resource.c',
Index: uspace/lib/ui/private/menu.h
===================================================================
--- uspace/lib/ui/private/menu.h	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/private/menu.h	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -42,4 +42,5 @@
 #include <stdbool.h>
 #include <types/ui/menu.h>
+#include <types/ui/resource.h>
 
 /** Actual structure of menu.
@@ -54,6 +55,6 @@
 	/** Caption */
 	char *caption;
-	/** Menu is currently open */
-	bool open;
+	/** Popup window or @c NULL if menu is not currently open */
+	struct ui_popup *popup;
 	/** Selected menu entry or @c NULL */
 	struct ui_menu_entry *selected;
@@ -80,4 +81,5 @@
 
 extern void ui_menu_get_geom(ui_menu_t *, gfx_coord2_t *, ui_menu_geom_t *);
+extern ui_resource_t *ui_menu_get_res(ui_menu_t *);
 
 #endif
Index: uspace/lib/ui/private/menubar.h
===================================================================
--- uspace/lib/ui/private/menubar.h	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/private/menubar.h	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -50,5 +50,7 @@
 	/** Base control object */
 	struct ui_control *control;
-	/** UI resource */
+	/** UI */
+	struct ui *ui;
+	/** UI resource (for window containing menu bar) */
 	struct ui_resource *res;
 	/** Menu bar rectangle */
@@ -56,11 +58,9 @@
 	/** Selected menu or @c NULL */
 	struct ui_menu *selected;
-	/** Position of selected entry */
-	gfx_coord2_t sel_pos;
 	/** List of menus (ui_menu_t) */
 	list_t menus;
 };
 
-extern void ui_menu_bar_select(ui_menu_bar_t *, gfx_coord2_t *, ui_menu_t *);
+extern void ui_menu_bar_select(ui_menu_bar_t *, gfx_rect_t *, ui_menu_t *);
 
 #endif
Index: uspace/lib/ui/private/popup.h
===================================================================
--- uspace/lib/ui/private/popup.h	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
+++ uspace/lib/ui/private/popup.h	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2021 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 libui
+ * @{
+ */
+/**
+ * @file Popup structure
+ *
+ */
+
+#ifndef _UI_PRIVATE_POPUP_H
+#define _UI_PRIVATE_POPUP_H
+
+#include <gfx/coord.h>
+#include <types/ui/window.h>
+
+/** Actual structure of popup window.
+ *
+ * This is private to libui.
+ */
+struct ui_popup {
+	/** Containing user interface */
+	struct ui *ui;
+	/** Callbacks */
+	struct ui_popup_cb *cb;
+	/** Callback argument */
+	void *arg;
+	/** Window */
+	struct ui_window *window;
+	/** Placement rectangle */
+	gfx_rect_t place;
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/src/menu.c
===================================================================
--- uspace/lib/ui/src/menu.c	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/src/menu.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -45,4 +45,5 @@
 #include <ui/control.h>
 #include <ui/paint.h>
+#include <ui/popup.h>
 #include <ui/menu.h>
 #include <ui/menuentry.h>
@@ -59,4 +60,10 @@
 };
 
+static void ui_menu_popup_pos(ui_popup_t *, void *, pos_event_t *);
+
+static ui_popup_cb_t ui_menu_popup_cb = {
+	.pos = ui_menu_popup_pos
+};
+
 /** Create new menu.
  *
@@ -203,4 +210,56 @@
 }
 
+/** Get UI resource from menu.
+ *
+ * @param menu Menu
+ * @return UI resource
+ */
+ui_resource_t *ui_menu_get_res(ui_menu_t *menu)
+{
+	return ui_popup_get_res(menu->popup);
+}
+
+/** Open menu.
+ *
+ * @param menu Menu
+ * @param prect Parent rectangle around which the menu should be placed
+ */
+errno_t ui_menu_open(ui_menu_t *menu, gfx_rect_t *prect)
+{
+	ui_popup_t *popup = NULL;
+	ui_popup_params_t params;
+	ui_menu_geom_t geom;
+	gfx_coord2_t mpos;
+	errno_t rc;
+
+	/* Determine menu dimensions */
+
+	mpos.x = 0;
+	mpos.y = 0;
+	ui_menu_get_geom(menu, &mpos, &geom);
+
+	ui_popup_params_init(&params);
+	params.rect = geom.outer_rect;
+
+	rc = ui_popup_create(menu->mbar->ui, &params, &popup);
+	if (rc != EOK)
+		return rc;
+
+	menu->popup = popup;
+	ui_popup_set_cb(popup, &ui_menu_popup_cb, menu);
+
+	return ui_menu_paint(menu, &mpos);
+}
+
+/** Close menu.
+ *
+ * @param menu Menu
+ */
+void ui_menu_close(ui_menu_t *menu)
+{
+	ui_popup_destroy(menu->popup);
+	menu->popup = NULL;
+}
+
 /** Paint menu.
  *
@@ -218,5 +277,5 @@
 	errno_t rc;
 
-	res = menu->mbar->res;
+	res = ui_menu_get_res(menu);
 	ui_menu_get_geom(menu, spos, &geom);
 
@@ -261,15 +320,4 @@
 error:
 	return rc;
-}
-
-/** Unpaint menu.
- *
- * @param menu Menu
- * @return EOK on success or an error code
- */
-errno_t ui_menu_unpaint(ui_menu_t *menu)
-{
-	ui_resource_expose(menu->mbar->res);
-	return EOK;
 }
 
@@ -312,6 +360,6 @@
 	} else {
 		/* Press outside menu - close it */
-		if (event->type == POS_PRESS)
-			ui_menu_bar_select(menu->mbar, NULL, NULL);
+//		if (event->type == POS_PRESS)
+//			ui_menu_bar_select(menu->mbar, NULL, NULL);
 	}
 
@@ -319,4 +367,20 @@
 }
 
+/** Handle position event in menu popup window.
+ *
+ * @param popup Menu popup window
+ * @param arg Argument (ui_menu_t *)
+ * @param event Position event
+ */
+static void ui_menu_popup_pos(ui_popup_t *popup, void *arg, pos_event_t *event)
+{
+	ui_menu_t *menu = (ui_menu_t *)arg;
+	gfx_coord2_t spos;
+
+	spos.x = 0;
+	spos.y = 0;
+	ui_menu_pos_event(menu, &spos, event);
+}
+
 /** @}
  */
Index: uspace/lib/ui/src/menubar.c
===================================================================
--- uspace/lib/ui/src/menubar.c	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/src/menubar.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -72,9 +72,11 @@
 /** Create new menu bar.
  *
+ * @param ui UI
  * @param res UI resource
  * @param rmbar Place to store pointer to new menu bar
  * @return EOK on success, ENOMEM if out of memory
  */
-errno_t ui_menu_bar_create(ui_resource_t *res, ui_menu_bar_t **rmbar)
+errno_t ui_menu_bar_create(ui_t *ui, ui_resource_t *res,
+    ui_menu_bar_t **rmbar)
 {
 	ui_menu_bar_t *mbar;
@@ -91,4 +93,5 @@
 	}
 
+	mbar->ui = ui;
 	mbar->res = res;
 	list_initialize(&mbar->menus);
@@ -232,11 +235,10 @@
  *
  * @param mbar Menu bar
- * @param pos Position (top-left corner) of menu bar entry
+ * @param rect Menu bar entry rectangle
  * @param menu Menu to select (or deselect if selected) or @c NULL
  */
-void ui_menu_bar_select(ui_menu_bar_t *mbar, gfx_coord2_t *pos,
+void ui_menu_bar_select(ui_menu_bar_t *mbar, gfx_rect_t *rect,
     ui_menu_t *menu)
 {
-	gfx_coord2_t spos;
 	ui_menu_t *old_menu;
 
@@ -248,19 +250,12 @@
 		mbar->selected = NULL;
 
-	/* Need to clear the menu has just been closed */
+	/* Close previously open menu */
 	if (old_menu != NULL)
-		(void) ui_menu_unpaint(old_menu);
+		(void) ui_menu_close(old_menu);
 
 	(void) ui_menu_bar_paint(mbar);
 
 	if (mbar->selected != NULL) {
-		/* Cache position of selected entry */
-		mbar->sel_pos = *pos;
-
-		/* Position menu under selected menu bar entry */
-		spos.x = pos->x;
-		spos.y = mbar->rect.p1.y;
-
-		(void) ui_menu_paint(mbar->selected, &spos);
+		(void) ui_menu_open(mbar->selected, rect);
 	}
 }
@@ -275,5 +270,4 @@
 {
 	gfx_coord2_t pos;
-	gfx_coord2_t spos;
 	gfx_rect_t rect;
 	ui_menu_t *menu;
@@ -282,5 +276,4 @@
 	gfx_coord_t hpad;
 	gfx_coord2_t ppos;
-	ui_evclaim_t claimed;
 
 	ppos.x = event->hpos;
@@ -307,20 +300,8 @@
 		if (event->type == POS_PRESS &&
 		    gfx_pix_inside_rect(&ppos, &rect)) {
-			ui_menu_bar_select(mbar, &pos, menu);
+			ui_menu_bar_select(mbar, &rect, menu);
 			return ui_claimed;
 		}
 
-		if (menu == mbar->selected) {
-			/* Open menu is positioned below menu bar entry */
-			spos.x = pos.x;
-			spos.y = mbar->rect.p1.y;
-
-			ui_menu_get_rect(menu, &spos, &rect);
-
-			claimed = ui_menu_pos_event(menu, &spos, event);
-			if (claimed == ui_claimed)
-				return ui_claimed;
-		}
-
 		pos.x += width;
 		menu = ui_menu_next(menu);
@@ -336,5 +317,5 @@
 void ui_menu_bar_unfocus(ui_menu_bar_t *mbar)
 {
-	ui_menu_bar_select(mbar, NULL, NULL);
+//	ui_menu_bar_select(mbar, NULL, NULL);
 }
 
Index: uspace/lib/ui/src/menuentry.c
===================================================================
--- uspace/lib/ui/src/menuentry.c	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/src/menuentry.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -203,4 +203,10 @@
 	ui_resource_t *res;
 
+	/*
+	 * This needs to work even if the menu is not open, so we cannot
+	 * use the menu's resource, which is only created after the menu
+	 * is open (and its window is created). Use the menu bar's
+	 * resource instead.
+	 */
 	res = mentry->menu->mbar->res;
 
@@ -223,4 +229,10 @@
 	gfx_coord_t width;
 
+	/*
+	 * This needs to work even if the menu is not open, so we cannot
+	 * use the menu's resource, which is only created after the menu
+	 * is open (and its window is created). Use the menu bar's
+	 * resource instead.
+	 */
 	res = menu->mbar->res;
 
@@ -256,4 +268,10 @@
 	gfx_coord_t vpad;
 
+	/*
+	 * This needs to work even if the menu is not open, so we cannot
+	 * use the menu's resource, which is only created after the menu
+	 * is open (and its window is created). Use the menu bar's
+	 * resource instead.
+	 */
 	res = mentry->menu->mbar->res;
 
@@ -294,5 +312,5 @@
 	errno_t rc;
 
-	res = mentry->menu->mbar->res;
+	res = ui_menu_get_res(mentry->menu);
 
 	ui_menu_entry_get_geom(mentry, pos, &geom);
@@ -379,6 +397,5 @@
 	if (mentry->inside) {
 		/* Close menu */
-		ui_menu_bar_select(mentry->menu->mbar,
-		    &mentry->menu->mbar->sel_pos, NULL);
+		ui_menu_bar_select(mentry->menu->mbar, NULL, NULL);
 
 		/* Call back */
@@ -487,5 +504,5 @@
 	gfx_coord_t width;
 
-	res = mentry->menu->mbar->res;
+	res = ui_menu_get_res(mentry->menu);
 
 	if (res->textmode) {
Index: uspace/lib/ui/src/popup.c
===================================================================
--- uspace/lib/ui/src/popup.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
+++ uspace/lib/ui/src/popup.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2021 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 libui
+ * @{
+ */
+/**
+ * @file Popup window
+ */
+
+#include <errno.h>
+#include <gfx/context.h>
+//#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <mem.h>
+#include <stdlib.h>
+#include <ui/control.h>
+#include <ui/popup.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "../private/popup.h"
+
+static void ui_popup_window_pos(ui_window_t *, void *, pos_event_t *);
+
+static ui_window_cb_t ui_popup_window_cb = {
+	.pos = ui_popup_window_pos
+};
+
+/** Initialize popup parameters structure.
+ *
+ * Popup parameters structure must always be initialized using this function
+ * first.
+ *
+ * @param params Popup parameters structure
+ */
+void ui_popup_params_init(ui_popup_params_t *params)
+{
+	memset(params, 0, sizeof(ui_popup_params_t));
+}
+
+/** Create new popup window.
+ *
+ * @param ui User interface
+ * @param params Popup parameters
+ * @param rpopup Place to store pointer to new popup window
+ * @return EOK on success or an error code
+ */
+errno_t ui_popup_create(ui_t *ui, ui_popup_params_t *params,
+    ui_popup_t **rpopup)
+{
+	ui_popup_t *popup;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t wparams;
+	errno_t rc;
+
+	popup = calloc(1, sizeof(ui_popup_t));
+	if (popup == NULL)
+		return ENOMEM;
+
+	ui_wnd_params_init(&wparams);
+	wparams.rect = params->rect;
+	wparams.caption = "";
+	wparams.style &= ~ui_wds_decorated;
+
+	rc = ui_window_create(ui, &wparams, &window);
+	if (rc != EOK)
+		goto error;
+
+	popup->ui = ui;
+	popup->window = window;
+
+	ui_window_set_cb(window, &ui_popup_window_cb, popup);
+
+	*rpopup = popup;
+	return EOK;
+error:
+	free(popup);
+	return rc;
+}
+
+/** Destroy popup window.
+ *
+ * @param popup Popup window or @c NULL
+ */
+void ui_popup_destroy(ui_popup_t *popup)
+{
+	if (popup == NULL)
+		return;
+
+	ui_window_destroy(popup->window);
+	free(popup);
+}
+
+/** Add control to popup window.
+ *
+ * Only one control can be added to a popup window. If more than one control
+ * is added, the results are undefined.
+ *
+ * @param popup Popup window
+ * @param control Control
+ * @return EOK on success, ENOMEM if out of memory
+ */
+void ui_popup_add(ui_popup_t *popup, ui_control_t *control)
+{
+	ui_window_add(popup->window, control);
+}
+
+/** Remove control from popup window.
+ *
+ * @param popup Popup window
+ * @param control Control
+ */
+void ui_popup_remove(ui_popup_t *popup, ui_control_t *control)
+{
+	ui_window_remove(popup->window, control);
+}
+
+/** Set popup window callbacks.
+ *
+ * @param popup Popup window
+ * @param cb Popup window callbacks
+ * @param arg Callback argument
+ */
+void ui_popup_set_cb(ui_popup_t *popup, ui_popup_cb_t *cb, void *arg)
+{
+	popup->cb = cb;
+	popup->arg = arg;
+}
+
+/** Get UI resource from popup window.
+ *
+ * @param window Window
+ * @return UI resource
+ */
+ui_resource_t *ui_popup_get_res(ui_popup_t *popup)
+{
+	return ui_window_get_res(popup->window);
+}
+
+/** Get popup window GC.
+ *
+ * @param popup Popup window
+ * @return GC (relative to popup window)
+ */
+gfx_context_t *ui_popup_get_gc(ui_popup_t *popup)
+{
+	return ui_window_get_gc(popup->window);
+}
+
+/** Handle position event in popup window.
+ *
+ * @param window Window
+ * @param arg Argument (ui_popup_t *)
+ * @param event Position event
+ */
+static void ui_popup_window_pos(ui_window_t *window, void *arg,
+    pos_event_t *event)
+{
+	ui_popup_t *popup = (ui_popup_t *)arg;
+
+	if (popup->cb != NULL && popup->cb->pos != NULL)
+		popup->cb->pos(popup, popup->arg, event);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/src/window.c
===================================================================
--- uspace/lib/ui/src/window.c	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/src/window.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -336,5 +336,5 @@
  * is added, the results are undefined.
  *
- * @param fixed Fixed layout
+ * @param window Window
  * @param control Control
  * @return EOK on success, ENOMEM if out of memory
@@ -509,5 +509,5 @@
  *
  * @param window Window
- * @param cb Window decoration callbacks
+ * @param cb Window callbacks
  * @param arg Callback argument
  */
Index: uspace/lib/ui/test/main.c
===================================================================
--- uspace/lib/ui/test/main.c	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/test/main.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -42,4 +42,5 @@
 PCUT_IMPORT(paint);
 PCUT_IMPORT(pbutton);
+PCUT_IMPORT(popup);
 PCUT_IMPORT(rbutton);
 PCUT_IMPORT(resource);
Index: uspace/lib/ui/test/menu.c
===================================================================
--- uspace/lib/ui/test/menu.c	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/test/menu.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -46,10 +46,4 @@
 PCUT_TEST_SUITE(menu);
 
-typedef struct {
-	bool expose;
-} test_resp_t;
-
-static void test_expose(void *);
-
 /** Create and destroy menu */
 PCUT_TEST(create_destroy)
@@ -59,5 +53,5 @@
 	errno_t rc;
 
-	rc = ui_menu_bar_create(NULL, &mbar);
+	rc = ui_menu_bar_create(NULL, NULL, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -102,5 +96,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -148,5 +142,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -188,5 +182,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -218,7 +212,9 @@
 	dummy_gc_t *dgc;
 	gfx_context_t *gc;
-	ui_resource_t *resource = NULL;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
+	ui_t *ui = NULL;
+	ui_resource_t *resource = NULL;
+	ui_menu_bar_t *mbar = NULL;
+	ui_menu_t *menu = NULL;
+	gfx_rect_t prect;
 	gfx_coord2_t pos;
 	errno_t rc;
@@ -229,15 +225,27 @@
 	gc = dummygc_get_ctx(dgc);
 
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_create(mbar, "Test", &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	/* Menu needs to be open to be able to paint it */
+	rc = ui_menu_open(menu, &prect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	pos.x = 0;
@@ -248,44 +256,5 @@
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
-	dummygc_destroy(dgc);
-}
-
-/** ui_menu_unpaint() calls expose callback */
-PCUT_TEST(unpaint)
-{
-	dummy_gc_t *dgc;
-	gfx_context_t *gc;
-	ui_resource_t *resource = NULL;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	test_resp_t resp;
-	errno_t rc;
-
-	rc = dummygc_create(&dgc);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	gc = dummygc_get_ctx(dgc);
-
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	ui_resource_set_expose_cb(resource, test_expose, &resp);
-
-	resp.expose = false;
-	rc = ui_menu_unpaint(menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_TRUE(resp.expose);
-
-	ui_menu_bar_destroy(mbar);
-	ui_resource_destroy(resource);
+	ui_destroy(ui);
 	dummygc_destroy(dgc);
 }
@@ -313,5 +282,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -334,55 +303,4 @@
 }
 
-/** ui_menu_pos_event() outside menu closes it */
-PCUT_TEST(pos_event_outside)
-{
-	dummy_gc_t *dgc;
-	gfx_context_t *gc;
-	ui_resource_t *resource = NULL;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	ui_evclaim_t claimed;
-	gfx_coord2_t pos;
-	pos_event_t event;
-	errno_t rc;
-
-	rc = dummygc_create(&dgc);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	gc = dummygc_get_ctx(dgc);
-
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	pos.x = 0;
-	pos.y = 0;
-	ui_menu_bar_select(mbar, &pos, menu);
-	PCUT_ASSERT_EQUALS(menu, mbar->selected);
-
-	pos.x = 10;
-	pos.y = 0;
-	event.type = POS_PRESS;
-	event.hpos = 0;
-	event.vpos = 0;
-	claimed = ui_menu_pos_event(menu, &pos, &event);
-	PCUT_ASSERT_EQUALS(ui_unclaimed, claimed);
-
-	/* Press event outside menu should close it */
-	PCUT_ASSERT_NULL(mbar->selected);
-
-	ui_menu_bar_destroy(mbar);
-	ui_resource_destroy(resource);
-	dummygc_destroy(dgc);
-}
-
 /** Computing menu geometry */
 PCUT_TEST(get_geom)
@@ -406,5 +324,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -432,10 +350,3 @@
 }
 
-static void test_expose(void *arg)
-{
-	test_resp_t *resp = (test_resp_t *) arg;
-
-	resp->expose = true;
-}
-
 PCUT_EXPORT(menu);
Index: uspace/lib/ui/test/menubar.c
===================================================================
--- uspace/lib/ui/test/menubar.c	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/test/menubar.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -50,5 +50,5 @@
 	errno_t rc;
 
-	rc = ui_menu_bar_create(NULL, &mbar);
+	rc = ui_menu_bar_create(NULL, NULL, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -70,5 +70,5 @@
 	errno_t rc;
 
-	rc = ui_menu_bar_create(NULL, &mbar);
+	rc = ui_menu_bar_create(NULL, NULL, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -99,5 +99,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -137,5 +137,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -154,4 +154,5 @@
 	dummy_gc_t *dgc;
 	gfx_context_t *gc;
+	ui_t *ui = NULL;
 	ui_resource_t *resource = NULL;
 	ui_menu_bar_t *mbar = NULL;
@@ -167,9 +168,12 @@
 	gc = dummygc_get_ctx(dgc);
 
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -197,18 +201,17 @@
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
-	dummygc_destroy(dgc);
-}
-
-/** Position event is forwarded to menu */
-PCUT_TEST(pos_event_menu)
-{
-	dummy_gc_t *dgc;
-	gfx_context_t *gc;
+	ui_destroy(ui);
+	dummygc_destroy(dgc);
+}
+
+/** Calling ui_menu_bar_select() with the same menu twice deselects it */
+PCUT_TEST(select_same)
+{
+	dummy_gc_t *dgc;
+	gfx_context_t *gc;
+	ui_t *ui = NULL;
 	ui_resource_t *resource = NULL;
 	ui_menu_bar_t *mbar = NULL;
 	ui_menu_t *menu = NULL;
-	ui_evclaim_t claimed;
-	pos_event_t event;
-	gfx_coord2_t pos;
 	gfx_rect_t rect;
 	errno_t rc;
@@ -219,124 +222,33 @@
 	gc = dummygc_get_ctx(dgc);
 
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(mbar);
+
+	rc = ui_menu_create(mbar, "Test", &menu);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(menu);
 
 	rect.p0.x = 0;
 	rect.p0.y = 0;
-	rect.p1.x = 50;
-	rect.p1.y = 25;
-	ui_menu_bar_set_rect(mbar, &rect);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	pos.x = 0;
-	pos.y = 0;
-	ui_menu_bar_select(mbar, &pos, menu);
+	rect.p1.x = 0;
+	rect.p1.y = 0;
+	ui_menu_bar_select(mbar, &rect, menu);
 	PCUT_ASSERT_EQUALS(menu, mbar->selected);
 
-	event.type = POS_PRESS;
-	event.hpos = 4;
-	event.vpos = 30;
-	claimed = ui_menu_bar_pos_event(mbar, &event);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
-
-	ui_menu_bar_destroy(mbar);
-	ui_resource_destroy(resource);
-	dummygc_destroy(dgc);
-}
-
-/* Unfocusing window closes open menu */
-PCUT_TEST(unfocus)
-{
-	dummy_gc_t *dgc;
-	gfx_context_t *gc;
-	ui_resource_t *resource = NULL;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_coord2_t pos;
-	errno_t rc;
-
-	rc = dummygc_create(&dgc);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	gc = dummygc_get_ctx(dgc);
-
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	/*
-	 * Position does not matter here. Probably should get rid of this
-	 * argument, storing the position in the menu itself.
-	 */
-	pos.x = 0;
-	pos.y = 0;
-	ui_menu_bar_select(mbar, &pos, menu);
-	PCUT_ASSERT_EQUALS(menu, mbar->selected);
-
-	/* This should unselect the menu */
-	ui_menu_bar_unfocus(mbar);
+	/* Selecting again should unselect the menu */
+	ui_menu_bar_select(mbar, &rect, menu);
 	PCUT_ASSERT_NULL(mbar->selected);
 
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
-	dummygc_destroy(dgc);
-}
-
-/** Calling ui_menu_bar_select() with the same menu twice deselects it */
-PCUT_TEST(select_same)
-{
-	dummy_gc_t *dgc;
-	gfx_context_t *gc;
-	ui_resource_t *resource = NULL;
-	ui_menu_bar_t *mbar = NULL;
-	ui_menu_t *menu = NULL;
-	gfx_coord2_t pos;
-	errno_t rc;
-
-	rc = dummygc_create(&dgc);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	gc = dummygc_get_ctx(dgc);
-
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(mbar);
-
-	rc = ui_menu_create(mbar, "Test", &menu);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(menu);
-
-	pos.x = 0;
-	pos.y = 0;
-	ui_menu_bar_select(mbar, &pos, menu);
-	PCUT_ASSERT_EQUALS(menu, mbar->selected);
-
-	/* Selecting again should unselect the menu */
-	ui_menu_bar_select(mbar, &pos, menu);
-	PCUT_ASSERT_NULL(mbar->selected);
-
-	ui_menu_bar_destroy(mbar);
-	ui_resource_destroy(resource);
+	ui_destroy(ui);
 	dummygc_destroy(dgc);
 }
@@ -347,21 +259,25 @@
 	dummy_gc_t *dgc;
 	gfx_context_t *gc;
+	ui_t *ui = NULL;
 	ui_resource_t *resource = NULL;
 	ui_menu_bar_t *mbar = NULL;
 	ui_menu_t *menu1 = NULL;
 	ui_menu_t *menu2 = NULL;
-	gfx_coord2_t pos;
-	errno_t rc;
-
-	rc = dummygc_create(&dgc);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	gc = dummygc_get_ctx(dgc);
-
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = dummygc_create(&dgc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	gc = dummygc_get_ctx(dgc);
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -375,15 +291,18 @@
 	PCUT_ASSERT_NOT_NULL(menu2);
 
-	pos.x = 0;
-	pos.y = 0;
-	ui_menu_bar_select(mbar, &pos, menu1);
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 0;
+	rect.p1.y = 0;
+	ui_menu_bar_select(mbar, &rect, menu1);
 	PCUT_ASSERT_EQUALS(menu1, mbar->selected);
 
 	/* Selecting different menu should select it */
-	ui_menu_bar_select(mbar, &pos, menu2);
+	ui_menu_bar_select(mbar, &rect, menu2);
 	PCUT_ASSERT_EQUALS(menu2, mbar->selected);
 
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
+	ui_destroy(ui);
 	dummygc_destroy(dgc);
 }
Index: uspace/lib/ui/test/menuentry.c
===================================================================
--- uspace/lib/ui/test/menuentry.c	(revision d09100787736fb19d0105e318fd7919a9a3ae6ba)
+++ uspace/lib/ui/test/menuentry.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -71,5 +71,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -110,5 +110,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -156,5 +156,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -201,5 +201,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -255,5 +255,5 @@
 	PCUT_ASSERT_NOT_NULL(resource);
 
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_menu_bar_create(NULL, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -287,4 +287,5 @@
 	dummy_gc_t *dgc;
 	gfx_context_t *gc;
+	ui_t *ui = NULL;
 	ui_resource_t *resource = NULL;
 	ui_menu_bar_t *mbar = NULL;
@@ -292,16 +293,20 @@
 	ui_menu_entry_t *mentry = NULL;
 	gfx_coord2_t pos;
-	errno_t rc;
-
-	rc = dummygc_create(&dgc);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	gc = dummygc_get_ctx(dgc);
-
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
+	gfx_rect_t prect;
+	errno_t rc;
+
+	rc = dummygc_create(&dgc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	gc = dummygc_get_ctx(dgc);
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -314,4 +319,12 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mentry);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	rc = ui_menu_open(menu, &prect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	pos.x = 0;
@@ -322,4 +335,5 @@
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
+	ui_destroy(ui);
 	dummygc_destroy(dgc);
 }
@@ -330,4 +344,5 @@
 	dummy_gc_t *dgc;
 	gfx_context_t *gc;
+	ui_t *ui = NULL;
 	ui_resource_t *resource = NULL;
 	ui_menu_bar_t *mbar = NULL;
@@ -335,4 +350,5 @@
 	ui_menu_entry_t *mentry = NULL;
 	gfx_coord2_t pos;
+	gfx_rect_t prect;
 	test_resp_t resp;
 	errno_t rc;
@@ -343,9 +359,12 @@
 	gc = dummygc_get_ctx(dgc);
 
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -361,4 +380,12 @@
 	ui_menu_entry_set_cb(mentry, test_entry_cb, &resp);
 	resp.activated = false;
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	rc = ui_menu_open(menu, &prect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	pos.x = 0;
@@ -375,4 +402,5 @@
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
+	ui_destroy(ui);
 	dummygc_destroy(dgc);
 }
@@ -383,4 +411,5 @@
 	dummy_gc_t *dgc;
 	gfx_context_t *gc;
+	ui_t *ui = NULL;
 	ui_resource_t *resource = NULL;
 	ui_menu_bar_t *mbar = NULL;
@@ -388,4 +417,5 @@
 	ui_menu_entry_t *mentry = NULL;
 	gfx_coord2_t pos;
+	gfx_rect_t prect;
 	test_resp_t resp;
 	errno_t rc;
@@ -396,9 +426,12 @@
 	gc = dummygc_get_ctx(dgc);
 
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -414,4 +447,12 @@
 	ui_menu_entry_set_cb(mentry, test_entry_cb, &resp);
 	resp.activated = false;
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	rc = ui_menu_open(menu, &prect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	pos.x = 0;
@@ -433,4 +474,5 @@
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
+	ui_destroy(ui);
 	dummygc_destroy(dgc);
 }
@@ -441,4 +483,5 @@
 	dummy_gc_t *dgc;
 	gfx_context_t *gc;
+	ui_t *ui = NULL;
 	ui_resource_t *resource = NULL;
 	ui_menu_bar_t *mbar = NULL;
@@ -446,4 +489,5 @@
 	ui_menu_entry_t *mentry = NULL;
 	gfx_coord2_t pos;
+	gfx_rect_t prect;
 	test_resp_t resp;
 	errno_t rc;
@@ -454,9 +498,12 @@
 	gc = dummygc_get_ctx(dgc);
 
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -472,4 +519,12 @@
 	ui_menu_entry_set_cb(mentry, test_entry_cb, &resp);
 	resp.activated = false;
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	rc = ui_menu_open(menu, &prect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	pos.x = 0;
@@ -496,4 +551,5 @@
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
+	ui_destroy(ui);
 	dummygc_destroy(dgc);
 }
@@ -504,4 +560,5 @@
 	dummy_gc_t *dgc;
 	gfx_context_t *gc;
+	ui_t *ui = NULL;
 	ui_resource_t *resource = NULL;
 	ui_menu_bar_t *mbar = NULL;
@@ -509,4 +566,5 @@
 	ui_menu_entry_t *mentry = NULL;
 	gfx_coord2_t pos;
+	gfx_rect_t prect;
 	pos_event_t event;
 	errno_t rc;
@@ -517,9 +575,12 @@
 	gc = dummygc_get_ctx(dgc);
 
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -532,4 +593,12 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mentry);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	rc = ui_menu_open(menu, &prect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	pos.x = 0;
@@ -546,4 +615,5 @@
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
+	ui_destroy(ui);
 	dummygc_destroy(dgc);
 }
@@ -554,4 +624,5 @@
 	dummy_gc_t *dgc;
 	gfx_context_t *gc;
+	ui_t *ui = NULL;
 	ui_resource_t *resource = NULL;
 	ui_menu_bar_t *mbar = NULL;
@@ -559,4 +630,5 @@
 	ui_menu_entry_t *mentry = NULL;
 	gfx_coord2_t pos;
+	gfx_rect_t prect;
 	pos_event_t event;
 	errno_t rc;
@@ -567,9 +639,12 @@
 	gc = dummygc_get_ctx(dgc);
 
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -582,4 +657,12 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mentry);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	rc = ui_menu_open(menu, &prect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	pos.x = 0;
@@ -596,4 +679,5 @@
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
+	ui_destroy(ui);
 	dummygc_destroy(dgc);
 }
@@ -604,4 +688,5 @@
 	dummy_gc_t *dgc;
 	gfx_context_t *gc;
+	ui_t *ui = NULL;
 	ui_resource_t *resource = NULL;
 	ui_menu_bar_t *mbar = NULL;
@@ -609,4 +694,5 @@
 	ui_menu_entry_t *mentry = NULL;
 	gfx_coord2_t pos;
+	gfx_rect_t prect;
 	pos_event_t event;
 	errno_t rc;
@@ -617,9 +703,12 @@
 	gc = dummygc_get_ctx(dgc);
 
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -632,4 +721,12 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mentry);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	rc = ui_menu_open(menu, &prect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	pos.x = 0;
@@ -649,4 +746,5 @@
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
+	ui_destroy(ui);
 	dummygc_destroy(dgc);
 }
@@ -657,4 +755,5 @@
 	dummy_gc_t *dgc;
 	gfx_context_t *gc;
+	ui_t *ui = NULL;
 	ui_resource_t *resource = NULL;
 	ui_menu_bar_t *mbar = NULL;
@@ -662,4 +761,5 @@
 	ui_menu_entry_t *mentry = NULL;
 	gfx_coord2_t pos;
+	gfx_rect_t prect;
 	pos_event_t event;
 	errno_t rc;
@@ -670,9 +770,12 @@
 	gc = dummygc_get_ctx(dgc);
 
-	rc = ui_resource_create(gc, false, &resource);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_NOT_NULL(resource);
-
-	rc = ui_menu_bar_create(resource, &mbar);
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_resource_create(gc, false, &resource);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(resource);
+
+	rc = ui_menu_bar_create(ui, resource, &mbar);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mbar);
@@ -685,4 +788,12 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 	PCUT_ASSERT_NOT_NULL(mentry);
+
+	prect.p0.x = 0;
+	prect.p0.y = 0;
+	prect.p1.x = 0;
+	prect.p1.y = 0;
+
+	rc = ui_menu_open(menu, &prect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	event.type = POS_UPDATE;
@@ -699,4 +810,5 @@
 	ui_menu_bar_destroy(mbar);
 	ui_resource_destroy(resource);
+	ui_destroy(ui);
 	dummygc_destroy(dgc);
 }
Index: uspace/lib/ui/test/popup.c
===================================================================
--- uspace/lib/ui/test/popup.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
+++ uspace/lib/ui/test/popup.c	(revision 3c8c5808e19c14b9cbc2453181414284f31291a4)
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2021 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.
+ */
+
+#include <gfx/context.h>
+#include <gfx/coord.h>
+#include <gfx/render.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <mem.h>
+#include <pcut/pcut.h>
+#include <stdbool.h>
+#include <ui/control.h>
+#include <ui/popup.h>
+#include <ui/resource.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "../private/popup.h"
+#include "../private/window.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(popup);
+
+static void test_popup_close(ui_popup_t *, void *);
+static void test_popup_kbd(ui_popup_t *, void *, kbd_event_t *);
+static errno_t test_popup_paint(ui_popup_t *, void *);
+static void test_popup_pos(ui_popup_t *, void *, pos_event_t *);
+
+static ui_popup_cb_t test_popup_cb = {
+	.close = test_popup_close,
+	.kbd = test_popup_kbd,
+	.paint = test_popup_paint,
+	.pos = test_popup_pos,
+};
+
+static ui_popup_cb_t dummy_popup_cb = {
+};
+
+static errno_t test_ctl_paint(void *);
+static ui_evclaim_t test_ctl_pos_event(void *, pos_event_t *);
+static void test_ctl_unfocus(void *);
+
+static ui_control_ops_t test_ctl_ops = {
+	.paint = test_ctl_paint,
+	.pos_event = test_ctl_pos_event,
+	.unfocus = test_ctl_unfocus
+};
+
+typedef struct {
+	errno_t rc;
+	bool close;
+	bool focus;
+	bool kbd;
+	kbd_event_t kbd_event;
+	bool paint;
+	bool pos;
+	pos_event_t pos_event;
+	bool unfocus;
+} test_cb_resp_t;
+
+typedef struct {
+	errno_t rc;
+	ui_evclaim_t claim;
+	bool paint;
+	bool pos;
+	pos_event_t pos_event;
+	bool unfocus;
+} test_ctl_resp_t;
+
+/** Create and destroy popup window */
+PCUT_TEST(create_destroy)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_popup_params_t params;
+	ui_popup_t *popup = NULL;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_popup_params_init(&params);
+
+	rc = ui_popup_create(ui, &params, &popup);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(popup);
+
+	ui_popup_destroy(popup);
+	ui_destroy(ui);
+}
+
+/** ui_popup_destroy() can take NULL argument (no-op) */
+PCUT_TEST(destroy_null)
+{
+	ui_popup_destroy(NULL);
+}
+
+/** ui_popup_add()/ui_popup_remove() ... */
+PCUT_TEST(add_remove)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_popup_params_t params;
+	ui_popup_t *popup = NULL;
+	ui_control_t *control = NULL;
+	test_ctl_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_popup_params_init(&params);
+
+	rc = ui_popup_create(ui, &params, &popup);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(popup);
+
+	rc = ui_control_new(&test_ctl_ops, &resp, &control);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Control not called since it hasn't been added yet */
+	resp.rc = ENOMEM;
+	resp.paint = false;
+	rc = ui_window_def_paint(popup->window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_FALSE(resp.paint);
+
+	ui_popup_add(popup, control);
+
+	/* Now paint request should be delivered to control */
+	resp.rc = EOK;
+	resp.paint = false;
+	rc = ui_window_def_paint(popup->window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(resp.paint);
+
+	ui_popup_remove(popup, control);
+
+	/*
+	 * After having removed the control the request should no longer
+	 * be delivered to it.
+	 */
+	resp.rc = ENOMEM;
+	resp.paint = false;
+	rc = ui_window_def_paint(popup->window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_FALSE(resp.paint);
+
+	ui_popup_destroy(popup);
+	ui_destroy(ui);
+}
+
+/** ui_popup_get_res/gc() return valid objects */
+PCUT_TEST(get_res_gc)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_wnd_params_t params;
+	ui_window_t *window = NULL;
+	ui_resource_t *res;
+	gfx_context_t *gc;
+	gfx_rect_t rect;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Hello";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(window);
+
+	res = ui_window_get_res(window);
+	PCUT_ASSERT_NOT_NULL(res);
+
+	gc = ui_window_get_gc(window);
+	PCUT_ASSERT_NOT_NULL(gc);
+
+	ui_window_get_app_rect(window, &rect);
+
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test position event callback set via ui_popup_set_cb() */
+PCUT_TEST(send_pos)
+{
+	errno_t rc;
+	ui_t *ui = NULL;
+	ui_popup_params_t params;
+	ui_popup_t *popup = NULL;
+	pos_event_t pos_event;
+	test_cb_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_popup_params_init(&params);
+
+	rc = ui_popup_create(ui, &params, &popup);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(popup);
+
+	pos_event.pos_id = 1;
+	pos_event.type = POS_PRESS;
+	pos_event.btn_num = 2;
+	pos_event.hpos = 3;
+	pos_event.vpos = 4;
+
+	/* Pos callback with no callbacks set */
+	ui_window_send_pos(popup->window, &pos_event);
+
+	/* Pos callback with pos callback not implemented */
+	ui_popup_set_cb(popup, &dummy_popup_cb, NULL);
+	ui_window_send_pos(popup->window, &pos_event);
+
+	/* Pos callback with real callback set */
+	resp.pos = false;
+	ui_popup_set_cb(popup, &test_popup_cb, &resp);
+	ui_window_send_pos(popup->window, &pos_event);
+	PCUT_ASSERT_TRUE(resp.pos);
+	PCUT_ASSERT_INT_EQUALS(pos_event.pos_id, resp.pos_event.pos_id);
+	PCUT_ASSERT_EQUALS(pos_event.type, resp.pos_event.type);
+	PCUT_ASSERT_INT_EQUALS(pos_event.btn_num, resp.pos_event.btn_num);
+	PCUT_ASSERT_INT_EQUALS(pos_event.hpos, resp.pos_event.hpos);
+	PCUT_ASSERT_INT_EQUALS(pos_event.vpos, resp.pos_event.vpos);
+
+	ui_popup_destroy(popup);
+	ui_destroy(ui);
+}
+
+static void test_popup_close(ui_popup_t *popup, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->close = true;
+}
+
+static void test_popup_kbd(ui_popup_t *popup, void *arg,
+    kbd_event_t *event)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->kbd = true;
+	resp->kbd_event = *event;
+}
+
+static errno_t test_popup_paint(ui_popup_t *popup, void *arg)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->paint = true;
+	return resp->rc;
+}
+
+static void test_popup_pos(ui_popup_t *popup, void *arg,
+    pos_event_t *event)
+{
+	test_cb_resp_t *resp = (test_cb_resp_t *) arg;
+
+	resp->pos = true;
+	resp->pos_event = *event;
+}
+
+static errno_t test_ctl_paint(void *arg)
+{
+	test_ctl_resp_t *resp = (test_ctl_resp_t *) arg;
+
+	resp->paint = true;
+	return resp->rc;
+}
+
+static ui_evclaim_t test_ctl_pos_event(void *arg, pos_event_t *event)
+{
+	test_ctl_resp_t *resp = (test_ctl_resp_t *) arg;
+
+	resp->pos = true;
+	resp->pos_event = *event;
+
+	return resp->claim;
+}
+
+static void test_ctl_unfocus(void *arg)
+{
+	test_ctl_resp_t *resp = (test_ctl_resp_t *) arg;
+
+	resp->unfocus = true;
+}
+
+PCUT_EXPORT(popup);
