Index: uspace/srv/hid/console/console.c
===================================================================
--- uspace/srv/hid/console/console.c	(revision f78308153763110781d49aaa03881ea1bc9d48fb)
+++ uspace/srv/hid/console/console.c	(revision 24c452b35ddd0c461fd764673507864e5fc2fbcd)
@@ -124,4 +124,5 @@
 static errno_t input_ev_abs_move(input_t *, unsigned, unsigned, unsigned, unsigned);
 static errno_t input_ev_button(input_t *, int, int);
+static errno_t input_ev_dclick(input_t *, int);
 
 static input_ev_ops_t input_ev_ops = {
@@ -131,5 +132,6 @@
 	.move = input_ev_move,
 	.abs_move = input_ev_abs_move,
-	.button = input_ev_button
+	.button = input_ev_button,
+	.dclick = input_ev_dclick
 };
 
@@ -440,4 +442,18 @@
 }
 
+static errno_t input_ev_dclick(input_t *input, int bnum)
+{
+	cons_event_t event;
+
+	event.type = CEV_POS;
+	event.ev.pos.type = POS_DCLICK;
+	event.ev.pos.btn_num = bnum;
+	event.ev.pos.hpos = pointer_x / mouse_scale_x;
+	event.ev.pos.vpos = pointer_y / mouse_scale_y;
+
+	console_queue_cons_event(active_console, &event);
+	return EOK;
+}
+
 /** Process a character from the client (TTY emulation). */
 static void cons_write_char(console_t *cons, char32_t ch)
Index: uspace/srv/hid/display/input.c
===================================================================
--- uspace/srv/hid/display/input.c	(revision f78308153763110781d49aaa03881ea1bc9d48fb)
+++ uspace/srv/hid/display/input.c	(revision 24c452b35ddd0c461fd764673507864e5fc2fbcd)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2021 Jiri Svoboda
  * All rights reserved.
  *
@@ -49,4 +49,5 @@
 static errno_t ds_input_ev_abs_move(input_t *, unsigned, unsigned, unsigned, unsigned);
 static errno_t ds_input_ev_button(input_t *, int, int);
+static errno_t ds_input_ev_dclick(input_t *, int);
 
 static input_ev_ops_t ds_input_ev_ops = {
@@ -56,5 +57,6 @@
 	.move = ds_input_ev_move,
 	.abs_move = ds_input_ev_abs_move,
-	.button = ds_input_ev_button
+	.button = ds_input_ev_button,
+	.dclick = ds_input_ev_dclick
 };
 
@@ -131,4 +133,21 @@
 
 	event.type = bpress ? PTD_PRESS : PTD_RELEASE;
+	event.btn_num = bnum;
+	event.dmove.x = 0;
+	event.dmove.y = 0;
+
+	ds_display_lock(disp);
+	rc = ds_display_post_ptd_event(disp, &event);
+	ds_display_unlock(disp);
+	return rc;
+}
+
+static errno_t ds_input_ev_dclick(input_t *input, int bnum)
+{
+	ds_display_t *disp = (ds_display_t *) input->user;
+	ptd_event_t event;
+	errno_t rc;
+
+	event.type = PTD_DCLICK;
 	event.btn_num = bnum;
 	event.dmove.x = 0;
Index: uspace/srv/hid/display/seat.c
===================================================================
--- uspace/srv/hid/display/seat.c	(revision f78308153763110781d49aaa03881ea1bc9d48fb)
+++ uspace/srv/hid/display/seat.c	(revision 24c452b35ddd0c461fd764673507864e5fc2fbcd)
@@ -362,8 +362,21 @@
 	}
 
-	if (event->type == PTD_PRESS || event->type == PTD_RELEASE) {
+	if (event->type == PTD_PRESS || event->type == PTD_RELEASE ||
+	    event->type == PTD_DCLICK) {
 		pevent.pos_id = 0;
-		pevent.type = (event->type == PTD_PRESS) ?
-		    POS_PRESS : POS_RELEASE;
+		switch (event->type) {
+		case PTD_PRESS:
+			pevent.type = POS_PRESS;
+			break;
+		case PTD_RELEASE:
+			pevent.type = POS_RELEASE;
+			break;
+		case PTD_DCLICK:
+			pevent.type = POS_DCLICK;
+			break;
+		default:
+			assert(false);
+		}
+
 		pevent.btn_num = event->btn_num;
 		pevent.hpos = seat->pntpos.x;
Index: uspace/srv/hid/display/types/display/ptd_event.h
===================================================================
--- uspace/srv/hid/display/types/display/ptd_event.h	(revision f78308153763110781d49aaa03881ea1bc9d48fb)
+++ uspace/srv/hid/display/types/display/ptd_event.h	(revision 24c452b35ddd0c461fd764673507864e5fc2fbcd)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2019 Jiri Svoboda
+ * Copyright (c) 2021 Jiri Svoboda
  * All rights reserved.
  *
@@ -42,5 +42,6 @@
 	PTD_ABS_MOVE,
 	PTD_PRESS,
-	PTD_RELEASE
+	PTD_RELEASE,
+	PTD_DCLICK
 } ptd_event_type_t;
 
Index: uspace/srv/hid/input/input.c
===================================================================
--- uspace/srv/hid/input/input.c	(revision f78308153763110781d49aaa03881ea1bc9d48fb)
+++ uspace/srv/hid/input/input.c	(revision 24c452b35ddd0c461fd764673507864e5fc2fbcd)
@@ -1,5 +1,5 @@
 /*
+ * Copyright (c) 2021 Jiri Svoboda
  * Copyright (c) 2006 Josef Cejka
- * Copyright (c) 2011 Jiri Svoboda
  * All rights reserved.
  *
@@ -311,5 +311,17 @@
 }
 
-/** Arbitrate client actiovation */
+/** Mouse button has been double-clicked. */
+void mouse_push_event_dclick(mouse_dev_t *mdev, int bnum)
+{
+	list_foreach(clients, link, client_t, client) {
+		if (client->active) {
+			async_exch_t *exch = async_exchange_begin(client->sess);
+			async_msg_1(exch, INPUT_EVENT_DCLICK, bnum);
+			async_exchange_end(exch);
+		}
+	}
+}
+
+/** Arbitrate client activation */
 static void client_arbitration(void)
 {
Index: uspace/srv/hid/input/mouse.h
===================================================================
--- uspace/srv/hid/input/mouse.h	(revision f78308153763110781d49aaa03881ea1bc9d48fb)
+++ uspace/srv/hid/input/mouse.h	(revision 24c452b35ddd0c461fd764673507864e5fc2fbcd)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2011 Jiri Svoboda
+ * Copyright (c) 2021 Jiri Svoboda
  * All rights reserved.
  *
@@ -66,4 +66,5 @@
     unsigned int, unsigned int);
 extern void mouse_push_event_button(mouse_dev_t *, int, int);
+extern void mouse_push_event_dclick(mouse_dev_t *, int);
 
 #endif
Index: uspace/srv/hid/input/proto/mousedev.c
===================================================================
--- uspace/srv/hid/input/proto/mousedev.c	(revision f78308153763110781d49aaa03881ea1bc9d48fb)
+++ uspace/srv/hid/input/proto/mousedev.c	(revision 24c452b35ddd0c461fd764673507864e5fc2fbcd)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2021 Jiri Svoboda
  * Copyright (c) 2011 Martin Decky
  * All rights reserved.
@@ -43,4 +44,5 @@
 #include <loc.h>
 #include <stdlib.h>
+#include <time.h>
 #include "../mouse.h"
 #include "../mouse_port.h"
@@ -48,8 +50,17 @@
 #include "../input.h"
 
+enum {
+	/** Default double-click speed in milliseconds */
+	dclick_delay_ms = 500
+};
+
 /** Mousedev softstate */
 typedef struct {
 	/** Link to generic mouse device */
 	mouse_dev_t *mouse_dev;
+	/** Button number of last button pressed (or -1 if none) */
+	int press_bnum;
+	/** Time at which button was last pressed */
+	struct timespec press_time;
 } mousedev_t;
 
@@ -61,4 +72,5 @@
 
 	mousedev->mouse_dev = mdev;
+	mousedev->press_bnum = -1;
 
 	return mousedev;
@@ -68,4 +80,28 @@
 {
 	free(mousedev);
+}
+
+static void mousedev_press(mousedev_t *mousedev, int bnum)
+{
+	struct timespec now;
+	nsec_t ms_delay;
+
+	getuptime(&now);
+
+	/* Same button was pressed previously */
+	if (mousedev->press_bnum == bnum) {
+		/* Compute milliseconds since previous press */
+		ms_delay = ts_sub_diff(&now, &mousedev->press_time) / 1000000;
+
+		if (ms_delay <= dclick_delay_ms) {
+			mouse_push_event_dclick(mousedev->mouse_dev, bnum);
+			mousedev->press_bnum = -1;
+			return;
+		}
+	}
+
+	/* Record which button was last pressed and at what time */
+	mousedev->press_bnum = bnum;
+	mousedev->press_time = now;
 }
 
@@ -103,4 +139,6 @@
 			mouse_push_event_button(mousedev->mouse_dev,
 			    ipc_get_arg1(&call), ipc_get_arg2(&call));
+			if (ipc_get_arg2(&call) != 0)
+				mousedev_press(mousedev, ipc_get_arg1(&call));
 			retval = EOK;
 			break;
