shell: Start implementing the popup surface type

This lands the basic behavior of the popup surface type, but there are still
a number of details to be worked out.  Mainly there's a hardcoded timeout
to handle the case of releasing the popup button outside any of the
client windows, which triggers popup_end if it happens after the timeout.
Maybe we just need to add that as an argument, or we could add a new event
that fires in this case to let the client decide whether it ends the popup
or not.
diff --git a/src/shell.c b/src/shell.c
index b27067d..9386d1e 100644
--- a/src/shell.c
+++ b/src/shell.c
@@ -76,7 +76,8 @@
 
 	SHELL_SURFACE_TOPLEVEL,
 	SHELL_SURFACE_TRANSIENT,
-	SHELL_SURFACE_FULLSCREEN
+	SHELL_SURFACE_FULLSCREEN,
+	SHELL_SURFACE_POPUP
 };
 
 struct shell_surface {
@@ -84,10 +85,18 @@
 
 	struct weston_surface *surface;
 	struct wl_listener surface_destroy_listener;
+	struct shell_surface *parent;
 
 	enum shell_surface_type type;
 	int32_t saved_x, saved_y;
 
+	struct {
+		struct wl_grab grab;
+		uint32_t time;
+		int32_t x, y;
+		int32_t initial_up;
+	} popup;
+
 	struct weston_output *output;
 	struct wl_list link;
 };
@@ -337,6 +346,7 @@
 	case SHELL_SURFACE_NONE:
 	case SHELL_SURFACE_TOPLEVEL:
 	case SHELL_SURFACE_TRANSIENT:
+	case SHELL_SURFACE_POPUP:
 		break;
 	}
 
@@ -415,12 +425,118 @@
 	shsurf->type = SHELL_SURFACE_FULLSCREEN;
 }
 
+static void
+popup_grab_focus(struct wl_grab *grab, uint32_t time,
+		 struct wl_surface *surface, int32_t x, int32_t y)
+{
+	struct wl_input_device *device = grab->input_device;
+	struct shell_surface *priv =
+		container_of(grab, struct shell_surface, popup.grab);
+	struct wl_client *client = priv->surface->surface.resource.client;
+
+	if (surface->resource.client == client) {
+		wl_input_device_set_pointer_focus(device, surface, time,
+						  device->x, device->y, x, y);
+		grab->focus = surface;
+	} else {
+		wl_input_device_set_pointer_focus(device, NULL,
+						  time, 0, 0, 0, 0);
+		grab->focus = NULL;
+	}
+}
+
+static void
+popup_grab_motion(struct wl_grab *grab,
+		  uint32_t time, int32_t x, int32_t y)
+{
+	struct wl_input_device *device = grab->input_device;
+	struct wl_resource *resource;
+
+	resource = grab->input_device->pointer_focus_resource;
+	if (resource)
+		wl_resource_post_event(resource, WL_INPUT_DEVICE_MOTION,
+				       time, device->x, device->y, x, y);
+}
+
+static void
+popup_grab_button(struct wl_grab *grab,
+		  uint32_t time, int32_t button, int32_t state)
+{
+	struct wl_resource *resource;
+	struct shell_surface *shsurf =
+		container_of(grab, struct shell_surface, popup.grab);
+
+	resource = grab->input_device->pointer_focus_resource;
+	if (resource) {
+		wl_resource_post_event(resource, WL_INPUT_DEVICE_BUTTON,
+				       time, button, state);
+	} else if (state == 0 &&
+		   (shsurf->popup.initial_up ||
+		    time - shsurf->popup.time > 500)) {
+		wl_resource_post_event(&shsurf->resource,
+				       WL_SHELL_SURFACE_POPUP_DONE);
+		wl_input_device_end_grab(grab->input_device, time);
+		shsurf->popup.grab.input_device = NULL;
+	}
+
+	if (state == 0)
+		shsurf->popup.initial_up = 1;
+}
+
+static const struct wl_grab_interface popup_grab_interface = {
+	popup_grab_focus,
+	popup_grab_motion,
+	popup_grab_button,
+};
+
+static void
+shell_map_popup(struct shell_surface *shsurf, uint32_t time)
+{
+	struct wl_input_device *device;
+	struct weston_surface *es = shsurf->surface;
+	struct weston_surface *parent = shsurf->parent->surface;
+
+	es->output = parent->output;
+
+	shsurf->popup.grab.interface = &popup_grab_interface;
+	device = es->compositor->input_device;
+
+	es->x = shsurf->parent->surface->x + shsurf->popup.x;
+	es->y = shsurf->parent->surface->y + shsurf->popup.y;
+
+	shsurf->popup.grab.input_device = device;
+	shsurf->popup.time = device->grab_time;
+	shsurf->popup.initial_up = 0;
+
+	wl_input_device_start_grab(shsurf->popup.grab.input_device,
+				   &shsurf->popup.grab, shsurf->popup.time);
+}
+
+static void
+shell_surface_set_popup(struct wl_client *client,
+			struct wl_resource *resource,
+			struct wl_resource *input_device_resource,
+			uint32_t time,
+			struct wl_resource *parent_resource,
+			int32_t x, int32_t y, uint32_t flags)
+{
+	struct shell_surface *shsurf = resource->data;
+	struct weston_surface *es = shsurf->surface;
+
+	weston_surface_damage(es);
+	shsurf->type = SHELL_SURFACE_POPUP;
+	shsurf->parent = parent_resource->data;
+	shsurf->popup.x = x;
+	shsurf->popup.y = y;
+}
+
 static const struct wl_shell_surface_interface shell_surface_implementation = {
 	shell_surface_move,
 	shell_surface_resize,
 	shell_surface_set_toplevel,
 	shell_surface_set_transient,
-	shell_surface_set_fullscreen
+	shell_surface_set_fullscreen,
+	shell_surface_set_popup
 };
 
 static void
@@ -428,6 +544,9 @@
 {
 	struct shell_surface *shsurf = resource->data;
 
+	if (shsurf->popup.grab.input_device)
+		wl_input_device_end_grab(shsurf->popup.grab.input_device, 0);
+
 	/* in case cleaning up a dead client destroys shell_surface first */
 	if (shsurf->surface)
 		wl_list_remove(&shsurf->surface_destroy_listener.link);
@@ -1066,9 +1185,25 @@
 		}
 	}
 
-	if (do_configure)
-		weston_surface_configure(surface,
-				       surface->x, surface->y, width, height);
+	switch (surface_type) {
+	case SHELL_SURFACE_TOPLEVEL:
+		surface->x = 10 + random() % 400;
+		surface->y = 10 + random() % 400;
+		break;
+	case SHELL_SURFACE_POPUP:
+		shell_map_popup(shsurf, shsurf->popup.time);
+		break;
+	default:
+		break;
+	}
+
+	surface->width = width;
+	surface->height = height;
+	if (do_configure) {
+		weston_surface_configure(surface, surface->x, surface->y,
+					 width, height);
+		weston_compositor_repick(compositor);
+	}
 
 	switch (surface_type) {
 	case SHELL_SURFACE_TOPLEVEL: